4 # Error cases to handle
5 # if +CFUN:6, then "+CFUN=1" can produce "+CME ERROR: operation not supported"
6 # close/open sometimes fixes.
7 # +CIMI can produce +CME ERROR: operation not allowed
8 # close/open seems to fix.
9 # +CLIP? can produce CME ERROR: network rejected request
10 # don't know what fixed it
11 # +CSCB=1 can produce +CMS ERROR: 500
12 # just give up and retry later.
13 # CFUN:4 can be fixed by writing CFUN=1
14 # CFUN:6 needs close/open
15 # _OPSYS=3,2 can produce ERROR. Just try much later I guess.
21 # repeat status until full success
22 # Keep suspend blocked while any messages are queued.
23 # Need to detect reset and reset configs
24 # use CLCC to get number
29 from atchan import AtChannel
30 import dnotify, suspend
31 from tracing import log
32 from subprocess import Popen
33 from evdev import EvDev
38 def safe_read(file, default=''):
49 def record(key, value):
52 f = open('/run/gsm-state/.new.' + key, 'w')
55 os.rename('/run/gsm-state/.new.' + key,
56 '/run/gsm-state/' + key)
58 # I got this once on the rename, don't know why
60 recording[key] = value
62 def recall(key, nofile = ""):
63 return safe_read("/run/gsm-state/" + key, nofile)
66 def call_log(key, msg):
67 f = open('/var/log/' + key, 'a')
68 now = time.strftime("%Y-%m-%d %H:%M:%S")
69 f.write(now + ' ' + msg + "\n")
73 def call_log_end(key):
75 call_log(key, '-end-')
78 def set_alert(key, value):
79 path = '/run/alert/' + key
80 tpath = '/run/alert/.new.' + key
91 os.rename(tpath, path)
96 def gpio_set(line, val):
97 file = "/sys/class/gpio/gpio%d/value" % line
100 fd.write("%d\n" % val)
107 # There are three ways we interact with suspend
108 # 1/ we block suspend when something important is happening:
109 # - phone call active
110 # - during initialisation?
111 # - AT command with timeout longer than 10 seconds.
112 # 2/ on suspend request we performs some checks before allowing the suspend,
113 # and send notificaitons on resume
114 # - If an AT command or async is pending, we don't ack the suspend request
115 # until a reply comes.
116 # 3/ Some timeouts can wake up from suspend - e.g. CFUN poll.
118 # Each Engine can individually block suspend. The number that are
119 # active is maintained separately and suspend is blocked when that number
120 # is positive. On turn-off, everything is forced to allow suspend.
121 # When suspend is pending, "set_suspend" is called on each engine.
122 # An engine an return False to say that it doesn't want to suspend just now.
123 # It should have started blocking, or signalled something.
124 # Engines are informed for Resume when it happens.
126 # A 'retry' can have a non-resuming timeout and a resuming timeout.
127 # The resuming timeout should be set while suspend is blocked, but can be
128 # extended at other times.
130 # Important things should happen with a clear chain of suspend blocking.
131 # e.g. The 'incoming' must be checked in the presuspend handler and create a
132 # suspend block if needed. That should be retained until txt messages are read
133 # or phone call completes.
134 # CFUN resuming timeout should block suspend until an answer is read and it is
136 # On startup we block until everyone has registerd the power-on. etc.
144 # - I keep getting EOF - why is that?
145 # - double close is bad
146 # - modem delaying of suspend doesn't quite seem right.
151 self.handle = suspend.blocker(False)
161 self.handle.unblock()
167 self.wakealarm = None
168 # delay is the default timeout for 'retry'
170 # resuming_delay is the default timeout if we suspend
171 self.resuming_delay = None
172 # 'blocked' if true if we asked to block suspend.
174 # 'blocking' is a handle if we refused to let suspend continue yet.
177 def set_on(self, state):
179 def set_service(self, state):
181 def set_resume(self):
183 def set_suspend(self):
186 def retry(self, delay = None, resuming = None):
188 gobject.source_remove(self.timer)
190 if not delay is False:
193 self.timer = gobject.timeout_add(delay, self.call_retry)
194 if not resuming is False:
196 resuming = self.resuming_delay
198 when = time.time() + resuming
199 if self.wakealarm and not self.wakealarm.s:
200 self.wakealarm = None
202 self.wakealarm.realarm(when)
204 self.wakealarm = wakealarm.wakealarm(when,
207 self.wakealarm.release()
208 self.wakealarm = None
210 def wake_retry(self, handle):
211 self.wakealarm = None
215 def call_retry(self):
218 # Must be manually reset
226 log( "block by", self)
235 log( "unblock by", self)
254 global engines, state, sus
255 if state.on == value:
258 log("set on to", value)
269 log("done setting on to", value)
271 def set_service(value):
272 global engines, state
273 if state.service == value:
275 state.service = value
279 def set_suspend(blocker):
280 global engines, state, sus
284 suspend.abort_cycle()
297 global engines, state
298 if not state.suspend:
300 state.suspend = False
305 def watch(dir, base, handle):
307 if not dir in watchers:
308 watchers[dir] = dnotify.dir(dir)
309 watchers[dir].watch(base, lambda x: gobject.idle_add(handle, x))
314 # manage a channel or two, allowing requests to be
315 # queued and handling async notifications.
316 # Each request has text to send, a reply handler, and
317 # a timeout. The reply handler indicates if more is expected.
318 # Queued message might be marked 'ignore in suspend'.
320 # when told to switch on, raise the GPIO, open the devices
321 # send ATE0V1+CMEE=2;+CRC=1;+CMGF=0
322 # Then process queue.
323 # Every message that looks like it is async is handled as such
324 # and may have follow-ons.
325 # Every command should eventually be followed by OK or ERROR
326 # or +CM[ES] ERROR or timeout.
327 # After a timeout we probe with 'AT' to get an 'OK'
328 # If no response and close/open doesn't work we rmmod ehci_omap and
331 # When told to switch off, we drop the GPIO and queue a $QCPWRDN
333 # When an engine schedules an 'at' command, it either will get at
334 # least one callback, or will get 'set_on' to False, and then True
337 class CarrierDetect(AtChannel):
338 # on the hso modem in the GTA04, the 'NO CARRIER' signal
339 # arrives on the 'Modem' port, not on the 'Application' port.
340 # So we listen to the 'Modem' port, and report any
341 # 'NO CARRIER' we see - or indeed anything that we see.
342 def __init__(self, path, main):
343 AtChannel.__init__(self, path = path)
346 def takeline(self, line):
347 self.main.takeline(line)
349 class modem(Engine,AtChannel):
351 Engine.__init__(self)
352 AtChannel.__init__(self, "/dev/ttyHS_Application")
353 self.altchan = CarrierDetect("/dev/ttyHS_Modem", self)
356 self.async_pending = None
357 self.pending_command = None
358 self.suspended = False
359 self.open_queued = False
361 def set_on(self, state):
366 self.async_pending = None
367 self.pending_command = None
375 Popen("rmmod ehci_hcd", shell=True).wait();
377 def set_suspend(self):
378 self.suspended = True
379 if self.pending_command or self.async_pending:
380 log("Modem delays suspend")
382 log("Modem allows suspend")
386 def set_resume(self):
388 self.suspended = False
390 self.cancel_timeout()
392 self.pending_command = self.ignore
397 self.cancel_timeout()
398 self.altchan.disconnect()
406 while not self.connected:
407 time.sleep(sleep_time)
410 if self.altchan.connect(5):
414 print "will now reboot"
416 Popen("/sbin/reboot -f", shell=True).wait()
417 Popen('rmmod ehci_omap; rmmod ehci-hcd; modprobe ehci-hcd; modprobe ehci_omap', shell=True).wait()
423 l = self.wait_line(100)
425 l = self.wait_line(100)
426 self.pending_command = self.ignore
427 self.open_queued = False
428 self.atcmd('V1E0+CMEE=2;+CRC=1;+CMGF=0')
431 if not self.open_queued:
432 self.open_queued = True
433 gobject.idle_add(self.open)
436 if self.open_queued or self.pending_command or self.queue or self.async_pending:
437 print "cannot unblock:",self.open_queued, self.pending_command, self.queue, self.async_pending, self.suspended
439 print "modem unblock"
443 def takeline(self, line):
445 # Just an extra '\r', ignore it.
450 # reply for recent command
451 # final OK/ERR for recent command
456 if self.async_pending:
457 if self.async_pending(line):
459 self.async_pending = None
461 if self.async_match(line):
463 return self.async_pending == None
464 if self.pending_command:
465 if not self.pending_command(line):
466 self.pending_command = None
467 if re.match('^(OK|\+CM[ES] ERROR|ERROR)', line):
468 self.pending_command = None
469 if self.pending_command:
471 gobject.idle_add(self.check_queue)
475 if self.pending_command:
476 self.pending_command(None)
477 self.pending_command = None
478 if self.async_pending:
479 self.async_pending(None)
480 self.async_pending = None
481 self.pending_command = self.probe
483 self.atcmd('', 10000)
485 def probe(self, line):
491 def check_queue(self):
492 if not self.queue or self.pending_command or self.async_pending:
495 if not self.connected:
499 cmd, cb, timeout = self.queue.pop()
502 self.pending_command = cb
503 self.atcmd(cmd, timeout)
505 def ignore(self, line):
506 ## assume more to come until we see OK or ERROR
509 def at_queue(self, cmd, handle, timeout):
510 if not self.connected:
513 self.queue.append((cmd, handle, timeout))
514 gobject.idle_add(self.check_queue)
516 def clear_queue(self):
518 cmd, cb, timeout = self.queue.pop()
522 def async_match(self, line):
523 for prefix, handle, extra in self.async:
524 if line[:len(prefix)] == prefix:
527 self.async_pending = extra
529 self.set_timeout(1000)
533 def request_async(self, prefix, handle, extra):
534 self.async.append((prefix, handle, extra))
539 def request_async(prefix, handle, extras = None):
540 """ 'handle' should return True for a real match,
541 False if it was a false positive.
542 'extras' should return True if more is expected, or
543 False if there are no more async extras
546 mdm.request_async(prefix, handle, extras)
548 def at_queue(cmd, handle, timeout = 5000):
550 mdm.at_queue(cmd, handle, timeout)
554 # monitor the 'flightmode' file. Powers the modem
555 # on or off. Reports off or on to all handlers
556 # uses CFUN and PWRDN commands
557 class flight(Engine):
559 Engine.__init__(self)
560 watch('/var/lib/misc/flightmode','active', self.check)
561 gobject.idle_add(self.check)
563 def check(self, f = None):
565 l = safe_read('/var/lib/misc/flightmode/active')
566 gobject.idle_add(self.turn_on, len(l) == 0)
568 def turn_on(self, state):
572 def set_suspend(self):
574 l = safe_read('/var/lib/misc/flightmode/active')
575 if len(l) == 0 and not state.on:
577 gobject.idle_add(self.turn_on, True)
578 elif len(l) > 0 and state.on:
580 gobject.idle_add(self.turn_on, False)
587 # transitions from 'on' to 'service' and reports
588 # 'no-service' when 'off' or no signal.
590 # +COPS=0 - auto select
591 # +COPS=1,2,50502 - select specific (2 == numeric)
592 # +COPS=3,1 - report format is long (1 == long)
593 # +COPS=4,2,50502 - select specific with auto-fallback
594 # http://www.shapeshifter.se/2008/04/30/list-of-at-commands/
595 class register(Engine):
597 Engine.__init__(self)
598 self.resuming_delay = 600
600 def set_on(self, state):
607 at_queue('+CFUN?', self.gotline, 10000)
609 def wake_retry(self, handle):
614 at_queue('+CFUN?', self.got_wake_line, 8000)
617 def got_wake_line(self, line):
618 log("CFUN wake for %s" % line)
622 def gotline(self, line):
624 print "retry 1000 gotline not line"
628 m = re.match('\+CFUN: (\d)', line)
631 if n == '0' or n == '4':
633 at_queue('+CFUN=1', self.did_set, 10000)
642 print "retry end gotline"
646 def did_set(self, line):
647 print "retry 100 did_set"
656 # While there is service, monitor signal strength.
657 class signal(Engine):
659 Engine.__init__(self)
660 request_async('_OSIGQ:', self.get_async)
664 def set_service(self, state):
666 at_queue('_OSQI=1', None)
668 record('signal_strength', '-/32')
671 def get_async(self, line):
672 m = re.match('_OSIGQ: ([0-9]+),([0-9]+)', line)
679 at_queue('+CSQ', self.get_csq)
681 def get_csq(self, line):
685 m = re.match('\+CSQ: ([0-9]+),([0-9]+)', line)
689 def set(self, strength):
690 record('signal_strength', '%s/32'%strength)
699 if self.zero_count > 10:
707 # There are three ways we interact with suspend
708 # 1/ we block suspend when something important is happening:
709 # - any AT commands pending or active
710 # - phone call active
711 # - during initialisation?
712 # 2/ on suspend request we performs some checks before allowing the suspend,
713 # and send notificaitons on resume
714 # 3/ Some timeouts can wake up from suspend - e.g. CFUN poll.
715 # When a suspend is pending, check call state and
716 # sms gpio state and possibly abort.
719 """initialise a counter to '1' and when it hits zero
722 def __init__(self, cb):
733 class suspender(Engine):
735 Engine.__init__(self)
736 self.mon = suspend.monitor(self.want_suspend, self.resuming)
738 def want_suspend(self):
739 b = Blocker(lambda : self.mon.release())
745 gobject.idle_add(set_resume)
747 add_engine(suspender())
750 # when service, monitor cellid etc.
751 class Cellid(Engine):
753 Engine.__init__(self)
754 request_async('+CREG:', self.async)
755 request_async('+CBM:', self.cellname, extras=self.the_name)
762 def set_on(self, state):
766 def set_resume(self):
767 # might have moved while we slept
768 if time.time() > self.last_try + 2*60:
772 def set_service(self, state):
777 record('carrier','-')
780 self.last_try = time.time()
781 at_queue('+CREG?', self.got, 5000)
788 if line[:9] == '+CREG: 0,':
789 at_queue('+CREG=2', None)
790 m = re.match('\+CREG: 2,(\d)(,"([^"]*)","([^"]*)")', line)
795 def async(self, line):
796 m = re.match('\+CREG: ([012])(,"([^"]*)","([^"]*)")?$', line)
798 if m.group(1) == '1' and m.group(2):
801 if m.group(1) == '0':
806 def cellname(self, line):
807 # get something like +CBM: 1568,50,1,1,1
808 # don't know what that means, just collect the 'extra' line
809 # I think the '50' means 'this is a cell id'. I should
810 # probably test for that.
811 # Subsequent lines are the cell name.
812 m = re.match('\+CBM: \d+,\d+,\d+,\d+,\d+', line)
814 #ignore CBM content for now.
819 def the_name(self, line):
823 l = re.sub('[^!-~]+',' ', self.newname)
825 self.names[self.cellid] = l
835 if m.groups()[3] != None:
836 lac = int(m.group(3), 16)
837 cellid = int(m.group(4), 16)
838 record('cellid', '%04X %06X' % (lac, cellid))
839 self.cellid = cellid;
840 if cellid in self.cellnames:
841 record('cell', self.cellnames[cellid])
844 # check we still have correct carrier
845 at_queue('_OSIMOP', self.got_carrier)
846 # Make sure we are getting async cell notifications
847 at_queue('+CSCB=1', None)
848 def got_carrier(self, line):
849 #_OSIMOP: "YES OPTUS","YES OPTUS","50502"
852 m = re.match('_OSIMOP: "(.*)",".*","(.*)"', line)
854 record('sid', m.group(2))
855 record('carrier', m.group(1))
863 # get CIMI once per 'on'
864 class SIM_ID(Engine):
866 Engine.__init__(self)
870 def set_on(self, state):
880 m = re.match('(\d\d\d+)', line)
882 self.CIMI = m.group(1)
883 record('sim', self.CIMI)
887 self.retry(self.timeout)
888 if self.timeout < 60*60*1000:
889 self.timeout += self.timeout
894 at_queue("+CIMI", self.got, 5000)
899 # monitor 2g/3g protocol and select preferred
900 # 0=only2g 1=only3g 2=prefer2g 3=prefer3g 4=staywhereyouare 5=auto
904 Engine.__init__(self)
905 self.confirmed = False
907 watch('/var/lib/misc/gsm','mode', self.update)
911 self.set_service(state.service)
913 def set_service(self, state):
915 self.confirmed = False
920 l = safe_read("/var/lib/misc/gsm/mode", "3")
921 if len(l) and l[0] in "012345":
922 if self.mode != l[0]:
924 self.confirmed = False
930 at_queue("_OPSYS=%s,2" % self.mode, self.got, 5000)
934 self.confirmed = True;
943 # _OWANDATA _OWANCALL +CGDCONT
945 # if /run/gsm-state/data-APN contains an APN, make a data call
946 # else hangup any data.
948 # +CGDCONT=1,"IP","$APN"
950 # We then poll _OWANCALL?, get _OWANCALL: (\d), (\d)
951 # first number is '1' for us, second is
952 # 0 = Disconnected, 1 = Connected, 2 = In setup, 3 = Call setup failed.
953 # once connected, _OWANDATA? results in
954 # _OWANDATA: 1, ([0-9.]+), [0-9.]+, ([0-9.]+), ([0-9.]+), [0-9.]+, [0-9.]+,\d+$'
957 #_OWANDATA: 1, 115.70.17.232, 0.0.0.0, 58.96.1.28, 220.233.0.4, 0.0.0.0, 0.0.0.0,144000
958 # IP is stored in 'data' and used with 'ifconfig'
959 # DNS are stored in 'dns'
962 Engine.__init__(self)
966 self.retry_state = ''
967 self.last_data_usage = None
968 self.last_data_time = time.time()
969 request_async('_OWANCALL:', self.call_status)
970 watch('/run/gsm-state', 'data-APN', self.check_apn)
972 def set_on(self, state):
977 def set_service(self, state):
983 def check_apn(self, f):
985 l = recall('data-APN')
988 if not state.service:
995 at_queue('_OWANCALL=1,0,0', None)
1002 os.system('/sbin/ifconfig hso0 0.0.0.0 down')
1008 self.retry_state = 'reconnect'
1010 self.retry_state = ''
1016 # reply to +CGDCONT isn't interesting, and reply to
1017 # _OWANCALL is handle by async handler.
1018 at_queue('+CGDCONT=1,"IP","%s"' % self.apn, None)
1019 at_queue('_OWANCALL=1,1,0', None)
1020 self.retry_state = 'calling'
1025 if self.retry_state == 'calling':
1026 at_queue('_OWANCALL?', None)
1028 if self.retry_state == 'reconnect':
1030 if self.retry_state == 'connected':
1031 self.check_connect()
1033 def check_connect(self):
1034 self.retry_state = 'connected'
1037 at_queue('_OWANDATA=1', self.connect_data)
1039 def connect_data(self, line):
1040 m = re.match('_OWANDATA: 1, ([0-9.]+), [0-9.]+, ([0-9.]+), ([0-9.]+), [0-9.]+, [0-9.]+, \d+$', line)
1042 dns = (m.group(2), m.group(3))
1044 record('dns', '%s %s' % dns)
1049 os.system('/sbin/ifconfig hso0 up %s' % ip)
1056 def call_status(self, line):
1057 m = re.match('_OWANCALL: (\d+), (\d+)', line)
1060 if m.group(1) != '1':
1068 self.check_connect()
1078 def log_update(self, force = False):
1080 if 'sim' in recording and recording['sim']:
1081 sim = recording['sim']
1085 data_usage = self.last_data_usage
1086 data_time = self.last_data_time
1088 if not data_usage or (not force and
1089 self.last_data_time - data_time < 10 * 60):
1091 call_log('gsm-data', '%s %d %d' % (
1093 data_usage[0] - self.last_data_usage[0],
1094 data_usage[1] - self.last_data_usage[1]))
1096 def usage_update(self):
1097 self.last_data_usage = self.read_iface_usage()
1098 self.last_data_time = time.time()
1099 # data-last-usage allows app to add instantaneous current usage to
1101 record('data-last-usage', '%s %s' % last_data_usage)
1109 # Make sure CMGF CNMI etc all happen once per 'service'.
1110 # +CNMI=1,1,2,0,0 +CLIP?
1113 class config(Engine):
1115 Engine.__init__(self)
1116 def set_service(self, state):
1118 at_queue('+CLIP=1', None)
1119 at_queue('+CNMI=1,1,2,0,0', None)
1121 add_engine(config())
1126 # async +CRING RING +CLIP "NO CARRIER" "BUSY"
1132 # If we get '+CRING' or 'RING' we alert a call:
1133 # record number to 'incoming', INCOMING to status and alert 'ring'
1135 # If we get +CLIP:, record and log the call detail
1136 # If we get 'NO CARRIER', clear 'status' and 'call'
1137 # If we get 'BUSY' clear 'call' and record status==BUSY
1140 # 'call' :might be 'answer', or a number or empty, to hang up
1141 # 'dtmf' : clear file and send DTMF tones
1144 # 'incoming' is "-" for private, or "number" of incoming (or empty)
1145 # 'status' is "INCOMING" or 'BUSY' or 'on-call' (or empty)
1147 # While 'on-call' we poll with +CLCC
1149 # +CLCC: 1,1,4,0,0,"0403463349",128
1151 # +CLCC: 1,1,0,0,0,"0403463349",128
1153 # +CLCC: 1,0,3,0,0,"0403463349",129
1154 # outgoing, got hangup
1155 # +CLCC: 1,0,0,0,0,"0403463349",129
1158 # Call : idle -> active
1159 # hangup: active,incoming,busy -> idle
1160 # ring: idle -> incoming
1161 # answer: incoming -> active
1162 # BUSY: active -> busy
1166 class voice(Engine):
1168 Engine.__init__(self)
1173 request_async('+CRING', self.incoming)
1174 request_async('RING', self.incoming)
1175 request_async('+CLIP:', self.incoming_number)
1176 request_async('NO CARRIER', self.hangup)
1177 request_async('BUSY', self.busy)
1178 request_async('_OLCC', self.async_activity)
1179 watch('/run/gsm-state', 'call', self.check_call)
1180 watch('/run/gsm-state', 'dtmf', self.check_dtmf)
1181 self.f = EvDev('/dev/input/incoming', self.incoming_wake)
1183 def set_on(self, state):
1186 record('incoming', '')
1187 record('status', '')
1189 def set_suspend(self):
1190 self.f.read(None, None)
1191 print "voice allows suspend"
1194 def incoming_wake(self, dc, mom, typ, code, val):
1195 if typ == 1 and val == 1:
1200 at_queue('+CLCC', self.get_activity)
1201 def get_activity(self, line):
1202 m = re.match('\+CLCC: \d+,(\d+),(\d+),\d+,\d+,"([^"]*)".*', line)
1211 self.to_incoming(num)
1217 def async_activity(self, line):
1218 m = re.match('_OLCC: \d+,(\d+),(\d+),\d+,\d+,"([^"]*)".*', line)
1227 self.to_incoming(num)
1231 def incoming(self, line):
1234 def incoming_number(self, line):
1235 m = re.match('\+CLIP: "([^"]+)",[0-9,]*', line)
1237 self.to_incoming(m.group(1))
1239 def hangup(self, line):
1242 def busy(self, line):
1243 if self.state == 'active':
1244 record('status', 'BUSY')
1247 def check_call(self, f):
1252 if self.state == 'incoming':
1253 at_queue('A', self.answered)
1254 set_alert('ring', None)
1255 elif self.state == 'idle':
1256 call_log('outgoing', l)
1257 at_queue('D%s;' % l, None)
1260 def answered(self, line):
1263 def check_dtmf(self, f):
1267 if self.state == 'active' and l:
1268 at_queue('+VTS=%s' % l, None)
1271 if self.state == 'incoming' or self.state == 'active':
1272 call_log_end('incoming')
1273 call_log_end('outgoing')
1274 if self.state != 'idle':
1275 at_queue('+CHUP', None)
1276 record('incoming', '')
1277 record('status', '')
1278 if self.state == 'incoming':
1279 num = 'Unknown Number'
1282 sms = storesms.SMSmesg(source='MISSED-CALL', sender=num, text=('Missed call from %s' % self.number), state = 'NEW')
1283 st = storesms.SMSstore(os.path.join(storesms.find_sms(),'SMS'))
1287 self.router.send_signal(15)
1291 os.unlink('/run/sound/00-voicecall')
1299 def to_incoming(self, number = None):
1306 if self.state != 'incoming' or (number and not self.number):
1307 call_log('incoming', n)
1309 self.number = number
1310 record('incoming', n)
1311 record('status', 'INCOMING')
1314 self.state = 'incoming'
1317 def to_active(self):
1321 open('/run/sound/00-voicecall','w').close()
1324 self.router = Popen('/usr/local/bin/gsm-voice-routing',
1326 record('status', 'on-call')
1327 self.state = 'active'
1341 # If we receive +CMTI, or a signal on /dev/input/incoming, then
1342 # we +CMGL=4 and collect messages an add them to the sms database
1343 class sms_recv(Engine):
1345 Engine.__init__(self)
1346 self.check_needed = True
1347 request_async('+CMTI', self.must_check)
1348 self.f = EvDev("/dev/input/incoming", self.incoming)
1349 self.expecting_line = False
1353 def set_suspend(self):
1354 self.f.read(None, None)
1357 def incoming(self, dc, mom, typ, code, val):
1358 if typ == 1 and val == 1:
1361 def must_check(self, line):
1362 self.check_needed = True
1366 def set_on(self, state):
1367 if state and self.check_needed:
1373 if not self.check_needed:
1374 if not self.to_delete:
1376 t = self.to_delete[0]
1377 self.to_delete = self.to_delete[1:]
1378 at_queue('+CMGD=%s' % t, self.did_delete)
1381 if 'sim' not in recording or not recording['sim']:
1385 # must not check when there is an incoming call
1386 at_queue('+CPAS', self.cpas)
1388 def cpas(self, line):
1389 m = re.match('\+CPAS: (\d)', line)
1390 if m and m.group(1) == '3':
1394 at_queue('+CMGL=4', self.one_line, 40000)
1397 def did_delete(self, line):
1403 def one_line(self, line):
1406 self.check_needed = False
1407 found, res = storesms.sms_update(self.messages, recording['sim'])
1408 if res != None and len(res) > 10:
1409 self.to_delete = res[:-10]
1412 set_alert('sms',reduce(lambda x,y: x+','+y, found))
1415 if not line or line[:5] == 'ERROR' or line[:10] == '+CMS ERROR':
1416 self.check_needed = True
1420 if self.expecting_line:
1421 self.expecting_line = False
1422 if self.designation != '0' and self.designation != '1':
1425 if len(line) < self.msg_len:
1427 sender, date, ref, part, txt = sms.extract(line)
1428 self.messages[self.index] = (sender, date, txt, ref, part)
1430 m = re.match('^\+CMGL: (\d+),(\d+),("[^"]*")?,(\d+)$', line)
1432 self.expecting_line = True
1433 self.index = m.group(1)
1434 self.designation = m.group(2)
1435 self.msg_len = int(m.group(4), 10)
1439 add_engine(sms_recv())
1446 c = gobject.main_context_default()