]> git.neil.brown.name Git - plato.git/commitdiff
GSMD - add management of data connection.
authorNeilBrown <neilb@suse.de>
Mon, 31 Dec 2012 09:41:16 +0000 (20:41 +1100)
committerNeilBrown <neilb@suse.de>
Mon, 31 Dec 2012 09:41:16 +0000 (20:41 +1100)
If /run/gsm-state/data-APN contains an APN, then gsmd
tries to establish and maintain a data connection.
The 'hso' interface is ifconfiged and the IP is written to
/run/gsm-state/data.
DNS servers are written to /run/gsm-state/dns

gsm/gsmd.py
gsm/notes

index 66d99c0335ba982d4f978a7cf97e6af6321a6c31..75f9122b1df142421ee9cd72f948c5c20e5e3c46 100644 (file)
@@ -29,14 +29,15 @@ def record(key, value):
     os.rename('/run/gsm-state/.new.' + key,
               '/run/gsm-state/' + key)
 
-def recall(key):
+def recall(key, nofile = ""):
     try:
         fd = open("/run/gsm-state/" + key)
         l = fd.read(1000)
+        l = l.strip()
         fd.close()
     except IOError:
-        l = ""
-    return l.strip()
+        l = nofile
+    return l
 
 def set_alert(key, value):
     path = '/run/alert/' + key
@@ -425,6 +426,67 @@ def call_status(channel, line, m):
         #    channel.set_state('on-call')
         pass
 
+def data_handle(channel, line, m):
+    # Response to _OWANDATA - should contain IP address etc
+    if not m:
+        if 'matched' in channel.state:
+            # already handled match
+            return
+        # no connection active
+        data_hungup(channel, failed=False)
+        return
+    # m[0] is gateway/IP addr.  m[1] and m[2] are DNS servers
+    dns = (m.groups()[1], m.groups()[2])
+    if channel.data_DNS != dns:
+        record('dns', '%s %s' % dns)
+        channel.data_DNS = dns
+    ip = m.groups()[0]
+    if channel.data_IP != ip:
+        channel.data_IP = ip
+        os.system('/sbin/ifconfig hso0 up %s' % ip)
+        record('data', ip)
+
+def data_call(channel, line, m):
+    # delayed reponse to _OWANCALL.  Maybe be async, may be
+    # polled with '_OWANCALL?'
+    if not m:
+        return
+    if m.groups()[0] != '1':
+        return
+    s = int(m.groups()[1])
+    #   0 = Disconnected, 1 = Connected, 2 = In setup,  3 = Call setup failed.
+    if s == 0:
+        data_hungup(channel, failed=False)
+    elif s == 1:
+        channel.set_state('idle')
+    elif s == 2:
+        # try again soon
+        pass
+    elif s == 3:
+        data_hungup(channel, failed=True)
+
+def data_hungup(channel, failed):
+    if channel.data_IP:
+        os.system('/sbin/ifconfig hso0 down')
+    record('dns', '')
+    record('data', '')
+    channel.data_IP = None
+    channel.data_DNS = None
+    # FIXME should I retry, or reset?
+    if channel.data_APN:
+        # We still want a connection
+        if failed:
+            channel.set_state('reset')
+            return
+        elif channel.next_data_call <= time.time():
+            channel.next_data_call = (time.time() +
+                                      time.time() - channel.last_data_call);
+            channel.last_data_call = time.time()
+            channel.set_state('data-call')
+            return
+    if channel.gstate == 'data-call':
+        channel.set_state('idle')
+
 control = {}
 
 # For flight mode, we turn the power off.
@@ -518,6 +580,21 @@ control['init'] = [
     ChangeStateAction('idle')
     ]
 
