11"""
22KingWatch Pro v17 - core/network.py
3-
4- HYBRID BAND DETECTION MATRIX:
5- Layer 1: TelephonyManager.getDataNetworkType() (primary, most accurate)
6- Layer 2: NetworkCapabilities.getLinkDownstreamBandwidthKbps() (no permission)
7- Layer 3: Heuristic from actual measured speed (always works)
8- Layer 4: sysfs interface name fallback
9-
10- Band is determined by best available source, updated every 5s.
11- Returns keys: dl, ul, sig, arc
3+ Hybrid band detection + ping via background thread.
4+ Returns keys: dl, ul, sig, ping, arc
125"""
136import time as _time
147import glob
158from threading import Thread as _Thread
169
1710_signal = "Detecting..."
11+ _ping_str = "--"
1812_started = False
1913_bw = {}
2014_dl = 0.0
2115_ul = 0.0
2216_EMA = 0.4
2317_band_bps = 10.0 * 125000
18+ _spdhist = [] # speed history for heuristic
19+
20+
21+ # -- Ping ------------------------------------------------------------------
22+ def _ping_once ():
23+ """TCP connect ping - all via getattr to avoid Python 3.11 cache bug."""
24+ _sk = __import__ ('socket' )
25+ _AF_INET = getattr (_sk , 'AF_INET' )
26+ _SOCK_STREAM = getattr (_sk , 'SOCK_STREAM' )
27+ _SocketCls = getattr (_sk , 'socket' )
28+ _now = getattr (_time , 'time' )
29+ for ip , port in [("8.8.8.8" , 53 ), ("1.1.1.1" , 443 )]:
30+ try :
31+ s = _SocketCls (_AF_INET , _SOCK_STREAM )
32+ _st = getattr (s , 'settimeout' )
33+ _cx = getattr (s , 'connect_ex' )
34+ _cl = getattr (s , 'close' )
35+ _st (2.0 )
36+ t0 = _now ()
37+ res = _cx ((ip , port ))
38+ ms = round ((_now () - t0 ) * 1000 )
39+ _cl ()
40+ if not res : # res == 0 means connected
41+ return str (ms ) + "ms"
42+ except Exception :
43+ pass
44+ return "--"
45+
2446
25- # Speed history for heuristic (last 10 samples in bytes/s)
26- _speed_hist = []
47+ def _ping_loop ():
48+ global _ping_str
49+ _sleep = getattr (_time , 'sleep' )
50+ _sleep (3 )
51+ while True :
52+ _ping_str = _ping_once ()
53+ _sleep (8 )
2754
2855
56+ # -- Band detection ---------------------------------------------------------
2957def _quality (dbm ):
3058 if - 50 < dbm : return "Excellent"
3159 if - 60 < dbm : return "Good"
@@ -34,16 +62,12 @@ def _quality(dbm):
3462 return "Poor"
3563
3664
37- def _heuristic_band (dl_bps ):
38- """
39- Layer 3: Classify band from actual measured speed.
40- Uses median of recent speed history for stability.
41- """
65+ def _heuristic (dl_bps ):
66+ """Layer 3: classify band from measured speed median."""
4267 global _band_bps
43- if len (_speed_hist ) < 3 :
68+ if len (_spdhist ) < 3 :
4469 return ""
45- samples = sorted (_speed_hist )
46- med = samples [len (samples ) // 2 ]
70+ med = sorted (_spdhist )[len (_spdhist ) // 2 ]
4771 mbps = med / 125000.0
4872 if 50.0 < mbps : _band_bps = 500.0 * 125000 ; return "5G (speed)"
4973 if 10.0 < mbps : _band_bps = 50.0 * 125000 ; return "4G LTE (speed)"
@@ -52,46 +76,6 @@ def _heuristic_band(dl_bps):
5276 return ""
5377
5478
55- def _telephony_band (ctx , Ctx ):
56- """Layer 1: TelephonyManager - most accurate when available."""
57- global _band_bps
58- try :
59- from jnius import autoclass # type: ignore
60- TM = autoclass ("android.telephony.TelephonyManager" )
61- tm = getattr (ctx , 'getSystemService' )(getattr (Ctx , 'TELEPHONY_SERVICE' ))
62- nt = getattr (tm , 'getDataNetworkType' )()
63- if nt in (0 ,): return "" # UNKNOWN - try next layer
64- if nt in (20 ,): _band_bps = 500.0 * 125000 ; return "5G NR Max 500Mbps"
65- if nt in (13 ,): _band_bps = 50.0 * 125000 ; return "4G LTE Max 50Mbps"
66- if nt in (19 ,): _band_bps = 150.0 * 125000 ; return "4G LTE-CA Max 150Mbps"
67- if nt in (15 ,): _band_bps = 42.0 * 125000 ; return "4G HSPA+ Max 42Mbps"
68- if nt in (8 ,9 ,10 ,3 ,5 ,6 ,12 ,14 ):
69- _band_bps = 14.0 * 125000 ; return "3G Max 14Mbps"
70- if nt in (1 ,2 ,4 ,7 ,11 ,16 ):
71- _band_bps = 0.2 * 125000 ; return "2G Max 0.2Mbps"
72- _band_bps = 10.0 * 125000 ; return "Mobile"
73- except Exception :
74- pass
75- return ""
76-
77-
78- def _caps_band (caps ):
79- """Layer 2: NetworkCapabilities bandwidth estimate - no permission needed."""
80- global _band_bps
81- try :
82- _gDL = getattr (caps , 'getLinkDownstreamBandwidthKbps' )
83- dl = _gDL ()
84- if 0 < dl :
85- if 50000 < dl : _band_bps = 500.0 * 125000 ; return "5G ~" + str (dl // 1000 ) + "Mbps"
86- if 5000 < dl : _band_bps = 50.0 * 125000 ; return "4G LTE ~" + str (dl // 1000 ) + "Mbps"
87- if 1000 < dl : _band_bps = 20.0 * 125000 ; return "4G ~" + str (dl // 1000 ) + "Mbps"
88- if 200 < dl : _band_bps = 14.0 * 125000 ; return "3G ~" + str (dl // 1000 ) + "Mbps"
89- _band_bps = 0.2 * 125000 ; return "2G ~" + str (dl ) + "Kbps"
90- except Exception :
91- pass
92- return ""
93-
94-
9579def _detect ():
9680 global _band_bps
9781 try :
@@ -125,27 +109,46 @@ def _detect():
125109 except Exception :
126110 _band_bps = 100.0 * 125000 ; return "WiFi"
127111
128- # Cellular - hybrid matrix
112+ # Cellular - Layer 1: TelephonyManager
129113 if _hT (0 ):
130- # Layer 1: TelephonyManager
131- result = _telephony_band (ctx , Ctx )
132- if result :
133- return result
134- # Layer 2: NetworkCapabilities bandwidth
135- result = _caps_band (caps )
136- if result :
137- return result
138- # Layer 3: Heuristic from speed history
139- result = _heuristic_band (_dl )
140- if result :
141- return result
142- # All failed - show Mobile
143- _band_bps = 10.0 * 125000
144- return "Mobile"
114+ try :
115+ TM = autoclass ("android.telephony.TelephonyManager" )
116+ tm = getattr (ctx , 'getSystemService' )(getattr (Ctx , 'TELEPHONY_SERVICE' ))
117+ nt = getattr (tm , 'getDataNetworkType' )()
118+ if nt in (20 ,): _band_bps = 500.0 * 125000 ; return "5G NR Max 500Mbps"
119+ if nt in (13 ,): _band_bps = 50.0 * 125000 ; return "4G LTE Max 50Mbps"
120+ if nt in (19 ,): _band_bps = 150.0 * 125000 ; return "4G LTE-CA Max 150Mbps"
121+ if nt in (15 ,): _band_bps = 42.0 * 125000 ; return "4G HSPA+ Max 42Mbps"
122+ if nt in (8 ,9 ,10 ,3 ,5 ,6 ,12 ,14 ):
123+ _band_bps = 14.0 * 125000 ; return "3G Max 14Mbps"
124+ if nt in (1 ,2 ,4 ,7 ,11 ,16 ):
125+ _band_bps = 0.2 * 125000 ; return "2G Max 0.2Mbps"
126+ # nt==0 UNKNOWN - try Layer 2
127+ except Exception :
128+ pass
129+
130+ # Layer 2: bandwidth from NetworkCapabilities
131+ try :
132+ _gDL = getattr (caps , 'getLinkDownstreamBandwidthKbps' )
133+ dl = _gDL ()
134+ if 0 < dl :
135+ if 50000 < dl : _band_bps = 500.0 * 125000 ; return "5G ~" + str (dl // 1000 ) + "Mbps"
136+ if 5000 < dl : _band_bps = 50.0 * 125000 ; return "4G LTE ~" + str (dl // 1000 ) + "Mbps"
137+ if 1000 < dl : _band_bps = 20.0 * 125000 ; return "4G ~" + str (dl // 1000 ) + "Mbps"
138+ if 200 < dl : _band_bps = 14.0 * 125000 ; return "3G ~" + str (dl // 1000 ) + "Mbps"
139+ _band_bps = 0.2 * 125000 ; return "2G ~" + str (dl ) + "Kbps"
140+ except Exception :
141+ pass
142+
143+ # Layer 3: speed heuristic
144+ h = _heuristic (_dl )
145+ if h :
146+ return h
147+
148+ _band_bps = 10.0 * 125000 ; return "Mobile"
145149
146150 if _hT (3 ):
147- _band_bps = 1000.0 * 125000
148- return "Ethernet Max 1000Mbps"
151+ _band_bps = 1000.0 * 125000 ; return "Ethernet Max 1000Mbps"
149152
150153 return "Connected"
151154 except Exception :
@@ -182,6 +185,7 @@ def _signal_loop():
182185 _sleep (5 )
183186
184187
188+ # -- Traffic ----------------------------------------------------------------
185189def _bytes ():
186190 try :
187191 from jnius import autoclass # type: ignore
@@ -214,37 +218,46 @@ def _fmt(b):
214218 return "0 KB/s"
215219
216220
221+ # -- Public API -------------------------------------------------------------
217222def get_network ():
223+ # Returns keys: dl, ul, sig, ping, arc
218224 global _started , _dl , _ul
219225 if not _started :
220226 _started = True
221227 _t1 = _Thread (target = _signal_loop , daemon = True )
222228 getattr (_t1 , 'start' )()
229+ _t2 = _Thread (target = _ping_loop , daemon = True )
230+ getattr (_t2 , 'start' )()
223231
224232 rx , tx = _bytes ()
225233 now = getattr (_time , 'time' )()
226234 sig = _signal
235+ ping = _ping_str
227236
228237 if not _bw :
229238 _bw .update ({"rx" : rx , "tx" : tx , "t" : now })
230- return {"dl" : "0 KB/s" , "ul" : "0 KB/s" , "sig" : sig , "arc" : 0.0 }
239+ return {"dl" : "0 KB/s" , "ul" : "0 KB/s" , "sig" : sig , "ping" : ping , " arc" : 0.0 }
231240
232241 dt = now - _bw ["t" ]
233242 if dt < 0.3 :
234243 if 0 < _band_bps :
235244 arc = min (100.0 , _dl / _band_bps * 100 )
236245 else :
237246 arc = 0.0
238- return {"dl" : _fmt (_dl ), "ul" : _fmt (_ul ), "sig" : sig , "arc" : arc }
247+ return {"dl" : _fmt (_dl ), "ul" : _fmt (_ul ), "sig" : sig , "ping" : ping , " arc" : arc }
239248
240249 prx = _bw ["rx" ]
241250 ptx = _bw ["tx" ]
242251 _bw .update ({"rx" : rx , "tx" : tx , "t" : now })
243252
244253 if prx < rx :
245254 dl_raw = (rx - prx ) / dt
255+ _spdhist .append (dl_raw )
256+ if not (len (_spdhist ) < 11 ):
257+ _spdhist .pop (0 )
246258 else :
247259 dl_raw = 0.0
260+
248261 if ptx < tx :
249262 ul_raw = (tx - ptx ) / dt
250263 else :
@@ -253,15 +266,9 @@ def get_network():
253266 _dl = _EMA * dl_raw + (1 - _EMA ) * _dl
254267 _ul = _EMA * ul_raw + (1 - _EMA ) * _ul
255268
256- # Feed speed history for heuristic
257- if 0 < dl_raw :
258- _speed_hist .append (dl_raw )
259- if not (len (_speed_hist ) < 11 ):
260- _speed_hist .pop (0 )
261-
262269 if 0 < _band_bps :
263270 arc = min (100.0 , _dl / _band_bps * 100 )
264271 else :
265272 arc = 0.0
266273
267- return {"dl" : _fmt (_dl ), "ul" : _fmt (_ul ), "sig" : sig , "arc" : arc }
274+ return {"dl" : _fmt (_dl ), "ul" : _fmt (_ul ), "sig" : sig , "ping" : ping , " arc" : arc }
0 commit comments