4 # Calls can be made by writing a number to
6 # Status get set call 'Calling' and then 'BUSY' or ''
7 # Call can be answered by writing 'answer' to 'call'
8 # or can be cancelled by writing ''.
9 # During a call, chars can be written to
14 # e.g. receive AT response +CREG: 1,"08A7","6E48"
15 # show that SIM is now ready
16 # cope with /var/lock/suspend not existing yet
19 import re, time, gobject, os
20 from atchan import AtChannel
21 import dnotify, suspend
22 from tracing import log
23 from subprocess import Popen
25 def record(key, value):
26 f = open('/run/gsm-state/.new.' + key, 'w')
29 os.rename('/run/gsm-state/.new.' + key,
30 '/run/gsm-state/' + key)
32 def recall(key, nofile = ""):
34 fd = open("/run/gsm-state/" + key)
42 def set_alert(key, value):
43 path = '/run/alert/' + key
58 def calllog(key, msg):
59 f = open('/var/log/' + key, 'a')
60 now = time.strftime("%Y-%m-%d %H:%M:%S")
61 f.write(now + ' ' + msg + "\n")
71 def __init__(self, repeat):
74 def start(self, channel):
75 # take the first action for this task
77 def takeline(self, channel, line):
78 # a line has arrived that is presumably for us
80 def timeout(self, channel):
81 # we asked for a timeout and got it
85 # An AtAction involves:
86 # optionally sending an AT command to check some value
87 # matching the result against a string, possibly storing the value
88 # if there is no match send some other AT command, probably to set a value
90 # States are 'init' 'checking', 'setting', 'done'
91 ok = re.compile("^OK")
92 busy = re.compile("\+CMS ERROR.*SIM busy")
93 not_ok = re.compile("^(ERROR|\+CM[SE] ERROR:)")
94 def __init__(self, check = None, ok = None, record = None, at = None,
95 timeout=None, handle = None, repeat = None, arg = None,
96 critical = True, noreply = None, retries = 5):
97 Task.__init__(self, repeat)
101 self.okre = re.compile(ok)
105 self.retries = retries
106 self.timeout_time = timeout
108 self.critical = critical
109 self.noreply = noreply
111 def start(self, channel):
112 channel.state['retries'] = 0
113 channel.state['stage'] = 'init'
114 self.advance(channel)
116 def takeline(self, channel, line):
118 channel.force_state('reset')
121 m = self.ok.match(line)
123 channel.cancel_timeout()
125 self.handle(channel, line, None)
126 return self.advance(channel)
128 if self.busy.match(line):
129 channel.cancel_timeout()
130 channel.set_timeout(5000)
132 if self.not_ok.match(line):
133 return channel.abort_timeout()
135 if channel.state['stage'] == 'checking':
136 m = self.okre.match(line)
138 channel.state['matched'] = True
140 record(self.record[0], m.expand(self.record[1]))
141 if len(self.record) > 3:
142 record(self.record[2], m.expand(self.record[3]))
144 self.handle(channel, line, m)
147 if channel.state['stage'] == 'setting':
148 # didn't really expect anything here..
151 def timeout(self, channel):
152 if channel.state['retries'] >= self.retries:
154 channel.force_state('reset')
157 channel.state['retries'] += 1
158 channel.state['stage'] = 'init'
161 def advance(self, channel):
162 st = channel.state['stage']
163 if st == 'init' and self.check:
164 channel.state['stage'] = 'checking'
165 if self.timeout_time:
166 channel.atcmd(self.check, timeout = self.timeout_time)
168 channel.atcmd(self.check)
169 elif (st == 'init' or st == 'checking') and self.at and not 'matched' in channel.state:
170 channel.state['stage'] = 'setting'
173 at = at % channel.args[self.arg]
174 if self.timeout_time:
175 channel.atcmd(at, timeout = self.timeout_time)
179 channel.cancel_timeout()
184 class PowerAction(Task):
185 # A PowerAction ensure that we have a connection to the modem
186 # and sets the power on or off, or resets the modem
187 def __init__(self, cmd):
188 Task.__init__(self, None)
191 def start(self, channel):
193 if not channel.connected:
195 if not channel.altchan.connected:
196 channel.altchan.connect()
197 channel.check_flightmode()
198 elif self.cmd == "off":
199 record('carrier', '')
201 record('signal_strength','0/32')
203 channel.altchan.disconnect()
204 elif self.cmd == 'reopen':
206 channel.altchan.disconnect()
208 channel.altchan.connect()
209 return channel.advance()
211 class ChangeStateAction(Task):
212 # This action changes to a new state, like a goto
213 def __init__(self, state):
214 Task.__init__(self, None)
215 self.newstate = state
216 def start(self, channel):
218 state = self.newstate
219 elif channel.nextstate:
220 state = channel.nextstate.pop(0)
224 channel.gstate = state
225 channel.tasknum = None
226 log("ChangeStateAction chooses", channel.gstate)
227 n = len(control[channel.gstate])
228 channel.lastrun = n * [0]
229 return channel.advance()
231 class CheckSMS(Task):
233 Task.__init__(self, None)
234 def start(self, channel):
235 if 'incoming' in channel.nextstate:
236 # now is not a good time
237 return channel.advance()
238 if channel.pending_sms:
239 channel.pending_sms = False
240 p = Popen('gsm-getsms -n', shell=True, close_fds = True)
242 return channel.advance()
244 class RouteVoice(Task):
245 def __init__(self, on):
246 Task.__init__(self, None)
248 def start(self, channel):
250 channel.sound_on = True
252 f = open("/run/sound/00-voicecall","w")
256 p = Popen('/usr/local/bin/gsm-voice-routing', close_fds = True)
257 log('Running gsm-voice-routing pid', p.pid)
258 channel.voice_route = p
259 elif channel.sound_on:
260 if channel.voice_route:
261 channel.voice_route.send_signal(15)
262 channel.voice_route.wait()
263 channel.voice_route = None
265 os.unlink("/run/sound/00-voicecall")
268 channel.sound_on = False
269 return channel.advance()
271 class BlockSuspendAction(Task):
272 def __init__(self, enable):
273 Task.__init__(self, None)
275 def start(self, channel):
276 print "BlockSuspendAction sets", self.enable
278 channel.suspend_blocker.block()
279 # No point holding a pending suspend any more
280 if channel.suspend_pending:
281 channel.suspend_pending = False
282 print "BlockSuspendAction calls release"
283 suspend.abort_cycle()
284 channel.suspend_handle.release()
286 channel.suspend_blocker.unblock()
291 def __init__(self, msg, handle, handle_extra = None):
293 self.msgre = re.compile(msg)
295 self.handle_extra = handle_extra
297 def match(self, line):
298 return self.msgre.match(line)
304 def status_update(channel, line, m):
305 if m and m.groups()[3] != None:
306 global LAC, CELLID, cellnames
307 LAC = int(m.groups()[2],16)
308 CELLID = int(m.groups()[3],16)
309 record('cellid', "%04X %06X" % (LAC, CELLID));
310 if CELLID in cellnames:
311 record('cell', cellnames[CELLID])
312 log("That one is", cellnames[CELLID])
314 def new_sms(channel, line, m):
316 channel.pending_sms = False
317 record('newsms', m.groups()[1])
318 p = Popen('gsm-getsms -n', shell=True, close_fds = True)
321 def maybe_sms(line, channel):
322 channel.pending_sms = True
324 def sigstr(channel, line, m):
326 record('signal_strength', m.groups()[0] + '/32')
328 global incoming_cell_id
329 def cellid_update(channel, line, m):
330 # get something like +CBM: 1568,50,1,1,1
331 # don't know what that means, just collect the 'extra' line
332 # I think the '50' means 'this is a cell id'. I should
333 # probably test for that.
335 # response can be multi-line
336 global incoming_cell_id
337 incoming_cell_id = ""
339 def cellid_new(channel, line):
340 global CELLID, cellnames, incoming_cell_id
344 l = re.sub('[^!-~]+',' ',incoming_cell_id)
346 cellnames[CELLID] = l
351 incoming_cell_id += ' ' + line
353 incoming_cell_id = line
357 def incoming(channel, line, m):
360 record('incoming', incoming_num)
362 record('incoming', '-')
363 set_alert('ring', 'new')
364 if channel.gstate not in ['on-call', 'incoming', 'answer']:
365 calllog('incoming', '-call-')
366 channel.set_state('incoming')
367 record('status', 'INCOMING')
371 def incoming_number(channel, line, m):
375 if incoming_num == None:
376 calllog('incoming', num);
378 record('incoming', incoming_num)
380 def no_carrier(channel, line, m):
383 if channel.gstate != 'idle':
384 channel.set_state('idle')
386 def busy(channel, line, m):
387 record('status', 'BUSY')
390 def ussd(channel, line, m):
394 def call_status(channel, line, m):
397 log("call_status got", line)
400 s = int(m.groups()[0])
406 if cpas_zero_cnt <= 3:
411 record('incoming', '')
412 if channel.gstate in ['on-call','incoming','call']:
413 calllog_end('incoming')
414 calllog_end('outgoing')
416 if channel.gstate != 'idle' and channel.gstate != 'suspend':
417 channel.set_state('idle')
422 if channel.gstate not in ['incoming', 'answer']:
424 channel.set_state('incoming')
425 record('status', 'INCOMING')
426 set_alert('ring', 'new')
427 record('incoming', '-')
429 # on a call - but could be just a data call, so don't do anything
430 #if channel.gstate != 'on-call' and channel.gstate != 'hangup':
431 # channel.set_state('on-call')
434 def check_cfun(channel, line, m):
436 # If '1', then advance from init1 to init2
437 # else if not 0, possibly do a reset
440 if m.groups()[0] == '1':
441 if channel.gstate == 'init1':
442 channel.set_state('init2')
444 if m.groups()[0] != '0':
445 if channel.last_reset + 100 < time.time():
446 channel.last_reset = time.time()
447 channel.set_state('reset')
450 def data_handle(channel, line, m):
451 # Response to _OWANDATA - should contain IP address etc
453 if 'matched' in channel.state:
454 # already handled match
456 # no connection active
457 data_hungup(channel, failed=False)
459 # m[0] is gateway/IP addr. m[1] and m[2] are DNS servers
460 dns = (m.groups()[1], m.groups()[2])
461 if channel.data_DNS != dns:
462 record('dns', '%s %s' % dns)
463 channel.data_DNS = dns
465 if channel.data_IP != ip:
467 os.system('/sbin/ifconfig hso0 up %s' % ip)
471 def data_call(channel, line, m):
472 # delayed reponse to _OWANCALL. Maybe be async, may be
473 # polled with '_OWANCALL?'
476 if m.groups()[0] != '1':
478 s = int(m.groups()[1])
479 # 0 = Disconnected, 1 = Connected, 2 = In setup, 3 = Call setup failed.
481 data_hungup(channel, failed=False)
483 channel.set_state('idle')
488 data_hungup(channel, failed=True)
490 def data_hungup(channel, failed):
492 os.system('/sbin/ifconfig hso0 0.0.0.0 down')
495 channel.data_IP = None
496 channel.data_DNS = None
497 # FIXME should I retry, or reset?
499 # We still want a connection
501 channel.set_state('reset')
503 elif channel.next_data_call <= time.time():
504 channel.next_data_call = (time.time() +
505 time.time() - channel.last_data_call);
506 channel.last_data_call = time.time()
508 channel.set_state('data-call')
510 if channel.gstate == 'data-call':
511 channel.set_state('idle')
513 # DATA traffic logging - goes to /var/log/gsm-data
514 def read_bytes(dev = 'hso0'):
515 f = file('/proc/net/dev')
518 w = l.strip().split()
519 if w[0] == dev + ':':
520 rv = ( int(w[1]), int(w[9]) )
525 last_data_usage = None
528 def data_log_reset():
529 global last_data_usage, last_data_time
530 last_data_usage = read_bytes()
531 last_data_time = time.time()
532 record('data-last-usage', '%s %s' % last_data_usage)
534 def data_log_update(force = False):
535 global last_data_usage, last_data_time, SIM
541 if not last_data_usage:
544 if not force and time.time() - last_data_time < 10*60:
547 data_usage = read_bytes()
549 calllog('gsm-data', '%s %d %d' %
551 data_usage[0] - last_data_usage[0],
552 data_usage[1] - last_data_usage[1]))
554 last_data_usage = data_usage
555 last_data_time = time.time()
556 record('data-last-usage', '%s %s' % last_data_usage)
560 # For flight mode, we turn the power off.
561 control['to-flight'] = [
562 AtAction(at='+CFUN=0'),
564 ChangeStateAction('flight')
566 control['flight'] = [
567 BlockSuspendAction(False),
571 # turning power off just kills everything!!!
572 #AtAction(at='_ORESET', critical = False),
573 AtAction(at='$QCPWRDN', critical = False, retries = 0),
574 PowerAction('reopen'),
576 AtAction(at='E0', timeout=30000),
577 ChangeStateAction('init1'),
580 # For suspend, we want power on, but no wakups for status or cellid
581 control['suspend'] = [
582 AtAction(check='+CPAS', ok='\+CPAS: (\d)', handle = call_status),
584 ChangeStateAction(None), # allow async state change
585 AtAction(at='+CNMI=1,1,0,0,0'),
586 AtAction(at='_OSQI=0'),
587 AtAction(at='_OEANT=0'),
588 AtAction(at='_OSSYS=0'),
589 AtAction(at='_OPONI=0'),
590 AtAction(at='+CREG=0'),
592 control['resume'] = [
593 BlockSuspendAction(True),
594 AtAction(check='+CPAS', ok='\+CPAS: (\d)', handle = call_status),
595 AtAction(at='+CNMI=1,1,2,0,0', critical=False),
596 AtAction(at='_OSQI=1', critical=False),
597 AtAction(at='+CREG=2'),
599 ChangeStateAction(None),
600 ChangeStateAction('idle'),
603 control['listenerr'] = [
606 AtAction(at='+CMEE=2;+CRC=1')
609 # init1 checks phone status and once we are online
610 # we switch to init2, then idle
612 BlockSuspendAction(True),
615 AtAction(at='+CMEE=2;+CRC=1'),
616 # Turn the device on.
617 AtAction(check='+CFUN?', ok='\+CFUN: 1', at='+CFUN=1', timeout=10000),
618 # Report carrier as long name
619 AtAction(at='+COPS=3,0'),
620 # register with a carrier
621 #AtAction(check='+COPS?', ok='\+COPS: \d+,\d+,"([^"]*)"', at='+COPS',
622 # record=('carrier', '\\1'), timeout=10000),
623 AtAction(check='+COPS?', ok='\+COPS: \d+,\d+,"([^"]*)"', at='+COPS=0',
624 record=('carrier', '\\1'), timeout=10000),
625 AtAction(check='+CFUN?', ok='\+CFUN: (\d)', at='+CFUN=1', timeout=10000, handle=check_cfun, repeat=5000),
629 # text format for various messages such SMS
630 AtAction(check='+CMGF?', ok='\+CMGF: 0', at='+CMGF=0'),
631 # get location status updates
632 AtAction(at='+CREG=2'),
633 AtAction(check='+CREG?', ok='\+CREG: 2,(\d)(,"([^"]*)","([^"]*)")',
634 handle=status_update, timeout=4000),
635 # Enable collection of Cell Info message
636 #AtAction(check='+CSCB?', ok='\+CSCB: 1,.*', at='+CSCB=1'),
637 #AtAction(at='+CSCB=0'),
638 AtAction(at='+CSCB=1', critical=False),
639 # Enable async reporting of TXT and Cell info messages
640 #AtAction(check='+CNMI?', ok='\+CNMI: 1,1,2,0,0', at='+CNMI=1,1,2,0,0'),
641 AtAction(at='+CNMI=1,0,0,0,0', critical=False),
642 AtAction(at='+CNMI=1,1,2,0,0', critical=False),
643 # Enable async reporting of signal strength
644 AtAction(at='_OSQI=1', critical=False),
645 AtAction(check='+CIMI', ok='(\d\d\d+)', record=('sim','\\1')),
646 #_OSIMOP: "YES OPTUS","YES OPTUS","50502"
647 AtAction(check='_OSIMOP', ok='_OSIMOP: "(.*)",".*","(.*)"',
648 record=('sid','\\2', 'carrier','\\1'), critical=False),
650 # Make sure to use both 2G and 3G
651 AtAction(at='_OPSYS=3,2', critical=False),
653 # Enable reporting of Caller number id.
654 AtAction(check='+CLIP?', ok='\+CLIP: 1,[012]', at='+CLIP=1', timeout=10000,
656 AtAction(check='+CPAS', ok='\+CPAS: (\d)', handle = call_status),
657 ChangeStateAction('idle')
660 def if_data(channel):
661 if not channel.data_APN and not channel.data_IP:
664 if channel.data_APN and channel.data_IP:
665 # connection is set up - slow watch
668 # must be shutting down - poll quickly, it shouldn't take long
670 # we want a connection but don't have one, so we retry
671 if time.time() < channel.next_data_call:
672 return int((channel.next_data_call - time.time()) * 1000)
678 BlockSuspendAction(False),
679 AtAction(check='+CFUN?', ok='\+CFUN: (\d)', timeout=10000, handle=check_cfun, repeat=30000),
680 AtAction(check='+COPS?', ok='\+COPS: \d+,\d+,"([^"]*)"', at='+COPS',
681 record=('carrier', '\\1'), timeout=10000),
682 #AtAction(check='+COPS?', ok='\+COPS: \d+,\d+,"([^"]*)"', at='+COPS=0',
683 # record=('carrier', '\\1'), timeout=10000, repeat=37000),
685 AtAction(check='+CSQ', ok='\+CSQ: (\d+),(\d+)',
686 record=('signal_strength','\\1/32'), repeat=29000),
687 AtAction(check='_OWANDATA?',
688 ok='_OWANDATA: 1, ([0-9.]+), [0-9.]+, ([0-9.]+), ([0-9.]+), [0-9.]+, [0-9.]+,\d+$',
689 handle=data_handle, repeat=if_data),
692 control['data-call'] = [
693 AtAction(at='+CGDCONT=1,"IP","%s"', arg='APN'),
694 AtAction(at='_OWANCALL=1,1,0'),
695 AtAction(at='_OWANCALL?', handle=data_call, repeat=2000),
696 #ChangeStateAction('idle')
699 control['data-hangup'] = [
700 AtAction(at='_OWANCALL=1,0,0'),
701 ChangeStateAction('idle')
704 control['incoming'] = [
705 BlockSuspendAction(True),
706 AtAction(check='+CPAS', ok='\+CPAS: (\d)', handle = call_status, repeat=500),
708 # monitor signal strength
709 AtAction(check='+CSQ', ok='\+CSQ: (\d+),(\d+)',
710 record=('signal_strength','\\1/32'), repeat=30000)
713 control['answer'] = [
716 ChangeStateAction('on-call')
720 AtAction(at='D%s;', arg='number'),
722 ChangeStateAction('on-call')
726 AtAction(at='+VTS=%s', arg='dtmf', noreply=True),
727 ChangeStateAction('on-call')
730 control['hangup'] = [
731 AtAction(at='+CHUP', critical=False, retries=0),
732 ChangeStateAction('idle')
735 control['on-call'] = [
736 BlockSuspendAction(True),
737 AtAction(check='+CPAS', ok='\+CPAS: (\d)', handle = call_status, repeat=2000),
739 # get signal strength
740 AtAction(check='+CSQ', ok='\+CSQ: (\d+),(\d+)',
741 record=('signal_strength','\\1/32'), repeat=30000)
745 Async(msg='\+CREG: ([01])(,"([^"]*)","([^"]*)")?', handle=status_update),
746 Async(msg='\+CMTI: "([A-Z]+)",(\d+)', handle = new_sms),
747 Async(msg='\+CBM: \d+,\d+,\d+,\d+,\d+', handle=cellid_update,
748 handle_extra = cellid_new),
749 Async(msg='\+CRING: (.*)', handle = incoming),
750 Async(msg='RING', handle = incoming),
751 Async(msg='\+CLIP: "([^"]+)",[0-9,]*', handle = incoming_number),
752 Async(msg='NO CARRIER', handle = no_carrier),
753 Async(msg='BUSY', handle = busy),
754 Async(msg='\+CUSD: ([012])(,"(.*)"(,[0-9]+)?)?$', handle = ussd),
755 Async(msg='_OSIGQ: ([0-9]+),([0-9]*)$', handle = sigstr),
757 Async(msg='_OWANCALL: (\d), (\d)', handle = data_call),
760 class GsmD(AtChannel):
762 # gsmd works like a state machine
763 # the high level states are: flight suspend idle incoming on-call
764 # Note that the whole 'call-waiting' experience is not coverred here.
765 # That needs to be handled by whoever answers calls and allows interaction
766 # between user and phone system.
768 # Each state contains a list of tasks such as setting and
769 # checking config options and monitoring state (e.g. signal strength)
770 # Some tasks are single-shot and only need to complete each time the state is
771 # entered. Others are repeating (such as status monitoring).
772 # We take the first task of the current list and execute it, or wait
773 # until one will be ready.
774 # Tasks themselves can be state machines, so we keep track of what 'stage'
775 # we are up to in the current task.
777 # The system is (naturally) event driven. The main two events that we
779 # 'takeline' which presents one line of text from the GSM device, and
780 # 'timeout' which indicates that a timeout set when a command was sent has
783 # 'taskready' when the time of the next pending task arrives.
784 # 'flight' when the state of the 'flight mode' has changed
785 # 'suspend' when a suspend has been requested.
787 # Each event does some event specific processing to modify the state,
788 # Then calls 'self.advance' to progress the state machine.
789 # When high level state changes are requested, any pending task is discarded.
791 # If a task detects an error (gsm device not responding properly) it might
792 # request a reset. This involves sending a modem_reset command and then
793 # restarting the current state from the top.
794 # A task can also indicate:
795 # The next stage to try
796 # How long to wait before retrying (or None)
799 def __init__(self, path, altpath):
800 AtChannel.__init__(self, path = path)
803 self.flightmode = True
806 self.suspend_pending = False
807 self.pending_sms = False
809 self.voice_route = None
811 self.altpath = altpath
812 self.altchan = CarrierDetect(altpath, self)
816 self.last_data_call = 0
817 self.next_data_call = 0
820 self.last_reset = time.time()
824 record('incoming','')
825 record('signal_strength','')
829 record('data-APN', '')
833 # set the initial state
834 self.set_state('flight')
836 # Monitor other external events which affect us
837 d = dnotify.dir('/var/lib/misc/flightmode')
838 self.flightmode_watcher = d.watch('active', self.check_flightmode)
839 d = dnotify.dir('/run/gsm-state')
840 self.call_watcher = d.watch('call', self.check_call)
841 self.dtmf_watcher = d.watch('dtmf', self.check_dtmf)
842 self.data_watcher = d.watch('data-APN', self.check_data)
844 self.suspend_handle = suspend.monitor(self.do_suspend, self.do_resume)
845 self.suspend_blocker = suspend.blocker()
847 # Check the externally imposed state
848 self.check_flightmode(self.flightmode_watcher)
853 def check_call(self, f = None):
855 log("Check call got", l)
857 if self.gstate != 'idle':
860 self.set_state('hangup')
862 record('incoming','')
863 calllog_end('incoming')
864 calllog_end('outgoing')
866 if self.gstate == 'incoming':
867 record('status', 'on-call')
868 record('incoming','')
869 set_alert('ring', None)
870 self.set_state('answer')
872 if self.gstate == 'idle':
875 self.args['number'] = l
876 self.set_state('call')
877 calllog('outgoing',l)
878 record('status', 'on-call')
880 def check_dtmf(self, f = None):
882 log("Check dtmf got", l)
884 self.args['dtmf'] = l
885 self.set_state('dtmf')
888 def check_data(self, f = None):
889 l = recall('data-APN')
890 log("Check data got", l)
893 if self.data_APN != l:
895 self.args['APN'] = self.data_APN
897 self.set_state('data-hangup')
898 data_log_update(True)
899 elif self.gstate == 'idle' and self.data_APN:
900 self.last_data_call = time.time()
901 self.next_data_call = time.time()
903 self.set_state('data-call')
905 def check_flightmode(self, f = None):
907 fd = open("/var/lib/misc/flightmode/active")
912 log("check flightmode got", len(l))
915 self.flightmode = False
916 if self.suspend_handle.suspended:
917 self.set_state('suspend')
919 self.set_state('init1')
921 if not self.flightmode:
922 self.flightmode = True
923 self.set_state('to-flight')
925 def do_suspend(self):
926 self.suspend_pending = True
927 if self.gstate not in ['flight', 'resume']:
928 print "do suspend sets suspend"
929 self.set_state('suspend')
931 print "do suspend avoids suspend"
936 if self.gstate == 'suspend':
937 self.set_state('resume')
939 def set_state(self, state):
940 # this happens asynchronously so we must be careful
941 # about changing things. Just record the new state
942 # and abort any timeout
943 if state == self.gstate or state in self.nextstate:
944 log("state already destined to be", state)
946 log("state should become", state)
947 self.nextstate.append(state)
950 def force_state(self, state):
951 # immediately go to new state - must be called carefully
952 log("Force state to", state);
953 self.cancel_timeout()
955 n = len(control[state])
956 self.lastrun = n * [0]
961 # 'advance' is called by a 'Task' when it has finished
962 # It may have called 'set_state' first either to report
963 # an error or to effect a regular state change
964 now = int(time.time()*1000)
965 if self.tasknum != None:
966 self.lastrun[self.tasknum] = now
968 (t, delay) = self.next_cmd()
969 log("advance %s chooses %d, %d" % (self.gstate, t, delay))
970 if delay and self.nextstate:
971 # time to effect 'set_state' synchronously
972 self.gstate = self.nextstate.pop(0)
973 log("state becomes", self.gstate)
974 n = len(control[self.gstate])
975 self.lastrun = n * [0]
976 t, delay = self.next_cmd()
979 log("Sleeping for %f seconds" % (delay/1000.0))
980 self.set_timeout(delay)
981 if self.suspend_pending:
982 # It is important that this comes after set_timeout
983 # as we might get an abort_timeout as a result of the
984 # release, and there needs to be a timeout to abort
985 self.suspend_pending = False
986 print "advance calls release"
987 self.suspend_handle.release()
991 control[self.gstate][t].start(self)
993 def takeline(self, line):
996 # an async message is multi-line and we need to handle
998 if not self.extra.handle_extra(self, line):
1003 self.force_state('reset')
1008 # Check for an async message
1012 m.handle(self, line, mt)
1017 # else pass it to the task
1018 if self.tasknum != None:
1019 control[self.gstate][self.tasknum].takeline(self, line)
1022 if self.tasknum == None:
1025 control[self.gstate][self.tasknum].timeout(self)
1028 # Find a command to execute, or a delay
1030 # cmd is an index into control[state],
1031 # time is seconds until try something
1032 mindelay = 60*60*1000
1033 if self.gstate == None:
1034 return (0, mindelay)
1035 cs = control[self.gstate]
1037 now = int(time.time()*1000)
1039 if self.lastrun[i] == 0:
1041 repeat = cs[i].repeat
1044 elif type(repeat) != int:
1045 repeat = repeat(self)
1047 if repeat and self.lastrun[i] + repeat <= now:
1050 delay = (self.lastrun[i] + repeat) - now;
1051 if delay < mindelay:
1053 return (0, mindelay)
1055 class CarrierDetect(AtChannel):
1056 # on the hso modem in the GTA04, the 'NO CARRIER' signal
1057 # arrives on the 'Modem' port, not on the 'Application' port.
1058 # So we listen to the 'Modem' port, and report any
1059 # 'NO CARRIER' we see - or indeed anything that we see.
1060 def __init__(self, path, main):
1061 AtChannel.__init__(self, path = path)
1064 def takeline(self, line):
1065 self.main.takeline(line)
1068 # watch for changes on a sysfs file and report them
1069 # We read the content, report that, wait for a change
1071 def __init__(self, path, action):
1073 self.action = action
1074 self.fd = open(path, "r")
1075 self.watcher = gobject.io_add_watch(self.fd, gobject.IO_PRI, self.read)
1078 def read(self, *args):
1081 r = self.fd.read(4096)
1088 os.mkdir("/run/gsm-state")
1093 a = GsmD('/dev/ttyHS_Application', '/dev/ttyHS_Modem')
1094 print "GsmD started"
1097 f = open("/sys/class/gpio/gpio176/edge", "w")
1103 w = SysfsWatcher("/sys/class/gpio/gpio176/value",
1104 lambda l: maybe_sms(l, a))
1107 def check_evt(dc, mom, typ, code, val):
1108 if typ == 1 and val == 1:
1112 f = evdev.EvDev("/dev/input/incoming", check_evt)
1115 c = gobject.main_context_default()