From da5a0c41a00e4de076c2846ed54049c3dff3bc5a Mon Sep 17 00:00:00 2001 From: Willem Melching Date: Wed, 9 Mar 2022 11:36:52 +0100 Subject: [PATCH] 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 cereal --- cereal | 2 +- selfdrive/athena/athenad.py | 33 ++++++++++++++++------------- selfdrive/common/params.cc | 1 - selfdrive/hardware/base.py | 7 ++++++ selfdrive/hardware/tici/hardware.py | 32 ++++++++++++++++++++++++++-- selfdrive/loggerd/uploader.py | 11 +++++----- selfdrive/thermald/thermald.py | 5 ++++- 7 files changed, 66 insertions(+), 25 deletions(-) diff --git a/cereal b/cereal index 9416d75c2..7e9837f76 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit 9416d75c20ba59154d647a0b14f9ea1fc62c34fa +Subproject commit 7e9837f768dd24d9f56edb450926c76f7f259aa6 diff --git a/selfdrive/athena/athenad.py b/selfdrive/athena/athenad.py index 7250754d5..1bb99bf2d 100755 --- a/selfdrive/athena/athenad.py +++ b/selfdrive/athena/athenad.py @@ -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() diff --git a/selfdrive/common/params.cc b/selfdrive/common/params.cc index 5bb49d7ce..3f64f790c 100644 --- a/selfdrive/common/params.cc +++ b/selfdrive/common/params.cc @@ -91,7 +91,6 @@ std::unordered_map 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}, diff --git a/selfdrive/hardware/base.py b/selfdrive/hardware/base.py index b551138ce..20258b85d 100644 --- a/selfdrive/hardware/base.py +++ b/selfdrive/hardware/base.py @@ -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 diff --git a/selfdrive/hardware/tici/hardware.py b/selfdrive/hardware/tici/hardware.py index 41ae316d3..ede0c3e00 100644 --- a/selfdrive/hardware/tici/hardware.py +++ b/selfdrive/hardware/tici/hardware.py @@ -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 diff --git a/selfdrive/loggerd/uploader.py b/selfdrive/loggerd/uploader.py index afdc5a6ed..4dd982a02 100644 --- a/selfdrive/loggerd/uploader.py +++ b/selfdrive/loggerd/uploader.py @@ -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()) diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index 2add71ea1..f5e818acf 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -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