Skip to content

Commit da5a0c4

Browse files
authored
C3: detect metered Android hotspot (commaai#23734)
* C3: detect metered networks * show in ui * fix text layout * bump cereal * revert ui changes * set networkMetered * add athena method * add metered logging to uploader * use in athena uploader * remove param * use networkmanager properties to set cell to unmetered * fix indentation * no need to check * bump cereal * review * bump cereal
1 parent 0b47800 commit da5a0c4

File tree

7 files changed

+66
-25
lines changed

7 files changed

+66
-25
lines changed

cereal

selfdrive/athena/athenad.py

+18-15
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,6 @@ def upload_handler(end_event: threading.Event) -> None:
163163
sm = messaging.SubMaster(['deviceState'])
164164
tid = threading.get_ident()
165165

166-
cellular_unmetered = Params().get_bool("CellularUnmetered")
167-
168166
while not end_event.is_set():
169167
cur_upload_items[tid] = None
170168

@@ -181,46 +179,45 @@ def upload_handler(end_event: threading.Event) -> None:
181179
cloudlog.event("athena.upload_handler.expired", item=cur_upload_items[tid], error=True)
182180
continue
183181

184-
# Check if uploading over cell is allowed
182+
# Check if uploading over metered connection is allowed
185183
sm.update(0)
186-
cell = sm['deviceState'].networkType not in [NetworkType.wifi, NetworkType.ethernet]
187-
if cell and (not cur_upload_items[tid].allow_cellular) and (not cellular_unmetered):
184+
metered = sm['deviceState'].networkMetered
185+
network_type = sm['deviceState'].networkType.raw
186+
if metered and (not cur_upload_items[tid].allow_cellular):
188187
retry_upload(tid, end_event, False)
189188
continue
190189

191190
try:
192191
def cb(sz, cur):
193-
# Abort transfer if connection changed to cell after starting upload
192+
# Abort transfer if connection changed to metered after starting upload
194193
sm.update(0)
195-
cell = sm['deviceState'].networkType not in [NetworkType.wifi, NetworkType.ethernet]
196-
if cell and (not cur_upload_items[tid].allow_cellular) and (not cellular_unmetered):
194+
metered = sm['deviceState'].networkMetered
195+
if metered and (not cur_upload_items[tid].allow_cellular):
197196
raise AbortTransferException
198197

199198
cur_upload_items[tid] = cur_upload_items[tid]._replace(progress=cur / sz if sz else 1)
200199

201-
202-
network_type = sm['deviceState'].networkType.raw
203200
fn = cur_upload_items[tid].path
204201
try:
205202
sz = os.path.getsize(fn)
206203
except OSError:
207204
sz = -1
208205

209-
cloudlog.event("athena.upload_handler.upload_start", fn=fn, sz=sz, network_type=network_type)
206+
cloudlog.event("athena.upload_handler.upload_start", fn=fn, sz=sz, network_type=network_type, metered=metered)
210207
response = _do_upload(cur_upload_items[tid], cb)
211208

212209
if response.status_code not in (200, 201, 403, 412):
213-
cloudlog.event("athena.upload_handler.retry", status_code=response.status_code, fn=fn, sz=sz, network_type=network_type)
210+
cloudlog.event("athena.upload_handler.retry", status_code=response.status_code, fn=fn, sz=sz, network_type=network_type, metered=metered)
214211
retry_upload(tid, end_event)
215212
else:
216-
cloudlog.event("athena.upload_handler.success", fn=fn, sz=sz, network_type=network_type)
213+
cloudlog.event("athena.upload_handler.success", fn=fn, sz=sz, network_type=network_type, metered=metered)
217214

218215
UploadQueueCache.cache(upload_queue)
219216
except (requests.exceptions.Timeout, requests.exceptions.ConnectionError, requests.exceptions.SSLError):
220-
cloudlog.event("athena.upload_handler.timeout", fn=fn, sz=sz, network_type=network_type)
217+
cloudlog.event("athena.upload_handler.timeout", fn=fn, sz=sz, network_type=network_type, metered=metered)
221218
retry_upload(tid, end_event)
222219
except AbortTransferException:
223-
cloudlog.event("athena.upload_handler.abort", fn=fn, sz=sz, network_type=network_type)
220+
cloudlog.event("athena.upload_handler.abort", fn=fn, sz=sz, network_type=network_type, metered=metered)
224221
retry_upload(tid, end_event, False)
225222

226223
except queue.Empty:
@@ -459,6 +456,12 @@ def getNetworkType():
459456
return HARDWARE.get_network_type()
460457

461458

459+
@dispatcher.add_method
460+
def getNetworkMetered():
461+
network_type = HARDWARE.get_network_type()
462+
return HARDWARE.get_network_metered(network_type)
463+
464+
462465
@dispatcher.add_method
463466
def getNetworks():
464467
return HARDWARE.get_networks()

selfdrive/common/params.cc

-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ std::unordered_map<std::string, uint32_t> keys = {
9191
{"CarParams", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON},
9292
{"CarParamsCache", CLEAR_ON_MANAGER_START},
9393
{"CarVin", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON},
94-
{"CellularUnmetered", PERSISTENT},
9594
{"CompletedTrainingVersion", PERSISTENT},
9695
{"ControlsReady", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON},
9796
{"CurrentRoute", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON},

selfdrive/hardware/base.py

+7
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22
from collections import namedtuple
33
from typing import Dict
44

5+
from cereal import log
6+
57
ThermalConfig = namedtuple('ThermalConfig', ['cpu', 'gpu', 'mem', 'bat', 'ambient', 'pmic'])
8+
NetworkType = log.DeviceState.NetworkType
9+
610

711
class HardwareBase(ABC):
812
@staticmethod
@@ -67,6 +71,9 @@ def get_sim_info(self):
6771
def get_network_strength(self, network_type):
6872
pass
6973

74+
def get_network_metered(self, network_type) -> bool:
75+
return network_type not in (NetworkType.none, NetworkType.wifi, NetworkType.ethernet)
76+
7077
@staticmethod
7178
def set_bandwidth_limit(upload_speed_kbps: int, download_speed_kbps: int) -> None:
7279
pass

selfdrive/hardware/tici/hardware.py

+30-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
NM = 'org.freedesktop.NetworkManager'
1515
NM_CON_ACT = NM + '.Connection.Active'
16+
NM_DEV = NM + '.Device'
1617
NM_DEV_WL = NM + '.Device.Wireless'
1718
NM_AP = NM + '.AccessPoint'
1819
DBUS_PROPS = 'org.freedesktop.DBus.Properties'
@@ -37,6 +38,13 @@ class MM_MODEM_STATE(IntEnum):
3738
CONNECTING = 10
3839
CONNECTED = 11
3940

41+
class NMMetered(IntEnum):
42+
NM_METERED_UNKNOWN = 0
43+
NM_METERED_YES = 1
44+
NM_METERED_NO = 2
45+
NM_METERED_GUESS_YES = 3
46+
NM_METERED_GUESS_NO = 4
47+
4048
TIMEOUT = 0.1
4149

4250
NetworkType = log.DeviceState.NetworkType
@@ -91,11 +99,10 @@ def get_network_type(self):
9199
primary_connection = self.nm.Get(NM, 'PrimaryConnection', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)
92100
primary_connection = self.bus.get_object(NM, primary_connection)
93101
primary_type = primary_connection.Get(NM_CON_ACT, 'Type', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)
94-
primary_id = primary_connection.Get(NM_CON_ACT, 'Id', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)
95102

96103
if primary_type == '802-3-ethernet':
97104
return NetworkType.ethernet
98-
elif primary_type == '802-11-wireless' and primary_id != 'Hotspot':
105+
elif primary_type == '802-11-wireless':
99106
return NetworkType.wifi
100107
else:
101108
active_connections = self.nm.Get(NM, 'ActiveConnections', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)
@@ -218,6 +225,27 @@ def get_network_strength(self, network_type):
218225

219226
return network_strength
220227

228+
def get_network_metered(self, network_type) -> bool:
229+
try:
230+
primary_connection = self.nm.Get(NM, 'PrimaryConnection', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)
231+
primary_connection = self.bus.get_object(NM, primary_connection)
232+
primary_devices = primary_connection.Get(NM_CON_ACT, 'Devices', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)
233+
234+
for dev in primary_devices:
235+
dev_obj = self.bus.get_object(NM, str(dev))
236+
metered_prop = dev_obj.Get(NM_DEV, 'Metered', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)
237+
238+
if network_type == NetworkType.wifi:
239+
if metered_prop in [NMMetered.NM_METERED_YES, NMMetered.NM_METERED_GUESS_YES]:
240+
return True
241+
elif network_type in [NetworkType.cell2G, NetworkType.cell3G, NetworkType.cell4G, NetworkType.cell5G]:
242+
if metered_prop == NMMetered.NM_METERED_NO:
243+
return False
244+
except Exception:
245+
pass
246+
247+
return super().get_network_metered(network_type)
248+
221249
@staticmethod
222250
def set_bandwidth_limit(upload_speed_kbps: int, download_speed_kbps: int) -> None:
223251
upload_speed_kbps = int(upload_speed_kbps) # Ensure integer value

selfdrive/loggerd/uploader.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -165,14 +165,14 @@ def normal_upload(self, key, fn):
165165

166166
return self.last_resp
167167

168-
def upload(self, key, fn, network_type):
168+
def upload(self, key, fn, network_type, metered):
169169
try:
170170
sz = os.path.getsize(fn)
171171
except OSError:
172172
cloudlog.exception("upload: getsize failed")
173173
return False
174174

175-
cloudlog.event("upload_start", key=key, fn=fn, sz=sz, network_type=network_type)
175+
cloudlog.event("upload_start", key=key, fn=fn, sz=sz, network_type=network_type, metered=metered)
176176

177177
if sz == 0:
178178
try:
@@ -195,10 +195,10 @@ def upload(self, key, fn, network_type):
195195
self.last_time = time.monotonic() - start_time
196196
self.last_speed = (sz / 1e6) / self.last_time
197197
success = True
198-
cloudlog.event("upload_success" if stat.status_code != 412 else "upload_ignored", key=key, fn=fn, sz=sz, network_type=network_type)
198+
cloudlog.event("upload_success" if stat.status_code != 412 else "upload_ignored", key=key, fn=fn, sz=sz, network_type=network_type, metered=metered)
199199
else:
200200
success = False
201-
cloudlog.event("upload_failed", stat=stat, exc=self.last_exc, key=key, fn=fn, sz=sz, network_type=network_type)
201+
cloudlog.event("upload_failed", stat=stat, exc=self.last_exc, key=key, fn=fn, sz=sz, network_type=network_type, metered=metered)
202202

203203
return success
204204

@@ -247,7 +247,7 @@ def uploader_fn(exit_event):
247247

248248
key, fn = d
249249

250-
success = uploader.upload(key, fn, sm['deviceState'].networkType.raw)
250+
success = uploader.upload(key, fn, sm['deviceState'].networkType.raw, sm['deviceState'].networkMetered)
251251
if success:
252252
backoff = 0.1
253253
elif allow_sleep:
@@ -257,6 +257,7 @@ def uploader_fn(exit_event):
257257

258258
pm.send("uploaderState", uploader.get_msg())
259259

260+
260261
def main():
261262
uploader_fn(threading.Event())
262263

selfdrive/thermald/thermald.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
PANDA_STATES_TIMEOUT = int(1000 * 2.5 * DT_TRML) # 2.5x the expected pandaState frequency
3535

3636
ThermalBand = namedtuple("ThermalBand", ['min_temp', 'max_temp'])
37-
HardwareState = namedtuple("HardwareState", ['network_type', 'network_strength', 'network_info', 'nvme_temps', 'modem_temps'])
37+
HardwareState = namedtuple("HardwareState", ['network_type', 'network_metered', 'network_strength', 'network_info', 'nvme_temps', 'modem_temps'])
3838

3939
# List of thermal bands. We will stay within this region as long as we are within the bounds.
4040
# When exiting the bounds, we'll jump to the lower or higher band. Bands are ordered in the dict.
@@ -95,6 +95,7 @@ def hw_state_thread(end_event, hw_queue):
9595

9696
hw_state = HardwareState(
9797
network_type=network_type,
98+
network_metered=HARDWARE.get_network_metered(network_type),
9899
network_strength=HARDWARE.get_network_strength(network_type),
99100
network_info=HARDWARE.get_network_info(),
100101
nvme_temps=HARDWARE.get_nvme_temperatures(),
@@ -144,6 +145,7 @@ def thermald_thread(end_event, hw_queue):
144145

145146
last_hw_state = HardwareState(
146147
network_type=NetworkType.none,
148+
network_metered=False,
147149
network_strength=NetworkStrength.unknown,
148150
network_info=None,
149151
nvme_temps=[],
@@ -205,6 +207,7 @@ def thermald_thread(end_event, hw_queue):
205207
msg.deviceState.gpuUsagePercent = int(round(HARDWARE.get_gpu_usage_percent()))
206208

207209
msg.deviceState.networkType = last_hw_state.network_type
210+
msg.deviceState.networkMetered = last_hw_state.network_metered
208211
msg.deviceState.networkStrength = last_hw_state.network_strength
209212
if last_hw_state.network_info is not None:
210213
msg.deviceState.networkInfo = last_hw_state.network_info

0 commit comments

Comments
 (0)