C3: detect metered Android hotspot (#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 cerealpull/23937/head
parent
0b47800e74
commit
da5a0c41a0
2
cereal
2
cereal
|
@ -1 +1 @@
|
|||
Subproject commit 9416d75c20ba59154d647a0b14f9ea1fc62c34fa
|
||||
Subproject commit 7e9837f768dd24d9f56edb450926c76f7f259aa6
|
|
@ -163,8 +163,6 @@ def upload_handler(end_event: threading.Event) -> None:
|
|||
sm = messaging.SubMaster(['deviceState'])
|
||||
tid = threading.get_ident()
|
||||
|
||||
cellular_unmetered = Params().get_bool("CellularUnmetered")
|
||||
|
||||
while not end_event.is_set():
|
||||
cur_upload_items[tid] = None
|
||||
|
||||
|
@ -181,46 +179,45 @@ def upload_handler(end_event: threading.Event) -> None:
|
|||
cloudlog.event("athena.upload_handler.expired", item=cur_upload_items[tid], error=True)
|
||||
continue
|
||||
|
||||
# Check if uploading over cell is allowed
|
||||
# Check if uploading over metered connection is allowed
|
||||
sm.update(0)
|
||||
cell = sm['deviceState'].networkType not in [NetworkType.wifi, NetworkType.ethernet]
|
||||
if cell and (not cur_upload_items[tid].allow_cellular) and (not cellular_unmetered):
|
||||
metered = sm['deviceState'].networkMetered
|
||||
network_type = sm['deviceState'].networkType.raw
|
||||
if metered and (not cur_upload_items[tid].allow_cellular):
|
||||
retry_upload(tid, end_event, False)
|
||||
continue
|
||||
|
||||
try:
|
||||
def cb(sz, cur):
|
||||
# Abort transfer if connection changed to cell after starting upload
|
||||
# Abort transfer if connection changed to metered after starting upload
|
||||
sm.update(0)
|
||||
cell = sm['deviceState'].networkType not in [NetworkType.wifi, NetworkType.ethernet]
|
||||
if cell and (not cur_upload_items[tid].allow_cellular) and (not cellular_unmetered):
|
||||
metered = sm['deviceState'].networkMetered
|
||||
if metered and (not cur_upload_items[tid].allow_cellular):
|
||||
raise AbortTransferException
|
||||
|
||||
cur_upload_items[tid] = cur_upload_items[tid]._replace(progress=cur / sz if sz else 1)
|
||||
|
||||
|
||||
network_type = sm['deviceState'].networkType.raw
|
||||
fn = cur_upload_items[tid].path
|
||||
try:
|
||||
sz = os.path.getsize(fn)
|
||||
except OSError:
|
||||
sz = -1
|
||||
|
||||
cloudlog.event("athena.upload_handler.upload_start", fn=fn, sz=sz, network_type=network_type)
|
||||
cloudlog.event("athena.upload_handler.upload_start", fn=fn, sz=sz, network_type=network_type, metered=metered)
|
||||
response = _do_upload(cur_upload_items[tid], cb)
|
||||
|
||||
if response.status_code not in (200, 201, 403, 412):
|
||||
cloudlog.event("athena.upload_handler.retry", status_code=response.status_code, fn=fn, sz=sz, network_type=network_type)
|
||||
cloudlog.event("athena.upload_handler.retry", status_code=response.status_code, fn=fn, sz=sz, network_type=network_type, metered=metered)
|
||||
retry_upload(tid, end_event)
|
||||
else:
|
||||
cloudlog.event("athena.upload_handler.success", fn=fn, sz=sz, network_type=network_type)
|
||||
cloudlog.event("athena.upload_handler.success", fn=fn, sz=sz, network_type=network_type, metered=metered)
|
||||
|
||||
UploadQueueCache.cache(upload_queue)
|
||||
except (requests.exceptions.Timeout, requests.exceptions.ConnectionError, requests.exceptions.SSLError):
|
||||
cloudlog.event("athena.upload_handler.timeout", fn=fn, sz=sz, network_type=network_type)
|
||||
cloudlog.event("athena.upload_handler.timeout", fn=fn, sz=sz, network_type=network_type, metered=metered)
|
||||
retry_upload(tid, end_event)
|
||||
except AbortTransferException:
|
||||
cloudlog.event("athena.upload_handler.abort", fn=fn, sz=sz, network_type=network_type)
|
||||
cloudlog.event("athena.upload_handler.abort", fn=fn, sz=sz, network_type=network_type, metered=metered)
|
||||
retry_upload(tid, end_event, False)
|
||||
|
||||
except queue.Empty:
|
||||
|
@ -459,6 +456,12 @@ def getNetworkType():
|
|||
return HARDWARE.get_network_type()
|
||||
|
||||
|
||||
@dispatcher.add_method
|
||||
def getNetworkMetered():
|
||||
network_type = HARDWARE.get_network_type()
|
||||
return HARDWARE.get_network_metered(network_type)
|
||||
|
||||
|
||||
@dispatcher.add_method
|
||||
def getNetworks():
|
||||
return HARDWARE.get_networks()
|
||||
|
|
|
@ -91,7 +91,6 @@ std::unordered_map<std::string, uint32_t> keys = {
|
|||
{"CarParams", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON},
|
||||
{"CarParamsCache", CLEAR_ON_MANAGER_START},
|
||||
{"CarVin", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON},
|
||||
{"CellularUnmetered", PERSISTENT},
|
||||
{"CompletedTrainingVersion", PERSISTENT},
|
||||
{"ControlsReady", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON},
|
||||
{"CurrentRoute", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON},
|
||||
|
|
|
@ -2,7 +2,11 @@ from abc import abstractmethod, ABC
|
|||
from collections import namedtuple
|
||||
from typing import Dict
|
||||
|
||||
from cereal import log
|
||||
|
||||
ThermalConfig = namedtuple('ThermalConfig', ['cpu', 'gpu', 'mem', 'bat', 'ambient', 'pmic'])
|
||||
NetworkType = log.DeviceState.NetworkType
|
||||
|
||||
|
||||
class HardwareBase(ABC):
|
||||
@staticmethod
|
||||
|
@ -67,6 +71,9 @@ class HardwareBase(ABC):
|
|||
def get_network_strength(self, network_type):
|
||||
pass
|
||||
|
||||
def get_network_metered(self, network_type) -> bool:
|
||||
return network_type not in (NetworkType.none, NetworkType.wifi, NetworkType.ethernet)
|
||||
|
||||
@staticmethod
|
||||
def set_bandwidth_limit(upload_speed_kbps: int, download_speed_kbps: int) -> None:
|
||||
pass
|
||||
|
|
|
@ -13,6 +13,7 @@ from selfdrive.hardware.tici.amplifier import Amplifier
|
|||
|
||||
NM = 'org.freedesktop.NetworkManager'
|
||||
NM_CON_ACT = NM + '.Connection.Active'
|
||||
NM_DEV = NM + '.Device'
|
||||
NM_DEV_WL = NM + '.Device.Wireless'
|
||||
NM_AP = NM + '.AccessPoint'
|
||||
DBUS_PROPS = 'org.freedesktop.DBus.Properties'
|
||||
|
@ -37,6 +38,13 @@ class MM_MODEM_STATE(IntEnum):
|
|||
CONNECTING = 10
|
||||
CONNECTED = 11
|
||||
|
||||
class NMMetered(IntEnum):
|
||||
NM_METERED_UNKNOWN = 0
|
||||
NM_METERED_YES = 1
|
||||
NM_METERED_NO = 2
|
||||
NM_METERED_GUESS_YES = 3
|
||||
NM_METERED_GUESS_NO = 4
|
||||
|
||||
TIMEOUT = 0.1
|
||||
|
||||
NetworkType = log.DeviceState.NetworkType
|
||||
|
@ -91,11 +99,10 @@ class Tici(HardwareBase):
|
|||
primary_connection = self.nm.Get(NM, 'PrimaryConnection', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)
|
||||
primary_connection = self.bus.get_object(NM, primary_connection)
|
||||
primary_type = primary_connection.Get(NM_CON_ACT, 'Type', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)
|
||||
primary_id = primary_connection.Get(NM_CON_ACT, 'Id', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)
|
||||
|
||||
if primary_type == '802-3-ethernet':
|
||||
return NetworkType.ethernet
|
||||
elif primary_type == '802-11-wireless' and primary_id != 'Hotspot':
|
||||
elif primary_type == '802-11-wireless':
|
||||
return NetworkType.wifi
|
||||
else:
|
||||
active_connections = self.nm.Get(NM, 'ActiveConnections', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)
|
||||
|
@ -218,6 +225,27 @@ class Tici(HardwareBase):
|
|||
|
||||
return network_strength
|
||||
|
||||
def get_network_metered(self, network_type) -> bool:
|
||||
try:
|
||||
primary_connection = self.nm.Get(NM, 'PrimaryConnection', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)
|
||||
primary_connection = self.bus.get_object(NM, primary_connection)
|
||||
primary_devices = primary_connection.Get(NM_CON_ACT, 'Devices', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)
|
||||
|
||||
for dev in primary_devices:
|
||||
dev_obj = self.bus.get_object(NM, str(dev))
|
||||
metered_prop = dev_obj.Get(NM_DEV, 'Metered', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)
|
||||
|
||||
if network_type == NetworkType.wifi:
|
||||
if metered_prop in [NMMetered.NM_METERED_YES, NMMetered.NM_METERED_GUESS_YES]:
|
||||
return True
|
||||
elif network_type in [NetworkType.cell2G, NetworkType.cell3G, NetworkType.cell4G, NetworkType.cell5G]:
|
||||
if metered_prop == NMMetered.NM_METERED_NO:
|
||||
return False
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return super().get_network_metered(network_type)
|
||||
|
||||
@staticmethod
|
||||
def set_bandwidth_limit(upload_speed_kbps: int, download_speed_kbps: int) -> None:
|
||||
upload_speed_kbps = int(upload_speed_kbps) # Ensure integer value
|
||||
|
|
|
@ -165,14 +165,14 @@ class Uploader():
|
|||
|
||||
return self.last_resp
|
||||
|
||||
def upload(self, key, fn, network_type):
|
||||
def upload(self, key, fn, network_type, metered):
|
||||
try:
|
||||
sz = os.path.getsize(fn)
|
||||
except OSError:
|
||||
cloudlog.exception("upload: getsize failed")
|
||||
return False
|
||||
|
||||
cloudlog.event("upload_start", key=key, fn=fn, sz=sz, network_type=network_type)
|
||||
cloudlog.event("upload_start", key=key, fn=fn, sz=sz, network_type=network_type, metered=metered)
|
||||
|
||||
if sz == 0:
|
||||
try:
|
||||
|
@ -195,10 +195,10 @@ class Uploader():
|
|||
self.last_time = time.monotonic() - start_time
|
||||
self.last_speed = (sz / 1e6) / self.last_time
|
||||
success = True
|
||||
cloudlog.event("upload_success" if stat.status_code != 412 else "upload_ignored", key=key, fn=fn, sz=sz, network_type=network_type)
|
||||
cloudlog.event("upload_success" if stat.status_code != 412 else "upload_ignored", key=key, fn=fn, sz=sz, network_type=network_type, metered=metered)
|
||||
else:
|
||||
success = False
|
||||
cloudlog.event("upload_failed", stat=stat, exc=self.last_exc, key=key, fn=fn, sz=sz, network_type=network_type)
|
||||
cloudlog.event("upload_failed", stat=stat, exc=self.last_exc, key=key, fn=fn, sz=sz, network_type=network_type, metered=metered)
|
||||
|
||||
return success
|
||||
|
||||
|
@ -247,7 +247,7 @@ def uploader_fn(exit_event):
|
|||
|
||||
key, fn = d
|
||||
|
||||
success = uploader.upload(key, fn, sm['deviceState'].networkType.raw)
|
||||
success = uploader.upload(key, fn, sm['deviceState'].networkType.raw, sm['deviceState'].networkMetered)
|
||||
if success:
|
||||
backoff = 0.1
|
||||
elif allow_sleep:
|
||||
|
@ -257,6 +257,7 @@ def uploader_fn(exit_event):
|
|||
|
||||
pm.send("uploaderState", uploader.get_msg())
|
||||
|
||||
|
||||
def main():
|
||||
uploader_fn(threading.Event())
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ DISCONNECT_TIMEOUT = 5. # wait 5 seconds before going offroad after disconnect
|
|||
PANDA_STATES_TIMEOUT = int(1000 * 2.5 * DT_TRML) # 2.5x the expected pandaState frequency
|
||||
|
||||
ThermalBand = namedtuple("ThermalBand", ['min_temp', 'max_temp'])
|
||||
HardwareState = namedtuple("HardwareState", ['network_type', 'network_strength', 'network_info', 'nvme_temps', 'modem_temps'])
|
||||
HardwareState = namedtuple("HardwareState", ['network_type', 'network_metered', 'network_strength', 'network_info', 'nvme_temps', 'modem_temps'])
|
||||
|
||||
# List of thermal bands. We will stay within this region as long as we are within the bounds.
|
||||
# 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):
|
|||
|
||||
hw_state = HardwareState(
|
||||
network_type=network_type,
|
||||
network_metered=HARDWARE.get_network_metered(network_type),
|
||||
network_strength=HARDWARE.get_network_strength(network_type),
|
||||
network_info=HARDWARE.get_network_info(),
|
||||
nvme_temps=HARDWARE.get_nvme_temperatures(),
|
||||
|
@ -144,6 +145,7 @@ def thermald_thread(end_event, hw_queue):
|
|||
|
||||
last_hw_state = HardwareState(
|
||||
network_type=NetworkType.none,
|
||||
network_metered=False,
|
||||
network_strength=NetworkStrength.unknown,
|
||||
network_info=None,
|
||||
nvme_temps=[],
|
||||
|
@ -205,6 +207,7 @@ def thermald_thread(end_event, hw_queue):
|
|||
msg.deviceState.gpuUsagePercent = int(round(HARDWARE.get_gpu_usage_percent()))
|
||||
|
||||
msg.deviceState.networkType = last_hw_state.network_type
|
||||
msg.deviceState.networkMetered = last_hw_state.network_metered
|
||||
msg.deviceState.networkStrength = last_hw_state.network_strength
|
||||
if last_hw_state.network_info is not None:
|
||||
msg.deviceState.networkInfo = last_hw_state.network_info
|
||||
|
|
Loading…
Reference in New Issue