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):
511 self.queue.append((cmd, handle, timeout))
512 gobject.idle_add(self.check_queue)
514 def clear_queue(self):
516 cmd, cb, timeout = self.queue.pop()
520 def async_match(self, line):
521 for prefix, handle, extra in self.async:
522 if line[:len(prefix)] == prefix:
525 self.async_pending = extra
527 self.set_timeout(1000)
531 def request_async(self, prefix, handle, extra):
532 self.async.append((prefix, handle, extra))
537 def request_async(prefix, handle, extras = None):
538 """ 'handle' should return True for a real match,
539 False if it was a false positive.
540 'extras' should return True if more is expected, or
541 False if there are no more async extras
544 mdm.request_async(prefix, handle, extras)
546 def at_queue(cmd, handle, timeout = 5000):
548 mdm.at_queue(cmd, handle, timeout)
552 # monitor the 'flightmode' file. Powers the modem
553 # on or off. Reports off or on to all handlers
554 # uses CFUN and PWRDN commands
555 class flight(Engine):
557 Engine.__init__(self)
558 watch('/var/lib/misc/flightmode','active', self.check)
559 gobject.idle_add(self.check)
561 def check(self, f = None):
563 l = safe_read('/var/lib/misc/flightmode/active')
564 gobject.idle_add(self.turn_on, len(l) == 0)
566 def turn_on(self, state):
570 def set_suspend(self):
572 l = safe_read('/var/lib/misc/flightmode/active')
573 if len(l) == 0 and not state.on:
575 gobject.idle_add(self.turn_on, True)
576 elif len(l) > 0 and state.on:
578 gobject.idle_add(self.turn_on, False)
585 # transitions from 'on' to 'service' and reports
586 # 'no-service' when 'off' or no signal.
588 # +COPS=0 - auto select
589 # +COPS=1,2,50502 - select specific (2 == numeric)
590 # +COPS=3,1 - report format is long (1 == long)
591 # +COPS=4,2,50502 - select specific with auto-fallback
592 # http://www.shapeshifter.se/2008/04/30/list-of-at-commands/
593 class register(Engine):
595 Engine.__init__(self)
596 self.resuming_delay = 600
598 def set_on(self, state):
605 at_queue('+CFUN?', self.gotline, 10000)
607 def wake_retry(self, handle):
612 at_queue('+CFUN?', self.got_wake_line, 8000)
615 def got_wake_line(self, line):
616 log("CFUN wake for %s" % line)
620 def gotline(self, line):
622 print "retry 1000 gotline not line"
626 m = re.match('\+CFUN: (\d)', line)
629 if n == '0' or n == '4':
631 at_queue('+CFUN=1', self.did_set, 10000)
640 print "retry end gotline"
644 def did_set(self, line):
645 print "retry 100 did_set"
654 # While there is service, monitor signal strength.
655 class signal(Engine):
657 Engine.__init__(self)
658 request_async('_OSIGQ:', self.get_async)
662 def set_service(self, state):
664 at_queue('_OSQI=1', None)
666 record('signal_strength', '-/32')
669 def get_async(self, line):
670 m = re.match('_OSIGQ: ([0-9]+),([0-9]+)', line)
677 at_queue('+CSQ', self.get_csq)
679 def get_csq(self, line):
683 m = re.match('\+CSQ: ([0-9]+),([0-9]+)', line)
687 def set(self, strength):
688 record('signal_strength', '%s/32'%strength)
697 if self.zero_count > 10:
705 # There are three ways we interact with suspend
706 # 1/ we block suspend when something important is happening:
707 # - any AT commands pending or active
708 # - phone call active
709 # - during initialisation?
710 # 2/ on suspend request we performs some checks before allowing the suspend,
711 # and send notificaitons on resume
712 # 3/ Some timeouts can wake up from suspend - e.g. CFUN poll.
713 # When a suspend is pending, check call state and
714 # sms gpio state and possibly abort.
717 """initialise a counter to '1' and when it hits zero
720 def __init__(self, cb):
731 class suspender(Engine):
733 Engine.__init__(self)
734 self.mon = suspend.monitor(self.want_suspend, self.resuming)
736 def want_suspend(self):
737 b = Blocker(lambda : self.mon.release())
743 gobject.idle_add(set_resume)
745 add_engine(suspender())
748 # when service, monitor cellid etc.
749 class Cellid(Engine):
751 Engine.__init__(self)
752 request_async('+CREG:', self.async)
753 request_async('+CBM:', self.cellname, extras=self.the_name)
760 def set_on(self, state):
764 def set_resume(self):
765 # might have moved while we slept
766 if time.time() > self.last_try + 2*60:
770 def set_service(self, state):
775 record('carrier','-')
778 self.last_try = time.time()
779 at_queue('+CREG?', self.got, 5000)
786 if line[:9] == '+CREG: 0,':
787 at_queue('+CREG=2', None)
788 m = re.match('\+CREG: 2,(\d)(,"([^"]*)","([^"]*)")', line)
793 def async(self, line):
794 m = re.match('\+CREG: ([012])(,"([^"]*)","([^"]*)")?$', line)
796 if m.group(1) == '1' and m.group(2):
799 if m.group(1) == '0':
804 def cellname(self, line):
805 # get something like +CBM: 1568,50,1,1,1
806 # don't know what that means, just collect the 'extra' line
807 # I think the '50' means 'this is a cell id'. I should
808 # probably test for that.
809 # Subsequent lines are the cell name.
810 m = re.match('\+CBM: \d+,\d+,\d+,\d+,\d+', line)
812 #ignore CBM content for now.
817 def the_name(self, line):
821 l = re.sub('[^!-~]+',' ', self.newname)
823 self.names[self.cellid] = l
833 if m.groups()[3] != None:
834 lac = int(m.group(3), 16)
835 cellid = int(m.group(4), 16)
836 record('cellid', '%04X %06X' % (lac, cellid))
837 self.cellid = cellid;
838 if cellid in self.cellnames:
839 record('cell', self.cellnames[cellid])
842 # check we still have correct carrier
843 at_queue('_OSIMOP', self.got_carrier)
844 # Make sure we are getting async cell notifications
845 at_queue('+CSCB=1', None)
846 def got_carrier(self, line):
847 #_OSIMOP: "YES OPTUS","YES OPTUS","50502"
850 m = re.match('_OSIMOP: "(.*)",".*","(.*)"', line)
852 record('sid', m.group(2))
853 record('carrier', m.group(1))
861 # get CIMI once per 'on'
862 class SIM_ID(Engine):
864 Engine.__init__(self)
868 def set_on(self, state):
878 m = re.match('(\d\d\d+)', line)
880 self.CIMI = m.group(1)
881 record('sim', self.CIMI)
885 self.retry(self.timeout)
886 if self.timeout < 60*60*1000:
887 self.timeout += self.timeout
892 at_queue("+CIMI", self.got, 5000)
897 # monitor 2g/3g protocol and select preferred
898 # 0=only2g 1=only3g 2=prefer2g 3=prefer3g 4=staywhereyouare 5=auto
902 Engine.__init__(self)
903 self.confirmed = False
905 watch('/var/lib/misc/gsm','mode', self.update)
909 self.set_service(state.service)
911 def set_service(self, state):
913 self.confirmed = False
918 l = safe_read("/var/lib/misc/gsm/mode", "3")
919 if len(l) and l[0] in "012345":
920 if self.mode != l[0]:
922 self.confirmed = False
928 at_queue("_OPSYS=%s,2" % self.mode, self.got, 5000)
932 self.confirmed = True;
941 # _OWANDATA _OWANCALL +CGDCONT
943 # if /run/gsm-state/data-APN contains an APN, make a data call
944 # else hangup any data.
946 # +CGDCONT=1,"IP","$APN"
948 # We then poll _OWANCALL?, get _OWANCALL: (\d), (\d)
949 # first number is '1' for us, second is
950 # 0 = Disconnected, 1 = Connected, 2 = In setup, 3 = Call setup failed.
951 # once connected, _OWANDATA? results in
952 # _OWANDATA: 1, ([0-9.]+), [0-9.]+, ([0-9.]+), ([0-9.]+), [0-9.]+, [0-9.]+,\d+$'
955 #_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
956 # IP is stored in 'data' and used with 'ifconfig'
957 # DNS are stored in 'dns'
960 Engine.__init__(self)
964 self.retry_state = ''
965 self.last_data_usage = None
966 self.last_data_time = time.time()
967 request_async('_OWANCALL:', self.call_status)
968 watch('/run/gsm-state', 'data-APN', self.check_apn)
970 def set_on(self, state):
975 def set_service(self, state):
981 def check_apn(self, f):
983 l = recall('data-APN')
986 if not state.service:
993 at_queue('_OWANCALL=1,0,0', None)
1000 os.system('/sbin/ifconfig hso0 0.0.0.0 down')
1006 self.retry_state = 'reconnect'
1008 self.retry_state = ''
1014 # reply to +CGDCONT isn't interesting, and reply to
1015 # _OWANCALL is handle by async handler.
1016 at_queue('+CGDCONT=1,"IP","%s"' % self.apn, None)
1017 at_queue('_OWANCALL=1,1,0', None)
1018 self.retry_state = 'calling'
1023 if self.retry_state == 'calling':
1024 at_queue('_OWANCALL?', None)
1026 if self.retry_state == 'reconnect':
1028 if self.retry_state == 'connected':
1029 self.check_connect()
1031 def check_connect(self):
1032 self.retry_state = 'connected'
1035 at_queue('_OWANDATA=1', self.connect_data)
1037 def connect_data(self, line):
1038 m = re.match('_OWANDATA: 1, ([0-9.]+), [0-9.]+, ([0-9.]+), ([0-9.]+), [0-9.]+, [0-9.]+, \d+$', line)
1040 dns = (m.group(2), m.group(3))
1042 record('dns', '%s %s' % dns)
1047 os.system('/sbin/ifconfig hso0 up %s' % ip)
1054 def call_status(self, line):
1055 m = re.match('_OWANCALL: (\d+), (\d+)', line)
1058 if m.group(1) != '1':
1066 self.check_connect()
1076 def log_update(self, force = False):
1078 if 'sim' in recording and recording['sim']:
1079 sim = recording['sim']
1083 data_usage = self.last_data_usage
1084 data_time = self.last_data_time
1086 if not data_usage or (not force and
1087 self.last_data_time - data_time < 10 * 60):
1089 call_log('gsm-data', '%s %d %d' % (
1091 data_usage[0] - self.last_data_usage[0],
1092 data_usage[1] - self.last_data_usage[1]))
1094 def usage_update(self):
1095 self.last_data_usage = self.read_iface_usage()
1096 self.last_data_time = time.time()
1097 # data-last-usage allows app to add instantaneous current usage to
1099 record('data-last-usage', '%s %s' % last_data_usage)
1107 # Make sure CMGF CNMI etc all happen once per 'service'.
1108 # +CNMI=1,1,2,0,0 +CLIP?
1111 class config(Engine):
1113 Engine.__init__(self)
1114 def set_service(self, state):
1116 at_queue('+CLIP=1', None)
1117 at_queue('+CNMI=1,1,2,0,0', None)
1119 add_engine(config())
1124 # async +CRING RING +CLIP "NO CARRIER" "BUSY"
1130 # If we get '+CRING' or 'RING' we alert a call:
1131 # record number to 'incoming', INCOMING to status and alert 'ring'
1133 # If we get +CLIP:, record and log the call detail
1134 # If we get 'NO CARRIER', clear 'status' and 'call'
1135 # If we get 'BUSY' clear 'call' and record status==BUSY
1138 # 'call' :might be 'answer', or a number or empty, to hang up
1139 # 'dtmf' : clear file and send DTMF tones
1142 # 'incoming' is "-" for private, or "number" of incoming (or empty)
1143 # 'status' is "INCOMING" or 'BUSY' or 'on-call' (or empty)
1145 # While 'on-call' we poll with +CLCC
1147 # +CLCC: 1,1,4,0,0,"0403463349",128
1149 # +CLCC: 1,1,0,0,0,"0403463349",128
1151 # +CLCC: 1,0,3,0,0,"0403463349",129
1152 # outgoing, got hangup
1153 # +CLCC: 1,0,0,0,0,"0403463349",129
1156 # Call : idle -> active
1157 # hangup: active,incoming,busy -> idle
1158 # ring: idle -> incoming
1159 # answer: incoming -> active
1160 # BUSY: active -> busy
1164 class voice(Engine):
1166 Engine.__init__(self)
1171 request_async('+CRING', self.incoming)
1172 request_async('RING', self.incoming)
1173 request_async('+CLIP:', self.incoming_number)
1174 request_async('NO CARRIER', self.hangup)
1175 request_async('BUSY', self.busy)
1176 request_async('_OLCC', self.async_activity)
1177 watch('/run/gsm-state', 'call', self.check_call)
1178 watch('/run/gsm-state', 'dtmf', self.check_dtmf)
1179 self.f = EvDev('/dev/input/incoming', self.incoming_wake)
1181 def set_on(self, state):
1184 record('incoming', '')
1185 record('status', '')
1187 def set_suspend(self):
1188 self.f.read(None, None)
1189 print "voice allows suspend"
1192 def incoming_wake(self, dc, mom, typ, code, val):
1193 if typ == 1 and val == 1:
1198 at_queue('+CLCC', self.get_activity)
1199 def get_activity(self, line):
1200 m = re.match('\+CLCC: \d+,(\d+),(\d+),\d+,\d+,"([^"]*)".*', line)
1209 self.to_incoming(num)
1215 def async_activity(self, line):
1216 m = re.match('_OLCC: \d+,(\d+),(\d+),\d+,\d+,"([^"]*)".*', line)
1225 self.to_incoming(num)
1229 def incoming(self, line):
1232 def incoming_number(self, line):
1233 m = re.match('\+CLIP: "([^"]+)",[0-9,]*', line)
1235 self.to_incoming(m.group(1))
1237 def hangup(self, line):
1240 def busy(self, line):
1241 if self.state == 'active':
1242 record('status', 'BUSY')
1245 def check_call(self, f):
1250 if self.state == 'incoming':
1251 at_queue('A', self.answered)
1252 set_alert('ring', None)
1253 elif self.state == 'idle':
1254 call_log('outgoing', l)
1255 at_queue('D%s;' % l, None)
1258 def answered(self, line):
1261 def check_dtmf(self, f):
1265 if self.state == 'active' and l:
1266 at_queue('+VTS=%s' % l, None)
1269 if self.state == 'incoming' or self.state == 'active':
1270 call_log_end('incoming')
1271 call_log_end('outgoing')
1272 if self.state != 'idle':
1273 at_queue('+CHUP', None)
1274 record('incoming', '')
1275 record('status', '')
1276 if self.state == 'incoming':
1277 num = 'Unknown Number'
1280 sms = storesms.SMSmesg(source='MISSED-CALL', sender=num, text=('Missed call from %s' % self.number), state = 'NEW')
1281 st = storesms.SMSstore(os.path.join(storesms.find_sms(),'SMS'))
1285 self.router.send_signal(15)
1289 os.unlink('/run/sound/00-voicecall')
1297 def to_incoming(self, number = None):
1304 if self.state != 'incoming' or (number and not self.number):
1305 call_log('incoming', n)
1307 self.number = number
1308 record('incoming', n)
1309 record('status', 'INCOMING')
1312 self.state = 'incoming'
1315 def to_active(self):
1319 open('/run/sound/00-voicecall','w').close()
1322 self.router = Popen('/usr/local/bin/gsm-voice-routing',
1324 record('status', 'on-call')
1325 self.state = 'active'
1339 # If we receive +CMTI, or a signal on /dev/input/incoming, then
1340 # we +CMGL=4 and collect messages an add them to the sms database
1341 class sms_recv(Engine):
1343 Engine.__init__(self)
1344 self.check_needed = True
1345 request_async('+CMTI', self.must_check)
1346 self.f = EvDev("/dev/input/incoming", self.incoming)
1347 self.expecting_line = False
1351 def set_suspend(self):
1352 self.f.read(None, None)
1355 def incoming(self, dc, mom, typ, code, val):
1356 if typ == 1 and val == 1:
1359 def must_check(self, line):
1360 self.check_needed = True
1364 def set_on(self, state):
1365 if state and self.check_needed:
1371 if not self.check_needed:
1372 if not self.to_delete:
1374 t = self.to_delete[0]
1375 self.to_delete = self.to_delete[1:]
1376 at_queue('+CMGD=%s' % t, self.did_delete)
1379 if 'sim' not in recording or not recording['sim']:
1383 # must not check when there is an incoming call
1384 at_queue('+CPAS', self.cpas)
1386 def cpas(self, line):
1387 m = re.match('\+CPAS: (\d)', line)
1388 if m and m.group(1) == '3':
1392 at_queue('+CMGL=4', self.one_line, 40000)
1395 def did_delete(self, line):
1401 def one_line(self, line):
1404 self.check_needed = False
1405 found, res = storesms.sms_update(self.messages, recording['sim'])
1406 if res != None and len(res) > 10:
1407 self.to_delete = res[:-10]
1410 set_alert('sms',reduce(lambda x,y: x+','+y, found))
1413 if not line or line[:5] == 'ERROR' or line[:10] == '+CMS ERROR':
1414 self.check_needed = True
1418 if self.expecting_line:
1419 self.expecting_line = False
1420 if self.designation != '0' and self.designation != '1':
1423 if len(line) < self.msg_len:
1425 sender, date, ref, part, txt = sms.extract(line)
1426 self.messages[self.index] = (sender, date, txt, ref, part)
1428 m = re.match('^\+CMGL: (\d+),(\d+),("[^"]*")?,(\d+)$', line)
1430 self.expecting_line = True
1431 self.index = m.group(1)
1432 self.designation = m.group(2)
1433 self.msg_len = int(m.group(4), 10)
1437 add_engine(sms_recv())
1444 c = gobject.main_context_default()