+def if_data(channel):
+    if not channel.data_APN and not channel.data_IP:
+        # no data happening
+        return 0
+    if channel.data_APN and channel.data_IP:
+        # connection is set up - slow watch
+        return 30000
+    if channel.data_IP:
+        # must be shutting down - poll quickly, it shouldn't take long
+        return 2000
+    # we want a connection but don't have one, so we retry
+    if time.time() < self.next_data_call:
+        return int((self.next_data_call - time.time()) * 1000)
+    return 1000
+
 control['idle'] = [
     RouteVoice(False),
     CheckSMS(),
@@ -531,8 +608,23 @@ control['idle'] = [
     # get signal string
     AtAction(check='+CSQ', ok='\+CSQ: (\d+),(\d+)',
              record=('signal_strength','\\1/32'), repeat=29000)
+    AtAction(check='_OWANDATA?',
+             ok='_OWANDATA: 1, ([0-9.]+), [0-9.]+, ([0-9.]+), ([0-9.]+), [0-9.]+, [0-9.]+,\d+$',
+             handle=data_handle, repeat=if_data),
     ]
 
+control['data-call'] = [
+    AtAction(at='+CGDCONT=1,"IP","%s"', arg='APN'),
+    AtAction(at='_OWANCALL=1,1,0'),
+    AtAction(at='_OWANCALL?', handle=data_call, repeat=2000),
+    #ChangeStateAction('idle')
+]
+
+control['data-hangup'] = [
+    AtAction(at='_OWANCALL=1,0,0'),
+    ChangeStateAction('idle')
+]
+
 control['incoming'] = [
     BlockSuspendAction(True),
     AtAction(check='+CPAS', ok='\+CPAS: (\d)', handle = call_status, repeat=500),
@@ -586,6 +678,7 @@ async = [
     Async(msg='\+CUSD: ([012])(,"(.*)"(,[0-9]+)?)?$', handle = ussd),
     Async(msg='_OSIGQ: ([0-9]+),([0-9]*)$', handle = sigstr),
 
+    Async(msg='_OWANCALL: (\d), (\d)', handle = data_call),
     ]
 
 class GsmD(AtChannel):
@@ -641,6 +734,11 @@ class GsmD(AtChannel):
         self.tasknum = None
         self.altpath = altpath
         self.altchan = CarrierDetect(altpath, self)
+        self.data_APN = None
+        self.data_IP =  None
+        self.data_DNS = None
+        self.last_data_call = 0
+        self.next_data_call = 0
         self.gstate = None
         self.nextstate = []
 
@@ -651,6 +749,9 @@ class GsmD(AtChannel):
         record('status', '')
         record('sim','')
         record('sid','')
+        record('data-APN', '')
+        record('data', '')
+        record('dns', '')
 
         # set the initial state
         self.set_state('flight')
@@ -661,6 +762,7 @@ class GsmD(AtChannel):
         d = dnotify.dir('/run/gsm-state')
         self.call_watcher = d.watch('call', self.check_call)
         self.dtmf_watcher = d.watch('dtmf', self.check_dtmf)
+        self.data_watcher = d.watch('data-APN', self.check_data)
 
         self.suspend_handle = suspend.monitor(self.do_suspend, self.do_resume)
         self.suspend_blocker = suspend.blocker()
@@ -706,6 +808,21 @@ class GsmD(AtChannel):
             self.set_state('dtmf')
             record('dtmf','')
 
+    def check_data(self, f = None):
+        l = recall('data-APN')
+        log("Check data got", l)
+        if l == "":
+            l = None
+        if self.data_APN != l:
+            self.data_APN = l
+            self.args['APN'] = self.data_APN
+            if self.data_IP:
+                self.set_state('data-hangup')
+            elif self.gstate == 'idle' and self.data_APN:
+                self.last_data_call = time.time()
+                self.next_data_call = time.time()
+                self.set_state('data-call')
+
     def check_flightmode(self, f = None):
         try:
             fd = open("/var/lib/misc/flightmode/active")
index 9a9264d515e420b8ae11c038951e5b4714c465fb..47b4b0e2ab46aa63ba5784f616afe394b8e19e96 100644 (file)
--- a/gsm/notes
+++ b/gsm/notes
@@ -137,3 +137,161 @@ We sometimes check gstate directly:
  call_status0: if 'on-call, incoming, call, clear call
                if not idle or suspend, become idle
  call_status3: if not 'incoming' or 'answer', become 'incoming'
+
+----------------------------
+
+I want gsmd to manage data too.
+We use a file: /run/gsm-state/data
+
+If it is removed, we abort any data connection.
+If it exists we start a data connection and write the
+IP address and DNS servers to it.
+
+If we fail to make a connection we record failure and retry
+with exponential backoff to 1024 seconds.
+If we are connected and want to be, we check every 30 seconds
+If not connected and don't want to be, re-check every hour
+
+Both 'idle' and 'on-call' need to periodically monitor
+status.
+
+So a significant change to the 'data' file triggers a switch
+to 'data' if idle, which switches back to 'idle'.
+'idle' and  'on-call' have 'check-data' action.
+
+BUT: I need to set APN somehow.  Also need to auto-detect
+ roaming and disable data in that case.
+
+Commands are:
+ AT+CGDCONT=1,"IP","$APN"
+
+ AT_OWANCALL=1,1,1
+which should get async reply
+   first '1' is channel
+   second '1' is 'connect'
+   third '1' is 'async reply wanted'
+   _OWANCALL: 1, 1
+but gets
+   _OWANCALL: 1, 3
+on error
+  0 = Disconnected, 1 = Connected, 2 = In setup,  3 = Call setup failed.
+
+ Once connected:
+  AT_OWANDATA?
+ returns
+_OWANDATA: 1, <gateway>, <IP>, <DNS 1>, <DNS 2>, <NBNS1>, <NBNS2>, <speed>
+e.g.
+_OWANDATA: 1, 115.70.17.232, 0.0.0.0, 58.96.1.28, 220.233.0.3, 0.0.0.0, 0.0.0.0,144000
+
+If we consistently get Call setup failure, a reset ($QCPWRDWN) might help.
+or maybe a reset; AT_ORESET
+
+
+Roaming can be detected by comparing SIM card to services.
+AT+CIMI
+gets sim card number: 505023202571742
+First 5 digits are carrier:  50502.  3 are country, 2 are service.
+Match against AT_OSIMOP?
+
+https://bugs.archlinux.org/task/20864?getfile=5724
+
+
+_ONCI: 3G
+_ONCI: "0898FED3"
+_ONCI: "50502"
+_ONCI: 1
+_ONCI: (188,0,126)
+_ONCI: 8,1,(193,0,),(187,0,),(183,0,158)
+_ONCI: 8,2,(182,0,158),(181,0,),(194,0,158)
+_ONCI: 8,3,(185,0,),(190,0,158),(204,0,158)
+_ONCI: 8,4,(202,0,158),(198,0,158),(197,0,158)
+_ONCI: 8,5,(196,0,),(201,0,158),(200,0,158)
+_ONCI: 8,6,(199,0,158),(186,0,158),(184,0,158)
+_ONCI: 8,7,(10,0,),(11,0,),(203,0,)
+_ONCI: 8,8,(189,0,158)
+
+
+_ONCI: 3G
+_ONCI: "0898FED3"
+_ONCI: "50502"
+_ONCI: 1
+_ONCI: (188,0,118)
+_ONCI: 8,1,(189,0,158),(187,0,),(183,0,158)
+_ONCI: 8,2,(182,0,158),(181,0,),(194,0,158)
+_ONCI: 8,3,(193,0,),(190,0,158),(204,0,158)
+_ONCI: 8,4,(203,0,),(202,0,158),(198,0,158)
+_ONCI: 8,5,(197,0,158),(196,0,),(201,0,158)
+_ONCI: 8,6,(200,0,158),(199,0,158),(186,0,158)
+_ONCI: 8,7,(185,0,),(10,0,),(11,0,)
+_ONCI: 8,8,(184,0,158)
+
+_ONCI: 3G
+_ONCI: "0898FED3"
+_ONCI: "50502"
+_ONCI: 1
+_ONCI: (188,0,158)
+_ONCI: 7,1,(203,0,134),(201,0,),(182,0,158)
+_ONCI: 7,2,(181,0,),(10,0,),(193,0,)
+_ONCI: 7,3,(190,0,158),(204,0,126),(187,0,158)
+_ONCI: 7,4,(183,0,158),(198,0,158),(197,0,158)
+_ONCI: 7,5,(196,0,158),(202,0,158),(200,0,138)
+_ONCI: 7,6,(199,0,139),(194,0,128),(184,0,158)
+_ONCI: 7,7,(11,0,158),(186,0,158),(185,0,158)
+
+
+_ONCI: 3G
+_ONCI: "08989C66"
+_ONCI: "50502"
+_ONCI: 0
+_ONCI: (143,0,)
+_ONCI: 5,1,(144,0,),(124,0,158),(125,0,158)
+_ONCI: 5,2,(126,0,),(121,0,120),(122,0,)
+_ONCI: 5,3,(123,0,141),(145,0,142),(146,0,)
+_ONCI: 5,4,(147,0,),(142,0,158),(135,0,)
+_ONCI: 5,5,(133,0,158),(134,0,158)
+
+
+
+First number is up to 204.
+Seconds is 0
+Third is blank or 158.  but can br 126, 118 for first triple.
+
+_OPON: 1,594553204F50545553,3
+         Y E S spO P T U S
+
+_OSIMOP: "YES OPTUS","YES OPTUS","50502"
+
+_OSSYS 0 -> TECH_GPRS (use OCTI for details)
+_OSSYS 2 -> TECH_UMTS (use OWCTI for details)
+
+OCTI 1 -> GSM
+OCTI 2 -> GPRS
+OCTI 3 -> EDGE
+
+OWCTI 1 -> UMTS
+OWCTI 2 -> HSDPA
+OWCTI 3 -> HSUPA
+OWCTI 4 -> HSPA
+
+
+???
+Issues:
+ - Who manages the APN?  I think netman should do that.  So it requests
+    data service by writing the APN to /run/gsm-state/data-APN
+ - gsmd must check roaming status and report operator, SIM.
+ - gsmd can do the ifconfig, but leave routing and DNS to netman.
+ - gsmd should monitor usage, record current session in stable storage
+   and provide a log
+ - report +CSQ and OSIGQ separately?
+
+
+Data sequencing:
+
+ 'data' state is like 'idle' but regularly checks data status
+ 'data-hangup' state to disconnect data
+ 'data-call' state  to start data call
+
+ DataCheck action which tracks usage...
+
+
+ADD CLCC monitoring to get out of 'idle' into 'on-call' ?