def main() -> NoReturn: first_run = True params = Params() while True: try: params.delete("PandaSignatures") # Flash all Pandas in DFU mode for p in PandaDFU.list(): cloudlog.info(f"Panda in DFU mode found, flashing recovery {p}") PandaDFU(p).recover() time.sleep(1) panda_serials = Panda.list() if len(panda_serials) == 0: continue cloudlog.info(f"{len(panda_serials)} panda(s) found, connecting - {panda_serials}") # Flash pandas pandas = [] for serial in panda_serials: pandas.append(flash_panda(serial)) # check health for lost heartbeat for panda in pandas: health = panda.health() if health["heartbeat_lost"]: params.put_bool("PandaHeartbeatLost", True) cloudlog.event("heartbeat lost", deviceState=health, serial=panda.get_usb_serial()) #if first_run: # cloudlog.info(f"Resetting panda {panda.get_usb_serial()}") # panda.reset() # sort pandas to have deterministic order pandas.sort(key=cmp_to_key(panda_sort_cmp)) panda_serials = list(map(lambda p: p.get_usb_serial(), pandas)) # log panda fw versions params.put("PandaSignatures", b','.join(p.get_signature() for p in pandas)) # close all pandas for p in pandas: p.close() except (usb1.USBErrorNoDevice, usb1.USBErrorPipe): # a panda was disconnected while setting everything up. let's try again cloudlog.exception("Panda USB exception while setting up") continue first_run = False # run boardd with all connected serials as arguments os.chdir(os.path.join(BASEDIR, "selfdrive/boardd")) subprocess.run(["./boardd", *panda_serials], check=True)
def set_params(new_version: bool, failed_count: int, exception: Optional[str]) -> None: params = Params() params.put("UpdateFailedCount", str(failed_count)) last_update = datetime.datetime.utcnow() if failed_count == 0: t = last_update.isoformat() params.put("LastUpdateTime", t.encode('utf8')) else: try: t = params.get("LastUpdateTime", encoding='utf8') last_update = datetime.datetime.fromisoformat(t) except (TypeError, ValueError): pass if exception is None: params.delete("LastUpdateException") else: params.put("LastUpdateException", exception) # Write out release notes for new versions if new_version: try: with open(os.path.join(FINALIZED, "RELEASES.md"), "rb") as f: r = f.read().split(b'\n\n', 1)[0] # Slice latest release notes try: params.put("ReleaseNotes", parse_markdown(r.decode("utf-8"))) except Exception: params.put("ReleaseNotes", r + b"\n") except Exception: params.put("ReleaseNotes", "") params.put_bool("UpdateAvailable", True) # Handle user prompt for alert in ("Offroad_UpdateFailed", "Offroad_ConnectivityNeeded", "Offroad_ConnectivityNeededPrompt"): set_offroad_alert(alert, False) now = datetime.datetime.utcnow() dt = now - last_update if failed_count > 15 and exception is not None: if is_tested_branch(): extra_text = "Ensure the software is correctly installed" else: extra_text = exception set_offroad_alert("Offroad_UpdateFailed", True, extra_text=extra_text) elif dt.days > DAYS_NO_CONNECTIVITY_MAX and failed_count > 1: set_offroad_alert("Offroad_ConnectivityNeeded", True) elif dt.days > DAYS_NO_CONNECTIVITY_PROMPT: remaining = max(DAYS_NO_CONNECTIVITY_MAX - dt.days, 1) set_offroad_alert( "Offroad_ConnectivityNeededPrompt", True, extra_text=f"{remaining} day{'' if remaining == 1 else 's'}.")
def snapshot(): params = Params() front_camera_allowed = int(params.get("RecordFront")) params.put("IsTakingSnapshot", "1") params.put("Offroad_IsTakingSnapshot", json.dumps(OFFROAD_ALERTS["Offroad_IsTakingSnapshot"])) time.sleep( 2.0 ) # Give thermald time to read the param, or if just started give camerad time to start # Check if camerad is already started ps = subprocess.Popen("ps | grep camerad", shell=True, stdout=subprocess.PIPE) ret = list( filter(lambda x: 'grep ' not in x, ps.communicate()[0].decode('utf-8').strip().split("\n"))) if len(ret) > 0: params.put("IsTakingSnapshot", "0") params.delete("Offroad_IsTakingSnapshot") return None proc = subprocess.Popen(os.path.join(BASEDIR, "selfdrive/camerad/camerad"), cwd=os.path.join(BASEDIR, "selfdrive/camerad")) time.sleep(1.0) ret = None start_time = time.time() while time.time() - start_time < 5.0: try: ipc = VisionIPC() pic = ipc.get() del ipc if front_camera_allowed: ipc_front = VisionIPC(front=True) fpic = ipc_front.get() del ipc_front else: fpic = None ret = pic, fpic break except Exception: time.sleep(1) proc.send_signal(signal.SIGINT) proc.communicate() params.put("IsTakingSnapshot", "0") params.delete("Offroad_IsTakingSnapshot") return ret
def manage_tokens(api): if not TICI: return try: params = Params() mapbox = api.get(f"/v1/tokens/mapbox/{api.dongle_id}/", timeout=5.0, access_token=api.get_token()) if mapbox.status_code == 200: params.put("MapboxToken", mapbox.json()["token"]) else: params.delete("MapboxToken") except Exception: cloudlog.exception("Failed to update tokens")
def snapshot(): params = Params() front_camera_allowed = int(params.get("RecordFront")) if params.get("IsOffroad") != b"1" or params.get( "IsTakingSnapshot") == b"1": print("Already taking snapshot") return None, None params.put("IsTakingSnapshot", "1") set_offroad_alert("Offroad_IsTakingSnapshot", True) time.sleep( 2.0 ) # Give thermald time to read the param, or if just started give camerad time to start # Check if camerad is already started try: subprocess.check_call(["pgrep", "camerad"]) print("Camerad already running") params.put("IsTakingSnapshot", "0") params.delete("Offroad_IsTakingSnapshot") return None, None except subprocess.CalledProcessError: pass env = os.environ.copy() env["SEND_REAR"] = "1" env["SEND_WIDE"] = "1" env["SEND_FRONT"] = "1" proc = subprocess.Popen(os.path.join(BASEDIR, "selfdrive/camerad/camerad"), cwd=os.path.join(BASEDIR, "selfdrive/camerad"), env=env) time.sleep(3.0) frame = "wideRoadCameraState" if TICI else "roadCameraState" rear, front = get_snapshots(frame) proc.send_signal(signal.SIGINT) proc.communicate() params.put("IsTakingSnapshot", "0") set_offroad_alert("Offroad_IsTakingSnapshot", False) if not front_camera_allowed: front = None return rear, front
def main(): params = Params() dongle_id = params.get("DongleId").decode('utf-8') cloudlog.bind_global(dongle_id=dongle_id, version=get_version(), dirty=is_dirty()) try: while 1: cloudlog.info("starting athena daemon") proc = Process(name='athenad', target=launcher, args=('selfdrive.athena.athenad', 'athenad')) proc.start() proc.join() cloudlog.event("athenad exited", exitcode=proc.exitcode) time.sleep(5) except Exception: cloudlog.exception("manage_athenad.exception") finally: params.delete(ATHENA_MGR_PID_PARAM)
def snapshot(): params = Params() front_camera_allowed = params.get_bool("RecordFront") if (not params.get_bool("IsOffroad") ) or params.get_bool("IsTakingSnapshot"): print("Already taking snapshot") return None, None params.put_bool("IsTakingSnapshot", True) set_offroad_alert("Offroad_IsTakingSnapshot", True) time.sleep( 2.0 ) # Give thermald time to read the param, or if just started give camerad time to start # Check if camerad is already started try: subprocess.check_call(["pgrep", "camerad"]) print("Camerad already running") params.put_bool("IsTakingSnapshot", False) params.delete("Offroad_IsTakingSnapshot") return None, None except subprocess.CalledProcessError: pass os.environ["SEND_ROAD"] = "1" os.environ["SEND_WIDE_ROAD"] = "1" if front_camera_allowed: os.environ["SEND_DRIVER"] = "1" try: managed_processes['camerad'].start() frame = "wideRoadCameraState" if TICI else "roadCameraState" front_frame = "driverCameraState" if front_camera_allowed else None focus_perc_threshold = 0. if TICI else 10 / 12. rear, front = get_snapshots(frame, front_frame, focus_perc_threshold) finally: managed_processes['camerad'].stop() params.put_bool("IsTakingSnapshot", False) set_offroad_alert("Offroad_IsTakingSnapshot", False) if not front_camera_allowed: front = None return rear, front
def setup_env(simulation=False, CP=None, cfg=None, controlsState=None): params = Params() params.clear_all() params.put_bool("OpenpilotEnabledToggle", True) params.put_bool("Passive", False) params.put_bool("DisengageOnAccelerator", True) params.put_bool("WideCameraOnly", False) params.put_bool("DisableLogging", False) os.environ["NO_RADAR_SLEEP"] = "1" os.environ["REPLAY"] = "1" os.environ['SKIP_FW_QUERY'] = "" os.environ['FINGERPRINT'] = "" if cfg is not None: # Clear all custom processConfig environment variables for config in CONFIGS: for k, _ in config.environ.items(): if k in os.environ: del os.environ[k] os.environ.update(cfg.environ) os.environ['PROC_NAME'] = cfg.proc_name if simulation: os.environ["SIMULATION"] = "1" elif "SIMULATION" in os.environ: del os.environ["SIMULATION"] # Initialize controlsd with a controlsState packet if controlsState is not None: params.put("ReplayControlsState", controlsState.as_builder().to_bytes()) else: params.delete("ReplayControlsState") # Regen or python process if CP is not None: if CP.alternativeExperience == ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS: params.put_bool("DisengageOnAccelerator", False) if CP.fingerprintSource == "fw": params.put("CarParamsCache", CP.as_builder().to_bytes()) else: os.environ['SKIP_FW_QUERY'] = "1" os.environ['FINGERPRINT'] = CP.carFingerprint
def snapshot(): params = Params() if (not params.get_bool("IsOffroad") ) or params.get_bool("IsTakingSnapshot"): print("Already taking snapshot") return None, None front_camera_allowed = params.get_bool("RecordFront") params.put_bool("IsTakingSnapshot", True) set_offroad_alert("Offroad_IsTakingSnapshot", True) time.sleep( 2.0 ) # Give thermald time to read the param, or if just started give camerad time to start # Check if camerad is already started try: subprocess.check_call(["pgrep", "camerad"]) print("Camerad already running") params.put_bool("IsTakingSnapshot", False) params.delete("Offroad_IsTakingSnapshot") return None, None except subprocess.CalledProcessError: pass try: # Allow testing on replay on PC if not PC: managed_processes['camerad'].start() frame = "wideRoadCameraState" front_frame = "driverCameraState" if front_camera_allowed else None rear, front = get_snapshots(frame, front_frame) finally: managed_processes['camerad'].stop() params.put_bool("IsTakingSnapshot", False) set_offroad_alert("Offroad_IsTakingSnapshot", False) if not front_camera_allowed: front = None return rear, front
def main(): params = Params() dongle_id = params.get("DongleId", encoding='utf-8') crash.init() crash.bind_user(id=dongle_id) crash.bind_extra(dirty=dirty, origin=origin, branch=branch, commit=commit, device=HARDWARE.get_device_type()) ws_uri = ATHENA_HOST + "/ws/v2/" + dongle_id api = Api(dongle_id) conn_retries = 0 while 1: try: cloudlog.event("athenad.main.connecting_ws", ws_uri=ws_uri) ws = create_connection(ws_uri, cookie="jwt=" + api.get_token(), enable_multithread=True, timeout=1.0) cloudlog.event("athenad.main.connected_ws", ws_uri=ws_uri) ws.settimeout(1) conn_retries = 0 handle_long_poll(ws) except (KeyboardInterrupt, SystemExit): break except (ConnectionError, TimeoutError, WebSocketException): conn_retries += 1 params.delete("LastAthenaPingTime") except Exception: crash.capture_exception() cloudlog.exception("athenad.main.exception") conn_retries += 1 params.delete("LastAthenaPingTime") time.sleep(backoff(conn_retries))
def set_params(new_version: bool, failed_count: int, exception: Optional[str]) -> None: params = Params() params.put("UpdateFailedCount", str(failed_count)) if failed_count == 0: t = datetime.datetime.utcnow().isoformat() params.put("LastUpdateTime", t.encode('utf8')) if exception is None: params.delete("LastUpdateException") else: params.put("LastUpdateException", exception) if new_version: try: with open(os.path.join(FINALIZED, "RELEASES.md"), "rb") as f: r = f.read() r = r[:r.find(b'\n\n')] # Slice latest release notes params.put("ReleaseNotes", r + b"\n") except Exception: params.put("ReleaseNotes", "") params.put("UpdateAvailable", "1")
def main(): try: set_core_affinity([0, 1, 2, 3]) except Exception: cloudlog.exception("failed to set core affinity") params = Params() dongle_id = params.get("DongleId", encoding='utf-8') UploadQueueCache.initialize(upload_queue) ws_uri = ATHENA_HOST + "/ws/v2/" + dongle_id api = Api(dongle_id) conn_retries = 0 while 1: try: cloudlog.event("athenad.main.connecting_ws", ws_uri=ws_uri) ws = create_connection(ws_uri, cookie="jwt=" + api.get_token(), enable_multithread=True, timeout=30.0) cloudlog.event("athenad.main.connected_ws", ws_uri=ws_uri) conn_retries = 0 cur_upload_items.clear() handle_long_poll(ws) except (KeyboardInterrupt, SystemExit): break except (ConnectionError, TimeoutError, WebSocketException): conn_retries += 1 params.delete("LastAthenaPingTime") except socket.timeout: params.delete("LastAthenaPingTime") except Exception: cloudlog.exception("athenad.main.exception") conn_retries += 1 params.delete("LastAthenaPingTime") time.sleep(backoff(conn_retries))
def thermald_thread(): health_timeout = int(1000 * 2.5 * DT_TRML) # 2.5x the expected health frequency # now loop thermal_sock = messaging.pub_sock('thermal') health_sock = messaging.sub_sock('health', timeout=health_timeout) location_sock = messaging.sub_sock('gpsLocation') fan_speed = 0 count = 0 startup_conditions = { "ignition": False, } startup_conditions_prev = startup_conditions.copy() off_ts = None started_ts = None started_seen = False thermal_status = ThermalStatus.green usb_power = True current_branch = get_git_branch() network_type = NetworkType.none network_strength = NetworkStrength.unknown current_filter = FirstOrderFilter(0., CURRENT_TAU, DT_TRML) cpu_temp_filter = FirstOrderFilter(0., CPU_TEMP_TAU, DT_TRML) health_prev = None should_start_prev = False handle_fan = None is_uno = False has_relay = False params = Params() pm = PowerMonitoring() no_panda_cnt = 0 thermal_config = get_thermal_config() while 1: health = messaging.recv_sock(health_sock, wait=True) location = messaging.recv_sock(location_sock) location = location.gpsLocation if location else None msg = read_thermal(thermal_config) if health is not None: usb_power = health.health.usbPowerMode != log.HealthData.UsbPowerMode.client # If we lose connection to the panda, wait 5 seconds before going offroad if health.health.hwType == log.HealthData.HwType.unknown: no_panda_cnt += 1 if no_panda_cnt > DISCONNECT_TIMEOUT / DT_TRML: if startup_conditions["ignition"]: cloudlog.error("Lost panda connection while onroad") startup_conditions["ignition"] = False else: no_panda_cnt = 0 startup_conditions["ignition"] = health.health.ignitionLine or health.health.ignitionCan # Setup fan handler on first connect to panda if handle_fan is None and health.health.hwType != log.HealthData.HwType.unknown: is_uno = health.health.hwType == log.HealthData.HwType.uno has_relay = health.health.hwType in [log.HealthData.HwType.blackPanda, log.HealthData.HwType.uno, log.HealthData.HwType.dos] if (not EON) or is_uno: cloudlog.info("Setting up UNO fan handler") handle_fan = handle_fan_uno else: cloudlog.info("Setting up EON fan handler") setup_eon_fan() handle_fan = handle_fan_eon # Handle disconnect if health_prev is not None: if health.health.hwType == log.HealthData.HwType.unknown and \ health_prev.health.hwType != log.HealthData.HwType.unknown: params.panda_disconnect() health_prev = health # get_network_type is an expensive call. update every 10s if (count % int(10. / DT_TRML)) == 0: try: network_type = HARDWARE.get_network_type() network_strength = HARDWARE.get_network_strength(network_type) except Exception: cloudlog.exception("Error getting network status") msg.thermal.freeSpace = get_available_percent(default=100.0) / 100.0 msg.thermal.memUsedPercent = int(round(psutil.virtual_memory().percent)) msg.thermal.cpuPerc = int(round(psutil.cpu_percent())) msg.thermal.networkType = network_type msg.thermal.networkStrength = network_strength msg.thermal.batteryPercent = get_battery_capacity() msg.thermal.batteryStatus = get_battery_status() msg.thermal.batteryCurrent = get_battery_current() msg.thermal.batteryVoltage = get_battery_voltage() msg.thermal.usbOnline = get_usb_present() # Fake battery levels on uno for frame if (not EON) or is_uno: msg.thermal.batteryPercent = 100 msg.thermal.batteryStatus = "Charging" msg.thermal.bat = 0 current_filter.update(msg.thermal.batteryCurrent / 1e6) # TODO: add car battery voltage check max_cpu_temp = cpu_temp_filter.update(max(msg.thermal.cpu)) max_comp_temp = max(max_cpu_temp, msg.thermal.mem, max(msg.thermal.gpu)) bat_temp = msg.thermal.bat if handle_fan is not None: fan_speed = handle_fan(max_cpu_temp, bat_temp, fan_speed, startup_conditions["ignition"]) msg.thermal.fanSpeed = fan_speed # If device is offroad we want to cool down before going onroad # since going onroad increases load and can make temps go over 107 # We only do this if there is a relay that prevents the car from faulting is_offroad_for_5_min = (started_ts is None) and ((not started_seen) or (off_ts is None) or (sec_since_boot() - off_ts > 60 * 5)) if max_cpu_temp > 107. or bat_temp >= 63. or (has_relay and is_offroad_for_5_min and max_cpu_temp > 70.0): # onroad not allowed thermal_status = ThermalStatus.danger elif max_comp_temp > 96.0 or bat_temp > 60.: # hysteresis between onroad not allowed and engage not allowed thermal_status = clip(thermal_status, ThermalStatus.red, ThermalStatus.danger) elif max_cpu_temp > 94.0: # hysteresis between engage not allowed and uploader not allowed thermal_status = clip(thermal_status, ThermalStatus.yellow, ThermalStatus.red) elif max_cpu_temp > 80.0: # uploader not allowed thermal_status = ThermalStatus.yellow elif max_cpu_temp > 75.0: # hysteresis between uploader not allowed and all good thermal_status = clip(thermal_status, ThermalStatus.green, ThermalStatus.yellow) else: # all good thermal_status = ThermalStatus.green # **** starting logic **** # Check for last update time and display alerts if needed now = datetime.datetime.utcnow() # show invalid date/time alert startup_conditions["time_valid"] = now.year >= 2019 set_offroad_alert_if_changed("Offroad_InvalidTime", (not startup_conditions["time_valid"])) # Show update prompt try: last_update = datetime.datetime.fromisoformat(params.get("LastUpdateTime", encoding='utf8')) except (TypeError, ValueError): last_update = now dt = now - last_update update_failed_count = params.get("UpdateFailedCount") update_failed_count = 0 if update_failed_count is None else int(update_failed_count) last_update_exception = params.get("LastUpdateException", encoding='utf8') if update_failed_count > 15 and last_update_exception is not None: if current_branch in ["release2", "dashcam"]: extra_text = "Ensure the software is correctly installed" else: extra_text = last_update_exception set_offroad_alert_if_changed("Offroad_ConnectivityNeeded", False) set_offroad_alert_if_changed("Offroad_ConnectivityNeededPrompt", False) set_offroad_alert_if_changed("Offroad_UpdateFailed", True, extra_text=extra_text) elif dt.days > DAYS_NO_CONNECTIVITY_MAX and update_failed_count > 1: set_offroad_alert_if_changed("Offroad_UpdateFailed", False) set_offroad_alert_if_changed("Offroad_ConnectivityNeededPrompt", False) set_offroad_alert_if_changed("Offroad_ConnectivityNeeded", True) elif dt.days > DAYS_NO_CONNECTIVITY_PROMPT: remaining_time = str(max(DAYS_NO_CONNECTIVITY_MAX - dt.days, 0)) set_offroad_alert_if_changed("Offroad_UpdateFailed", False) set_offroad_alert_if_changed("Offroad_ConnectivityNeeded", False) set_offroad_alert_if_changed("Offroad_ConnectivityNeededPrompt", True, extra_text=f"{remaining_time} days.") else: set_offroad_alert_if_changed("Offroad_UpdateFailed", False) set_offroad_alert_if_changed("Offroad_ConnectivityNeeded", False) set_offroad_alert_if_changed("Offroad_ConnectivityNeededPrompt", False) startup_conditions["not_uninstalling"] = not params.get("DoUninstall") == b"1" startup_conditions["accepted_terms"] = params.get("HasAcceptedTerms") == terms_version completed_training = params.get("CompletedTrainingVersion") == training_version panda_signature = params.get("PandaFirmware") startup_conditions["fw_version_match"] = (panda_signature is None) or (panda_signature == FW_SIGNATURE) # don't show alert is no panda is connected (None) set_offroad_alert_if_changed("Offroad_PandaFirmwareMismatch", (not startup_conditions["fw_version_match"])) # with 2% left, we killall, otherwise the phone will take a long time to boot startup_conditions["free_space"] = msg.thermal.freeSpace > 0.02 startup_conditions["completed_training"] = completed_training or (current_branch in ['dashcam', 'dashcam-staging']) startup_conditions["not_driver_view"] = not params.get("IsDriverViewEnabled") == b"1" startup_conditions["not_taking_snapshot"] = not params.get("IsTakingSnapshot") == b"1" # if any CPU gets above 107 or the battery gets above 63, kill all processes # controls will warn with CPU above 95 or battery above 60 startup_conditions["device_temp_good"] = thermal_status < ThermalStatus.danger set_offroad_alert_if_changed("Offroad_TemperatureTooHigh", (not startup_conditions["device_temp_good"])) should_start = all(startup_conditions.values()) if should_start: if not should_start_prev: params.delete("IsOffroad") off_ts = None if started_ts is None: started_ts = sec_since_boot() started_seen = True os.system('echo performance > /sys/class/devfreq/soc:qcom,cpubw/governor') else: if startup_conditions["ignition"] and (startup_conditions != startup_conditions_prev): cloudlog.event("Startup blocked", startup_conditions=startup_conditions) if should_start_prev or (count == 0): params.put("IsOffroad", "1") started_ts = None if off_ts is None: off_ts = sec_since_boot() os.system('echo powersave > /sys/class/devfreq/soc:qcom,cpubw/governor') # Offroad power monitoring pm.calculate(health) msg.thermal.offroadPowerUsage = pm.get_power_used() msg.thermal.carBatteryCapacity = max(0, pm.get_car_battery_capacity()) # Check if we need to disable charging (handled by boardd) msg.thermal.chargingDisabled = pm.should_disable_charging(health, off_ts) # Check if we need to shut down if pm.should_shutdown(health, off_ts, started_seen, LEON): cloudlog.info(f"shutting device down, offroad since {off_ts}") # TODO: add function for blocking cloudlog instead of sleep time.sleep(10) os.system('LD_LIBRARY_PATH="" svc power shutdown') msg.thermal.chargingError = current_filter.x > 0. and msg.thermal.batteryPercent < 90 # if current is positive, then battery is being discharged msg.thermal.started = started_ts is not None msg.thermal.startedTs = int(1e9*(started_ts or 0)) msg.thermal.thermalStatus = thermal_status thermal_sock.send(msg.to_bytes()) set_offroad_alert_if_changed("Offroad_ChargeDisabled", (not usb_power)) should_start_prev = should_start startup_conditions_prev = startup_conditions.copy() # report to server once per minute if (count % int(60. / DT_TRML)) == 0: cloudlog.event("STATUS_PACKET", count=count, health=(health.to_dict() if health else None), location=(location.to_dict() if location else None), thermal=msg.to_dict()) count += 1
def thermald_thread(): # prevent LEECO from undervoltage BATT_PERC_OFF = 10 if LEON else 3 health_timeout = int(1000 * 2.5 * DT_TRML) # 2.5x the expected health frequency # now loop thermal_sock = messaging.pub_sock('thermal') health_sock = messaging.sub_sock('health', timeout=health_timeout) location_sock = messaging.sub_sock('gpsLocation') ignition = False fan_speed = 0 count = 0 off_ts = None started_ts = None started_seen = False thermal_status = ThermalStatus.green thermal_status_prev = ThermalStatus.green usb_power = True usb_power_prev = True network_type = NetworkType.none current_filter = FirstOrderFilter(0., CURRENT_TAU, DT_TRML) health_prev = None fw_version_match_prev = True current_connectivity_alert = None time_valid_prev = True should_start_prev = False is_uno = (read_tz(29, clip=False) < -1000) if is_uno or not ANDROID: handle_fan = handle_fan_uno else: setup_eon_fan() handle_fan = handle_fan_eon params = Params() while 1: health = messaging.recv_sock(health_sock, wait=True) location = messaging.recv_sock(location_sock) location = location.gpsLocation if location else None msg = read_thermal() # clear car params when panda gets disconnected if health is None and health_prev is not None: params.panda_disconnect() health_prev = health if health is not None: usb_power = health.health.usbPowerMode != log.HealthData.UsbPowerMode.client # get_network_type is an expensive call. update every 10s if (count % int(10. / DT_TRML)) == 0: try: network_type = get_network_type() except Exception: pass msg.thermal.freeSpace = get_available_percent(default=100.0) / 100.0 msg.thermal.memUsedPercent = int(round( psutil.virtual_memory().percent)) msg.thermal.cpuPerc = int(round(psutil.cpu_percent())) msg.thermal.networkType = network_type try: with open("/sys/class/power_supply/battery/capacity") as f: msg.thermal.batteryPercent = int(f.read()) with open("/sys/class/power_supply/battery/status") as f: msg.thermal.batteryStatus = f.read().strip() with open("/sys/class/power_supply/battery/current_now") as f: msg.thermal.batteryCurrent = int(f.read()) with open("/sys/class/power_supply/battery/voltage_now") as f: msg.thermal.batteryVoltage = int(f.read()) with open("/sys/class/power_supply/usb/present") as f: msg.thermal.usbOnline = bool(int(f.read())) except FileNotFoundError: pass # Fake battery levels on uno for frame if is_uno: msg.thermal.batteryPercent = 100 msg.thermal.batteryStatus = "Charging" current_filter.update(msg.thermal.batteryCurrent / 1e6) # TODO: add car battery voltage check max_cpu_temp = max(msg.thermal.cpu0, msg.thermal.cpu1, msg.thermal.cpu2, msg.thermal.cpu3) / 10.0 max_comp_temp = max(max_cpu_temp, msg.thermal.mem / 10., msg.thermal.gpu / 10.) bat_temp = msg.thermal.bat / 1000. fan_speed = handle_fan(max_cpu_temp, bat_temp, fan_speed, ignition) msg.thermal.fanSpeed = fan_speed # thermal logic with hysterisis if max_cpu_temp > 107. or bat_temp >= 63.: # onroad not allowed thermal_status = ThermalStatus.danger elif max_comp_temp > 92.5 or bat_temp > 60.: # CPU throttling starts around ~90C # hysteresis between onroad not allowed and engage not allowed thermal_status = clip(thermal_status, ThermalStatus.red, ThermalStatus.danger) elif max_cpu_temp > 87.5: # hysteresis between engage not allowed and uploader not allowed thermal_status = clip(thermal_status, ThermalStatus.yellow, ThermalStatus.red) elif max_cpu_temp > 80.0: # uploader not allowed thermal_status = ThermalStatus.yellow elif max_cpu_temp > 75.0: # hysteresis between uploader not allowed and all good thermal_status = clip(thermal_status, ThermalStatus.green, ThermalStatus.yellow) else: # all good thermal_status = ThermalStatus.green # **** starting logic **** # Check for last update time and display alerts if needed now = datetime.datetime.now() # show invalid date/time alert time_valid = now.year >= 2019 if time_valid and not time_valid_prev: params.delete("Offroad_InvalidTime") if not time_valid and time_valid_prev: params.put("Offroad_InvalidTime", json.dumps(OFFROAD_ALERTS["Offroad_InvalidTime"])) time_valid_prev = time_valid # Show update prompt try: last_update = datetime.datetime.fromisoformat( params.get("LastUpdateTime", encoding='utf8')) except (TypeError, ValueError): last_update = now dt = now - last_update update_failed_count = params.get("UpdateFailedCount") update_failed_count = 0 if update_failed_count is None else int( update_failed_count) if dt.days > DAYS_NO_CONNECTIVITY_MAX and update_failed_count > 1: if current_connectivity_alert != "expired": current_connectivity_alert = "expired" params.delete("Offroad_ConnectivityNeededPrompt") params.put( "Offroad_ConnectivityNeeded", json.dumps(OFFROAD_ALERTS["Offroad_ConnectivityNeeded"])) elif dt.days > DAYS_NO_CONNECTIVITY_PROMPT: remaining_time = str(max(DAYS_NO_CONNECTIVITY_MAX - dt.days, 0)) if current_connectivity_alert != "prompt" + remaining_time: current_connectivity_alert = "prompt" + remaining_time alert_connectivity_prompt = copy.copy( OFFROAD_ALERTS["Offroad_ConnectivityNeededPrompt"]) alert_connectivity_prompt["text"] += remaining_time + " days." params.delete("Offroad_ConnectivityNeeded") params.put("Offroad_ConnectivityNeededPrompt", json.dumps(alert_connectivity_prompt)) elif current_connectivity_alert is not None: current_connectivity_alert = None params.delete("Offroad_ConnectivityNeeded") params.delete("Offroad_ConnectivityNeededPrompt") # start constellation of processes when the car starts ignition = health is not None and (health.health.ignitionLine or health.health.ignitionCan) do_uninstall = params.get("DoUninstall") == b"1" accepted_terms = params.get("HasAcceptedTerms") == terms_version completed_training = params.get( "CompletedTrainingVersion") == training_version panda_signature = params.get("PandaFirmware") fw_version_match = (panda_signature is None) or ( panda_signature == FW_SIGNATURE ) # don't show alert is no panda is connected (None) should_start = ignition # with 2% left, we killall, otherwise the phone will take a long time to boot should_start = should_start and msg.thermal.freeSpace > 0.02 # confirm we have completed training and aren't uninstalling should_start = should_start and accepted_terms and completed_training and ( not do_uninstall) # check for firmware mismatch should_start = should_start and fw_version_match # check if system time is valid should_start = should_start and time_valid # don't start while taking snapshot if not should_start_prev: is_taking_snapshot = params.get("IsTakingSnapshot") == b"1" should_start = should_start and (not is_taking_snapshot) if fw_version_match and not fw_version_match_prev: params.delete("Offroad_PandaFirmwareMismatch") if not fw_version_match and fw_version_match_prev: params.put( "Offroad_PandaFirmwareMismatch", json.dumps(OFFROAD_ALERTS["Offroad_PandaFirmwareMismatch"])) # if any CPU gets above 107 or the battery gets above 63, kill all processes # controls will warn with CPU above 95 or battery above 60 if thermal_status >= ThermalStatus.danger: should_start = False if thermal_status_prev < ThermalStatus.danger: params.put( "Offroad_TemperatureTooHigh", json.dumps(OFFROAD_ALERTS["Offroad_TemperatureTooHigh"])) else: if thermal_status_prev >= ThermalStatus.danger: params.delete("Offroad_TemperatureTooHigh") if should_start: if not should_start_prev: params.delete("IsOffroad") off_ts = None if started_ts is None: started_ts = sec_since_boot() started_seen = True os.system( 'echo performance > /sys/class/devfreq/soc:qcom,cpubw/governor' ) else: if should_start_prev or (count == 0): params.put("IsOffroad", "1") started_ts = None if off_ts is None: off_ts = sec_since_boot() os.system( 'echo powersave > /sys/class/devfreq/soc:qcom,cpubw/governor' ) # shutdown if the battery gets lower than 3%, it's discharging, we aren't running for # more than a minute but we were running if msg.thermal.batteryPercent < BATT_PERC_OFF and msg.thermal.batteryStatus == "Discharging" and \ started_seen and (sec_since_boot() - off_ts) > 60: os.system('LD_LIBRARY_PATH="" svc power shutdown') msg.thermal.chargingError = current_filter.x > 0. and msg.thermal.batteryPercent < 90 # if current is positive, then battery is being discharged msg.thermal.started = started_ts is not None msg.thermal.startedTs = int(1e9 * (started_ts or 0)) msg.thermal.thermalStatus = thermal_status thermal_sock.send(msg.to_bytes()) if usb_power_prev and not usb_power: params.put("Offroad_ChargeDisabled", json.dumps(OFFROAD_ALERTS["Offroad_ChargeDisabled"])) elif usb_power and not usb_power_prev: params.delete("Offroad_ChargeDisabled") thermal_status_prev = thermal_status usb_power_prev = usb_power fw_version_match_prev = fw_version_match should_start_prev = should_start #print(msg) # report to server once per minute if (count % int(60. / DT_TRML)) == 0: cloudlog.event("STATUS_PACKET", count=count, health=(health.to_dict() if health else None), location=(location.to_dict() if location else None), thermal=msg.to_dict()) count += 1
class TestParams(unittest.TestCase): def setUp(self): self.tmpdir = tempfile.mkdtemp() print("using", self.tmpdir) self.params = Params(self.tmpdir) def tearDown(self): shutil.rmtree(self.tmpdir) def test_params_put_and_get(self): self.params.put("DongleId", "cb38263377b873ee") assert self.params.get("DongleId") == b"cb38263377b873ee" def test_persist_params_put_and_get(self): p = Params(persistent_params=True) p.put("DongleId", "cb38263377b873ee") assert p.get("DongleId") == b"cb38263377b873ee" def test_params_non_ascii(self): st = b"\xe1\x90\xff" self.params.put("CarParams", st) assert self.params.get("CarParams") == st def test_params_get_cleared_panda_disconnect(self): self.params.put("CarParams", "test") self.params.put("DongleId", "cb38263377b873ee") assert self.params.get("CarParams") == b"test" self.params.panda_disconnect() assert self.params.get("CarParams") is None assert self.params.get("DongleId") is not None def test_params_get_cleared_manager_start(self): self.params.put("CarParams", "test") self.params.put("DongleId", "cb38263377b873ee") assert self.params.get("CarParams") == b"test" self.params.manager_start() assert self.params.get("CarParams") is None assert self.params.get("DongleId") is not None def test_params_two_things(self): self.params.put("DongleId", "bob") self.params.put("AthenadPid", "123") assert self.params.get("DongleId") == b"bob" assert self.params.get("AthenadPid") == b"123" def test_params_get_block(self): def _delayed_writer(): time.sleep(0.1) self.params.put("CarParams", "test") threading.Thread(target=_delayed_writer).start() assert self.params.get("CarParams") is None assert self.params.get("CarParams", True) == b"test" def test_params_unknown_key_fails(self): with self.assertRaises(UnknownKeyName): self.params.get("swag") with self.assertRaises(UnknownKeyName): self.params.get_bool("swag") with self.assertRaises(UnknownKeyName): self.params.put("swag", "abc") with self.assertRaises(UnknownKeyName): self.params.put_bool("swag", True) def test_params_permissions(self): permissions = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IROTH | stat.S_IWOTH self.params.put("DongleId", "cb38263377b873ee") st_mode = os.stat(f"{self.tmpdir}/d/DongleId").st_mode assert (st_mode & permissions) == permissions def test_delete_not_there(self): assert self.params.get("CarParams") is None self.params.delete("CarParams") assert self.params.get("CarParams") is None def test_get_bool(self): self.params.delete("IsMetric") self.assertFalse(self.params.get_bool("IsMetric")) self.params.put_bool("IsMetric", True) self.assertTrue(self.params.get_bool("IsMetric")) self.params.put_bool("IsMetric", False) self.assertFalse(self.params.get_bool("IsMetric")) self.params.put("IsMetric", "1") self.assertTrue(self.params.get_bool("IsMetric")) self.params.put("IsMetric", "0") self.assertFalse(self.params.get_bool("IsMetric")) def test_put_non_blocking_with_get_block(self): q = Params(self.tmpdir) def _delayed_writer(): time.sleep(0.1) put_nonblocking("CarParams", "test", self.tmpdir) threading.Thread(target=_delayed_writer).start() assert q.get("CarParams") is None assert q.get("CarParams", True) == b"test"
def main(): params = Params() if params.get("DisableUpdates") == b"1": raise RuntimeError("updates are disabled by the DisableUpdates param") if os.geteuid() != 0: raise RuntimeError("updated must be launched as root!") # Set low io priority proc = psutil.Process() if psutil.LINUX: proc.ionice(psutil.IOPRIO_CLASS_BE, value=7) ov_lock_fd = open(LOCK_FILE, 'w') try: fcntl.flock(ov_lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB) except IOError: raise RuntimeError( "couldn't get overlay lock; is another updated running?") # Wait for IsOffroad to be set before our first update attempt wait_helper = WaitTimeHelper(proc) wait_helper.sleep(30) update_failed_count = 0 update_available = False overlay_initialized = False while not wait_helper.shutdown: wait_helper.ready_event.clear() # Check for internet every 30s time_wrong = datetime.datetime.utcnow().year < 2019 ping_failed = os.system(f"ping -W 4 -c 1 {TEST_IP}") != 0 if ping_failed or time_wrong: wait_helper.sleep(30) continue # Attempt an update exception = None update_failed_count += 1 try: # Re-create the overlay if BASEDIR/.git has changed since we created the overlay if overlay_initialized: overlay_init_fn = os.path.join(BASEDIR, ".overlay_init") git_dir_path = os.path.join(BASEDIR, ".git") new_files = run( ["find", git_dir_path, "-newer", overlay_init_fn]) if len(new_files.splitlines()): cloudlog.info(".git directory changed, recreating overlay") overlay_initialized = False if not overlay_initialized: init_ovfs() overlay_initialized = True if params.get("IsOffroad") == b"1": update_available = attempt_update( wait_helper) or update_available update_failed_count = 0 if not update_available and os.path.isdir(NEOSUPDATE_DIR): shutil.rmtree(NEOSUPDATE_DIR) else: cloudlog.info("not running updater, openpilot running") except subprocess.CalledProcessError as e: cloudlog.event("update process failed", cmd=e.cmd, output=e.output, returncode=e.returncode) exception = e overlay_initialized = False except Exception: cloudlog.exception("uncaught updated exception, shouldn't happen") params.put("UpdateFailedCount", str(update_failed_count)) if exception is None: params.delete("LastUpdateException") else: params.put("LastUpdateException", f"command failed: {exception.cmd}\n{exception.output}") # Wait 10 minutes between update attempts wait_helper.sleep(60 * 10) dismount_ovfs()
#!/usr/bin/env python3 import sys from cereal import car from common.params import Params # This script locks the safety model to a given value. # When the safety model is locked, boardd will preset panda to the locked safety model # run example: # ./lock_safety_model.py gm if __name__ == "__main__": params = Params() if len(sys.argv) < 2: params.delete("SafetyModelLock") print("Clear locked safety model") else: safety_model = getattr(car.CarParams.SafetyModel, sys.argv[1]) if type(safety_model) != int: raise Exception("Invalid safety model: " + sys.argv[1]) if safety_model == car.CarParams.SafetyModel.allOutput: raise Exception( "Locking the safety model to allOutput is not allowed") params.put("SafetyModelLock", str(safety_model)) print("Locked safety model: " + sys.argv[1])
def thermald_thread(): setup_eon_fan() # prevent LEECO from undervoltage BATT_PERC_OFF = 10 if LEON else 3 # now loop thermal_sock = messaging.pub_sock(service_list['thermal'].port) health_sock = messaging.sub_sock(service_list['health'].port) location_sock = messaging.sub_sock(service_list['gpsLocation'].port) fan_speed = 0 count = 0 off_ts = None started_ts = None started_seen = False thermal_status = ThermalStatus.green thermal_status_prev = ThermalStatus.green usb_power = True usb_power_prev = True health_sock.RCVTIMEO = int(1000 * 2 * DT_TRML) # 2x the expected health frequency current_filter = FirstOrderFilter(0., CURRENT_TAU, DT_TRML) health_prev = None current_connectivity_alert = None params = Params() while 1: health = messaging.recv_sock(health_sock, wait=True) location = messaging.recv_sock(location_sock) location = location.gpsLocation if location else None msg = read_thermal() # clear car params when panda gets disconnected if health is None and health_prev is not None: params.panda_disconnect() health_prev = health if health is not None: usb_power = health.health.usbPowerMode != log.HealthData.UsbPowerMode.client # loggerd is gated based on free space avail = get_available_percent() / 100.0 # thermal message now also includes free space msg.thermal.freeSpace = avail with open("/sys/class/power_supply/battery/capacity") as f: msg.thermal.batteryPercent = int(f.read()) with open("/sys/class/power_supply/battery/status") as f: msg.thermal.batteryStatus = f.read().strip() with open("/sys/class/power_supply/battery/current_now") as f: msg.thermal.batteryCurrent = int(f.read()) with open("/sys/class/power_supply/battery/voltage_now") as f: msg.thermal.batteryVoltage = int(f.read()) with open("/sys/class/power_supply/usb/present") as f: msg.thermal.usbOnline = bool(int(f.read())) current_filter.update(msg.thermal.batteryCurrent / 1e6) # TODO: add car battery voltage check max_cpu_temp = max(msg.thermal.cpu0, msg.thermal.cpu1, msg.thermal.cpu2, msg.thermal.cpu3) / 10.0 max_comp_temp = max(max_cpu_temp, msg.thermal.mem / 10., msg.thermal.gpu / 10.) bat_temp = msg.thermal.bat / 1000. fan_speed = handle_fan(max_cpu_temp, bat_temp, fan_speed) msg.thermal.fanSpeed = fan_speed # thermal logic with hysterisis if max_cpu_temp > 107. or bat_temp >= 63.: # onroad not allowed thermal_status = ThermalStatus.danger elif max_comp_temp > 92.5 or bat_temp > 60.: # CPU throttling starts around ~90C # hysteresis between onroad not allowed and engage not allowed thermal_status = clip(thermal_status, ThermalStatus.red, ThermalStatus.danger) elif max_cpu_temp > 87.5: # hysteresis between engage not allowed and uploader not allowed thermal_status = clip(thermal_status, ThermalStatus.yellow, ThermalStatus.red) elif max_cpu_temp > 80.0: # uploader not allowed thermal_status = ThermalStatus.yellow elif max_cpu_temp > 75.0: # hysteresis between uploader not allowed and all good thermal_status = clip(thermal_status, ThermalStatus.green, ThermalStatus.yellow) else: # all good thermal_status = ThermalStatus.green # **** starting logic **** # Check for last update time and display alerts if needed now = datetime.datetime.now() try: last_update = datetime.datetime.fromisoformat( params.get("LastUpdateTime", encoding='utf8')) except (TypeError, ValueError): last_update = now dt = now - last_update if dt.days > DAYS_NO_CONNECTIVITY_MAX: if current_connectivity_alert != "expired": current_connectivity_alert = "expired" params.delete("Offroad_ConnectivityNeededPrompt") params.put( "Offroad_ConnectivityNeeded", json.dumps(OFFROAD_ALERTS["Offroad_ConnectivityNeeded"])) elif dt.days > DAYS_NO_CONNECTIVITY_PROMPT: remaining_time = str(DAYS_NO_CONNECTIVITY_MAX - dt.days) if current_connectivity_alert != "prompt" + remaining_time: current_connectivity_alert = "prompt" + remaining_time alert_connectivity_prompt = copy.copy( OFFROAD_ALERTS["Offroad_ConnectivityNeededPrompt"]) alert_connectivity_prompt["text"] += remaining_time + " days." params.delete("Offroad_ConnectivityNeeded") params.put("Offroad_ConnectivityNeededPrompt", json.dumps(alert_connectivity_prompt)) elif current_connectivity_alert is not None: current_connectivity_alert = None params.delete("Offroad_ConnectivityNeeded") params.delete("Offroad_ConnectivityNeededPrompt") # start constellation of processes when the car starts ignition = health is not None and health.health.started do_uninstall = params.get("DoUninstall") == b"1" accepted_terms = params.get("HasAcceptedTerms") == terms_version completed_training = params.get( "CompletedTrainingVersion") == training_version should_start = ignition # have we seen a panda? passive = (params.get("Passive") == "1") # with 2% left, we killall, otherwise the phone will take a long time to boot should_start = should_start and msg.thermal.freeSpace > 0.02 # confirm we have completed training and aren't uninstalling should_start = should_start and accepted_terms and ( passive or completed_training) and (not do_uninstall) # if any CPU gets above 107 or the battery gets above 63, kill all processes # controls will warn with CPU above 95 or battery above 60 if thermal_status >= ThermalStatus.danger: should_start = False if thermal_status_prev < ThermalStatus.danger: params.put( "Offroad_TemperatureTooHigh", json.dumps(OFFROAD_ALERTS["Offroad_TemperatureTooHigh"])) else: if thermal_status_prev >= ThermalStatus.danger: params.delete("Offroad_TemperatureTooHigh") if should_start: off_ts = None if started_ts is None: started_ts = sec_since_boot() started_seen = True os.system( 'echo performance > /sys/class/devfreq/soc:qcom,cpubw/governor' ) else: started_ts = None if off_ts is None: off_ts = sec_since_boot() os.system( 'echo powersave > /sys/class/devfreq/soc:qcom,cpubw/governor' ) # shutdown if the battery gets lower than 3%, it's discharging, we aren't running for # more than a minute but we were running if msg.thermal.batteryPercent < BATT_PERC_OFF and msg.thermal.batteryStatus == "Discharging" and \ started_seen and (sec_since_boot() - off_ts) > 60: os.system('LD_LIBRARY_PATH="" svc power shutdown') msg.thermal.chargingError = current_filter.x > 0. and msg.thermal.batteryPercent < 90 # if current is positive, then battery is being discharged msg.thermal.started = started_ts is not None msg.thermal.startedTs = int(1e9 * (started_ts or 0)) msg.thermal.thermalStatus = thermal_status thermal_sock.send(msg.to_bytes()) if usb_power_prev and not usb_power: params.put("Offroad_ChargeDisabled", json.dumps(OFFROAD_ALERTS["Offroad_ChargeDisabled"])) elif usb_power and not usb_power_prev: params.delete("Offroad_ChargeDisabled") thermal_status_prev = thermal_status usb_power_prev = usb_power print(msg) # report to server once per minute if (count % int(60. / DT_TRML)) == 0: cloudlog.event("STATUS_PACKET", count=count, health=(health.to_dict() if health else None), location=(location.to_dict() if location else None), thermal=msg.to_dict()) count += 1
class TestUpdater(unittest.TestCase): def setUp(self): self.updated_proc = None self.tmp_dir = tempfile.TemporaryDirectory() org_dir = os.path.join(self.tmp_dir.name, "commaai") self.basedir = os.path.join(org_dir, "openpilot") self.git_remote_dir = os.path.join(org_dir, "openpilot_remote") self.staging_dir = os.path.join(org_dir, "safe_staging") for d in [ org_dir, self.basedir, self.git_remote_dir, self.staging_dir ]: os.mkdir(d) self.upper_dir = os.path.join(self.staging_dir, "upper") self.merged_dir = os.path.join(self.staging_dir, "merged") self.finalized_dir = os.path.join(self.staging_dir, "finalized") # setup local submodule remotes submodules = subprocess.check_output( "git submodule --quiet foreach 'echo $name'", shell=True, cwd=BASEDIR, encoding='utf8').split() for s in submodules: sub_path = os.path.join(org_dir, s.split("_repo")[0]) self._run(f"git clone {s} {sub_path}.git", cwd=BASEDIR) # setup two git repos, a remote and one we'll run updated in self._run([ f"git clone {BASEDIR} {self.git_remote_dir}", f"git clone {self.git_remote_dir} {self.basedir}", f"cd {self.basedir} && git submodule init && git submodule update", f"cd {self.basedir} && scons -j{os.cpu_count()} cereal" ]) self.params = Params(db=os.path.join(self.basedir, "persist/params")) self.params.clear_all() os.sync() def tearDown(self): try: if self.updated_proc is not None: self.updated_proc.terminate() self.updated_proc.wait(30) except Exception as e: print(e) self.tmp_dir.cleanup() # *** test helpers *** def _run(self, cmd, cwd=None): if not isinstance(cmd, list): cmd = (cmd, ) for c in cmd: subprocess.check_output(c, cwd=cwd, shell=True) def _get_updated_proc(self): os.environ["PYTHONPATH"] = self.basedir os.environ["GIT_AUTHOR_NAME"] = "testy tester" os.environ["GIT_COMMITTER_NAME"] = "testy tester" os.environ["GIT_AUTHOR_EMAIL"] = "*****@*****.**" os.environ["GIT_COMMITTER_EMAIL"] = "*****@*****.**" os.environ["UPDATER_TEST_IP"] = "localhost" os.environ["UPDATER_LOCK_FILE"] = os.path.join(self.tmp_dir.name, "updater.lock") os.environ["UPDATER_STAGING_ROOT"] = self.staging_dir updated_path = os.path.join(self.basedir, "selfdrive/updated.py") return subprocess.Popen(updated_path, env=os.environ) def _start_updater(self, offroad=True, nosleep=False): self.params.put("IsOffroad", "1" if offroad else "0") self.updated_proc = self._get_updated_proc() if not nosleep: time.sleep(1) def _update_now(self): self.updated_proc.send_signal(signal.SIGHUP) # TODO: this should be implemented in params def _read_param(self, key, timeout=1): ret = None start_time = time.monotonic() while ret is None: ret = self.params.get(key, encoding='utf8') if time.monotonic() - start_time > timeout: break time.sleep(0.01) return ret def _wait_for_update(self, timeout=30, clear_param=False): if clear_param: self.params.delete("LastUpdateTime") self._update_now() t = self._read_param("LastUpdateTime", timeout=timeout) if t is None: raise Exception("timed out waiting for update to complate") def _make_commit(self): all_dirs, all_files = [], [] for root, dirs, files in os.walk(self.git_remote_dir): if ".git" in root: continue for d in dirs: all_dirs.append(os.path.join(root, d)) for f in files: all_files.append(os.path.join(root, f)) # make a new dir and some new files new_dir = os.path.join(self.git_remote_dir, "this_is_a_new_dir") os.mkdir(new_dir) for _ in range(random.randrange(5, 30)): for d in (new_dir, random.choice(all_dirs)): with tempfile.NamedTemporaryFile(dir=d, delete=False) as f: f.write(os.urandom(random.randrange(1, 1000000))) # modify some files for f in random.sample(all_files, random.randrange(5, 50)): with open(f, "w+") as ff: txt = ff.readlines() ff.seek(0) for line in txt: ff.write(line[::-1]) # remove some files for f in random.sample(all_files, random.randrange(5, 50)): os.remove(f) # remove some dirs for d in random.sample(all_dirs, random.randrange(1, 10)): shutil.rmtree(d) # commit the changes self._run([ "git add -A", "git commit -m 'an update'", ], cwd=self.git_remote_dir) def _check_update_state(self, update_available): # make sure LastUpdateTime is recent t = self._read_param("LastUpdateTime") last_update_time = datetime.datetime.fromisoformat(t) td = datetime.datetime.utcnow() - last_update_time self.assertLess(td.total_seconds(), 10) self.params.delete("LastUpdateTime") # wait a bit for the rest of the params to be written time.sleep(0.1) # check params update = self._read_param("UpdateAvailable") self.assertEqual(update == "1", update_available, f"UpdateAvailable: {repr(update)}") self.assertEqual(self._read_param("UpdateFailedCount"), "0") # TODO: check that the finalized update actually matches remote # check the .overlay_init and .overlay_consistent flags self.assertTrue( os.path.isfile(os.path.join(self.basedir, ".overlay_init"))) self.assertEqual( os.path.isfile( os.path.join(self.finalized_dir, ".overlay_consistent")), update_available) # *** test cases *** # Run updated for 100 cycles with no update def test_no_update(self): self._start_updater() for _ in range(100): self._wait_for_update(clear_param=True) self._check_update_state(False) # Let the updater run with no update for a cycle, then write an update def test_update(self): self._start_updater() # run for a cycle with no update self._wait_for_update(clear_param=True) self._check_update_state(False) # write an update to our remote self._make_commit() # run for a cycle to get the update self._wait_for_update(timeout=60, clear_param=True) self._check_update_state(True) # run another cycle with no update self._wait_for_update(clear_param=True) self._check_update_state(True) # Let the updater run for 10 cycles, and write an update every cycle @unittest.skip("need to make this faster") def test_update_loop(self): self._start_updater() # run for a cycle with no update self._wait_for_update(clear_param=True) for _ in range(10): time.sleep(0.5) self._make_commit() self._wait_for_update(timeout=90, clear_param=True) self._check_update_state(True) # Test overlay re-creation after tracking a new file in basedir's git def test_overlay_reinit(self): self._start_updater() overlay_init_fn = os.path.join(self.basedir, ".overlay_init") # run for a cycle with no update self._wait_for_update(clear_param=True) self.params.delete("LastUpdateTime") first_mtime = os.path.getmtime(overlay_init_fn) # touch a file in the basedir self._run("touch new_file && git add new_file", cwd=self.basedir) # run another cycle, should have a new mtime self._wait_for_update(clear_param=True) second_mtime = os.path.getmtime(overlay_init_fn) self.assertTrue(first_mtime != second_mtime) # run another cycle, mtime should be same as last cycle self._wait_for_update(clear_param=True) new_mtime = os.path.getmtime(overlay_init_fn) self.assertTrue(second_mtime == new_mtime) # Make sure updated exits if another instance is running def test_multiple_instances(self): # start updated and let it run for a cycle self._start_updater() time.sleep(1) self._wait_for_update(clear_param=True) # start another instance second_updated = self._get_updated_proc() ret_code = second_updated.wait(timeout=5) self.assertTrue(ret_code is not None)
def thermald_thread(): # prevent LEECO from undervoltage BATT_PERC_OFF = 10 if LEON else 3 health_timeout = int(1000 * 2.5 * DT_TRML) # 2.5x the expected health frequency # now loop thermal_sock = messaging.pub_sock('thermal') health_sock = messaging.sub_sock('health', timeout=health_timeout) location_sock = messaging.sub_sock('gpsLocation') ignition = False fan_speed = 0 count = 0 off_ts = None started_ts = None started_seen = False thermal_status = ThermalStatus.green thermal_status_prev = ThermalStatus.green usb_power = True usb_power_prev = True current_branch = get_git_branch() network_type = NetworkType.none network_strength = NetworkStrength.unknown current_filter = FirstOrderFilter(0., CURRENT_TAU, DT_TRML) cpu_temp_filter = FirstOrderFilter(0., CPU_TEMP_TAU, DT_TRML) health_prev = None fw_version_match_prev = True current_update_alert = None time_valid_prev = True should_start_prev = False handle_fan = None is_uno = False has_relay = False params = Params() pm = PowerMonitoring() no_panda_cnt = 0 while 1: health = messaging.recv_sock(health_sock, wait=True) location = messaging.recv_sock(location_sock) location = location.gpsLocation if location else None msg = read_thermal() if health is not None: usb_power = health.health.usbPowerMode != log.HealthData.UsbPowerMode.client # If we lose connection to the panda, wait 5 seconds before going offroad if health.health.hwType == log.HealthData.HwType.unknown: no_panda_cnt += 1 if no_panda_cnt > DISCONNECT_TIMEOUT / DT_TRML: if ignition: cloudlog.error("Lost panda connection while onroad") ignition = False else: no_panda_cnt = 0 ignition = health.health.ignitionLine or health.health.ignitionCan # Setup fan handler on first connect to panda if handle_fan is None and health.health.hwType != log.HealthData.HwType.unknown: is_uno = health.health.hwType == log.HealthData.HwType.uno has_relay = health.health.hwType in [log.HealthData.HwType.blackPanda, log.HealthData.HwType.uno, log.HealthData.HwType.dos] if is_uno or not ANDROID: cloudlog.info("Setting up UNO fan handler") handle_fan = handle_fan_uno else: cloudlog.info("Setting up EON fan handler") setup_eon_fan() handle_fan = handle_fan_eon # Handle disconnect if health_prev is not None: if health.health.hwType == log.HealthData.HwType.unknown and \ health_prev.health.hwType != log.HealthData.HwType.unknown: params.panda_disconnect() health_prev = health # get_network_type is an expensive call. update every 10s if (count % int(10. / DT_TRML)) == 0: try: network_type = get_network_type() network_strength = get_network_strength(network_type) except Exception: cloudlog.exception("Error getting network status") msg.thermal.freeSpace = get_available_percent(default=100.0) / 100.0 msg.thermal.memUsedPercent = int(round(psutil.virtual_memory().percent)) msg.thermal.cpuPerc = int(round(psutil.cpu_percent())) msg.thermal.networkType = network_type msg.thermal.networkStrength = network_strength msg.thermal.batteryPercent = get_battery_capacity() msg.thermal.batteryStatus = get_battery_status() msg.thermal.batteryCurrent = get_battery_current() msg.thermal.batteryVoltage = get_battery_voltage() msg.thermal.usbOnline = get_usb_present() # Fake battery levels on uno for frame if is_uno: msg.thermal.batteryPercent = 100 msg.thermal.batteryStatus = "Charging" msg.thermal.bat = 0 current_filter.update(msg.thermal.batteryCurrent / 1e6) # TODO: add car battery voltage check max_cpu_temp = cpu_temp_filter.update( max(msg.thermal.cpu0, msg.thermal.cpu1, msg.thermal.cpu2, msg.thermal.cpu3) / 10.0) max_comp_temp = max(max_cpu_temp, msg.thermal.mem / 10., msg.thermal.gpu / 10.) bat_temp = msg.thermal.bat / 1000. if handle_fan is not None: fan_speed = handle_fan(max_cpu_temp, bat_temp, fan_speed, ignition) msg.thermal.fanSpeed = fan_speed # If device is offroad we want to cool down before going onroad # since going onroad increases load and can make temps go over 107 # We only do this if there is a relay that prevents the car from faulting if max_cpu_temp > 107. or bat_temp >= 63. or (has_relay and (started_ts is None) and max_cpu_temp > 70.0): # onroad not allowed thermal_status = ThermalStatus.danger elif max_comp_temp > 96.0 or bat_temp > 60.: # hysteresis between onroad not allowed and engage not allowed thermal_status = clip(thermal_status, ThermalStatus.red, ThermalStatus.danger) elif max_cpu_temp > 94.0: # hysteresis between engage not allowed and uploader not allowed thermal_status = clip(thermal_status, ThermalStatus.yellow, ThermalStatus.red) elif max_cpu_temp > 80.0: # uploader not allowed thermal_status = ThermalStatus.yellow elif max_cpu_temp > 75.0: # hysteresis between uploader not allowed and all good thermal_status = clip(thermal_status, ThermalStatus.green, ThermalStatus.yellow) else: # all good thermal_status = ThermalStatus.green # **** starting logic **** # Check for last update time and display alerts if needed now = datetime.datetime.utcnow() # show invalid date/time alert time_valid = now.year >= 2019 if time_valid and not time_valid_prev: set_offroad_alert("Offroad_InvalidTime", False) if not time_valid and time_valid_prev: set_offroad_alert("Offroad_InvalidTime", True) time_valid_prev = time_valid # Show update prompt try: last_update = datetime.datetime.fromisoformat(params.get("LastUpdateTime", encoding='utf8')) except (TypeError, ValueError): last_update = now dt = now - last_update update_failed_count = params.get("UpdateFailedCount") update_failed_count = 0 if update_failed_count is None else int(update_failed_count) last_update_exception = params.get("LastUpdateException", encoding='utf8') if update_failed_count > 15 and last_update_exception is not None: if current_branch in ["release2", "dashcam"]: extra_text = "Ensure the software is correctly installed" else: extra_text = last_update_exception if current_update_alert != "update" + extra_text: current_update_alert = "update" + extra_text set_offroad_alert("Offroad_ConnectivityNeeded", False) set_offroad_alert("Offroad_ConnectivityNeededPrompt", False) set_offroad_alert("Offroad_UpdateFailed", True, extra_text=extra_text) elif dt.days > DAYS_NO_CONNECTIVITY_MAX and update_failed_count > 1: if current_update_alert != "expired": current_update_alert = "expired" set_offroad_alert("Offroad_UpdateFailed", False) set_offroad_alert("Offroad_ConnectivityNeededPrompt", False) set_offroad_alert("Offroad_ConnectivityNeeded", True) elif dt.days > DAYS_NO_CONNECTIVITY_PROMPT: remaining_time = str(max(DAYS_NO_CONNECTIVITY_MAX - dt.days, 0)) if current_update_alert != "prompt" + remaining_time: current_update_alert = "prompt" + remaining_time set_offroad_alert("Offroad_UpdateFailed", False) set_offroad_alert("Offroad_ConnectivityNeeded", False) set_offroad_alert("Offroad_ConnectivityNeededPrompt", True, extra_text=f"{remaining_time} days.") elif current_update_alert is not None: current_update_alert = None set_offroad_alert("Offroad_UpdateFailed", False) set_offroad_alert("Offroad_ConnectivityNeeded", False) set_offroad_alert("Offroad_ConnectivityNeededPrompt", False) do_uninstall = params.get("DoUninstall") == b"1" accepted_terms = params.get("HasAcceptedTerms") == terms_version completed_training = params.get("CompletedTrainingVersion") == training_version panda_signature = params.get("PandaFirmware") fw_version_match = (panda_signature is None) or (panda_signature == FW_SIGNATURE) # don't show alert is no panda is connected (None) should_start = ignition # with 2% left, we killall, otherwise the phone will take a long time to boot should_start = should_start and msg.thermal.freeSpace > 0.02 # confirm we have completed training and aren't uninstalling should_start = should_start and accepted_terms and completed_training and (not do_uninstall) # check for firmware mismatch should_start = should_start and fw_version_match # check if system time is valid should_start = should_start and time_valid # don't start while taking snapshot if not should_start_prev: is_viewing_driver = params.get("IsDriverViewEnabled") == b"1" is_taking_snapshot = params.get("IsTakingSnapshot") == b"1" should_start = should_start and (not is_taking_snapshot) and (not is_viewing_driver) if fw_version_match and not fw_version_match_prev: set_offroad_alert("Offroad_PandaFirmwareMismatch", False) if not fw_version_match and fw_version_match_prev: set_offroad_alert("Offroad_PandaFirmwareMismatch", True) # if any CPU gets above 107 or the battery gets above 63, kill all processes # controls will warn with CPU above 95 or battery above 60 if thermal_status >= ThermalStatus.danger: should_start = False if thermal_status_prev < ThermalStatus.danger: set_offroad_alert("Offroad_TemperatureTooHigh", True) else: if thermal_status_prev >= ThermalStatus.danger: set_offroad_alert("Offroad_TemperatureTooHigh", False) if should_start: if not should_start_prev: params.delete("IsOffroad") off_ts = None if started_ts is None: started_ts = sec_since_boot() started_seen = True os.system('echo performance > /sys/class/devfreq/soc:qcom,cpubw/governor') else: if should_start_prev or (count == 0): put_nonblocking("IsOffroad", "1") started_ts = None if off_ts is None: off_ts = sec_since_boot() os.system('echo powersave > /sys/class/devfreq/soc:qcom,cpubw/governor') # shutdown if the battery gets lower than 3%, it's discharging, we aren't running for # more than a minute but we were running if msg.thermal.batteryPercent < BATT_PERC_OFF and msg.thermal.batteryStatus == "Discharging" and \ started_seen and (sec_since_boot() - off_ts) > 60: os.system('LD_LIBRARY_PATH="" svc power shutdown') # Offroad power monitoring pm.calculate(health) msg.thermal.offroadPowerUsage = pm.get_power_used() msg.thermal.chargingError = current_filter.x > 0. and msg.thermal.batteryPercent < 90 # if current is positive, then battery is being discharged msg.thermal.started = started_ts is not None msg.thermal.startedTs = int(1e9*(started_ts or 0)) msg.thermal.thermalStatus = thermal_status thermal_sock.send(msg.to_bytes()) if usb_power_prev and not usb_power: set_offroad_alert("Offroad_ChargeDisabled", True) elif usb_power and not usb_power_prev: set_offroad_alert("Offroad_ChargeDisabled", False) thermal_status_prev = thermal_status usb_power_prev = usb_power fw_version_match_prev = fw_version_match should_start_prev = should_start # report to server once per minute if (count % int(60. / DT_TRML)) == 0: cloudlog.event("STATUS_PACKET", count=count, health=(health.to_dict() if health else None), location=(location.to_dict() if location else None), thermal=msg.to_dict()) count += 1
def thermald_thread(): pm = messaging.PubMaster(['deviceState']) pandaState_timeout = int(1000 * 2.5 * DT_TRML) # 2.5x the expected pandaState frequency pandaState_sock = messaging.sub_sock('pandaState', timeout=pandaState_timeout) location_sock = messaging.sub_sock('gpsLocationExternal') managerState_sock = messaging.sub_sock('managerState', conflate=True) fan_speed = 0 count = 0 startup_conditions = { "ignition": False, } startup_conditions_prev = startup_conditions.copy() off_ts = None started_ts = None started_seen = False thermal_status = ThermalStatus.green usb_power = True current_branch = get_git_branch() network_type = NetworkType.none network_strength = NetworkStrength.unknown current_filter = FirstOrderFilter(0., CURRENT_TAU, DT_TRML) cpu_temp_filter = FirstOrderFilter(0., CPU_TEMP_TAU, DT_TRML) pandaState_prev = None should_start_prev = False handle_fan = None is_uno = False ui_running_prev = False params = Params() power_monitor = PowerMonitoring() no_panda_cnt = 0 thermal_config = HARDWARE.get_thermal_config() # CPR3 logging if EON: base_path = "/sys/kernel/debug/cpr3-regulator/" cpr_files = [p for p in Path(base_path).glob("**/*") if p.is_file()] cpr_data = {} for cf in cpr_files: with open(cf, "r") as f: try: cpr_data[str(cf)] = f.read().strip() except Exception: pass cloudlog.event("CPR", data=cpr_data) while 1: pandaState = messaging.recv_sock(pandaState_sock, wait=True) msg = read_thermal(thermal_config) if pandaState is not None: usb_power = pandaState.pandaState.usbPowerMode != log.PandaState.UsbPowerMode.client # If we lose connection to the panda, wait 5 seconds before going offroad if pandaState.pandaState.pandaType == log.PandaState.PandaType.unknown: no_panda_cnt += 1 if no_panda_cnt > DISCONNECT_TIMEOUT / DT_TRML: if startup_conditions["ignition"]: cloudlog.error("Lost panda connection while onroad") startup_conditions["ignition"] = False else: no_panda_cnt = 0 startup_conditions[ "ignition"] = pandaState.pandaState.ignitionLine or pandaState.pandaState.ignitionCan startup_conditions[ "hardware_supported"] = pandaState.pandaState.pandaType not in [ log.PandaState.PandaType.whitePanda, log.PandaState.PandaType.greyPanda ] set_offroad_alert_if_changed( "Offroad_HardwareUnsupported", not startup_conditions["hardware_supported"]) # Setup fan handler on first connect to panda if handle_fan is None and pandaState.pandaState.pandaType != log.PandaState.PandaType.unknown: is_uno = pandaState.pandaState.pandaType == log.PandaState.PandaType.uno if (not EON) or is_uno: cloudlog.info("Setting up UNO fan handler") handle_fan = handle_fan_uno else: cloudlog.info("Setting up EON fan handler") setup_eon_fan() handle_fan = handle_fan_eon # Handle disconnect if pandaState_prev is not None: if pandaState.pandaState.pandaType == log.PandaState.PandaType.unknown and \ pandaState_prev.pandaState.pandaType != log.PandaState.PandaType.unknown: params.panda_disconnect() pandaState_prev = pandaState # get_network_type is an expensive call. update every 10s if (count % int(10. / DT_TRML)) == 0: try: network_type = HARDWARE.get_network_type() network_strength = HARDWARE.get_network_strength(network_type) except Exception: cloudlog.exception("Error getting network status") msg.deviceState.freeSpacePercent = get_available_percent(default=100.0) msg.deviceState.memoryUsagePercent = int( round(psutil.virtual_memory().percent)) msg.deviceState.cpuUsagePercent = int(round(psutil.cpu_percent())) msg.deviceState.networkType = network_type msg.deviceState.networkStrength = network_strength msg.deviceState.batteryPercent = HARDWARE.get_battery_capacity() msg.deviceState.batteryStatus = HARDWARE.get_battery_status() msg.deviceState.batteryCurrent = HARDWARE.get_battery_current() msg.deviceState.batteryVoltage = HARDWARE.get_battery_voltage() msg.deviceState.usbOnline = HARDWARE.get_usb_present() # Fake battery levels on uno for frame if (not EON) or is_uno: msg.deviceState.batteryPercent = 100 msg.deviceState.batteryStatus = "Charging" msg.deviceState.batteryTempC = 0 current_filter.update(msg.deviceState.batteryCurrent / 1e6) # TODO: add car battery voltage check max_cpu_temp = cpu_temp_filter.update(max(msg.deviceState.cpuTempC)) max_comp_temp = max(max_cpu_temp, msg.deviceState.memoryTempC, max(msg.deviceState.gpuTempC)) bat_temp = msg.deviceState.batteryTempC if handle_fan is not None: fan_speed = handle_fan(max_cpu_temp, bat_temp, fan_speed, startup_conditions["ignition"]) msg.deviceState.fanSpeedPercentDesired = fan_speed # If device is offroad we want to cool down before going onroad # since going onroad increases load and can make temps go over 107 # We only do this if there is a relay that prevents the car from faulting is_offroad_for_5_min = (started_ts is None) and ( (not started_seen) or (off_ts is None) or (sec_since_boot() - off_ts > 60 * 5)) if max_cpu_temp > 107. or bat_temp >= 63. or (is_offroad_for_5_min and max_cpu_temp > 70.0): # onroad not allowed thermal_status = ThermalStatus.danger elif max_comp_temp > 96.0 or bat_temp > 60.: # hysteresis between onroad not allowed and engage not allowed thermal_status = clip(thermal_status, ThermalStatus.red, ThermalStatus.danger) elif max_cpu_temp > 94.0: # hysteresis between engage not allowed and uploader not allowed thermal_status = clip(thermal_status, ThermalStatus.yellow, ThermalStatus.red) elif max_cpu_temp > 80.0: # uploader not allowed thermal_status = ThermalStatus.yellow elif max_cpu_temp > 75.0: # hysteresis between uploader not allowed and all good thermal_status = clip(thermal_status, ThermalStatus.green, ThermalStatus.yellow) else: thermal_status = ThermalStatus.green # default to good condition # **** starting logic **** # Check for last update time and display alerts if needed now = datetime.datetime.utcnow() # show invalid date/time alert startup_conditions["time_valid"] = (now.year > 2020) or ( now.year == 2020 and now.month >= 10) set_offroad_alert_if_changed("Offroad_InvalidTime", (not startup_conditions["time_valid"])) # Show update prompt try: last_update = datetime.datetime.fromisoformat( params.get("LastUpdateTime", encoding='utf8')) except (TypeError, ValueError): last_update = now dt = now - last_update update_failed_count = params.get("UpdateFailedCount") update_failed_count = 0 if update_failed_count is None else int( update_failed_count) last_update_exception = params.get("LastUpdateException", encoding='utf8') if update_failed_count > 15 and last_update_exception is not None: if current_branch in ["release2", "dashcam"]: extra_text = "Ensure the software is correctly installed" else: extra_text = last_update_exception set_offroad_alert_if_changed("Offroad_ConnectivityNeeded", False) set_offroad_alert_if_changed("Offroad_ConnectivityNeededPrompt", False) set_offroad_alert_if_changed("Offroad_UpdateFailed", True, extra_text=extra_text) elif dt.days > DAYS_NO_CONNECTIVITY_MAX and update_failed_count > 1: set_offroad_alert_if_changed("Offroad_UpdateFailed", False) set_offroad_alert_if_changed("Offroad_ConnectivityNeededPrompt", False) set_offroad_alert_if_changed("Offroad_ConnectivityNeeded", True) elif dt.days > DAYS_NO_CONNECTIVITY_PROMPT: remaining_time = str(max(DAYS_NO_CONNECTIVITY_MAX - dt.days, 0)) set_offroad_alert_if_changed("Offroad_UpdateFailed", False) set_offroad_alert_if_changed("Offroad_ConnectivityNeeded", False) set_offroad_alert_if_changed("Offroad_ConnectivityNeededPrompt", True, extra_text=f"{remaining_time} days.") else: set_offroad_alert_if_changed("Offroad_UpdateFailed", False) set_offroad_alert_if_changed("Offroad_ConnectivityNeeded", False) set_offroad_alert_if_changed("Offroad_ConnectivityNeededPrompt", False) startup_conditions["up_to_date"] = params.get( "Offroad_ConnectivityNeeded") is None or params.get( "DisableUpdates") == b"1" startup_conditions["not_uninstalling"] = not params.get( "DoUninstall") == b"1" startup_conditions["accepted_terms"] = params.get( "HasAcceptedTerms") == terms_version panda_signature = params.get("PandaFirmware") startup_conditions["fw_version_match"] = (panda_signature is None) or ( panda_signature == FW_SIGNATURE ) # don't show alert is no panda is connected (None) set_offroad_alert_if_changed( "Offroad_PandaFirmwareMismatch", (not startup_conditions["fw_version_match"])) # with 2% left, we killall, otherwise the phone will take a long time to boot startup_conditions["free_space"] = msg.deviceState.freeSpacePercent > 2 startup_conditions["completed_training"] = params.get("CompletedTrainingVersion") == training_version or \ (current_branch in ['dashcam', 'dashcam-staging']) startup_conditions["not_driver_view"] = not params.get( "IsDriverViewEnabled") == b"1" startup_conditions["not_taking_snapshot"] = not params.get( "IsTakingSnapshot") == b"1" # if any CPU gets above 107 or the battery gets above 63, kill all processes # controls will warn with CPU above 95 or battery above 60 startup_conditions[ "device_temp_good"] = thermal_status < ThermalStatus.danger set_offroad_alert_if_changed( "Offroad_TemperatureTooHigh", (not startup_conditions["device_temp_good"])) # Handle offroad/onroad transition should_start = all(startup_conditions.values()) if should_start: if not should_start_prev: params.delete("IsOffroad") if TICI and DISABLE_LTE_ONROAD: os.system("sudo systemctl stop --no-block lte") off_ts = None if started_ts is None: started_ts = sec_since_boot() started_seen = True else: if startup_conditions["ignition"] and (startup_conditions != startup_conditions_prev): cloudlog.event("Startup blocked", startup_conditions=startup_conditions) if should_start_prev or (count == 0): params.put("IsOffroad", "1") if TICI and DISABLE_LTE_ONROAD: os.system("sudo systemctl start --no-block lte") started_ts = None if off_ts is None: off_ts = sec_since_boot() # Offroad power monitoring power_monitor.calculate(pandaState) msg.deviceState.offroadPowerUsageUwh = power_monitor.get_power_used() msg.deviceState.carBatteryCapacityUwh = max( 0, power_monitor.get_car_battery_capacity()) # Check if we need to disable charging (handled by boardd) msg.deviceState.chargingDisabled = power_monitor.should_disable_charging( pandaState, off_ts) # Check if we need to shut down if power_monitor.should_shutdown(pandaState, off_ts, started_seen): cloudlog.info(f"shutting device down, offroad since {off_ts}") # TODO: add function for blocking cloudlog instead of sleep time.sleep(10) HARDWARE.shutdown() # If UI has crashed, set the brightness to reasonable non-zero value manager_state = messaging.recv_one_or_none(managerState_sock) if manager_state is not None: ui_running = "ui" in (p.name for p in manager_state.managerState.processes if p.running) if ui_running_prev and not ui_running: HARDWARE.set_screen_brightness(20) ui_running_prev = ui_running msg.deviceState.chargingError = current_filter.x > 0. and msg.deviceState.batteryPercent < 90 # if current is positive, then battery is being discharged msg.deviceState.started = started_ts is not None msg.deviceState.startedMonoTime = int(1e9 * (started_ts or 0)) msg.deviceState.thermalStatus = thermal_status pm.send("deviceState", msg) if EON and not is_uno: set_offroad_alert_if_changed("Offroad_ChargeDisabled", (not usb_power)) should_start_prev = should_start startup_conditions_prev = startup_conditions.copy() # report to server once every 10 minutes if (count % int(600. / DT_TRML)) == 0: location = messaging.recv_sock(location_sock) cloudlog.event("STATUS_PACKET", count=count, pandaState=(strip_deprecated_keys( pandaState.to_dict()) if pandaState else None), location=(strip_deprecated_keys( location.gpsLocationExternal.to_dict()) if location else None), deviceState=strip_deprecated_keys(msg.to_dict())) count += 1
def thermald_thread(): # if limitting battery to Min-Max%. edit /data/bb_openpilot.cfg car_set = CarSettings() limitBatteryMinMax = car_set.get_value("limitBatteryMinMax") batt_min = car_set.get_value("limitBattery_Min") batt_max = car_set.get_value("limitBattery_Max") setup_eon_fan() # prevent LEECO from undervoltage BATT_PERC_OFF = 10 if LEON else 3 health_timeout = int(1000 * 2 * DT_TRML) # 2x the expected health frequency # now loop thermal_sock = messaging.pub_sock('thermal') health_sock = messaging.sub_sock('health', timeout=health_timeout) location_sock = messaging.sub_sock('gpsLocation') fan_speed = 0 count = 0 off_ts = None started_ts = None started_seen = False thermal_status = ThermalStatus.green thermal_status_prev = ThermalStatus.green usb_power = True usb_power_prev = True current_filter = FirstOrderFilter(0., CURRENT_TAU, DT_TRML) health_prev = None fw_version_match_prev = True current_connectivity_alert = None time_valid_prev = True # Make sure charging is enabled charging_disabled = False os.system('echo "1" > /sys/class/power_supply/battery/charging_enabled') params = Params() while 1: health = messaging.recv_sock(health_sock, wait=True) location = messaging.recv_sock(location_sock) location = location.gpsLocation if location else None msg = read_thermal() # clear car params when panda gets disconnected if health is None and health_prev is not None: params.panda_disconnect() health_prev = health if health is not None: usb_power = health.health.usbPowerMode != log.HealthData.UsbPowerMode.client # loggerd is gated based on free space avail = get_available_percent() / 100.0 # thermal message now also includes free space msg.thermal.freeSpace = avail charger_off = False with open("/sys/class/power_supply/battery/capacity") as f: msg.thermal.batteryPercent = int(f.read()) with open("/sys/class/power_supply/battery/status") as f: msg.thermal.batteryStatus = f.read().strip() with open("/sys/class/power_supply/battery/current_now") as f: msg.thermal.batteryCurrent = int(f.read()) with open("/sys/class/power_supply/battery/voltage_now") as f: msg.thermal.batteryVoltage = int(f.read()) with open("/sys/class/power_supply/usb/present") as f: msg.thermal.usbOnline = bool(int(f.read())) with open("/sys/class/power_supply/battery/charge_type") as f: charger_status = f.read().strip() charger_off = (charger_status == "N/A") current_filter.update(msg.thermal.batteryCurrent / 1e6) # TODO: add car battery voltage check max_cpu_temp = max(msg.thermal.cpu0, msg.thermal.cpu1, msg.thermal.cpu2, msg.thermal.cpu3) / 10.0 max_comp_temp = max(max_cpu_temp, msg.thermal.mem / 10., msg.thermal.gpu / 10.) bat_temp = msg.thermal.bat/1000. if health is not None and health.health.hwType == log.HealthData.HwType.uno: fan_speed = handle_fan_uno(max_cpu_temp, bat_temp, fan_speed) else: fan_speed = handle_fan_eon(max_cpu_temp, bat_temp, fan_speed) msg.thermal.fanSpeed = fan_speed # thermal logic with hysterisis if max_cpu_temp > 107. or bat_temp >= 63.: # onroad not allowed thermal_status = ThermalStatus.danger elif max_comp_temp > 92.5 or bat_temp > 60.: # CPU throttling starts around ~90C # hysteresis between onroad not allowed and engage not allowed thermal_status = clip(thermal_status, ThermalStatus.red, ThermalStatus.danger) elif max_cpu_temp > 87.5: # hysteresis between engage not allowed and uploader not allowed thermal_status = clip(thermal_status, ThermalStatus.yellow, ThermalStatus.red) elif max_cpu_temp > 80.0: # uploader not allowed thermal_status = ThermalStatus.yellow elif max_cpu_temp > 75.0: # hysteresis between uploader not allowed and all good thermal_status = clip(thermal_status, ThermalStatus.green, ThermalStatus.yellow) else: # all good thermal_status = ThermalStatus.green # **** starting logic **** # Check for last update time and display alerts if needed now = datetime.datetime.now() # show invalid date/time alert time_valid = now.year >= 2019 if time_valid and not time_valid_prev: params.delete("Offroad_InvalidTime") if not time_valid and time_valid_prev: params.put("Offroad_InvalidTime", json.dumps(OFFROAD_ALERTS["Offroad_InvalidTime"])) time_valid_prev = time_valid # Show update prompt try: last_update = datetime.datetime.fromisoformat(params.get("LastUpdateTime", encoding='utf8')) except (TypeError, ValueError): last_update = now dt = now - last_update if dt.days > DAYS_NO_CONNECTIVITY_MAX: if current_connectivity_alert != "expired": current_connectivity_alert = "expired" params.delete("Offroad_ConnectivityNeededPrompt") params.put("Offroad_ConnectivityNeeded", json.dumps(OFFROAD_ALERTS["Offroad_ConnectivityNeeded"])) elif dt.days > DAYS_NO_CONNECTIVITY_PROMPT: remaining_time = str(DAYS_NO_CONNECTIVITY_MAX - dt.days) if current_connectivity_alert != "prompt" + remaining_time: current_connectivity_alert = "prompt" + remaining_time alert_connectivity_prompt = copy.copy(OFFROAD_ALERTS["Offroad_ConnectivityNeededPrompt"]) alert_connectivity_prompt["text"] += remaining_time + " days." params.delete("Offroad_ConnectivityNeeded") params.put("Offroad_ConnectivityNeededPrompt", json.dumps(alert_connectivity_prompt)) elif current_connectivity_alert is not None: current_connectivity_alert = None params.delete("Offroad_ConnectivityNeeded") params.delete("Offroad_ConnectivityNeededPrompt") # start constellation of processes when the car starts ignition = health is not None and (health.health.ignitionLine or health.health.ignitionCan) do_uninstall = params.get("DoUninstall") == b"1" accepted_terms = params.get("HasAcceptedTerms") == terms_version completed_training = params.get("CompletedTrainingVersion") == training_version fw_version = params.get("PandaFirmware", encoding="utf8") fw_version_match = fw_version is None or fw_version.startswith(FW_VERSION) # don't show alert is no panda is connected (None) should_start = ignition # have we seen a panda? passive = (params.get("Passive") == "1") # with 2% left, we killall, otherwise the phone will take a long time to boot should_start = should_start and msg.thermal.freeSpace > 0.02 # confirm we have completed training and aren't uninstalling should_start = should_start and accepted_terms and (passive or completed_training) and (not do_uninstall) # check for firmware mismatch should_start = should_start and fw_version_match # check if system time is valid should_start = should_start and time_valid if fw_version_match and not fw_version_match_prev: params.delete("Offroad_PandaFirmwareMismatch") if not fw_version_match and fw_version_match_prev: params.put("Offroad_PandaFirmwareMismatch", json.dumps(OFFROAD_ALERTS["Offroad_PandaFirmwareMismatch"])) # if any CPU gets above 107 or the battery gets above 63, kill all processes # controls will warn with CPU above 95 or battery above 60 if thermal_status >= ThermalStatus.danger: should_start = False if thermal_status_prev < ThermalStatus.danger: params.put("Offroad_TemperatureTooHigh", json.dumps(OFFROAD_ALERTS["Offroad_TemperatureTooHigh"])) else: if thermal_status_prev >= ThermalStatus.danger: params.delete("Offroad_TemperatureTooHigh") if should_start: off_ts = None if started_ts is None: params.car_start() started_ts = sec_since_boot() started_seen = True os.system('echo performance > /sys/class/devfreq/soc:qcom,cpubw/governor') else: started_ts = None if off_ts is None: off_ts = sec_since_boot() os.system('echo powersave > /sys/class/devfreq/soc:qcom,cpubw/governor') # shutdown if the battery gets lower than 3%, it's discharging, we aren't running for # more than a minute but we were running if msg.thermal.batteryPercent < BATT_PERC_OFF and msg.thermal.batteryStatus == "Discharging" and \ started_seen and (sec_since_boot() - off_ts) > 60: os.system('LD_LIBRARY_PATH="" svc power shutdown') charging_disabled = check_car_battery_voltage(should_start, health, charging_disabled, msg, limitBatteryMinMax, batt_min, batt_max) msg.thermal.chargingDisabled = charging_disabled #BB added "and not charging_disabled" below so we don't show red LED when not charging msg.thermal.chargingError = (current_filter.x > 0.) and (msg.thermal.batteryPercent < 90) and not charging_disabled # if current is > 1A out, then charger might be off msg.thermal.started = started_ts is not None msg.thermal.startedTs = int(1e9*(started_ts or 0)) msg.thermal.thermalStatus = thermal_status thermal_sock.send(msg.to_bytes()) if usb_power_prev and not usb_power: params.put("Offroad_ChargeDisabled", json.dumps(OFFROAD_ALERTS["Offroad_ChargeDisabled"])) elif usb_power and not usb_power_prev: params.delete("Offroad_ChargeDisabled") thermal_status_prev = thermal_status usb_power_prev = usb_power fw_version_match_prev = fw_version_match #print(msg) # report to server once per minute if (count % int(60. / DT_TRML)) == 0: cloudlog.event("STATUS_PACKET", count=count, health=(health.to_dict() if health else None), location=(location.to_dict() if location else None), thermal=msg.to_dict()) count += 1
def manager_init() -> None: # update system time from panda set_time(cloudlog) # save boot log #subprocess.call("./bootlog", cwd=os.path.join(BASEDIR, "selfdrive/loggerd")) params = Params() params.clear_all(ParamKeyType.CLEAR_ON_MANAGER_START) default_params: List[Tuple[str, Union[str, bytes]]] = [ ("CompletedTrainingVersion", "0"), ("DisengageOnAccelerator", "0"), ("HasAcceptedTerms", "0"), ("OpenpilotEnabledToggle", "1"), ("IsMetric", "1"), # HKG ("LateralControl", "TORQUE"), ("UseClusterSpeed", "0"), ("LongControlEnabled", "0"), ("MadModeEnabled", "1"), ("IsLdwsCar", "0"), ("LaneChangeEnabled", "0"), ("AutoLaneChangeEnabled", "0"), ("SccSmootherSlowOnCurves", "0"), ("SccSmootherSyncGasPressed", "0"), ("StockNaviDecelEnabled", "0"), ("KeepSteeringTurnSignals", "0"), ("HapticFeedbackWhenSpeedCamera", "0"), ("DisableOpFcw", "0"), ("ShowDebugUI", "0"), ("NewRadarInterface", "0"), ] if not PC: default_params.append(("LastUpdateTime", datetime.datetime.utcnow().isoformat().encode('utf8'))) if params.get_bool("RecordFrontLock"): params.put_bool("RecordFront", True) if not params.get_bool("DisableRadar_Allow"): params.delete("DisableRadar") # set unset params for k, v in default_params: if params.get(k) is None: params.put(k, v) # is this dashcam? if os.getenv("PASSIVE") is not None: params.put_bool("Passive", bool(int(os.getenv("PASSIVE", "0")))) if params.get("Passive") is None: raise Exception("Passive must be set to continue") # Create folders needed for msgq try: os.mkdir("/dev/shm") except FileExistsError: pass except PermissionError: print("WARNING: failed to make /dev/shm") # set version params params.put("Version", get_version()) params.put("TermsVersion", terms_version) params.put("TrainingVersion", training_version) params.put("GitCommit", get_commit(default="")) params.put("GitBranch", get_short_branch(default="")) params.put("GitRemote", get_origin(default="")) # set dongle id reg_res = register(show_spinner=True) if reg_res: dongle_id = reg_res else: serial = params.get("HardwareSerial") raise Exception(f"Registration failed for device {serial}") os.environ['DONGLE_ID'] = dongle_id # Needed for swaglog if not is_dirty(): os.environ['CLEAN'] = '1' # init logging sentry.init(sentry.SentryProject.SELFDRIVE) cloudlog.bind_global(dongle_id=dongle_id, version=get_version(), dirty=is_dirty(), device=HARDWARE.get_device_type())
calibration_data = json.loads(calibration_data) calibration = np.array(calibration_data['calibration'], dtype=np.float32) if 'center_bias' in calibration_data: center_bias = calibration_data['center_bias'] if 'model_bias' in calibration_data: model_bias = calibration_data['model_bias'] if calibration_data is None or len(calibration) != (len(calibration_items[0]) + len(calibration_items[1])): calibration = [ np.zeros(len(calibration_items[0]), dtype=np.float32), np.zeros(len(calibration_items[1]), dtype=np.float32) ] calibrated = False print("resetting calibration") params.delete("CalibrationParams") else: calibration = [ np.array(calibration[:len(calibration_items[0])], dtype=np.float32), np.array(calibration[len(calibration_items[0]):], dtype=np.float32) ] lane_width = calibration_data['lane_width'] angle_bias = calibration_data['angle_bias'] print(calibration) stock_cam_frame_prev = -1 combine_flags = 0 vehicle_array = [] first_model = 0 last_model = len(models) - 1
def manager_init(): # update system time from panda set_time(cloudlog) # save boot log subprocess.call("./bootlog", cwd=os.path.join(BASEDIR, "selfdrive/loggerd")) params = Params() params.clear_all(ParamKeyType.CLEAR_ON_MANAGER_START) default_params = [ ("CompletedTrainingVersion", "0"), ("HasAcceptedTerms", "0"), ("OpenpilotEnabledToggle", "1"), ] if not PC: default_params.append(("LastUpdateTime", datetime.datetime.utcnow().isoformat().encode('utf8'))) if params.get_bool("RecordFrontLock"): params.put_bool("RecordFront", True) if not params.get_bool("DisableRadar_Allow"): params.delete("DisableRadar") # set unset params for k, v in default_params: if params.get(k) is None: params.put(k, v) # is this dashcam? if os.getenv("PASSIVE") is not None: params.put_bool("Passive", bool(int(os.getenv("PASSIVE")))) if params.get("Passive") is None: raise Exception("Passive must be set to continue") # Create folders needed for msgq try: os.mkdir("/dev/shm") except FileExistsError: pass except PermissionError: print("WARNING: failed to make /dev/shm") # set version params params.put("Version", version) params.put("TermsVersion", terms_version) params.put("TrainingVersion", training_version) params.put("GitCommit", get_git_commit(default="")) params.put("GitBranch", get_git_branch(default="")) params.put("GitRemote", get_git_remote(default="")) # set dongle id reg_res = register(show_spinner=True) if reg_res: dongle_id = reg_res else: serial = params.get("HardwareSerial") raise Exception(f"Registration failed for device {serial}") os.environ['DONGLE_ID'] = dongle_id # Needed for swaglog if not dirty: os.environ['CLEAN'] = '1' cloudlog.bind_global(dongle_id=dongle_id, version=version, dirty=dirty, device=HARDWARE.get_device_type()) if comma_remote and not (os.getenv("NOLOG") or os.getenv("NOCRASH") or PC): crash.init() crash.bind_user(id=dongle_id) crash.bind_extra(dirty=dirty, origin=origin, branch=branch, commit=commit, device=HARDWARE.get_device_type())
class TestParams(unittest.TestCase): def setUp(self): self.tmpdir = tempfile.mkdtemp() print("using", self.tmpdir) self.params = Params(self.tmpdir) def tearDown(self): shutil.rmtree(self.tmpdir) def test_params_put_and_get(self): self.params.put("DongleId", "cb38263377b873ee") assert self.params.get("DongleId") == b"cb38263377b873ee" def test_params_non_ascii(self): st = b"\xe1\x90\xff" self.params.put("CarParams", st) assert self.params.get("CarParams") == st def test_params_get_cleared_manager_start(self): self.params.put("CarParams", "test") self.params.put("DongleId", "cb38263377b873ee") assert self.params.get("CarParams") == b"test" self.params.clear_all(ParamKeyType.CLEAR_ON_MANAGER_START) assert self.params.get("CarParams") is None assert self.params.get("DongleId") is not None def test_params_two_things(self): self.params.put("DongleId", "bob") self.params.put("AthenadPid", "123") assert self.params.get("DongleId") == b"bob" assert self.params.get("AthenadPid") == b"123" def test_params_get_block(self): def _delayed_writer(): time.sleep(0.1) self.params.put("CarParams", "test") threading.Thread(target=_delayed_writer).start() assert self.params.get("CarParams") is None assert self.params.get("CarParams", True) == b"test" def test_params_unknown_key_fails(self): with self.assertRaises(UnknownKeyName): self.params.get("swag") with self.assertRaises(UnknownKeyName): self.params.get_bool("swag") with self.assertRaises(UnknownKeyName): self.params.put("swag", "abc") with self.assertRaises(UnknownKeyName): self.params.put_bool("swag", True) def test_delete_not_there(self): assert self.params.get("CarParams") is None self.params.delete("CarParams") assert self.params.get("CarParams") is None def test_get_bool(self): self.params.delete("IsMetric") self.assertFalse(self.params.get_bool("IsMetric")) self.params.put_bool("IsMetric", True) self.assertTrue(self.params.get_bool("IsMetric")) self.params.put_bool("IsMetric", False) self.assertFalse(self.params.get_bool("IsMetric")) self.params.put("IsMetric", "1") self.assertTrue(self.params.get_bool("IsMetric")) self.params.put("IsMetric", "0") self.assertFalse(self.params.get_bool("IsMetric")) def test_put_non_blocking_with_get_block(self): q = Params(self.tmpdir) def _delayed_writer(): time.sleep(0.1) put_nonblocking("CarParams", "test", self.tmpdir) threading.Thread(target=_delayed_writer).start() assert q.get("CarParams") is None assert q.get("CarParams", True) == b"test" def test_put_bool_non_blocking_with_get_block(self): q = Params(self.tmpdir) def _delayed_writer(): time.sleep(0.1) put_bool_nonblocking("CarParams", True, self.tmpdir) threading.Thread(target=_delayed_writer).start() assert q.get("CarParams") is None assert q.get("CarParams", True) == b"1"
can_function(pm, speed, steer_angle, rk.frame, rk.frame % 500 == 499) if rk.frame % 5 == 0: throttle, brake, steer = sendcan_function(sendcan) steer_angle += steer / 10000.0 # torque vc = carla.VehicleControl(throttle=throttle, steer=steer_angle, brake=brake) vehicle.apply_control(vc) print(speed, steer_angle, vc) rk.keep_time() if __name__ == "__main__": params = Params() params.delete("Offroad_ConnectivityNeeded") from selfdrive.version import terms_version, training_version params.put("HasAcceptedTerms", terms_version) params.put("CompletedTrainingVersion", training_version) params.put("CommunityFeaturesToggle", "1") threading.Thread(target=health_function).start() threading.Thread(target=fake_driver_monitoring).start() # no carla, still run try: import carla except ImportError: print("WARNING: NO CARLA") while 1: time.sleep(1)
def thermald_thread(): health_timeout = int(1000 * 2.5 * DT_TRML) # 2.5x the expected health frequency # now loop thermal_sock = messaging.pub_sock('thermal') health_sock = messaging.sub_sock('health', timeout=health_timeout) location_sock = messaging.sub_sock('gpsLocation') ignition = False fan_speed = 0 count = 0 off_ts = None started_ts = None started_seen = False thermal_status = ThermalStatus.green thermal_status_prev = ThermalStatus.green usb_power = True usb_power_prev = True current_branch = get_git_branch() network_type = NetworkType.none network_strength = NetworkStrength.unknown current_filter = FirstOrderFilter(0., CURRENT_TAU, DT_TRML) cpu_temp_filter = FirstOrderFilter(0., CPU_TEMP_TAU, DT_TRML) health_prev = None fw_version_match_prev = True charging_disabled = False current_update_alert = None time_valid_prev = True should_start_prev = False handle_fan = None is_uno = False has_relay = False params = Params() pm = PowerMonitoring() no_panda_cnt = 0 thermal_config = get_thermal_config() IsOpenpilotViewEnabled = 0 ts_last_ip = 0 ip_addr = '255.255.255.255' # sound trigger sound_trigger = 1 opkrAutoShutdown = 0 env = dict(os.environ) env['LD_LIBRARY_PATH'] = mediaplayer getoff_alert = Params().get('OpkrEnableGetoffAlert') == b'1' if int(params.get('OpkrAutoShutdown')) == 0: opkrAutoShutdown = 0 elif int(params.get('OpkrAutoShutdown')) == 1: opkrAutoShutdown = 5 elif int(params.get('OpkrAutoShutdown')) == 2: opkrAutoShutdown = 30 elif int(params.get('OpkrAutoShutdown')) == 3: opkrAutoShutdown = 60 elif int(params.get('OpkrAutoShutdown')) == 4: opkrAutoShutdown = 180 elif int(params.get('OpkrAutoShutdown')) == 5: opkrAutoShutdown = 300 elif int(params.get('OpkrAutoShutdown')) == 6: opkrAutoShutdown = 600 elif int(params.get('OpkrAutoShutdown')) == 7: opkrAutoShutdown = 1800 elif int(params.get('OpkrAutoShutdown')) == 8: opkrAutoShutdown = 3600 elif int(params.get('OpkrAutoShutdown')) == 9: opkrAutoShutdown = 10800 else: opkrAutoShutdown = 18000 while 1: ts = sec_since_boot() health = messaging.recv_sock(health_sock, wait=True) location = messaging.recv_sock(location_sock) location = location.gpsLocation if location else None msg = read_thermal(thermal_config) if health is not None: usb_power = health.health.usbPowerMode != log.HealthData.UsbPowerMode.client # If we lose connection to the panda, wait 5 seconds before going offroad if health.health.hwType == log.HealthData.HwType.unknown: no_panda_cnt += 1 if no_panda_cnt > DISCONNECT_TIMEOUT / DT_TRML: if ignition: cloudlog.error("Lost panda connection while onroad") ignition = False else: no_panda_cnt = 0 ignition = health.health.ignitionLine or health.health.ignitionCan # Setup fan handler on first connect to panda if handle_fan is None and health.health.hwType != log.HealthData.HwType.unknown: is_uno = health.health.hwType == log.HealthData.HwType.uno has_relay = health.health.hwType in [ log.HealthData.HwType.blackPanda, log.HealthData.HwType.uno, log.HealthData.HwType.dos ] if (not EON) or is_uno: cloudlog.info("Setting up UNO fan handler") handle_fan = handle_fan_uno else: cloudlog.info("Setting up EON fan handler") setup_eon_fan() handle_fan = handle_fan_eon # Handle disconnect if health_prev is not None: if health.health.hwType == log.HealthData.HwType.unknown and \ health_prev.health.hwType != log.HealthData.HwType.unknown: params.panda_disconnect() health_prev = health elif ignition == False or IsOpenpilotViewEnabled: IsOpenpilotViewEnabled = int(params.get("IsOpenpilotViewEnabled")) ignition = IsOpenpilotViewEnabled # get_network_type is an expensive call. update every 10s if (count % int(10. / DT_TRML)) == 0: try: network_type = HARDWARE.get_network_type() network_strength = HARDWARE.get_network_strength(network_type) except Exception: cloudlog.exception("Error getting network status") msg.thermal.freeSpace = get_available_percent(default=100.0) / 100.0 msg.thermal.memUsedPercent = int(round( psutil.virtual_memory().percent)) msg.thermal.cpuPerc = int(round(psutil.cpu_percent())) msg.thermal.networkType = network_type msg.thermal.networkStrength = network_strength msg.thermal.batteryPercent = get_battery_capacity() msg.thermal.batteryStatus = get_battery_status() msg.thermal.batteryCurrent = get_battery_current() msg.thermal.batteryVoltage = get_battery_voltage() msg.thermal.usbOnline = get_usb_present() # Fake battery levels on uno for frame if (not EON) or is_uno: msg.thermal.batteryPercent = 100 msg.thermal.batteryStatus = "Charging" msg.thermal.bat = 0 # update ip every 10 seconds ts = sec_since_boot() if ts - ts_last_ip >= 10.: try: result = subprocess.check_output(["ifconfig", "wlan0"], encoding='utf8') # pylint: disable=unexpected-keyword-arg ip_addr = re.findall(r"inet addr:((\d+\.){3}\d+)", result)[0][0] except: ip_addr = 'N/A' ts_last_ip = ts msg.thermal.ipAddr = ip_addr current_filter.update(msg.thermal.batteryCurrent / 1e6) # TODO: add car battery voltage check max_cpu_temp = cpu_temp_filter.update(max(msg.thermal.cpu)) max_comp_temp = max(max_cpu_temp, msg.thermal.mem, max(msg.thermal.gpu)) bat_temp = msg.thermal.bat if handle_fan is not None: fan_speed = handle_fan(max_cpu_temp, bat_temp, fan_speed, ignition) msg.thermal.fanSpeed = fan_speed # If device is offroad we want to cool down before going onroad # since going onroad increases load and can make temps go over 107 # We only do this if there is a relay that prevents the car from faulting is_offroad_for_5_min = (started_ts is None) and ( (not started_seen) or (off_ts is None) or (sec_since_boot() - off_ts > 60 * 5)) if max_cpu_temp > 107. or bat_temp >= 63. or (has_relay and is_offroad_for_5_min and max_cpu_temp > 70.0): # onroad not allowed thermal_status = ThermalStatus.danger elif max_comp_temp > 96.0 or bat_temp > 60.: # hysteresis between onroad not allowed and engage not allowed thermal_status = clip(thermal_status, ThermalStatus.red, ThermalStatus.danger) elif max_cpu_temp > 94.0: # hysteresis between engage not allowed and uploader not allowed thermal_status = clip(thermal_status, ThermalStatus.yellow, ThermalStatus.red) elif max_cpu_temp > 80.0: # uploader not allowed thermal_status = ThermalStatus.yellow elif max_cpu_temp > 75.0: # hysteresis between uploader not allowed and all good thermal_status = clip(thermal_status, ThermalStatus.green, ThermalStatus.yellow) else: # all good thermal_status = ThermalStatus.green # **** starting logic **** # Check for last update time and display alerts if needed now = datetime.datetime.utcnow() # show invalid date/time alert time_valid = now.year >= 2019 if time_valid and not time_valid_prev: set_offroad_alert("Offroad_InvalidTime", False) if not time_valid and time_valid_prev: set_offroad_alert("Offroad_InvalidTime", True) time_valid_prev = time_valid # Show update prompt # try: # last_update = datetime.datetime.fromisoformat(params.get("LastUpdateTime", encoding='utf8')) # except (TypeError, ValueError): # last_update = now # dt = now - last_update # update_failed_count = params.get("UpdateFailedCount") # update_failed_count = 0 if update_failed_count is None else int(update_failed_count) # last_update_exception = params.get("LastUpdateException", encoding='utf8') # if update_failed_count > 15 and last_update_exception is not None: # if current_branch in ["release2", "dashcam"]: # extra_text = "Ensure the software is correctly installed" # else: # extra_text = last_update_exception # if current_update_alert != "update" + extra_text: # current_update_alert = "update" + extra_text # set_offroad_alert("Offroad_ConnectivityNeeded", False) # set_offroad_alert("Offroad_ConnectivityNeededPrompt", False) # set_offroad_alert("Offroad_UpdateFailed", True, extra_text=extra_text) # elif dt.days > DAYS_NO_CONNECTIVITY_MAX and update_failed_count > 1: # if current_update_alert != "expired": # current_update_alert = "expired" # set_offroad_alert("Offroad_UpdateFailed", False) # set_offroad_alert("Offroad_ConnectivityNeededPrompt", False) # set_offroad_alert("Offroad_ConnectivityNeeded", True) # elif dt.days > DAYS_NO_CONNECTIVITY_PROMPT: # remaining_time = str(max(DAYS_NO_CONNECTIVITY_MAX - dt.days, 0)) # if current_update_alert != "prompt" + remaining_time: # current_update_alert = "prompt" + remaining_time # set_offroad_alert("Offroad_UpdateFailed", False) # set_offroad_alert("Offroad_ConnectivityNeeded", False) # set_offroad_alert("Offroad_ConnectivityNeededPrompt", True, extra_text=f"{remaining_time} days.") # elif current_update_alert is not None: # current_update_alert = None # set_offroad_alert("Offroad_UpdateFailed", False) # set_offroad_alert("Offroad_ConnectivityNeeded", False) # set_offroad_alert("Offroad_ConnectivityNeededPrompt", False) do_uninstall = params.get("DoUninstall") == b"1" accepted_terms = params.get("HasAcceptedTerms") == terms_version completed_training = params.get( "CompletedTrainingVersion") == training_version panda_signature = params.get("PandaFirmware") fw_version_match = (panda_signature is None) or ( panda_signature == FW_SIGNATURE ) # don't show alert is no panda is connected (None) should_start = ignition # with 2% left, we killall, otherwise the phone will take a long time to boot should_start = should_start and msg.thermal.freeSpace > 0.02 # confirm we have completed training and aren't uninstalling should_start = should_start and accepted_terms and (not do_uninstall) and \ (completed_training or current_branch in ['dashcam', 'dashcam-staging']) # check for firmware mismatch should_start = should_start and fw_version_match # check if system time is valid should_start = should_start and time_valid # don't start while taking snapshot if not should_start_prev: is_viewing_driver = params.get("IsDriverViewEnabled") == b"1" is_taking_snapshot = params.get("IsTakingSnapshot") == b"1" should_start = should_start and (not is_taking_snapshot) and ( not is_viewing_driver) if fw_version_match and not fw_version_match_prev: set_offroad_alert("Offroad_PandaFirmwareMismatch", False) if not fw_version_match and fw_version_match_prev: set_offroad_alert("Offroad_PandaFirmwareMismatch", True) # if any CPU gets above 107 or the battery gets above 63, kill all processes # controls will warn with CPU above 95 or battery above 60 if thermal_status >= ThermalStatus.danger: should_start = False if thermal_status_prev < ThermalStatus.danger: set_offroad_alert("Offroad_TemperatureTooHigh", True) else: if thermal_status_prev >= ThermalStatus.danger: set_offroad_alert("Offroad_TemperatureTooHigh", False) if should_start: if not should_start_prev: params.delete("IsOffroad") off_ts = None if started_ts is None: started_ts = sec_since_boot() started_seen = True os.system( 'echo performance > /sys/class/devfreq/soc:qcom,cpubw/governor' ) else: if should_start_prev or (count == 0): put_nonblocking("IsOffroad", "1") started_ts = None if off_ts is None: off_ts = sec_since_boot() os.system( 'echo powersave > /sys/class/devfreq/soc:qcom,cpubw/governor' ) if sound_trigger == 1 and msg.thermal.batteryStatus == "Discharging" and started_seen and ( sec_since_boot() - off_ts) > 1 and getoff_alert: subprocess.Popen([ mediaplayer + 'mediaplayer', '/data/openpilot/selfdrive/assets/sounds/eondetach.wav' ], shell=False, stdin=None, stdout=None, stderr=None, env=env, close_fds=True) sound_trigger = 0 # shutdown if the battery gets lower than 3%, it's discharging, we aren't running for # more than a minute but we were running if msg.thermal.batteryStatus == "Discharging" and \ started_seen and opkrAutoShutdown and (sec_since_boot() - off_ts) > opkrAutoShutdown: os.system('LD_LIBRARY_PATH="" svc power shutdown') charging_disabled = check_car_battery_voltage(should_start, health, charging_disabled, msg) if msg.thermal.batteryCurrent > 0: msg.thermal.batteryStatus = "Discharging" else: msg.thermal.batteryStatus = "Charging" msg.thermal.chargingDisabled = charging_disabled prebuiltlet = Params().get('PutPrebuiltOn') == b'1' if not os.path.isfile(prebuiltfile) and prebuiltlet: os.system("cd /data/openpilot; touch prebuilt") elif os.path.isfile(prebuiltfile) and not prebuiltlet: os.system("cd /data/openpilot; rm -f prebuilt") # Offroad power monitoring pm.calculate(health) msg.thermal.offroadPowerUsage = pm.get_power_used() msg.thermal.carBatteryCapacity = max(0, pm.get_car_battery_capacity()) # # Check if we need to disable charging (handled by boardd) # msg.thermal.chargingDisabled = pm.should_disable_charging(health, off_ts) # # Check if we need to shut down # if pm.should_shutdown(health, off_ts, started_seen, LEON): # cloudlog.info(f"shutting device down, offroad since {off_ts}") # # TODO: add function for blocking cloudlog instead of sleep # time.sleep(10) # os.system('LD_LIBRARY_PATH="" svc power shutdown') msg.thermal.chargingError = current_filter.x > 0. and msg.thermal.batteryPercent < 90 # if current is positive, then battery is being discharged msg.thermal.started = started_ts is not None msg.thermal.startedTs = int(1e9 * (started_ts or 0)) msg.thermal.thermalStatus = thermal_status thermal_sock.send(msg.to_bytes()) if usb_power_prev and not usb_power: set_offroad_alert("Offroad_ChargeDisabled", True) elif usb_power and not usb_power_prev: set_offroad_alert("Offroad_ChargeDisabled", False) thermal_status_prev = thermal_status usb_power_prev = usb_power fw_version_match_prev = fw_version_match should_start_prev = should_start # report to server once per minute if (count % int(60. / DT_TRML)) == 0: cloudlog.event("STATUS_PACKET", count=count, health=(health.to_dict() if health else None), location=(location.to_dict() if location else None), thermal=msg.to_dict()) count += 1
def main(): params = Params() dongle_id = params.get("DongleId", encoding='utf-8') ws_uri = ATHENA_HOST + "/ws/v2/" + dongle_id api = Api(dongle_id) conn_retries = 0 while 1: try: cloudlog.event("athenad.main.connecting_ws", ws_uri=ws_uri) ws = create_connection(ws_uri, cookie="jwt=" + api.get_token(), enable_multithread=True, timeout=30.0) cloudlog.event("athenad.main.connected_ws", ws_uri=ws_uri) params.delete("PrimeRedirected") manage_tokens(api) conn_retries = 0 handle_long_poll(ws) except (KeyboardInterrupt, SystemExit): break except (ConnectionError, TimeoutError, WebSocketException): conn_retries += 1 params.delete("PrimeRedirected") params.delete("LastAthenaPingTime") except socket.timeout: try: r = requests.get( "http://api.commadotai.com/v1/me", allow_redirects=False, headers={"User-Agent": f"openpilot-{version}"}, timeout=15.0) if r.status_code == 302 and r.headers['Location'].startswith( "http://u.web2go.com"): params.put_bool("PrimeRedirected", True) except Exception: cloudlog.exception("athenad.socket_timeout.exception") params.delete("LastAthenaPingTime") except Exception: cloudlog.exception("athenad.main.exception") conn_retries += 1 params.delete("PrimeRedirected") params.delete("LastAthenaPingTime") time.sleep(backoff(conn_retries))
def fingerprint(logcan, sendcan, has_relay): params = Params() car_params = params.get("CarParams") if not travis: cached_fingerprint = params.get('CachedFingerprint') else: cached_fingerprint = None if car_params is not None: car_params = car.CarParams.from_bytes(car_params) fixed_fingerprint = os.environ.get('FINGERPRINT', "") skip_fw_query = os.environ.get('SKIP_FW_QUERY', False) if has_relay and not fixed_fingerprint and not skip_fw_query: # Vin query only reliably works thorugh OBDII bus = 1 cached_params = Params().get("CarParamsCache") if cached_params is not None: cached_params = car.CarParams.from_bytes(cached_params) if cached_params.carName == "mock": cached_params = None if cached_params is not None and len(cached_params.carFw) > 0 and cached_params.carVin is not VIN_UNKNOWN: cloudlog.warning("Using cached CarParams") vin = cached_params.carVin car_fw = list(cached_params.carFw) else: cloudlog.warning("Getting VIN & FW versions") _, vin = get_vin(logcan, sendcan, bus) car_fw = get_fw_versions(logcan, sendcan, bus) fw_candidates = match_fw_to_car(car_fw) else: vin = VIN_UNKNOWN fw_candidates, car_fw = set(), [] cloudlog.warning("VIN %s", vin) Params().put("CarVin", vin) finger = gen_empty_fingerprint() candidate_cars = {i: all_known_cars() for i in [0, 1]} # attempt fingerprint on both bus 0 and 1 frame = 0 frame_fingerprint = 10 # 0.1s car_fingerprint = None done = False if cached_fingerprint is not None and use_car_caching: # if we previously identified a car and fingerprint and user hasn't disabled caching cached_fingerprint = json.loads(cached_fingerprint) if cached_fingerprint[0] is None or len(cached_fingerprint) < 3: params.delete('CachedFingerprint') else: finger[0] = {int(key): value for key, value in cached_fingerprint[2].items()} source = car.CarParams.FingerprintSource.can return (str(cached_fingerprint[0]), finger, vin, car_fw, cached_fingerprint[1]) while not done: a = messaging.get_one_can(logcan) for can in a.can: # need to independently try to fingerprint both bus 0 and 1 to work # for the combo black_panda and honda_bosch. Ignore extended messages # and VIN query response. # Include bus 2 for toyotas to disambiguate cars using camera messages # (ideally should be done for all cars but we can't for Honda Bosch) if can.src in range(0, 4): finger[can.src][can.address] = len(can.dat) for b in candidate_cars: if (can.src == b or (only_toyota_left(candidate_cars[b]) and can.src == 2)) and \ can.address < 0x800 and can.address not in [0x7df, 0x7e0, 0x7e8]: candidate_cars[b] = eliminate_incompatible_cars(can, candidate_cars[b]) # if we only have one car choice and the time since we got our first # message has elapsed, exit for b in candidate_cars: # Toyota needs higher time to fingerprint, since DSU does not broadcast immediately if only_toyota_left(candidate_cars[b]): frame_fingerprint = 100 # 1s if len(candidate_cars[b]) == 1: if frame > frame_fingerprint: # fingerprint done car_fingerprint = candidate_cars[b][0] elif len(candidate_cars[b]) < 4: # For the RAV4 2019 and Corolla 2020 LE Fingerprint problem if frame > 180: if any(("TOYOTA COROLLA TSS2 2019" in c) for c in candidate_cars[b]): car_fingerprint = "TOYOTA COROLLA TSS2 2019" if any(("TOYOTA COROLLA HYBRID TSS2 2019" in c) for c in candidate_cars[b]): car_fingerprint = "TOYOTA COROLLA HYBRID TSS2 2019" if any(("HYUNDAI IONIQ ELECTRIC LIMITED 2019" in c) for c in candidate_cars[b]): car_fingerprint = "HYUNDAI IONIQ ELECTRIC LIMITED 2019" # bail if no cars left or we've been waiting for more than 2s failed = all(len(cc) == 0 for cc in candidate_cars.values()) or frame > 200 succeeded = car_fingerprint is not None done = failed or succeeded frame += 1 source = car.CarParams.FingerprintSource.can # If FW query returns exactly 1 candidate, use it if len(fw_candidates) == 1: car_fingerprint = list(fw_candidates)[0] source = car.CarParams.FingerprintSource.fw if fixed_fingerprint: car_fingerprint = fixed_fingerprint source = car.CarParams.FingerprintSource.fixed cloudlog.warning("fingerprinted %s", car_fingerprint) params.put("CachedFingerprint", json.dumps([car_fingerprint, source, {int(key): value for key, value in finger[0].items()}])) return car_fingerprint, finger, vin, car_fw, source