# This is usually subclassed by code with an agenda.
import gobject, sys, os, time
-from trace import log
+from tracing import log
from socket import *
class AtChannel:
log("connect to", self.path)
s = socket(AF_UNIX, SOCK_STREAM)
s.connect(self.path)
+ s.setblocking(0)
self.watcher = gobject.io_add_watch(s, gobject.IO_IN, self.readdata)
self.sock = s
self.connected = True
log("send command", str)
if not self.command_pending:
self.command_pending = str
- self.sock.sendall(str + '\n')
- self.set_timeout(30000)
+ try:
+ self.sock.sendall(str + '\n')
+ except error:
+ self.set_timeout(10)
+ else:
+ self.set_timeout(30000)
def readdata(self, io, arg):
- r = self.sock.recv(1000)
+ try:
+ r = self.sock.recv(1000)
+ except error:
+ # no data there really.
+ return True
+ if not r:
+ # pipe closed
+ return False
r = self.buf + r
ra = r.split('\n')
self.buf = ra[-1];
for ln in ra:
ln = ln.strip('\r')
self.getline(ln)
+ # FIXME this should be configurable
+ if self.buf == '> ':
+ self.getline(self.buf)
+ self.buf = ''
return True
def getline(self, line):
"""
self.set_timeout(timeout)
log("send AT command", cmd, timeout)
- self.sock.sendall('AT' + cmd + '\r')
+ try:
+ self.sock.sendall('AT' + cmd + '\r')
+ except error:
+ self.cancel_timeout()
+ self.set_timeout(10)
def timer_fired(self):
log("Timer Fired")
def wait_line(self, timeout):
self.cancel_timeout()
+ self.set_timeout(timeout)
c = gobject.main_context_default()
- while not self.linelist:
+ while not self.linelist and self.pending:
c.iteration()
- l = self.linelist[0]
- del self.linelist[0]
- return l
+ if self.linelist:
+ self.cancel_timeout()
+ l = self.linelist[0]
+ del self.linelist[0]
+ return l
+ else:
+ return None
+ def timedout(self):
+ pass
+
+
+ def chat(self, mesg, resp, timeout = 1000):
+ """
+ Send the message (if not 'None') and wait up to
+ 'timeout' for one of the responses (regexp)
+ Return None on timeout, or number of response.
+ combined with an array of the messages received.
+ """
+ if mesg:
+ log("send command", mesg)
+ try:
+ self.sock.sendall(mesg + '\r\n')
+ except error:
+ timeout = 10
+
+ conv = []
+ while True:
+ l = self.wait_line(timeout)
+ if l == None:
+ return (None, conv)
+ conv.append(l)
+ for i in range(len(resp)):
+ ptn = resp[i]
+ if type(ptn) == str:
+ if ptn == l.strip():
+ return (i, conv)
+ else:
+ if resp[i].match(l):
+ return (i, conv)
+
+ def chat1(self, mesg, resp, timeout=1000):
+ n,c = self.chat(mesg, resp, timeout = timeout)
+ return n
+
+ def cmdchat(self, mesg):
+ self.command(mesg)
+ conv = []
+ while self.command_pending:
+ c = gobject.main_context_default()
+ while not self.linelist and self.pending:
+ c.iteration()
+ if self.linelist:
+ l = self.linelist[0]
+ del self.linelist[0]
+ conv.append(l)
+ return conv
+
+
+def found(list, patn):
+ """
+ see if patn can be found in the list of strings
+ """
+ for l in list:
+ l = l.strip()
+ if type(patn) == str:
+ if l == patn:
+ return True
+ else:
+ if patn.match(l):
+ return True
+ return False
+
--- /dev/null
+#!/usr/bin/env python
+
+# Collect SMS messages from the GSM device.
+# We store a list of messages that are thought to be
+# in the SIM card: number from date
+# e.g. 17 61403xxxxxx 09/02/17,20:28:36+44
+# As we read messages, if we find one that is not in that list,
+# we record it in the SMS store, then update the list
+#
+# An option can specify either 'new' or 'all.
+# 'new' will only ask for 'REC UNREAD' and so will be faster and so
+# is appropriate when we know that a new message has arrived.
+# 'all' reads all messages and so is appropriate for an occasional
+# 'sync' like when first turning the phone on.
+#
+# If we discover that the SMS card is more than half full, we
+# deleted the oldest messages.
+# We discover this by 'all' finding lots of messages, or 'new'
+# finding a message with a high index.
+# For now, we "know" that the SIM card can hold 30 messages.
+#
+# We need to be careful about long messages. A multi-part message
+# looks like e.g.
+#+CMGL: 19,"REC UNREAD","61403xxxxxx",,"09/02/18,10:51:46+44",145,140
+#0500031C0201A8E8F41C949E83C2207B599E07B1DFEE33A85D9ECFC3E732888E0ED34165FCB85C26CF41747419344FBBCFEC32A85D9ECFC3E732889D6EA7E9A0F91B444787E9A024681C7683E6E532E88E0ED341E939485E1E97D3F6321914A683E8E832E84D4797E5A0B29B0C7ABB41ED3CC8282F9741F2BADB5D96BB40D7329B0D9AD3D36C36887E2FBBE9
+#+CMGL: 20,"REC UNREAD","61403xxxxxx",,"09/02/18,10:51:47+44",145,30
+#0500031C0202F2A0B71C347F83D8653ABD2C9F83E86FD0F90D72B95C2E17
+
+# If that was just hex you could use
+# perl -e 'print pack("H*","050....")'
+# to print it.. but no...
+# Looks like it decodes as:
+# 05 - length of header, not including this byte
+# 00 - concatentated SMS with 8 bit ref number (08 means 16 bit ref number)
+# 03 - length of rest of header
+# 1C - ref number for this concat-SMS
+# 02 - number of parts in this SMS
+# 01 - number of this part - counting starts from 1
+# A8E8F41C949E83C22.... message, 7 bits per char. so:
+# A8 - 54 *2 + 0 54 == T 1010100 0 1010100
+# 0E8 - 68 *2 + 0 68 == h 1 1101000 1101000
+# F4 - 69 == i 11 110100 1101001 1
+# 1C 73 == s 000 11100 1110011 11
+# 94 20 == space 1001 0100 0100000 000
+# 9E 69 == i 10011 110 1101001 1001
+# 83 73 == s 100000 11 1110011 10011
+# 20 == space 0100000 0100000
+
+# 153 characters in first message. 19*8 + 1
+# that uses 19*7+1 == 134 octets
+# There are 6 in the header so a total of 140
+# second message has 27 letters - 3*8+3
+# That requires 3*7+3 == 24 octets. 30 with the 6 octet header.
+
+# then there are VCARD messages that look lie e.g.
+#+CMGL: 2,"REC READ","61403xxxxxx",,"09/01/29,13:01:26+44",145,137
+#06050423F40000424547494E3A56434152440D0A56455253494F4E3A322E310D0A4E3A....0D0A454E443A56434152440D0A
+#which is
+#06050423F40000
+#then
+#BEGIN:VCARD
+#VERSION:2.1
+#N: ...
+#...
+#END:VCARD
+# The 06050423f40000
+# might decode like:
+# 06 - length of rest of header
+# 05 - magic code meaning 'user data'
+# 04 - length of rest of header...
+# 23 -
+# f4 - destination port '23f4' means 'vcard'
+# 00 -
+# 00 - 0000 is the origin port.
+#
+#in hex/ascii
+#
+# For now, ignore anything longer than the specified length.
+
+
+import atchan, sys, re, os
+from storesms import SMSmesg, SMSstore
+
+
+def load_mirror(filename):
+ # load an array of index address date
+ # from the file and store in a hash
+ rv = {}
+ try:
+ f = file(filename)
+ except IOError:
+ return rv
+ l = f.readline()
+ while l:
+ fields = l.strip().split(None, 1)
+ rv[fields[0]] = fields[1]
+ l = f.readline()
+ return rv
+
+def save_mirror(filename, hash):
+ n = filename + '.new'
+ f = open(n, 'w')
+ for i in hash:
+ f.write(i + ' ' + hash[i] + '\n')
+ f.close()
+ os.rename(n, filename)
+
+
+def sms_decode(msg):
+ #msg is a 7-in-8 encoding of a longer message.
+ pos = 0
+ carry = 0
+ str = ''
+ while msg:
+ c = msg[0:2]
+ msg = msg[2:]
+ b = int(c, 16)
+
+ if pos == 0:
+ if carry:
+ str += chr(carry + (b&1)*64)
+ carry = 0
+ b /= 2
+ else:
+ b = (b << (pos-1)) | carry
+ carry = (b & 0xff80) >> 7
+ b &= 0x7f
+ if (b & 0x7f) != 0:
+ str += chr(b&0x7f)
+ pos = (pos+1) % 7
+ return str
+
+def main():
+ mode = 'all'
+ for a in sys.argv[1:]:
+ if a == '-n':
+ mode = 'new'
+ else:
+ print "Unknown option:", a
+ sys.exit(1)
+
+ pth = None
+ for p in ['/media/card','/media/disk','/var/tmp']:
+ if os.path.exists(os.path.join(p,'SMS')):
+ pth = p
+ break
+
+ if not pth:
+ print "Cannot find SMS directory"
+ sys.exit(1)
+
+ dir = os.path.join(pth, 'SMS')
+
+ store = SMSstore(dir)
+
+ chan = atchan.AtChannel()
+ chan.connect()
+
+ if not atchan.found(chan.cmdchat('get_power'), 'OK on'):
+ sys.exit(1)
+
+ if not atchan.found(chan.cmdchat('connect'), 'OK'):
+ sys.exit(1)
+
+ chan.chat1('', ['OK', 'ERROR']);
+ if chan.chat1('ATE0', ['OK', 'ERROR']) != 0:
+ sys.exit(1)
+
+ # get ID of SIM card
+ n,c = chan.chat('AT+CIMI', ['OK', 'ERROR'])
+ CIMI='unknown'
+ for l in c:
+ l = l.strip()
+ if re.match('^\d+$', l):
+ CIMI = l
+
+ mfile = os.path.join(dir, '.sim-mirror-'+CIMI)
+ #FIXME lock mirror file
+ mirror = load_mirror(mfile)
+ mirror_changed = False
+
+ chan.chat('AT+CMFG=1', ['OK','ERROR'])
+ if mode == 'new':
+ chan.atcmd('+CMGL="REC UNREAD"')
+ else:
+ chan.atcmd('+CMGL="ALL"')
+
+ # reading the msg list might fail for some reason, so
+ # we always prime the mirror list with the previous version
+ # and only replace things, never assume they aren't there
+ # because we cannot see them
+ newmirror = mirror
+
+ l = ''
+ state = 'waiting'
+ msg = ''
+ # indx state from name date type len
+ want = re.compile('^\+CMGL: (\d+),("[^"]*")?,("([^"]*)")?,("[^"]*")?,("([^"]*)")?,(\d+),(\d+)$')
+
+ while state == 'reading' or not (l[0:2] == 'OK' or l[0:5] == 'ERROR' or
+ l[0:10] == '+CMS ERROR'):
+ l = chan.wait_line(1000)
+ if l == None:
+ sys.exit(1)
+ if state == 'reading':
+ if msg:
+ msg += '\n'
+ msg += l
+ if len(msg) >= msg_len:
+ state = 'waiting'
+ ref = None; part = None
+ if len(msg) > msg_len:
+ if msg[0:6] == '050003':
+ # concatenated message with 8bit ref number
+ ref = msg[6:8]
+ part = ( int(msg[10:12],16), int(msg[8:10], 16))
+ msg = sms_decode(msg[12:])
+ else:
+ print "ignoring", index, sender, date, msg
+ continue
+ print "found", index, sender, date, msg
+ if index in mirror and mirror[index] == date + ' ' + sender:
+ print "Already have that one"
+ else:
+ sms = SMSmesg(source='GSM', time=date, sender=sender,
+ text = msg, state = 'NEW',
+ ref= ref, part = part)
+ store.store(sms)
+ newmirror[index] = date + ' ' + sender
+ else:
+ m = want.match(l)
+ if m:
+ g = m.groups()
+ index = g[0]
+ sender = g[3]
+ date = g[6]
+ typ = int(g[7])
+ if typ & 16:
+ sender = '+' + sender
+ msg_len = int(g[8])
+ msg = ''
+ state = 'reading'
+
+ mirror = newmirror
+
+ if len(mirror) > 10:
+ rev = {}
+ dlist = []
+ for i in mirror:
+ rev[mirror[i]] = i
+ dlist.append(mirror[i])
+ dlist.sort()
+ for i in range(len(mirror) - 10):
+ dt=dlist[i]
+ ind = rev[dt]
+ resp = chan.chat1('AT+CMGD=%s' % ind, ['OK', 'ERROR', '+CMS ERROR'],
+ timeout=3000)
+ if resp == 0:
+ del mirror[ind]
+
+ save_mirror(mfile, mirror)
+
+main()
--- /dev/null
+#!/usr/bin/env python
+
+# Send an SMS message using GSM.
+# Args are:
+# sender - ignored
+# recipient - phone number
+# message - no newlines
+#
+# We simply connect to the GSM module,
+# check for a registration
+# and
+# AT+CMGS="recipient"
+# > message
+# > control-Z
+#
+# Sending multipart sms messages:
+# ref: http://www.developershome.com/sms/cmgsCommand4.asp
+# 1/ set PDU mode with AT+CMGF=0
+# 2/ split message into 153-char bundles
+# 3/ create messages as follows:
+#
+# 00 - this says we aren't providing an SMSC number
+# 41 - TPDU header - type is SMS-SUBMIT, user-data header present
+# 00 - please assign a message reference number
+# xx - length in digits of phone number
+# 91 for IDD, 81 for "don't know what sort of number this is"
+# 164030... BCD phone number, nibble-swapped, pad with F at end if needed
+# 00 - protocol identifier??
+# 00 - encoding - 7 bit ascii
+# XX - length of message in septets
+# Then the message which starts with 7 septets of header that looks like 6 octets.
+# 05 - length of rest of header
+# 00 - multi-path with 1 byte id number
+# 03 - length of rest
+# idnumber - random id number
+# parts - number of parts
+# this part - this part, starts from '1'
+#
+# then AT+CMGS=len-of-TPDU in octets
+#
+# TPDU header byte:
+# Structure: (n) = bits
+# +--------+----------+---------+-------+-------+--------+
+# | RP (1) | UDHI (1) | SRI (1) | X (1) | X (1) | MTI(2) |
+# +--------+----------+---------+-------+-------+--------+
+# RP:
+# Reply path
+# UDHI:
+# User Data Header Indicator = Does the UD contains a header
+# 0 : Only the Short Message
+# 1 : Beginning of UD containsheader information
+# SRI:
+# Status Report Indication.
+# The SME (Short Message Entity) has requested a status report.
+# MTI:
+# 00 for SMS-Deliver
+# 01 for SMS-SUBMIT
+
+
+import atchan, sys, re, random
+
+def encode_number(recipient):
+ # encoded number is
+ # number of digits
+ # 91 for international, 81 for local interpretation
+ # BCD digits, nibble-swapped, F padded.
+ if recipient[0] == '+':
+ type = '91'
+ recipient = recipient[1:]
+ else:
+ type = '81'
+ leng = '%02X' % len(recipient)
+ if len(recipient) % 2 == 1:
+ recipient += 'F'
+ swap = ''
+ while recipient:
+ swap += recipient[1] + recipient[0]
+ recipient = recipient[2:]
+ return leng + type + swap
+
+def code7(pad, mesg):
+ # Encode the message as 8 chars in 7 bytes.
+ # pad with 'pad' 0 bits at the start (low in the byte)
+ carry = 0
+ # we have 'pad' low bits stored low in 'carry'
+ code = ''
+ while mesg:
+ c = ord(mesg[0])
+ mesg = mesg[1:]
+ if pad + 7 >= 8:
+ # have a full byte
+ b = carry & ((1<<pad)-1)
+ b += (c << pad) & 255
+ code += '%02X' % b
+ pad -= 1
+ carry = c >> (7-pad)
+ else:
+ # not a full byte yet, just a septet
+ pad = 7
+ carry = c
+ if pad:
+ # a few bits still to emit
+ b = carry & ((1<<pad)-1)
+ code += '%02X' % b
+ return code
+
+def add_len(mesg):
+ return ('%02X' % (len(mesg)/2)) + mesg
+
+def send(chan, dest, mesg):
+ n,c = chan.chat('AT+CMGS=%d' % (len(mesg)/2), ['OK','ERROR','>'])
+ if n == 2:
+ n,c = chan.chat('%s%s\r\n\032' % (dest, mesg), ['OK','ERROR'], 10000)
+ if n == 0 and atchan.found(c, re.compile('^\+CMGS: \d+') ):
+ return True
+ return False
+
+recipient = sys.argv[2]
+mesg = sys.argv[3]
+
+chan = atchan.AtChannel()
+chan.connect()
+
+if not atchan.found(chan.cmdchat('get_power'), 'OK on'):
+ print 'GSM disabled - message not sent'
+ sys.exit(1)
+
+if not atchan.found(chan.cmdchat('connect'), 'OK'):
+ print 'system error - message not sent'
+ sys.exit(1)
+chan.chat1(None, [ 'AT-Command Interpreter ready' ], 500)
+# clear any pending error status
+chan.chat1('ATE0', ['OK', 'ERROR'])
+if chan.chat1('ATE0', ['OK', 'ERROR']) != 0:
+ print 'system error - message not sent'
+ sys.exit(1)
+
+want = re.compile('^\+COPS:.*".+"')
+n,c = chan.chat('AT+COPS?', ['OK', 'ERROR'], 2000 )
+if n != 0 or not atchan.found(c, want):
+ print 'No Service - message not sent'
+ sys.exit(1)
+
+# use PDU mode
+n,c = chan.chat('AT+CMGF=0', ['OK', 'ERROR'])
+if n != 0:
+ print 'Unknown error'
+ sys.exit(1)
+
+# SMSC header and TPDU header
+SMSC = '00' # No SMSC number given, use default
+REF = '00' # ask network to assign ref number
+phone_num = encode_number(recipient)
+proto = '00' # don't know what this means
+encode = '00' # 7 bit ascii
+if len(mesg) <= 160:
+ # single SMS mesg
+ m1 = code7(0,mesg)
+ m2 = '%02X'%len(mesg) + m1
+ coded = "01" + REF + phone_num + proto + encode + m2
+ if send(chan, SMSC, coded):
+ print "OK message sent"
+ sys.exit(0)
+ else:
+ print "ERROR message not sent"
+ sys.exit(1)
+
+elif len(mesg) <= 5 * 153:
+ # Multiple messsage
+ packets = (len(mesg) + 152) / 153
+ packet = 0
+ mesgid = random.getrandbits(8)
+ while len(mesg) > 0:
+ m = mesg[0:153]
+ mesg = mesg[153:]
+ id = mesgid
+ packet = packet + 1
+ UDH = add_len('00' + add_len('%02X%02X%02X'%(id, packets, packet)))
+ m1 = UDH + code7(1, m)
+ m2 = '%02X'%(7+len(m)) + m1
+ coded = "41" + REF + phone_num + proto + encode + m2
+ if not send(chan, SMSC, coded):
+ print "ERROR message not sent at part %d/%d" % (packet,packets)
+ sys.exit(1)
+ print 'OK message sent in %d parts' % packets
+ sys.exit(0)
+else:
+ print 'Message is too long'
+ sys.exit(1)
+
#!/usr/bin/env python
-import re, time, gobject
+## FIXME
+# e.g. receive AT response +CREG: 1,"08A7","6E48"
+# show that SIM is now ready
+# cope with /var/lock/suspend not existing yet
+# define 'reset'
+
+import re, time, gobject, os
from atchan import AtChannel
import dnotify, suspend
-from trace import log
+from tracing import log
def record(key, value):
- f = open('/var/run/gsm-state/' + key, 'w')
+ f = open('/var/run/gsm-state/.new.' + key, 'w')
f.write(value)
+ f.close()
+ os.rename('/var/run/gsm-state/.new.' + key,
+ '/var/run/gsm-state/' + key)
+
+def calllog(key, msg):
+ f = open('/var/log/' + key, 'a')
+ now = time.strftime("%Y-%m-%d %H:%M:%S")
+ f.write(now + ' ' + msg + "\n")
+ f.close()
class Task:
def __init__(self, repeat):
#
# States are 'init' 'checking', 'setting', 'done'
ok = re.compile("^OK")
+ busy = re.compile("\+CMS ERROR.*SIM busy")
not_ok = re.compile("^(ERROR|\+CM[SE] ERROR:)")
def __init__(self, check = None, ok = None, record = None, at = None,
timeout=None, handle = None, repeat = None):
self.advance(channel)
def takeline(self, channel, line):
+ if line == None:
+ return
m = self.ok.match(line)
if m:
channel.cancel_timeout()
if self.handle:
self.handle(channel, line, None)
return self.advance(channel)
+
+ if self.busy.match(line):
+ channel.cancel_timeout()
+ channel.set_timeout(5000)
+ return
if self.not_ok.match(line):
channel.cancel_timeout()
return self.timeout(channel)
if not channel.connected:
channel.connect()
- channel.state['stage'] = 'setting'
+ channel.state['stage'] = 'connecting'
if self.cmd == "on":
+ channel.state['stage'] = 'needconnect'
channel.set_power(True)
+ channel.check_flightmode()
elif self.cmd == "off":
channel.set_power(False)
record('carrier', '')
record('cell', '')
record('signal_strength','0/32')
- elif self.cmd == "reset":
- channel.reset()
+ elif self.cmd == 'reopen':
+ channel.disconnect()
+ channel.connect()
+ channel.state['stage'] = 'done'
+ return channel.advance()
def takeline(self, channel, line):
# really a 'power_done' callback
- if channel.state['stage'] == 'setting':
+ if channel.state['stage'] == 'needconnect':
channel.state['stage'] = 'connecting'
return channel.atconnect()
if channel.state['stage'] == 'connecting':
return channel.advance()
raise
+ def timeout(self, channel):
+ # Hopefully a reset will work
+ channel.set_state('reset')
+ channel.advance()
+
class SuspendAction(Task):
# This action simply allows suspend to continue
def __init__(self):
channel.suspend_handle.release()
return channel.advance()
+class ChangeStateAction(Task):
+ # This action changes to a new state, like a goto
+ def __init__(self, state):
+ Task.__init__(self, None)
+ self.newstate = state
+ def start(self, channel):
+ channel.state['stage'] = 'done'
+ channel.set_state(self.newstate)
+ return channel.advance()
+
class Async:
def __init__(self, msg, handle, handle_extra = None):
self.msg = msg
global LAC, CELLID, cellnames
LAC = int(m.groups()[2],16)
CELLID = int(m.groups()[3],16)
+ record("cellid", "%04X %04X" % (LAC, CELLID));
if CELLID in cellnames:
record('cell', cellnames[CELLID])
log("That one is", cellnames[CELLID])
if m:
record('newsms', m.groups()[1])
+global incoming_cell_id
def cellid_update(channel, line, m):
# get something like +CBM: 1568,50,1,1,1
# don't know what that means, just collect the 'extra' line
- pass
+ # I think the '50' means 'this is a cell id'. I should
+ # probably test for that.
+ #
+ # response can be multi-line
+ global incoming_cell_id
+ incoming_cell_id = ""
+
def cellid_new(channel, line):
- global CELLID, cellnames
+ global CELLID, cellnames, incoming_cell_id
+ if not line:
+ # end of message
+ if incoming_cell_id:
+ l = re.sub('[^!-~]+',' ',incoming_cell_id)
+ if CELLID:
+ cellnames[CELLID] = l
+ record('cell', l)
+ return False
line = line.strip()
- if CELLID:
- cellnames[CELLID] = line
- record('cell', line)
+ if incoming_cell_id:
+ incoming_cell_id += ' ' + line
+ else:
+ incoming_cell_id = line
+ return True
incoming_num = None
def incoming(channel, line, m):
else:
record('incoming', '-')
if channel.gstate != 'incoming':
+ calllog('incoming', '-call-')
channel.set_state('incoming')
def incoming_number(channel, line, m):
global incoming_num
if m:
- incoming_num = m.groups()[0]
+ num = m.groups()[0]
+ if incoming_num == None:
+ calllog('incoming', num);
+ incoming_num = num
record('incoming', incoming_num)
+cpas_zero_cnt = 0
def call_status(channel, line, m):
+ global cpas_zero_cnt
log("call_status got", line)
if not m:
return
s = int(m.groups()[0])
log("s = %d" % s)
if s == 0:
+ cpas_zero_cnt += 1
+ if cpas_zero_cnt == 1:
+ return
# idle
global incoming_num
incoming_num = None
+ record('incoming', '')
if channel.gstate == 'incoming':
- record('incoming', '')
+ calllog('incoming','-end-')
if channel.gstate != 'idle':
channel.set_state('idle')
+ cpas_zero_cnt = 0
if s == 3:
# incoming call
if channel.gstate != 'incoming':
PowerAction('off')
]
+control['reset'] = [
+ # turning power off just kills everything!!!
+ #PowerAction('reopen'),
+ #PowerAction('off'),
+ AtAction(at='E0', timeout=30000),
+ ChangeStateAction('idle'),
+ ]
+
# For suspend, we want power on, but no wakups for status or cellid
control['suspend'] = [
AtAction(at='+CNMI=1,1,0,0,0'),
SuspendAction()
]
+control['listenerr'] = [
+ PowerAction('on'),
+ AtAction(at='V1E0'),
+ AtAction(at='+CMEE=2;+CRC=1')
+ ]
control['idle'] = [
PowerAction('on'),
AtAction(at='V1E0'),
AtAction(at='+CMEE=2;+CRC=1'),
# Turn the device on.
AtAction(check='+CFUN?', ok='\+CFUN: 1', at='+CFUN=1', timeout=10000),
+ # Report carrier as long name
+ AtAction(at='+COPS=3,0'),
# register with a carrier
- AtAction(check='+COPS?', ok='\+COPS: \d+,\d+,"([^"]*)"', at='+COPS',
- record=('carrier', '\\1'), timeout=10000),
- AtAction(check='+COPS?', ok='\+COPS: \d+,\d+,"([^"]*)"', at='+COPS',
+ #AtAction(check='+COPS?', ok='\+COPS: \d+,\d+,"([^"]*)"', at='+COPS',
+ # record=('carrier', '\\1'), timeout=10000),
+ AtAction(check='+COPS?', ok='\+COPS: \d+,\d+,"([^"]*)"', at='+COPS=0',
record=('carrier', '\\1'), timeout=10000, repeat=37000),
# fix a bug
AtAction(at='%SLEEP=2'),
AtAction(check='+CMGF?', ok='\+CMGF: 1', at='+CMGF=1'),
# get location status updates
AtAction(at='+CREG=2'),
- AtAction(check='+CREG?', ok='\+CREG: 2,(\d)(,"([^"]*)","([^"]*)")', handle=status_update),
+ AtAction(check='+CREG?', ok='\+CREG: 2,(\d)(,"([^"]*)","([^"]*)")',
+ handle=status_update, timeout=4000),
# Enable collection of Cell Info message
#AtAction(check='+CSCB?', ok='\+CSCB: 1,.*', at='+CSCB=1'),
AtAction(at='+CSCB=0'),
#AtAction(check='+CNMI?', ok='\+CNMI: 1,1,2,0,0', at='+CNMI=1,1,2,0,0'),
AtAction(at='+CNMI=1,0,0,0,0'),
AtAction(at='+CNMI=1,1,2,0,0'),
+
# Enable reporting of Caller number id.
- AtAction(check='+CLIP?', ok='\+CLIP: 1,2', at='+CLIP=1', timeout=5000),
+ AtAction(check='+CLIP?', ok='\+CLIP: 1,2', at='+CLIP=1', timeout=10000),
# Must be last: get signal string
AtAction(check='+CSQ', ok='\+CSQ: (\d+),(\d+)',
Async(msg='\+CBM: \d+,\d+,\d+,\d+,\d+', handle=cellid_update,
handle_extra = cellid_new),
Async(msg='\+CRING: (.*)', handle = incoming),
- Async(msg='\+CLIP: "([^"]*)",[0-9,]*', handle = incoming_number),
+ Async(msg='\+CLIP: "([^"]+)",[0-9,]*', handle = incoming_number),
]
class GsmD(AtChannel):
#
- def __init__(self):
+ def __init__(self, dostuff):
AtChannel.__init__(self, master = True)
- record('carrier','')
- record('cell','')
- record('incoming','')
- record('signal_strength','')
-
self.extra = None
self.flightmode = True
- # Monitor other external events which affect us
- d = dnotify.dir('/var/lib/misc/flightmode')
- self.flightmode_watcher = d.watch('active', self.check_flightmode)
+ self.state = None
- self.suspend_handle = suspend.monitor(self.do_suspend, self.do_resume)
+ if dostuff:
+ record('carrier','')
+ record('cell','')
+ record('incoming','')
+ record('signal_strength','')
- self.state = None
- # set the initial state
- self.set_state('flight')
+ # Monitor other external events which affect us
+ d = dnotify.dir('/var/lib/misc/flightmode')
+ self.flightmode_watcher = d.watch('active', self.check_flightmode)
+
+ self.suspend_handle = suspend.monitor(self.do_suspend, self.do_resume)
+
+ # set the initial state
+ self.set_state('flight')
- # Check the externally imposed state
- self.check_flightmode(self.flightmode_watcher)
+ # Check the externally imposed state
+ self.check_flightmode(self.flightmode_watcher)
+ else:
+ self.set_state('listenerr')
+
+
# and GO!
self.advance()
- def check_flightmode(self, f):
+ def check_flightmode(self, f = None):
try:
fd = open("/var/lib/misc/flightmode/active")
l = fd.read(1)
if self.state['stage'] == 'done':
self.lastrun[self.tasknum] = now
else:
- self.need_reset()
+ self.set_state('reset')
self.state = None
self.tasknum = None
(t, delay) = self.next_cmd()
+ log("advance %s chooses %d, %d" % (self.gstate, t, delay))
if delay:
- log("Sleeping for %f seconds" % (delay/1000))
+ log("Sleeping for %f seconds" % (delay/1000.0))
self.set_timeout(delay)
else:
self.tasknum = t
def takeline(self, line):
- if not line:
+ if self.extra:
+ if not self.extra.handle_extra(self, line):
+ self.extra = None
return False
- if self.extra:
- self.extra.handle_extra(self, line)
- self.extra = None
+ if not line:
return False
# Check for an async message
self.advance()
return False
-GsmD()
+a = GsmD(True)
+#b = GsmD(False)
c = gobject.main_context_default()
while True:
c.iteration()
--- /dev/null
+
+#
+# launcher plugin for watching for new sms messages.
+#
+# We watch /var/run/gsm-state/newsms and if that changes,
+# run "gsm-getsms -n"
+# When that completes, or after a timer, load new messages
+# and display from/time
+# Allow 'run sendsms -n'
+
+import dnotify
+from subprocess import Popen
+from storesms import SMSstore
+import gobject
+import sys,os,fcntl
+import re, time
+
+global gsmstate
+gsmstate = {};
+#
+# lastxt : time stamp of mos recent text to arrive, so we know if
+# current new text is actually new.
+gsmstate['lastxt'] = 0
+# watchsms: watch on 'newsms' file
+# watchmsg: watch on 'newmesg' file
+# watchcall: watch on 'incoming' file
+# store: the sms store
+# value: BUSY / name of incoming /
+# oncall: Boolean if making or receiving call
+gsmstate['oncall'] = False
+# tonestr: last string from which we had touch-tone chars
+# tonesent: list of touch-tone chars sent
+# channel: channel to GSMd for make/answer call. Holds 'call' object
+# book: phone book
+# newname
+# fullname
+# num
+# from
+# suspend-lock
+# buzz
+gsmstate['carriers'] = []
+gsmstate['carriers_valid'] = 0
+gsmstate['searching'] = False
+
+def suspend_lock():
+ try:
+ f = open("/var/run/suspend_disabled", "w+")
+ except:
+ f = None
+ if f:
+ try:
+ fcntl.lockf(f, fcntl.LOCK_SH | fcntl.LOCK_NB)
+ except:
+ f.close()
+ f = None
+ return f
+def suspend_unlock(f):
+ if f:
+ fcntl.lockf(f, fcntl.LOCK_UN)
+ f.close()
+ return None
+
+
+def sysfs_write(file, val):
+ try:
+ f = open(file, "w")
+ f.write(val)
+ f.close()
+ except:
+ pass
+
+def buzz_in(msec):
+ return gobject.timeout_add(msec, buzz_on)
+def buzz_off(ev):
+ if (ev):
+ gobject.source_remove(ev)
+ return None
+
+def buzz_on():
+ vib = "/sys/class/leds/neo1973:vibrator"
+ sysfs_write(vib+"/trigger", "none")
+ sysfs_write(vib+"/trigger", "timer")
+ sysfs_write(vib+"/delay_on", "50")
+ sysfs_write(vib+"/delay_off", "100")
+ vib_timer = gobject.timeout_add(1000, buzz_cancel)
+ return False
+def buzz_cancel():
+ vib = "/sys/class/leds/neo1973:vibrator"
+ sysfs_write(vib+"/trigger", "none")
+ return False
+
+
+def newmesg(cmd, obj):
+ global gsmstate
+ if len(obj.state) == 0:
+ init(obj)
+ if cmd == '_name':
+ return ('cmd', gsmstate['name'])
+ if cmd == '_options':
+ return obj.state['cmd'].options()
+ return obj.state['cmd'].event(cmd)
+
+def init(obj):
+ global gsmstate
+ obj.state['cmd'] = sys.modules['__main__'].CmdTask(',/usr/local/bin/sendsms -n,SendSMS')
+ gsmstate['name'] = '-'
+ d = dnotify.dir('/var/run/gsm-state')
+ w = d.watch('newsms', lambda f : got_sms(obj))
+ gsmstate['watchsms'] = w
+ for p in ['/media/card','/media/disk','/var/tmp']:
+ if os.path.exists(p):
+ pth = p+"/SMS"
+ break
+ d = dnotify.dir(pth)
+ w = d.watch('newmesg', lambda f : load_new(obj))
+ gsmstate['watchmsg'] = w
+ s = SMSstore(pth)
+ gsmstate['store'] = s
+ load_new(obj)
+
+def wrap(txt):
+ ln = 0
+ n = ''
+ w = ''
+ for l in txt:
+ if l != ' ' and l != '\n':
+ # still in word
+ w += l
+ continue
+ if ln + len(w) < 22:
+ # this word fits
+ if ln:
+ n += ' ' + w
+ else:
+ n += w
+ ln = 1
+ ln += len(w)
+ w = ''
+ continue
+ if ln == 0:
+ # line otherwise empty
+ n += w + '\n'
+ w = ''
+ continue
+ n += '\n' + w;
+ ln = len(w) + 1
+ w = ''
+ if w:
+ if ln:
+ n += ' ' + w
+ else:
+ n += w
+ return n
+
+def protect(txt):
+ txt = txt.replace('&', '&')
+ txt = txt.replace('<', '<')
+ txt = txt.replace('>', '>')
+ return txt
+
+def get_book():
+ global gsmstate
+ if not 'book' in gsmstate:
+ gsmstate['book'] = load_book("/media/card/address-book")
+ gsmstate['bookstamp'] = time.time()
+ return gsmstate['book']
+
+def load_new(obj):
+ global gsmstate
+ s = gsmstate['store']
+ (next, l) = s.lookup(None, 'NEW')
+ if len(l) == 0:
+ if gsmstate['name'] != '-':
+ obj.refresh(False)
+ gsmstate['name'] = '-'
+ gsmstate['lastxt'] = 0
+ return
+ cor = book_name(get_book(), l[0].correspondent)
+ if not cor:
+ cor = l[0].correspondent
+ else:
+ cor = cor[0]
+ txt = cor
+ if len(l) > 1:
+ txt += " (+%d)"% (len(l)-1)
+ txt += ': ' + l[0].text
+ txt = wrap(txt)
+ txt = protect(txt)
+ gsmstate['name'] = '<span size="10000">'+txt+'</span>'
+
+ wake = (l[0].stamp > gsmstate['lastxt'])
+ if wake:
+ gsmstate['lastxt'] = l[0].stamp
+ obj.refresh(wake)
+ return False
+
+def got_sms(obj):
+ Popen('gsm-getsms -n', shell=True, close_fds = True)
+ #the watcher does this.. gobject.timeout_add(1000, lambda *a : (load_new(obj)))
+ try:
+ f = open("/var/run/alert/sms", "w")
+ f.write("new")
+ f.close()
+ except:
+ pass
+
+##
+## incoming and outgoing calls
+##
+
+import atchan
+
+class Phone(atchan.AtChannel):
+ def __init__(self, refresh):
+ self.refresh = refresh
+ self.action = ''
+ atchan.AtChannel.__init__(self, master = False)
+ self.atconnect()
+
+ def power_done(self, line = None):
+ if line == "OK":
+ self.do_cmd()
+
+ def do_cmd(self):
+ if self.action == 'disconnect':
+ self.disconnect()
+ self.action = ''
+ if self.action == 'answer':
+ self.atcmd('%N0187;A')
+ self.action = ''
+ if self.action[0:5] == 'call:':
+ self.atcmd('%N0187;D'+self.action[5:]+';')
+ self.action = ''
+ if self.action == 'hangup':
+ self.atcmd('H')
+ self.action = 'disconnect'
+ if self.action[0:5] == 'tone:':
+ self.atcmd('+VTS='+self.action[5:])
+ self.action = ''
+ if self.action == 'rq-clear':
+ self.atcmd('+CUSD=0;H')
+ self.action = ''
+ if self.action[0:3] == 'rq ':
+ pre = ''
+ if self.action[3] == 'not0':
+ pre = "+CUSD=0;H;"
+ self.atcmd(pre + '+CUSD=' + self.action[3:])
+ self.action = ''
+
+ if self.action == 'search':
+ self.atcmd('+COPS=?', timeout = 40000)
+ self.action = ''
+
+ if self.action[0:7] == 'select:':
+ c = self.action[7:]
+ self.atcmd('+COPS=4,2,"%s"'%c, timeout=15000)
+ self.action = ''
+
+
+ def takeline(self, line):
+ global gsmstate
+
+ if line == 'AT-Command Interpreter ready':
+ return False
+
+ gsmstate['searching'] = False
+ if line == 'OK':
+ if self.pending:
+ self.pending = False
+ gobject.source_remove(self.timer)
+ self.timer = None
+ self.do_cmd()
+ return False
+
+ if line == 'BUSY':
+ gsmstate['value'] = 'BUSY'
+ self.refresh()
+ if line == 'NO CARRIER':
+ if gsmstate['oncall']:
+ reject()
+
+ if line[0:7] == "+CUSD: ":
+ m = re.match('^\+CUSD: ([012]),"(.*)"(,[0-9]+)?$', line)
+ if m:
+ if m.group(1) == '0':
+ gsmstate['request'] = 3
+ else:
+ gsmstate['request'] = 2
+ gsmstate['rqmsg'] = m.group(2)
+ self.refresh()
+
+ if line[0:7] == "+COPS: ":
+ gsmstate['carriers'] = re.findall('\((\d+),"([^"]*)","([^"]*)","([^"]*)"\)',line[7:])
+ gsmstate['carriers_valid'] = time.time()
+ self.refresh()
+
+ return False
+
+
+ def act(self, cmd):
+ self.action = cmd
+ if not self.pending and not self.command_pending:
+ self.do_cmd()
+
+
+def load_book(file):
+ try:
+ f = open(file)
+ except:
+ f = open("/home/neilb/home/mobile-numbers-jan-08")
+ rv = []
+ for l in f:
+ x = l.split(';')
+ rv.append([x[0],x[1]])
+ rv.sort(lambda x,y: cmp(x[0],y[0]))
+ return rv
+
+def book_lookup(book, name, num):
+ st=[]; mid=[]
+ for l in book:
+ if name.lower() == l[0][0:len(name)].lower():
+ st.append(l)
+ elif l[0].lower().find(name.lower()) >= 0:
+ mid.append(l)
+ st += mid
+ if len(st) == 0:
+ return [None, None]
+ if num >= len(st):
+ num = -1
+ return st[num]
+
+def book_speed(book, sym):
+ i = book_lookup(book, sym, 0)
+ if i[0] == None or i[0] != sym:
+ return None
+ j = book_lookup(book, i[1], 0)
+ if j[0] == None:
+ return (i[1], i[0])
+ return (j[1], j[0])
+
+def book_name(book, num):
+ if len(num) < 8:
+ return None
+ for ad in book:
+ if len(ad[1]) >= 8 and num[-8:] == ad[1][-8:]:
+ return ad
+ return None
+
+incoming = []
+def incoming_flush(start, end, number):
+ global incoming
+ if start:
+ incoming.append((start, end, number))
+def load_incoming():
+ global incoming
+ incoming = []
+
+ f = open("/var/log/incoming")
+ start = None; end=None; number=None
+ for l in f:
+ w = l.split()
+ if len(w) != 3:
+ continue
+ if w[2] == '-call-':
+ incoming_flush(start, end, number)
+ start = (w[0], w[1])
+ number = None; end = None
+ elif w[2] == '-end-':
+ end = (w[0], w[1])
+ incoming_flush(start, end, number)
+ start = None; end = None; number = None
+ else:
+ number = w[2]
+ if not start:
+ start = (w[0], w[1])
+ f.close()
+
+outgoing = []
+def outgoing_flush(start, end, number):
+ global outgoing
+ if start:
+ outgoing.append((start, end, number))
+def load_outgoing():
+ global outgoing
+ outgoing = []
+
+ f = open("/var/log/outgoing")
+ start = None; end=None; number=None
+ for l in f:
+ w = l.split()
+ if len(w) != 3:
+ continue
+ if w[2] == '-end-':
+ end = (w[0], w[1])
+ outgoing_flush(start, end, number)
+ start = None; end = None; number = None
+ else:
+ outgoing_flush(start, end, number)
+ start = (w[0], w[1])
+ number = w[2]
+ f.close()
+
+incoming_map = []
+def incoming_lookup(num):
+ global incoming, incoming_map
+ if num == 1:
+ load_incoming()
+ ln = 0
+ a = {}
+ for start, end, number in incoming:
+ if number == None:
+ continue
+ ln += 1
+ a[number] = ln
+ b = {}
+ for x in a:
+ b[a[x]] = x
+ b = b.keys()
+ k.sort()
+ incoming_map = []
+ for n in k:
+ incoming_map.append(b[n])
+ if num > len(incoming_map):
+ num = len(incoming_map)
+ return ["Caller %d" %num, incoming_map[-num]]
+
+def name_lookup(book, str):
+ # We need to report
+ # - a number - to dial
+ # - optionally a name that is associated with that number
+ # - optionally a new name to save the number as
+ # The name is normally alpha, but can be a single digit for
+ # speed-dial
+ # Dots following a name allow us to stop through multiple matches.
+ # So input can be:
+ # A single symbol.
+ # This is a speed dial. It maps to name, then number
+ # A string of >1 digits
+ # This is a literal number, we look up name if we can
+ # A string of dots
+ # This is a look up against recent incoming calls
+ # We look up name in phone book
+ # A string starting with alpha, possibly ending with dots
+ # This is a regular lookup in the phone book
+ # A number followed by a string
+ # This provides the string as a new name for saving
+ # A string of dots followed by a string
+ # This also provides the string as a newname
+ # An alpha string, with dots, followed by '+'then a single symbol
+ # This saves the match as a speed dial
+ #
+ # We return a triple of (number,oldname,newname)
+ if re.match('^[A-Za-z0-9]$', str):
+ # Speed dial lookup
+ s = book_speed(book, str)
+ print "speed", str, s
+ if s:
+ return (s[0], s[1], None)
+ return None
+ m = re.match('^(\+?\d+|[*#][*#0-9]*)([A-Za-z][A-Za-z0-9 ]*)?$', str)
+ if m:
+ # Number and possible newname
+ s = book_name(book, m.group(1))
+ if s:
+ return (m.group(1), s[0], m.group(2))
+ else:
+ return (m.group(1), None, m.group(2))
+ m = re.match('^(\.+)([A-Za-z][A-Za-z0-9 ]*)?$', str)
+ if m:
+ # dots and possible newname
+ i = incoming_lookup(len(m.group(1)))
+ s = book_name(book, i[1])
+ if s:
+ return (i[1], s[0], m.group(2))
+ else:
+ return (i[1], i[0], m.group(2))
+ m = re.match('^([A-Za-z][A-Za-z0-9 ]*)(\.*)(\+[A-Za-z0-9])?$', str)
+ if m:
+ # name and dots
+ speed = None
+ if m.group(3):
+ speed = m.group(3)[1]
+ i = book_lookup(book, m.group(1), len(m.group(2)))
+ if i[0]:
+ return (i[1], i[0], speed)
+ return None
+
+def encode(str):
+ return re.sub("&", "&", str)
+
+def incoming(cmd, obj):
+ global gsmstate
+
+ if len(obj.state) == 0:
+ init_incoming(obj)
+ gsmstate['obj'] = obj
+ havename = False; havenum = False
+ if gsmstate['oncall']:
+ # any new characters get sent via GSM if valid
+ ts = gsmstate['tonestr']
+ if obj.current_input[0:len(ts)] != ts:
+ # something has changed, reset
+ ts = obj.current_input
+
+ xtra = obj.current_input[len(ts):]
+ for c in xtra:
+ if c in "0123456789#*":
+ gsmstate['channel'].act('tone:' + c)
+ gsmstate['tonesent'] += c
+ gsmstate['tonestr'] = obj.current_input
+
+ x = name_lookup(gsmstate['book'], obj.current_input)
+ if x:
+ havenum = x[0]
+ havename= x[1]
+ gsmstate['newname'] = x[2]
+ gsmstate['fullname'] = havename
+ gsmstate['num'] = havenum
+ else:
+ havenum = False
+
+ if cmd == '_name':
+ if 'from' in gsmstate:
+ v = gsmstate['from']
+ elif 'value' in gsmstate:
+ v = gsmstate['value']
+ else:
+ v = False
+ if gsmstate['oncall'] and gsmstate['tonesent']:
+ v = 'Sent:' + gsmstate['tonesent']
+ if not v and gsmstate['request'] > 0:
+ if gsmstate['request'] == 1:
+ v = "... awaiting response ..."
+ else:
+ v = protect(wrap(gsmstate['rqmsg']))
+ if not v and havenum:
+ if gsmstate['newname']:
+ v = 'Save: ' + encode(gsmstate['newname'])
+ elif havename:
+ v = 'Call: ' + encode(havename)
+ else:
+ v = 'Call: ' + encode(havenum)
+ print 'now v = ', v
+ if not v and gsmstate['lastcaller']:
+ v = '(' + gsmstate['lastcaller'] + ')'
+ return ('cmd', v)
+ if cmd == '_options':
+ if gsmstate['oncall']:
+ o = ["Mute", "Hang Up"]
+ elif gsmstate['value']:
+ o = ["Answer", "Reject"]
+ elif gsmstate['request'] > 0:
+ if gsmstate['request'] == 2:
+ o = ["Quit", "Send Answer (%s)" % havenum]
+ else:
+ o = ["Quit"]
+ elif havenum:
+ if gsmstate['newname']:
+ o = ['Save ' + havenum ]
+ elif re.match('^[*#][*#0-9]*#$', havenum):
+ o = ["Request " + havenum ]
+ else:
+ o = ["Call " + havenum ]
+ elif gsmstate['lastcallnum']:
+ o = [ "Call-back", "Discard" ]
+ else:
+ o = ['Speed\nDial', 'Received\ncalls', 'Dialed\nNumbers']
+ return o
+ if gsmstate['oncall']:
+ if cmd == 0:
+ # Mute
+ return
+ if cmd == 1:
+ # Hang up
+ reject()
+ return
+ elif gsmstate['value']:
+ # incoming
+ gsmstate['lastcaller'] = ''
+ gsmstate['lastcallnum'] = ''
+ if cmd == 0:
+ answer(obj)
+ elif cmd == 1:
+ reject()
+ elif gsmstate['request'] > 0:
+ if cmd == 0:
+ # quit - abort and forget
+ gsmstate['request'] = 0
+ gsmstate['rqmsg'] = ''
+ if 'channel' in gsmstate:
+ chan = gsmstate['channel']
+ if chan:
+ chan.act('rq-clear')
+ elif cmd == 1:
+ # send update message
+ if 'channel' in gsmstate:
+ chan = gsmstate['channel']
+ if chan:
+ chan.act('rq 1,"%s"' % havenum)
+ gsmstate['request'] = 1
+
+ elif havenum and gsmstate['newname']:
+ f = open('/media/card/address-book', 'a')
+ nn = gsmstate['newname']
+ if len(nn) == 1 and havename:
+ # new speed dial
+ f.write(nn + ';' + havename + ';\n')
+ else:
+ f.write(nn + ';' + havenum + ';\n')
+ f.close()
+ gsmstate['book'] = load_book("/media/card/address-book")
+ gsmstate['bookstamp'] = time.time()
+ elif havenum:
+ if cmd == 0:
+ num = gsmstate['num']
+ if re.match('^[*#][*#0-9]*#$', num):
+ place_request(obj, num)
+ else:
+ place_call(num, lambda: obj.refresh(False), obj.current_input)
+ elif gsmstate['lastcallnum']:
+ if cmd == 0:
+ place_call(gsmstate['lastcallnum'],
+ lambda: obj.refresh(False), obj.current_input)
+ elif cmd == 1:
+ gsmstate['lastcaller'] = ''
+ gsmstate['lastcallnum'] = ''
+ obj.refresh(False)
+
+ elif cmd == 0:
+ obj.set_tasks(tasklist_speed_dial(), 0)
+ elif cmd == 1:
+ obj.set_tasks(tasklist_received(True), 0)
+ elif cmd == 2:
+ obj.set_tasks(tasklist_received(False), 0)
+
+
+def init_incoming(obj):
+ global gsmstate
+ print "init incoming"
+ obj.state['cmd'] = None
+ gsmstate['value'] = ''
+ gsmstate['num'] = False
+ gsmstate['name'] = False
+ gsmstate['suspend-lock'] = False
+ gsmstate['buzz'] = False
+ gsmstate['request'] = 0
+ gsmstate['lastcaller'] = ''
+ gsmstate['lastcallnum'] = ''
+ # request values:
+ # 1 == waiting reply
+ # 2 == have intermediate reply
+ # 3 == have final reply
+ gsmstate['rqmsg'] = ''
+ get_book()
+ d = dnotify.dir('/var/run/gsm-state')
+ w = d.watch('incoming', lambda f : got_incoming_later(obj))
+ gsmstate['watchcall'] = w
+
+def got_incoming_later(obj):
+ gobject.idle_add(lambda : got_incoming(obj))
+
+def got_incoming(obj):
+ global gsmstate
+ try:
+ f = open("/var/run/gsm-state/incoming")
+ l = f.readline().strip()
+ f.close()
+ except:
+ l = ""
+ print 'got l=', l
+ if l != gsmstate['value']:
+ obj.refresh(not not l)
+ gsmstate['value'] = l
+ if l:
+ try:
+ f = open("/var/run/alert/ring", "w")
+ f.write("ring")
+ f.close()
+ except:
+ pass
+ if 'channel' not in gsmstate:
+ try:
+ chan = Phone(lambda : obj.refresh(False))
+ gsmstate['channel'] = chan
+ except:
+ gsmstate['channel'] = None
+ else:
+ chan = gsmstate['channel']
+ else:
+ try:
+ os.unlink("/var/run/alert/ring")
+ except:
+ pass
+ reject()
+ fromnum = book_name(gsmstate['book'], l)
+ if fromnum == None:
+ gsmstate['from'] = l
+ else:
+ gsmstate['from'] = fromnum[0]
+ if len(gsmstate['from']) > 1:
+ gsmstate['lastcaller'] = gsmstate['from']
+ gsmstate['lastcallnum'] = l
+ return False
+
+def answer(obj):
+ # Need to
+ # lock against suspend
+ # alsactl -f /root/usr/share/openmoko/scenarios/gsmhandset.state restore
+ # silence 'sound'
+ # send command AT%N0187
+ # send command ATA
+ # set flag that we are on a call ['oncall'] = True
+ # pop up a touch-tone thing
+ # listen for carrier to drop
+ global gsmstate
+ try:
+ os.unlink("/var/run/alert/ring")
+ except:
+ pass
+ gsmstate['suspend-lock'] = suspend_lock()
+ Popen(['alsactl', '-f', '/root/usr/share/openmoko/scenarios/gsmhandset.state',
+ 'restore' ], shell=False, close_fds = True)
+ if 'channel' in gsmstate:
+ chan = gsmstate['channel']
+ if chan:
+ gsmstate['buzz'] = buzz_in(105000)
+ chan.act('answer')
+ gsmstate['oncall'] = True
+ gsmstate['tonestr'] = obj.current_input
+ gsmstate['tonesent'] = ''
+
+def calllog(key, msg):
+ f = open('/var/log/' + key, 'a')
+ now = time.strftime("%Y-%m-%d %H:%M:%S")
+ f.write(now + ' ' + msg + "\n")
+ f.close()
+
+def place_call(num, refresh, input):
+ # Need to
+ # lock against suspend
+ # alsactl -f /root/usr/share/openmoko/scenarios/gsmhandset.state restore
+ # silence 'sound'
+ # send command AT%N0187
+ # send command ATDwhatever;
+ # set flag that we are on a call ['oncall'] = True
+ # pop up a touch-tone thing
+ # listen for carrier to drop
+ global gsmstate
+ gsmstate['suspend-lock'] = suspend_lock()
+ Popen(['alsactl', '-f', '/root/usr/share/openmoko/scenarios/gsmhandset.state',
+ 'restore' ], shell=False, close_fds = True)
+ if 'channel' not in gsmstate:
+ chan = Phone(refresh)
+ gsmstate['channel'] = chan
+
+ gsmstate['buzz'] = buzz_in(105000)
+ chan = gsmstate['channel']
+ gsmstate['oncall'] = True
+ gsmstate['tonestr'] = input
+ gsmstate['tonesent'] = ''
+ chan.act('call:'+ num)
+ calllog('outgoing',num)
+
+def place_request(obj, num):
+ global gsmstate
+ if 'channel' not in gsmstate:
+ chan = Phone(lambda : obj.refresh(False))
+ gsmstate['channel'] = chan
+ chan = gsmstate['channel']
+ gsmstate['request'] = 1
+ chan.act('rq 0,"%s"'% num)
+
+
+
+def reject():
+ # Need to
+ # alsactl -f /root/usr/share/openmoko/scenarios/stereoout.state restore
+ # remove silence
+ # sent ATH
+ # remove touchtone thing
+ global gsmstate
+ if 'channel' in gsmstate:
+ chan = gsmstate['channel']
+ if chan:
+ chan.act('hangup')
+ del gsmstate['channel']
+ Popen(['alsactl', '-f', '/root/usr/share/openmoko/scenarios/stereoout.state',
+ 'restore' ], shell=False, close_fds = True)
+ calllog('outgoing','-end-')
+ gsmstate['oncall'] = False
+ gsmstate['value'] = ''
+ gsmstate['suspend-lock'] = suspend_unlock(gsmstate['suspend-lock'])
+ gsmstate['buzz'] = buzz_off(gsmstate['buzz'])
+
+
+
+def speed(n):
+ return lambda cmd, obj: speed_dial(cmd, obj, n)
+
+def empty(cmd, obj):
+ if cmd == '_name':
+ return ('cmd', '')
+ if cmd == '_options':
+ return []
+
+def speed_dial(cmd, obj, n):
+ global gsmstate
+ if 'stamp' not in obj.state or obj.state['stamp'] != gsmstate['bookstamp']:
+ s = book_speed(get_book(), n)
+ obj.state['num'] = s
+ obj.state['stamp'] = gsmstate['bookstamp']
+ else:
+ s = obj.state['num']
+ if s == None:
+ return empty(cmd, obj)
+ (num, name) = s
+ if cmd == '_name':
+ return ('cmd', n+': '+name)
+ if cmd == '_options':
+ if gsmstate['oncall']:
+ return ['Mute', 'Hang Up']
+ return [ 'Call ' + num]
+ if gsmstate['oncall']:
+ if cmd == 0:
+ # Mute
+ return
+ if cmd == 1:
+ # Hang up
+ reject()
+ return
+ if cmd == 0:
+ place_call(num, lambda: obj.refresh(False), obj.current_input)
+
+
+############################################
+# operator selecting
+# Display current operator (from /var/run/gsm-state/carrier)
+# If we know options, then one button for each carrier.
+# If we don't, then one button for 'search carriers'
+
+def carrier(cmd, obj):
+ global gsmstate
+
+ if len(obj.state) == 0:
+ obj.state['carrier_name'] = ''
+
+ if cmd == '_name' :
+ if gsmstate['searching']:
+ return ('cmd', '(searching)')
+ else:
+ try:
+ f = open("/var/run/gsm-state/carrier")
+ l = f.readline().strip()
+ f.close()
+ except OSError:
+ l = ''
+ if not l:
+ l = '(unknown)'
+ return ('cmd', l)
+
+ if cmd == '_options':
+ if gsmstate['carriers_valid'] + 5*60 < time.time():
+ return [ 'Search Carriers' ]
+ rv = []
+ for v in gsmstate['carriers']:
+ rv.append(v[2])
+ return rv
+
+ if 'channel' not in gsmstate:
+ try:
+ chan = Phone(lambda : obj.refresh(False))
+ gsmstate['channel'] = chan
+ except:
+ gsmstate['channel'] = None
+ else:
+ chan = gsmstate['channel']
+
+ if gsmstate['carriers_valid'] + 5*60 < time.time():
+ chan.act('search')
+ gsmstate['searching'] = True
+ return
+ if cmd >= len(gsmstate['carriers']):
+ return
+ c = gsmstate['carriers'][cmd]
+ chan.act('select:' + c[3])
+
+############################################
+# 'tasklist' for received calls
+class tasklist_received(sys.modules['__main__'].tasklist):
+ def __init__(self, income):
+ sys.modules['__main__'].tasklist.__init__(self)
+ self.incoming = income
+ if income:
+ self.name = 'Received Calls'
+ else:
+ self.name = 'Dialled Numbers'
+
+ def start_refresh(self):
+ global incoming, outgoing
+ if self.incoming:
+ load_incoming()
+ self.list = incoming
+ else:
+ load_outgoing()
+ self.list = outgoing
+
+ def info(self, n):
+ global incoming, gsmstate
+ (start, end, number) = self.list[-1-n]
+ if number == None:
+ number = "-private-number-"
+ else:
+ s = book_name(gsmstate['book'], number)
+ if s != None:
+ number = s[0]
+ return 'cmd', ('<span size="12000">%s</span>\n<span size="10000">%s %s</span>'
+ % (number, start[0], start[1]))
+ def options(self, n):
+ global gsmstate
+ if gsmstate['oncall']:
+ return []
+ (start, end, number) = self.list[-1-n]
+ if number == None:
+ return []
+ return ['Call back %s' % number]
+ def event(self, n, ev):
+ global gsmstate
+ if gsmstate['oncall']:
+ if ev == 0:
+ # Mute
+ return
+ if ev == 1:
+ reject()
+ if ev == 0:
+ (start, end, number) = self.list[-1-n]
+ gsmstate['lastcallnum'] = number
+ gsmstate['lastcaller'] = number
+ gsmstate['obj'].refresh(True)
+
+
+
+class tasklist_speed_dial(sys.modules['__main__'].tasklist):
+ def __init__(self):
+ sys.modules['__main__'].tasklist.__init__(self)
+ self.name = 'Speed Dials'
+
+ def start_refresh(self):
+ self.list = []
+ b = gsmstate['book']
+ print "Start Refresh"
+ for i in range(32,96):
+ i = book_speed(b, chr(i))
+ if i == None:
+ continue
+ self.list.append(i)
+
+ def info(self, n):
+ num, name = self.list[n]
+ return 'cmd', name
+
+ def options(self, n):
+ num, name = self.list[n]
+ return ['Call ' + num]
+ def event(self, n, ev):
+ if ev == 0:
+ global gsmstate
+ num, name = self.list[n]
+ gsmstate['lastcallnum'] = num
+ gsmstate['lastcaller'] = name
+ gsmstate['obj'].refresh(True)
--- /dev/null
+def sms_decode(msg):
+ #msg is a 7-in-8 encoding of a longer message.
+ pos = 0
+ carry = 0
+ str = ''
+ while msg:
+ c = msg[0:2]
+ msg = msg[2:]
+ b = int(c, 16)
+
+ if pos == 0:
+ if carry:
+ str += chr(carry + (b&1)*64)
+ carry = 0
+ b /= 2
+ else:
+ b = (b << (pos-1)) | carry
+ carry = (b & 0xff80) >> 7
+ b &= 0x7f
+ if (b & 0x7f) != 0:
+ str += chr(b&0x7f)
+ pos = (pos+1) % 7
+ return str
+
+import sys
+print sms_decode(sys.argv[1])
# trivial library for including tracing in programs
# It can be turned on with PYTRACE=1 in environment
-import os
+import os,time,sys
tracing = False
def log(*mesg):
if tracing:
+ print time.ctime(),
for m in mesg:
print m,
print
-
-
+ sys.stdout.flush()