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
26 def record(key, value):
29 f = open('/run/gsm-state/.new.' + key, 'w')
32 os.rename('/run/gsm-state/.new.' + key,
33 '/run/gsm-state/' + key)
35 # I got this once on the rename, don't know why
37 recording[key] = value
39 def recall(key, nofile = ""):
41 fd = open("/run/gsm-state/" + key)
49 def set_alert(key, value):
50 path = '/run/alert/' + key
65 def calllog(key, msg):
66 f = open('/var/log/' + key, 'a')
67 now = time.strftime("%Y-%m-%d %H:%M:%S")
68 f.write(now + ' ' + msg + "\n")
78 def __init__(self, repeat):
81 def start(self, channel):
82 # take the first action for this task
84 def takeline(self, channel, line):
85 # a line has arrived that is presumably for us
87 def timeout(self, channel):
88 # we asked for a timeout and got it
92 # An AtAction involves:
93 # optionally sending an AT command to check some value
94 # matching the result against a string, possibly storing the value
95 # if there is no match send some other AT command, probably to set a value
97 # States are 'init' 'checking', 'setting', 'done'
98 ok = re.compile("^OK")
99 busy = re.compile("\+CMS ERROR.*SIM busy")
100 not_ok = re.compile("^(ERROR|\+CM[SE] ERROR:)")
101 def __init__(self, check = None, ok = None, record = None, at = None,
102 timeout=None, handle = None, repeat = None, arg = None,
103 critical = True, noreply = None, retries = 5):
104 Task.__init__(self, repeat)
108 self.okre = re.compile(ok)
112 self.retries = retries
113 self.timeout_time = timeout
115 self.critical = critical
116 self.noreply = noreply
118 def start(self, channel):
119 channel.state['retries'] = 0
120 channel.state['stage'] = 'init'
121 self.advance(channel)
123 def takeline(self, channel, line):
125 channel.force_state('reset')
128 m = self.ok.match(line)
130 channel.cancel_timeout()
132 self.handle(channel, line, None)
133 return self.advance(channel)
135 if self.busy.match(line):
136 channel.cancel_timeout()
137 channel.set_timeout(5000)
139 if self.not_ok.match(line):
140 return channel.abort_timeout()
142 if channel.state['stage'] == 'checking':
143 m = self.okre.match(line)
145 channel.state['matched'] = True
147 record(self.record[0], m.expand(self.record[1]))
148 if len(self.record) > 3:
149 record(self.record[2], m.expand(self.record[3]))
151 self.handle(channel, line, m)
154 if channel.state['stage'] == 'setting':
155 # didn't really expect anything here..
158 def timeout(self, channel):
159 if channel.state['retries'] >= self.retries:
161 channel.force_state('reset')
164 channel.state['retries'] += 1
165 channel.state['stage'] = 'init'
168 def advance(self, channel):
169 st = channel.state['stage']
170 if st == 'init' and self.check:
171 channel.state['stage'] = 'checking'
172 if self.timeout_time:
173 channel.atcmd(self.check, timeout = self.timeout_time)
175 channel.atcmd(self.check)
176 elif (st == 'init' or st == 'checking') and self.at and not 'matched' in channel.state:
177 channel.state['stage'] = 'setting'
180 at = at % channel.args[self.arg]
181 if self.timeout_time:
182 channel.atcmd(at, timeout = self.timeout_time)
186 channel.cancel_timeout()
191 class PowerAction(Task):
192 # A PowerAction ensure that we have a connection to the modem
193 # and sets the power on or off, or resets the modem
194 def __init__(self, cmd):
195 Task.__init__(self, None)
198 def start(self, channel):
200 if not channel.connected:
202 if not channel.altchan.connected:
203 channel.altchan.connect()
204 channel.check_flightmode()
205 elif self.cmd == "off":
206 record('carrier', '')
208 record('signal_strength','-/32')
210 channel.altchan.disconnect()
211 elif self.cmd == 'reopen':
213 record('incoming','')
214 record('carrier', '')
216 record('signal_strength','-/32')
217 calllog_end('incoming')
218 calllog_end('outgoing')
220 channel.altchan.disconnect()
222 channel.altchan.connect()
223 return channel.advance()
225 class ChangeStateAction(Task):
226 # This action changes to a new state, like a goto
227 def __init__(self, state):
228 Task.__init__(self, None)
229 self.newstate = state
230 def start(self, channel):
232 state = self.newstate
233 elif channel.nextstate:
234 state = channel.nextstate.pop(0)
238 channel.gstate = state
239 channel.tasknum = None
240 log("ChangeStateAction chooses", channel.gstate)
241 n = len(control[channel.gstate])
242 channel.lastrun = n * [0]
243 return channel.advance()
245 class CheckSMS(Task):
247 Task.__init__(self, None)
248 def start(self, channel):
249 if 'incoming' in channel.nextstate:
250 # now is not a good time
251 return channel.advance()
252 if channel.pending_sms:
253 channel.pending_sms = False
254 p = Popen('gsm-getsms -n', shell=True, close_fds = True)
256 return channel.advance()
258 class RouteVoice(Task):
259 def __init__(self, on):
260 Task.__init__(self, None)
262 def start(self, channel):
264 channel.sound_on = True
266 f = open("/run/sound/00-voicecall","w")
270 p = Popen('/usr/local/bin/gsm-voice-routing', close_fds = True)
271 log('Running gsm-voice-routing pid', p.pid)
272 channel.voice_route = p
273 elif channel.sound_on:
274 if channel.voice_route:
275 channel.voice_route.send_signal(15)
276 channel.voice_route.wait()
277 channel.voice_route = None
279 os.unlink("/run/sound/00-voicecall")
282 channel.sound_on = False
283 return channel.advance()
285 class BlockSuspendAction(Task):
286 def __init__(self, enable):
287 Task.__init__(self, None)
289 def start(self, channel):
290 print "BlockSuspendAction sets", self.enable
292 channel.suspend_blocker.block()
293 # No point holding a pending suspend any more
294 if channel.suspend_pending:
295 channel.suspend_pending = False
296 print "BlockSuspendAction calls release"
297 suspend.abort_cycle()
298 channel.suspend_handle.release()
300 channel.suspend_blocker.unblock()
305 def __init__(self, msg, handle, handle_extra = None):
307 self.msgre = re.compile(msg)
309 self.handle_extra = handle_extra
311 def match(self, line):
312 return self.msgre.match(line)
318 def status_update(channel, line, m):
319 if m and m.groups()[3] != None:
320 global LAC, CELLID, cellnames
321 LAC = int(m.groups()[2],16)
322 CELLID = int(m.groups()[3],16)
323 record('cellid', "%04X %06X" % (LAC, CELLID));
324 if CELLID in cellnames:
325 record('cell', cellnames[CELLID])
326 log("That one is", cellnames[CELLID])
328 def new_sms(channel, line, m):
330 channel.pending_sms = False
331 record('newsms', m.groups()[1])
332 p = Popen('gsm-getsms -n', shell=True, close_fds = True)
335 def maybe_sms(line, channel):
336 channel.pending_sms = True
338 def sigstr(channel, line, m):
340 record('signal_strength', m.groups()[0] + '/32')
342 global incoming_cell_id
343 def cellid_update(channel, line, m):
344 # get something like +CBM: 1568,50,1,1,1
345 # don't know what that means, just collect the 'extra' line
346 # I think the '50' means 'this is a cell id'. I should
347 # probably test for that.
349 # response can be multi-line
350 global incoming_cell_id
351 incoming_cell_id = ""
353 def cellid_new(channel, line):
354 global CELLID, cellnames, incoming_cell_id
358 l = re.sub('[^!-~]+',' ',incoming_cell_id)
360 cellnames[CELLID] = l
365 incoming_cell_id += ' ' + line
367 incoming_cell_id = line
371 def incoming(channel, line, m):
374 record('incoming', incoming_num)
376 record('incoming', '-')
377 set_alert('ring', 'new')
378 if channel.gstate not in ['on-call', 'incoming', 'answer']:
379 calllog('incoming', '-call-')
380 channel.set_state('incoming')
381 record('status', 'INCOMING')
385 def incoming_number(channel, line, m):
389 if incoming_num == None:
390 calllog('incoming', num);
392 record('incoming', incoming_num)
394 def no_carrier(channel, line, m):
397 if channel.gstate != 'idle':
398 channel.set_state('idle')
400 def busy(channel, line, m):
401 record('status', 'BUSY')
404 def ussd(channel, line, m):
408 def call_status(channel, line, m):
411 log("call_status got", line)
414 s = int(m.groups()[0])
420 if cpas_zero_cnt <= 3:
425 record('incoming', '')
426 if channel.gstate in ['on-call','incoming','call']:
427 calllog_end('incoming')
428 calllog_end('outgoing')
430 if channel.gstate != 'idle' and channel.gstate != 'suspend':
431 channel.set_state('idle')
436 if channel.gstate not in ['incoming', 'answer']:
438 channel.set_state('incoming')
439 record('status', 'INCOMING')
440 set_alert('ring', 'new')
441 record('incoming', '-')
443 # on a call - but could be just a data call, so don't do anything
444 #if channel.gstate != 'on-call' and channel.gstate != 'hangup':
445 # channel.set_state('on-call')
448 def check_cfun(channel, line, m):
450 # If '1', then advance from init1 to init2
451 # else if not 0, possibly do a reset
454 if m.groups()[0] == '1':
455 if channel.gstate == 'init1':
456 channel.set_state('init2')
458 if m.groups()[0] != '0':
460 if 'signal_strength' in recording:
461 s = recording['signal_strength'].split('/')
465 if channel.last_reset + 100 < time.time():
466 channel.last_reset = time.time()
467 channel.set_state('refresh')
470 def data_handle(channel, line, m):
471 # Response to _OWANDATA - should contain IP address etc
473 if 'matched' in channel.state:
474 # already handled match
476 # no connection active
477 data_hungup(channel, failed=False)
479 # m[0] is gateway/IP addr. m[1] and m[2] are DNS servers
480 dns = (m.groups()[1], m.groups()[2])
481 if channel.data_DNS != dns:
482 record('dns', '%s %s' % dns)
483 channel.data_DNS = dns
485 if channel.data_IP != ip:
487 os.system('/sbin/ifconfig hso0 up %s' % ip)
491 def data_call(channel, line, m):
492 # delayed reponse to _OWANCALL. Maybe be async, may be
493 # polled with '_OWANCALL?'
496 if m.groups()[0] != '1':
498 s = int(m.groups()[1])
499 # 0 = Disconnected, 1 = Connected, 2 = In setup, 3 = Call setup failed.
501 data_hungup(channel, failed=False)
503 channel.set_state('idle')
508 data_hungup(channel, failed=True)
510 def data_hungup(channel, failed):
512 os.system('/sbin/ifconfig hso0 0.0.0.0 down')
515 channel.data_IP = None
516 channel.data_DNS = None
517 # FIXME should I retry, or reset?
519 # We still want a connection
521 channel.set_state('refresh')
523 elif channel.next_data_call <= time.time():
524 channel.next_data_call = (time.time() +
525 time.time() - channel.last_data_call);
526 channel.last_data_call = time.time()
528 channel.set_state('data-call')
530 if channel.gstate == 'data-call':
531 channel.set_state('idle')
533 # DATA traffic logging - goes to /var/log/gsm-data
534 def read_bytes(dev = 'hso0'):
535 f = file('/proc/net/dev')
538 w = l.strip().split()
539 if w[0] == dev + ':':
540 rv = ( int(w[1]), int(w[9]) )
545 last_data_usage = None
548 def data_log_reset():
549 global last_data_usage, last_data_time
550 last_data_usage = read_bytes()
551 last_data_time = time.time()
552 record('data-last-usage', '%s %s' % last_data_usage)
554 def data_log_update(force = False):
555 global last_data_usage, last_data_time, SIM
561 if not last_data_usage:
564 if not force and time.time() - last_data_time < 10*60:
567 data_usage = read_bytes()
569 calllog('gsm-data', '%s %d %d' %
571 data_usage[0] - last_data_usage[0],
572 data_usage[1] - last_data_usage[1]))
574 last_data_usage = data_usage
575 last_data_time = time.time()
576 record('data-last-usage', '%s %s' % last_data_usage)
580 # For flight mode, we turn the power off.
581 control['to-flight'] = [
582 AtAction(at='+CFUN=0'),
584 ChangeStateAction('flight')
586 control['flight'] = [
587 BlockSuspendAction(False),
590 control['refresh'] = [
591 # Soft reset: just use CFUN to turn off/on
592 # also reopen to clear status and just in case.
593 AtAction(at='+CFUN=0',critical=False, retries=0),
594 BlockSuspendAction(False),
595 PowerAction('reopen'),
596 BlockSuspendAction(True),
597 AtAction(at='E0', timeout=30000),
598 ChangeStateAction('init1'),
602 # Harder reset - use _ORESET and re-open devices.
603 # Don't block suspend if we cannot re-open.
604 AtAction(at='_ORESET', critical = False),
605 #AtAction(at='$QCPWRDN', critical = False, retries = 0),
606 BlockSuspendAction(False),
607 PowerAction('reopen'),
608 BlockSuspendAction(True),
609 AtAction(at='E0', timeout=30000),
610 ChangeStateAction('init1'),
613 # For suspend, we want power on, but no wakups for status or cellid
614 control['suspend'] = [
615 AtAction(check='+CPAS', ok='\+CPAS: (\d)', handle = call_status),
617 ChangeStateAction(None), # allow async state change
618 AtAction(at='+CNMI=1,1,0,0,0'),
619 AtAction(at='_OSQI=0'),
620 AtAction(at='_OEANT=0'),
621 AtAction(at='_OSSYS=0'),
622 AtAction(at='_OPONI=0'),
623 AtAction(at='+CREG=0'),
625 control['resume'] = [
626 BlockSuspendAction(True),
627 AtAction(check='+CPAS', ok='\+CPAS: (\d)', handle = call_status),
628 AtAction(at='+CNMI=1,1,2,0,0', critical=False),
629 AtAction(at='_OSQI=1', critical=False),
630 AtAction(at='+CREG=2'),
632 ChangeStateAction(None),
633 ChangeStateAction('idle'),
636 control['listenerr'] = [
639 AtAction(at='+CMEE=2;+CRC=1')
642 # init1 checks phone status and once we are online
643 # we switch to init2, then idle
645 BlockSuspendAction(True),
648 AtAction(at='+CMEE=2;+CRC=1'),
649 # Turn the device on.
650 AtAction(check='+CFUN?', ok='\+CFUN: 1', at='+CFUN=1', timeout=10000),
651 # Report carrier as long name
652 AtAction(at='+COPS=3,0'),
653 # register with a carrier
654 #AtAction(check='+COPS?', ok='\+COPS: \d+,\d+,"([^"]*)"', at='+COPS',
655 # record=('carrier', '\\1'), timeout=10000),
656 AtAction(check='+COPS?', ok='\+COPS: \d+,\d+,"([^"]*)"', at='+COPS=0',
657 record=('carrier', '\\1'), timeout=10000),
658 AtAction(check='+CFUN?', ok='\+CFUN: (\d)', at='+CFUN=1', timeout=10000, handle=check_cfun, repeat=5000),
662 # text format for various messages such SMS
663 AtAction(check='+CMGF?', ok='\+CMGF: 0', at='+CMGF=0'),
664 # get location status updates
665 AtAction(at='+CREG=2'),
666 AtAction(check='+CREG?', ok='\+CREG: 2,(\d)(,"([^"]*)","([^"]*)")',
667 handle=status_update, timeout=4000),
668 # Enable collection of Cell Info message
669 #AtAction(check='+CSCB?', ok='\+CSCB: 1,.*', at='+CSCB=1'),
670 #AtAction(at='+CSCB=0'),
671 AtAction(at='+CSCB=1', critical=False),
672 # Enable async reporting of TXT and Cell info messages
673 #AtAction(check='+CNMI?', ok='\+CNMI: 1,1,2,0,0', at='+CNMI=1,1,2,0,0'),
674 AtAction(at='+CNMI=1,0,0,0,0', critical=False),
675 AtAction(at='+CNMI=1,1,2,0,0', critical=False),
676 # Enable async reporting of signal strength
677 AtAction(at='_OSQI=1', critical=False),
678 AtAction(check='+CIMI', ok='(\d\d\d+)', record=('sim','\\1')),
679 #_OSIMOP: "YES OPTUS","YES OPTUS","50502"
680 AtAction(check='_OSIMOP', ok='_OSIMOP: "(.*)",".*","(.*)"',
681 record=('sid','\\2', 'carrier','\\1'), critical=False),
683 # Make sure to use both 2G and 3G
684 AtAction(at='_OPSYS=3,2', critical=False),
686 # Enable reporting of Caller number id.
687 AtAction(check='+CLIP?', ok='\+CLIP: 1,[012]', at='+CLIP=1', timeout=10000,
689 AtAction(check='+CPAS', ok='\+CPAS: (\d)', handle = call_status),
690 ChangeStateAction('idle')
693 def if_data(channel):
694 if not channel.data_APN and not channel.data_IP:
697 if channel.data_APN and channel.data_IP:
698 # connection is set up - slow watch
701 # must be shutting down - poll quickly, it shouldn't take long
703 # we want a connection but don't have one, so we retry
704 if time.time() < channel.next_data_call:
705 return int((channel.next_data_call - time.time()) * 1000)
711 BlockSuspendAction(False),
712 AtAction(check='+CFUN?', ok='\+CFUN: (\d)', timeout=10000, handle=check_cfun, repeat=30000),
713 AtAction(check='+COPS?', ok='\+COPS: \d+,\d+,"([^"]*)"', at='+COPS',
714 record=('carrier', '\\1'), timeout=10000),
715 #AtAction(check='+COPS?', ok='\+COPS: \d+,\d+,"([^"]*)"', at='+COPS=0',
716 # record=('carrier', '\\1'), timeout=10000, repeat=37000),
718 AtAction(check='+CSQ', ok='\+CSQ: (\d+),(\d+)',
719 record=('signal_strength','\\1/32'), repeat=29000),
720 AtAction(check='_OWANDATA?',
721 ok='_OWANDATA: 1, ([0-9.]+), [0-9.]+, ([0-9.]+), ([0-9.]+), [0-9.]+, [0-9.]+,\d+$',
722 handle=data_handle, repeat=if_data),
725 control['data-call'] = [
726 AtAction(at='+CGDCONT=1,"IP","%s"', arg='APN'),
727 AtAction(at='_OWANCALL=1,1,0'),
728 AtAction(at='_OWANCALL?', handle=data_call, repeat=2000),
729 #ChangeStateAction('idle')
732 control['data-hangup'] = [
733 AtAction(at='_OWANCALL=1,0,0'),
734 ChangeStateAction('idle')
737 control['incoming'] = [
738 BlockSuspendAction(True),
739 AtAction(check='+CPAS', ok='\+CPAS: (\d)', handle = call_status, repeat=500),
741 # monitor signal strength
742 AtAction(check='+CSQ', ok='\+CSQ: (\d+),(\d+)',
743 record=('signal_strength','\\1/32'), repeat=30000)
746 control['answer'] = [
749 ChangeStateAction('on-call')
753 AtAction(at='D%s;', arg='number'),
755 ChangeStateAction('on-call')
759 AtAction(at='+VTS=%s', arg='dtmf', noreply=True),
760 ChangeStateAction('on-call')
763 control['hangup'] = [
764 AtAction(at='+CHUP', critical=False, retries=0),
765 ChangeStateAction('idle')
768 control['on-call'] = [
769 BlockSuspendAction(True),
770 AtAction(check='+CPAS', ok='\+CPAS: (\d)', handle = call_status, repeat=2000),
772 # get signal strength
773 AtAction(check='+CSQ', ok='\+CSQ: (\d+),(\d+)',
774 record=('signal_strength','\\1/32'), repeat=30000)
778 Async(msg='\+CREG: ([01])(,"([^"]*)","([^"]*)")?', handle=status_update),
779 Async(msg='\+CMTI: "([A-Z]+)",(\d+)', handle = new_sms),
780 Async(msg='\+CBM: \d+,\d+,\d+,\d+,\d+', handle=cellid_update,
781 handle_extra = cellid_new),
782 Async(msg='\+CRING: (.*)', handle = incoming),
783 Async(msg='RING', handle = incoming),
784 Async(msg='\+CLIP: "([^"]+)",[0-9,]*', handle = incoming_number),
785 Async(msg='NO CARRIER', handle = no_carrier),
786 Async(msg='BUSY', handle = busy),
787 Async(msg='\+CUSD: ([012])(,"(.*)"(,[0-9]+)?)?$', handle = ussd),
788 Async(msg='_OSIGQ: ([0-9]+),([0-9]*)$', handle = sigstr),
790 Async(msg='_OWANCALL: (\d), (\d)', handle = data_call),
793 class GsmD(AtChannel):
795 # gsmd works like a state machine
796 # the high level states are: flight suspend idle incoming on-call
797 # Note that the whole 'call-waiting' experience is not coverred here.
798 # That needs to be handled by whoever answers calls and allows interaction
799 # between user and phone system.
801 # Each state contains a list of tasks such as setting and
802 # checking config options and monitoring state (e.g. signal strength)
803 # Some tasks are single-shot and only need to complete each time the state is
804 # entered. Others are repeating (such as status monitoring).
805 # We take the first task of the current list and execute it, or wait
806 # until one will be ready.
807 # Tasks themselves can be state machines, so we keep track of what 'stage'
808 # we are up to in the current task.
810 # The system is (naturally) event driven. The main two events that we
812 # 'takeline' which presents one line of text from the GSM device, and
813 # 'timeout' which indicates that a timeout set when a command was sent has
816 # 'taskready' when the time of the next pending task arrives.
817 # 'flight' when the state of the 'flight mode' has changed
818 # 'suspend' when a suspend has been requested.
820 # Each event does some event specific processing to modify the state,
821 # Then calls 'self.advance' to progress the state machine.
822 # When high level state changes are requested, any pending task is discarded.
824 # If a task detects an error (gsm device not responding properly) it might
825 # request a reset. This involves sending a modem_reset command and then
826 # restarting the current state from the top.
827 # A task can also indicate:
828 # The next stage to try
829 # How long to wait before retrying (or None)
832 def __init__(self, path, altpath):
833 AtChannel.__init__(self, path = path)
836 self.flightmode = True
839 self.suspend_pending = False
840 self.pending_sms = False
842 self.voice_route = None
844 self.altpath = altpath
845 self.altchan = CarrierDetect(altpath, self)
849 self.last_data_call = 0
850 self.next_data_call = 0
853 self.last_reset = time.time()
857 record('incoming','')
858 record('signal_strength','')
862 record('data-APN', '')
866 # set the initial state
867 self.set_state('flight')
869 # Monitor other external events which affect us
870 d = dnotify.dir('/var/lib/misc/flightmode')
871 self.flightmode_watcher = d.watch('active', self.check_flightmode)
872 d = dnotify.dir('/run/gsm-state')
873 self.call_watcher = d.watch('call', self.check_call)
874 self.dtmf_watcher = d.watch('dtmf', self.check_dtmf)
875 self.data_watcher = d.watch('data-APN', self.check_data)
877 self.suspend_handle = suspend.monitor(self.do_suspend, self.do_resume)
878 self.suspend_blocker = suspend.blocker()
880 # Check the externally imposed state
881 self.check_flightmode(self.flightmode_watcher)
886 def check_call(self, f = None):
888 log("Check call got", l)
890 if self.gstate != 'idle':
893 self.set_state('hangup')
895 record('incoming','')
896 calllog_end('incoming')
897 calllog_end('outgoing')
899 if self.gstate == 'incoming':
900 record('status', 'on-call')
901 record('incoming','')
902 set_alert('ring', None)
903 self.set_state('answer')
905 if self.gstate == 'idle':
908 self.args['number'] = l
909 self.set_state('call')
910 calllog('outgoing',l)
911 record('status', 'on-call')
913 def check_dtmf(self, f = None):
915 log("Check dtmf got", l)
917 self.args['dtmf'] = l
918 self.set_state('dtmf')
921 def check_data(self, f = None):
922 l = recall('data-APN')
923 log("Check data got", l)
926 if self.data_APN != l:
928 self.args['APN'] = self.data_APN
930 self.set_state('data-hangup')
931 data_log_update(True)
932 elif self.gstate == 'idle' and self.data_APN:
933 self.last_data_call = time.time()
934 self.next_data_call = time.time()
936 self.set_state('data-call')
938 def check_flightmode(self, f = None):
940 fd = open("/var/lib/misc/flightmode/active")
945 log("check flightmode got", len(l))
948 self.flightmode = False
949 if self.suspend_handle.suspended:
950 self.set_state('suspend')
952 self.set_state('init1')
954 if not self.flightmode:
955 self.flightmode = True
956 self.set_state('to-flight')
958 def do_suspend(self):
959 self.suspend_pending = True
960 if self.gstate not in ['flight', 'resume']:
961 print "do suspend sets suspend"
962 self.set_state('suspend')
964 print "do suspend avoids suspend"
969 if self.gstate == 'suspend':
970 self.set_state('resume')
972 def set_state(self, state):
973 # this happens asynchronously so we must be careful
974 # about changing things. Just record the new state
975 # and abort any timeout
976 if state == self.gstate or state in self.nextstate:
977 log("state already destined to be", state)
979 log("state should become", state)
980 self.nextstate.append(state)
983 def force_state(self, state):
984 # immediately go to new state - must be called carefully
985 log("Force state to", state);
986 self.cancel_timeout()
988 n = len(control[state])
989 self.lastrun = n * [0]
994 # 'advance' is called by a 'Task' when it has finished
995 # It may have called 'set_state' first either to report
996 # an error or to effect a regular state change
997 now = int(time.time()*1000)
998 if self.tasknum != None:
999 self.lastrun[self.tasknum] = now
1001 (t, delay) = self.next_cmd()
1002 log("advance %s chooses %d, %d" % (self.gstate, t, delay))
1003 if delay and self.nextstate:
1004 # time to effect 'set_state' synchronously
1005 self.gstate = self.nextstate.pop(0)
1006 log("state becomes", self.gstate)
1007 n = len(control[self.gstate])
1008 self.lastrun = n * [0]
1009 t, delay = self.next_cmd()
1012 log("Sleeping for %f seconds" % (delay/1000.0))
1013 self.set_timeout(delay)
1014 if self.suspend_pending:
1015 # It is important that this comes after set_timeout
1016 # as we might get an abort_timeout as a result of the
1017 # release, and there needs to be a timeout to abort
1018 self.suspend_pending = False
1019 print "advance calls release"
1020 self.suspend_handle.release()
1024 control[self.gstate][t].start(self)
1026 def takeline(self, line):
1029 # an async message is multi-line and we need to handle
1031 if not self.extra.handle_extra(self, line):
1036 self.force_state('reset')
1041 # Check for an async message
1045 m.handle(self, line, mt)
1050 # else pass it to the task
1051 if self.tasknum != None:
1052 control[self.gstate][self.tasknum].takeline(self, line)
1055 if self.tasknum == None:
1058 control[self.gstate][self.tasknum].timeout(self)
1061 # Find a command to execute, or a delay
1063 # cmd is an index into control[state],
1064 # time is seconds until try something
1065 mindelay = 60*60*1000
1066 if self.gstate == None:
1067 return (0, mindelay)
1068 cs = control[self.gstate]
1070 now = int(time.time()*1000)
1072 if self.lastrun[i] == 0:
1074 repeat = cs[i].repeat
1077 elif type(repeat) != int:
1078 repeat = repeat(self)
1080 if repeat and self.lastrun[i] + repeat <= now:
1083 delay = (self.lastrun[i] + repeat) - now;
1084 if delay < mindelay:
1086 return (0, mindelay)
1088 class CarrierDetect(AtChannel):
1089 # on the hso modem in the GTA04, the 'NO CARRIER' signal
1090 # arrives on the 'Modem' port, not on the 'Application' port.
1091 # So we listen to the 'Modem' port, and report any
1092 # 'NO CARRIER' we see - or indeed anything that we see.
1093 def __init__(self, path, main):
1094 AtChannel.__init__(self, path = path)
1097 def takeline(self, line):
1098 self.main.takeline(line)
1101 # watch for changes on a sysfs file and report them
1102 # We read the content, report that, wait for a change
1104 def __init__(self, path, action):
1106 self.action = action
1107 self.fd = open(path, "r")
1108 self.watcher = gobject.io_add_watch(self.fd, gobject.IO_PRI, self.read)
1111 def read(self, *args):
1114 r = self.fd.read(4096)
1121 os.mkdir("/run/gsm-state")
1126 a = GsmD('/dev/ttyHS_Application', '/dev/ttyHS_Modem')
1127 print "GsmD started"
1130 f = open("/sys/class/gpio/gpio176/edge", "w")
1136 w = SysfsWatcher("/sys/class/gpio/gpio176/value",
1137 lambda l: maybe_sms(l, a))
1140 def check_evt(dc, mom, typ, code, val):
1141 if typ == 1 and val == 1:
1145 f = evdev.EvDev("/dev/input/incoming", check_evt)
1148 c = gobject.main_context_default()