Ejemplo n.º 1
0
def thermald_thread():
    # prevent LEECO from undervoltage
    BATT_PERC_OFF = int(kegman.conf['battPercOff'])

    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
    charging_disabled = False
    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

        # update network_type every 5s, decrease cpu usage
        if (count % int(5. / DT_TRML)) == 0:
            try:
                network_type = get_network_type()
            except subprocess.CalledProcessError:
                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

        #    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

        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')

        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
        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
Ejemplo n.º 2
0
class LatControlINDI(LatControl):
    def __init__(self, CP, CI):
        super().__init__(CP, CI)
        self.angle_steers_des = 0.

        A = np.array([[1.0, DT_CTRL, 0.0], [0.0, 1.0, DT_CTRL],
                      [0.0, 0.0, 1.0]])
        C = np.array([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]])

        # Q = np.matrix([[1e-2, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 10.0]])
        # R = np.matrix([[1e-2, 0.0], [0.0, 1e3]])

        # (x, l, K) = control.dare(np.transpose(A), np.transpose(C), Q, R)
        # K = np.transpose(K)
        K = np.array([[7.30262179e-01, 2.07003658e-04],
                      [7.29394177e+00, 1.39159419e-02],
                      [1.71022442e+01, 3.38495381e-02]])

        self.speed = 0.

        self.K = K
        self.A_K = A - np.dot(K, C)
        self.x = np.array([[0.], [0.], [0.]])

        self._RC = (CP.lateralTuning.indi.timeConstantBP,
                    CP.lateralTuning.indi.timeConstantV)
        self._G = (CP.lateralTuning.indi.actuatorEffectivenessBP,
                   CP.lateralTuning.indi.actuatorEffectivenessV)
        self._outer_loop_gain = (CP.lateralTuning.indi.outerLoopGainBP,
                                 CP.lateralTuning.indi.outerLoopGainV)
        self._inner_loop_gain = (CP.lateralTuning.indi.innerLoopGainBP,
                                 CP.lateralTuning.indi.innerLoopGainV)

        self.steer_filter = FirstOrderFilter(0., self.RC, DT_CTRL)
        self.reset()

    @property
    def RC(self):
        return interp(self.speed, self._RC[0], self._RC[1])

    @property
    def G(self):
        return interp(self.speed, self._G[0], self._G[1])

    @property
    def outer_loop_gain(self):
        return interp(self.speed, self._outer_loop_gain[0],
                      self._outer_loop_gain[1])

    @property
    def inner_loop_gain(self):
        return interp(self.speed, self._inner_loop_gain[0],
                      self._inner_loop_gain[1])

    def reset(self):
        super().reset()
        self.steer_filter.x = 0.
        self.speed = 0.

    def update(self, active, CS, VM, params, last_actuators, steer_limited,
               desired_curvature, desired_curvature_rate, llk):
        self.speed = CS.vEgo
        # Update Kalman filter
        y = np.array([[math.radians(CS.steeringAngleDeg)],
                      [math.radians(CS.steeringRateDeg)]])
        self.x = np.dot(self.A_K, self.x) + np.dot(self.K, y)

        indi_log = log.ControlsState.LateralINDIState.new_message()
        indi_log.steeringAngleDeg = math.degrees(self.x[0])
        indi_log.steeringRateDeg = math.degrees(self.x[1])
        indi_log.steeringAccelDeg = math.degrees(self.x[2])

        steers_des = VM.get_steer_from_curvature(-desired_curvature, CS.vEgo,
                                                 params.roll)
        steers_des += math.radians(params.angleOffsetDeg)
        indi_log.steeringAngleDesiredDeg = math.degrees(steers_des)

        # desired rate is the desired rate of change in the setpoint, not the absolute desired curvature
        rate_des = VM.get_steer_from_curvature(-desired_curvature_rate,
                                               CS.vEgo, 0)
        indi_log.steeringRateDesiredDeg = math.degrees(rate_des)

        if CS.vEgo < MIN_STEER_SPEED or not active:
            indi_log.active = False
            self.steer_filter.x = 0.0
            output_steer = 0
        else:
            # Expected actuator value
            self.steer_filter.update_alpha(self.RC)
            self.steer_filter.update(last_actuators.steer)

            # Compute acceleration error
            rate_sp = self.outer_loop_gain * (steers_des -
                                              self.x[0]) + rate_des
            accel_sp = self.inner_loop_gain * (rate_sp - self.x[1])
            accel_error = accel_sp - self.x[2]

            # Compute change in actuator
            g_inv = 1. / self.G
            delta_u = g_inv * accel_error

            # If steering pressed, only allow wind down
            if CS.steeringPressed and (delta_u * last_actuators.steer > 0):
                delta_u = 0

            output_steer = self.steer_filter.x + delta_u

            output_steer = clip(output_steer, -self.steer_max, self.steer_max)

            indi_log.active = True
            indi_log.rateSetPoint = float(rate_sp)
            indi_log.accelSetPoint = float(accel_sp)
            indi_log.accelError = float(accel_error)
            indi_log.delayedOutput = float(self.steer_filter.x)
            indi_log.delta = float(delta_u)
            indi_log.output = float(output_steer)
            indi_log.saturated = self._check_saturation(
                self.steer_max - abs(output_steer) < 1e-3, CS, steer_limited)

        return float(output_steer), float(steers_des), indi_log
Ejemplo n.º 3
0
class DriverStatus():
    def __init__(self):
        self.pose = DriverPose()
        self.pose_calibrated = self.pose.pitch_offseter.filtered_stat.n > _POSE_OFFSET_MIN_COUNT and \
                                self.pose.yaw_offseter.filtered_stat.n > _POSE_OFFSET_MIN_COUNT
        self.blink = DriverBlink()
        self.awareness = 1.
        self.awareness_active = 1.
        self.awareness_passive = 1.
        self.driver_distracted = False
        self.driver_distraction_filter = FirstOrderFilter(
            0., _DISTRACTED_FILTER_TS, DT_DMON)
        self.face_detected = False
        self.terminal_alert_cnt = 0
        self.terminal_time = 0
        self.step_change = 0.
        self.active_monitoring_mode = True
        self.hi_stds = 0
        self.hi_std_alert_enabled = True
        self.threshold_prompt = _DISTRACTED_PROMPT_TIME_TILL_TERMINAL / _DISTRACTED_TIME

        self.is_rhd_region = False
        self.is_rhd_region_checked = False

        self._set_timers(active_monitoring=True)

    def _set_timers(self, active_monitoring):
        if self.active_monitoring_mode and self.awareness <= self.threshold_prompt:
            if active_monitoring:
                self.step_change = DT_DMON / _DISTRACTED_TIME
            else:
                self.step_change = 0.
            return  # no exploit after orange alert
        elif self.awareness <= 0.:
            return

        if active_monitoring:
            # when falling back from passive mode to active mode, reset awareness to avoid false alert
            if not self.active_monitoring_mode:
                self.awareness_passive = self.awareness
                self.awareness = self.awareness_active

            self.threshold_pre = _DISTRACTED_PRE_TIME_TILL_TERMINAL / _DISTRACTED_TIME
            self.threshold_prompt = _DISTRACTED_PROMPT_TIME_TILL_TERMINAL / _DISTRACTED_TIME
            self.step_change = DT_DMON / _DISTRACTED_TIME
            self.active_monitoring_mode = True
        else:
            if self.active_monitoring_mode:
                self.awareness_active = self.awareness
                self.awareness = self.awareness_passive

            self.threshold_pre = _AWARENESS_PRE_TIME_TILL_TERMINAL / _AWARENESS_TIME
            self.threshold_prompt = _AWARENESS_PROMPT_TIME_TILL_TERMINAL / _AWARENESS_TIME
            self.step_change = DT_DMON / _AWARENESS_TIME
            self.active_monitoring_mode = False

    def _is_driver_distracted(self, pose, blink):
        if not self.pose_calibrated:
            pitch_error = pose.pitch - _PITCH_NATURAL_OFFSET
            yaw_error = pose.yaw - _YAW_NATURAL_OFFSET
        else:
            pitch_error = pose.pitch - self.pose.pitch_offseter.filtered_stat.mean(
            )
            yaw_error = pose.yaw - self.pose.yaw_offseter.filtered_stat.mean()

        # positive pitch allowance
        if pitch_error > 0.:
            pitch_error = max(pitch_error - _PITCH_POS_ALLOWANCE, 0.)
        pitch_error *= _PITCH_WEIGHT
        pose_metric = sqrt(yaw_error**2 + pitch_error**2)

        if pose_metric > _METRIC_THRESHOLD * pose.cfactor:
            return DistractedType.BAD_POSE
        elif (blink.left_blink +
              blink.right_blink) * 0.5 > _BLINK_THRESHOLD * blink.cfactor:
            return DistractedType.BAD_BLINK
        else:
            return DistractedType.NOT_DISTRACTED

    def set_policy(self, model_data):
        ep = min(model_data.meta.engagedProb, 0.8) / 0.8
        self.pose.cfactor = interp(ep, [0, 0.5, 1], [
            _METRIC_THRESHOLD_STRICT, _METRIC_THRESHOLD,
            _METRIC_THRESHOLD_SLACK
        ]) / _METRIC_THRESHOLD
        self.blink.cfactor = interp(ep, [0, 0.5, 1], [
            _BLINK_THRESHOLD_STRICT, _BLINK_THRESHOLD, _BLINK_THRESHOLD_SLACK
        ]) / _BLINK_THRESHOLD

    def get_pose(self, driver_state, cal_rpy, car_speed, op_engaged):
        # 10 Hz
        if len(driver_state.faceOrientation) == 0 or len(
                driver_state.facePosition) == 0 or len(
                    driver_state.faceOrientationStd) == 0 or len(
                        driver_state.facePositionStd) == 0:
            return

        self.pose.roll, self.pose.pitch, self.pose.yaw = face_orientation_from_net(
            driver_state.faceOrientation, driver_state.facePosition, cal_rpy,
            self.is_rhd_region)
        self.pose.pitch_std = driver_state.faceOrientationStd[0]
        self.pose.yaw_std = driver_state.faceOrientationStd[1]
        # self.pose.roll_std = driver_state.faceOrientationStd[2]
        model_std_max = max(self.pose.pitch_std, self.pose.yaw_std)
        self.pose.low_std = model_std_max < _POSESTD_THRESHOLD
        self.blink.left_blink = driver_state.leftBlinkProb * (
            driver_state.leftEyeProb > _EYE_THRESHOLD) * (driver_state.sgProb <
                                                          _SG_THRESHOLD)
        self.blink.right_blink = driver_state.rightBlinkProb * (
            driver_state.rightEyeProb > _EYE_THRESHOLD) * (driver_state.sgProb
                                                           < _SG_THRESHOLD)
        self.face_detected = driver_state.faceProb > _FACE_THRESHOLD and \
                              abs(driver_state.facePosition[0]) <= 0.4 and abs(driver_state.facePosition[1]) <= 0.45

        self.driver_distracted = self._is_driver_distracted(
            self.pose, self.blink) > 0
        # first order filters
        self.driver_distraction_filter.update(self.driver_distracted)

        # update offseter
        # only update when driver is actively driving the car above a certain speed
        if self.face_detected and car_speed > _POSE_CALIB_MIN_SPEED and self.pose.low_std and (
                not op_engaged or not self.driver_distracted):
            self.pose.pitch_offseter.push_and_update(self.pose.pitch)
            self.pose.yaw_offseter.push_and_update(self.pose.yaw)

        self.pose_calibrated = self.pose.pitch_offseter.filtered_stat.n > _POSE_OFFSET_MIN_COUNT and \
                                self.pose.yaw_offseter.filtered_stat.n > _POSE_OFFSET_MIN_COUNT

        is_model_uncertain = self.hi_stds * DT_DMON > _HI_STD_FALLBACK_TIME
        self._set_timers(self.face_detected and not is_model_uncertain)
        if self.face_detected and not self.pose.low_std:
            if not is_model_uncertain:
                #self.step_change *= max(0, (model_std_max-0.5)*(model_std_max-2))
                self.step_change *= min(
                    1.0,
                    max(0.6,
                        1.6 * (model_std_max - 0.5) * (model_std_max - 2)))
            self.hi_stds += 1
        elif self.face_detected and self.pose.low_std:
            self.hi_stds = 0

    def update(self, events, driver_engaged, ctrl_active, standstill):
        if (driver_engaged and self.awareness > 0) or not ctrl_active:
            # reset only when on disengagement if red reached
            self.awareness = 1.
            self.awareness_active = 1.
            self.awareness_passive = 1.
            return

        driver_attentive = self.driver_distraction_filter.x < 0.37
        awareness_prev = self.awareness

        #if self.face_detected and self.hi_stds * DT_DMON > _HI_STD_TIMEOUT:
        if self.face_detected and self.hi_stds * DT_DMON > _HI_STD_TIMEOUT and self.hi_std_alert_enabled:
            events.add(EventName.driverMonitorLowAcc)
            self.hi_std_alert_enabled = False  # only showed once until orange prompt resets it

        if (driver_attentive and self.face_detected and self.pose.low_std
                and self.awareness > 0):
            # only restore awareness when paying attention and alert is not red
            self.awareness = min(
                self.awareness +
                ((_RECOVERY_FACTOR_MAX - _RECOVERY_FACTOR_MIN) *
                 (1. - self.awareness) + _RECOVERY_FACTOR_MIN) *
                self.step_change, 1.)
            if self.awareness == 1.:
                self.awareness_passive = min(
                    self.awareness_passive + self.step_change, 1.)
            # don't display alert banner when awareness is recovering and has cleared orange
            if self.awareness > self.threshold_prompt:
                return

        # should always be counting if distracted unless at standstill and reaching orange
        if (not (self.face_detected and self.hi_stds * DT_DMON <= _HI_STD_FALLBACK_TIME) or (self.driver_distraction_filter.x > 0.63 and self.driver_distracted and self.face_detected)) and \
           not (standstill and self.awareness - self.step_change <= self.threshold_prompt):
            self.awareness = max(self.awareness - self.step_change, -0.1)

        alert = None
        if self.awareness <= 0.:
            # terminal red alert: disengagement required
            alert = EventName.driverDistracted if self.active_monitoring_mode else EventName.driverUnresponsive
            self.terminal_time += 1
            if awareness_prev > 0.:
                self.terminal_alert_cnt += 1
        elif self.awareness <= self.threshold_prompt:
            # prompt orange alert
            alert = EventName.promptDriverDistracted if self.active_monitoring_mode else EventName.promptDriverUnresponsive
            self.hi_std_alert_enabled = True
        elif self.awareness <= self.threshold_pre:
            # pre green alert
            alert = EventName.preDriverDistracted if self.active_monitoring_mode else EventName.preDriverUnresponsive

        if alert is not None:
            events.add(alert)
Ejemplo n.º 4
0
class Planner:
    def __init__(self, CP, init_v=0.0, init_a=0.0):
        self.CP = CP
        self.mpc = LongitudinalMpc()

        self.fcw = False

        self.a_desired = init_a
        self.v_desired_filter = FirstOrderFilter(init_v, 2.0, DT_MDL)

        self.v_desired_trajectory = np.zeros(CONTROL_N)
        self.a_desired_trajectory = np.zeros(CONTROL_N)
        self.j_desired_trajectory = np.zeros(CONTROL_N)
        self.solverExecutionTime = 0.0

    def update(self, sm):
        v_ego = sm['carState'].vEgo

        v_cruise_kph = sm['controlsState'].vCruise
        v_cruise_kph = min(v_cruise_kph, V_CRUISE_MAX)
        v_cruise = v_cruise_kph * CV.KPH_TO_MS

        long_control_state = sm['controlsState'].longControlState
        force_slow_decel = sm['controlsState'].forceDecel

        # Reset current state when not engaged, or user is controlling the speed
        reset_state = long_control_state == LongCtrlState.off

        # No change cost when user is controlling the speed, or when standstill
        prev_accel_constraint = not (reset_state or sm['carState'].standstill)

        if reset_state:
            self.v_desired_filter.x = v_ego
            self.a_desired = 0.0

        # Prevent divergence, smooth in current v_ego
        self.v_desired_filter.x = max(0.0, self.v_desired_filter.update(v_ego))

        accel_limits = [A_CRUISE_MIN, get_max_accel(v_ego)]
        accel_limits_turns = limit_accel_in_turns(
            v_ego, sm['carState'].steeringAngleDeg, accel_limits, self.CP)
        if force_slow_decel:
            # if required so, force a smooth deceleration
            accel_limits_turns[1] = min(accel_limits_turns[1], AWARENESS_DECEL)
            accel_limits_turns[0] = min(accel_limits_turns[0],
                                        accel_limits_turns[1])
        # clip limits, cannot init MPC outside of bounds
        accel_limits_turns[0] = min(accel_limits_turns[0],
                                    self.a_desired + 0.05)
        accel_limits_turns[1] = max(accel_limits_turns[1],
                                    self.a_desired - 0.05)

        self.mpc.set_weights(prev_accel_constraint)
        self.mpc.set_accel_limits(accel_limits_turns[0], accel_limits_turns[1])
        self.mpc.set_cur_state(self.v_desired_filter.x, self.a_desired)
        self.mpc.update(sm['carState'], sm['radarState'], v_cruise)

        self.v_desired_trajectory = np.interp(T_IDXS[:CONTROL_N], T_IDXS_MPC,
                                              self.mpc.v_solution)
        self.a_desired_trajectory = np.interp(T_IDXS[:CONTROL_N], T_IDXS_MPC,
                                              self.mpc.a_solution)
        self.j_desired_trajectory = np.interp(T_IDXS[:CONTROL_N],
                                              T_IDXS_MPC[:-1],
                                              self.mpc.j_solution)

        # TODO counter is only needed because radar is glitchy, remove once radar is gone
        self.fcw = self.mpc.crash_cnt > 5
        if self.fcw:
            cloudlog.info("FCW triggered")

        # Interpolate 0.05 seconds and save as starting point for next iteration
        a_prev = self.a_desired
        self.a_desired = float(
            interp(DT_MDL, T_IDXS[:CONTROL_N], self.a_desired_trajectory))
        self.v_desired_filter.x = self.v_desired_filter.x + DT_MDL * (
            self.a_desired + a_prev) / 2.0

    def publish(self, sm, pm):
        plan_send = messaging.new_message('longitudinalPlan')

        plan_send.valid = sm.all_checks(
            service_list=['carState', 'controlsState'])

        longitudinalPlan = plan_send.longitudinalPlan
        longitudinalPlan.modelMonoTime = sm.logMonoTime['modelV2']
        longitudinalPlan.processingDelay = (plan_send.logMonoTime /
                                            1e9) - sm.logMonoTime['modelV2']

        longitudinalPlan.speeds = self.v_desired_trajectory.tolist()
        longitudinalPlan.accels = self.a_desired_trajectory.tolist()
        longitudinalPlan.jerks = self.j_desired_trajectory.tolist()

        longitudinalPlan.hasLead = sm['radarState'].leadOne.status
        longitudinalPlan.longitudinalPlanSource = self.mpc.source
        longitudinalPlan.fcw = self.fcw

        longitudinalPlan.solverExecutionTime = self.mpc.solve_time

        pm.send('longitudinalPlan', plan_send)
Ejemplo n.º 5
0
class Planner:
  def __init__(self, CP, init_v=0.0, init_a=0.0):
    self.CP = CP
    self.mpc = LongitudinalMpc()

    self.fcw = False

    self.a_desired = init_a
    self.v_desired_filter = FirstOrderFilter(init_v, 2.0, DT_MDL)

    self.v_desired_trajectory = np.zeros(CONTROL_N)
    self.a_desired_trajectory = np.zeros(CONTROL_N)
    self.j_desired_trajectory = np.zeros(CONTROL_N)

    self.use_cluster_speed = Params().get_bool('UseClusterSpeed')
    self.long_control_enabled = Params().get_bool('LongControlEnabled')

  def update(self, sm):
    v_ego = sm['carState'].vEgo
    a_ego = sm['carState'].aEgo

    v_cruise_kph = sm['controlsState'].vCruise
    v_cruise_kph = min(v_cruise_kph, V_CRUISE_MAX)
    v_cruise = v_cruise_kph * CV.KPH_TO_MS

    # neokii
    if not self.use_cluster_speed:
      vCluRatio = sm['carState'].vCluRatio
      if vCluRatio > 0.5:
        v_cruise *= vCluRatio
        v_cruise = int(v_cruise * CV.MS_TO_KPH) * CV.KPH_TO_MS

    long_control_state = sm['controlsState'].longControlState
    force_slow_decel = sm['controlsState'].forceDecel

    prev_accel_constraint = True
    if long_control_state == LongCtrlState.off or sm['carState'].gasPressed:
      self.v_desired_filter.x = v_ego
      self.a_desired = 0.0
      # Smoothly changing between accel trajectory is only relevant when OP is driving
      prev_accel_constraint = False

    # Prevent divergence, smooth in current v_ego
    self.v_desired_filter.x = max(0.0, self.v_desired_filter.update(v_ego))

    accel_limits = [A_CRUISE_MIN, get_max_accel(v_ego)]
    accel_limits_turns = limit_accel_in_turns(v_ego, sm['carState'].steeringAngleDeg, accel_limits, self.CP)
    if force_slow_decel:
      # if required so, force a smooth deceleration
      accel_limits_turns[1] = min(accel_limits_turns[1], AWARENESS_DECEL)
      accel_limits_turns[0] = min(accel_limits_turns[0], accel_limits_turns[1])
    # clip limits, cannot init MPC outside of bounds
    accel_limits_turns[0] = min(accel_limits_turns[0], self.a_desired + 0.05)
    accel_limits_turns[1] = max(accel_limits_turns[1], self.a_desired - 0.05)
    self.mpc.set_accel_limits(accel_limits_turns[0], accel_limits_turns[1])
    self.mpc.set_cur_state(self.v_desired_filter.x, self.a_desired)
    self.mpc.update(sm['carState'], sm['radarState'], v_cruise, prev_accel_constraint=prev_accel_constraint)
    self.v_desired_trajectory = np.interp(T_IDXS[:CONTROL_N], T_IDXS_MPC, self.mpc.v_solution)
    self.a_desired_trajectory = np.interp(T_IDXS[:CONTROL_N], T_IDXS_MPC, self.mpc.a_solution)
    self.j_desired_trajectory = np.interp(T_IDXS[:CONTROL_N], T_IDXS_MPC[:-1], self.mpc.j_solution)

    # TODO counter is only needed because radar is glitchy, remove once radar is gone
    self.fcw = self.mpc.crash_cnt > 5
    if self.fcw:
      cloudlog.info("FCW triggered")

    # Interpolate 0.05 seconds and save as starting point for next iteration
    a_prev = self.a_desired
    self.a_desired = float(interp(DT_MDL, T_IDXS[:CONTROL_N], self.a_desired_trajectory))
    self.v_desired_filter.x = self.v_desired_filter.x + DT_MDL * (self.a_desired + a_prev) / 2.0

  def publish(self, sm, pm):
    plan_send = messaging.new_message('longitudinalPlan')

    plan_send.valid = sm.all_alive_and_valid(service_list=['carState', 'controlsState'])

    longitudinalPlan = plan_send.longitudinalPlan
    longitudinalPlan.modelMonoTime = sm.logMonoTime['modelV2']
    longitudinalPlan.processingDelay = (plan_send.logMonoTime / 1e9) - sm.logMonoTime['modelV2']

    longitudinalPlan.speeds = self.v_desired_trajectory.tolist()
    longitudinalPlan.accels = self.a_desired_trajectory.tolist()
    longitudinalPlan.jerks = self.j_desired_trajectory.tolist()

    longitudinalPlan.hasLead = sm['radarState'].leadOne.status
    longitudinalPlan.longitudinalPlanSource = self.mpc.source
    longitudinalPlan.fcw = self.fcw

    longitudinalPlan.solverExecutionTime = self.mpc.solve_time

    pm.send('longitudinalPlan', plan_send)
Ejemplo n.º 6
0
class CarState(CarStateBase):
  def __init__(self, CP):
    super().__init__(CP)
    can_define = CANDefine(DBC[CP.carFingerprint]["pt"])
    self.shifter_values = can_define.dv["GEAR_PACKET"]["GEAR"]

    # On cars with cp.vl["STEER_TORQUE_SENSOR"]["STEER_ANGLE"]
    # the signal is zeroed to where the steering angle is at start.
    # Need to apply an offset as soon as the steering angle measurements are both received
    self.needs_angle_offset = True
    self.accurate_steer_angle_seen = False
    self.angle_offset = FirstOrderFilter(None, 60.0, DT_CTRL, initialized=False)

    self.low_speed_lockout = False
    self.acc_type = 1

  def update(self, cp, cp_cam):
    ret = car.CarState.new_message()

    ret.doorOpen = any([cp.vl["BODY_CONTROL_STATE"]["DOOR_OPEN_FL"], cp.vl["BODY_CONTROL_STATE"]["DOOR_OPEN_FR"],
                        cp.vl["BODY_CONTROL_STATE"]["DOOR_OPEN_RL"], cp.vl["BODY_CONTROL_STATE"]["DOOR_OPEN_RR"]])
    ret.seatbeltUnlatched = cp.vl["BODY_CONTROL_STATE"]["SEATBELT_DRIVER_UNLATCHED"] != 0

    ret.brakePressed = cp.vl["BRAKE_MODULE"]["BRAKE_PRESSED"] != 0
    ret.brakeHoldActive = cp.vl["ESP_CONTROL"]["BRAKE_HOLD_ACTIVE"] == 1
    if self.CP.enableGasInterceptor:
      ret.gas = (cp.vl["GAS_SENSOR"]["INTERCEPTOR_GAS"] + cp.vl["GAS_SENSOR"]["INTERCEPTOR_GAS2"]) / 2.
      ret.gasPressed = ret.gas > 15
    else:
      ret.gas = cp.vl["GAS_PEDAL"]["GAS_PEDAL"]
      ret.gasPressed = cp.vl["PCM_CRUISE"]["GAS_RELEASED"] == 0

    ret.wheelSpeeds = self.get_wheel_speeds(
      cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_FL"],
      cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_FR"],
      cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_RL"],
      cp.vl["WHEEL_SPEEDS"]["WHEEL_SPEED_RR"],
    )
    ret.vEgoRaw = mean([ret.wheelSpeeds.fl, ret.wheelSpeeds.fr, ret.wheelSpeeds.rl, ret.wheelSpeeds.rr])
    ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw)

    ret.standstill = ret.vEgoRaw < 0.001

    ret.steeringAngleDeg = cp.vl["STEER_ANGLE_SENSOR"]["STEER_ANGLE"] + cp.vl["STEER_ANGLE_SENSOR"]["STEER_FRACTION"]
    torque_sensor_angle_deg = cp.vl["STEER_TORQUE_SENSOR"]["STEER_ANGLE"]

    # Some newer models have a more accurate angle measurement in the TORQUE_SENSOR message. Use if non-zero
    if abs(torque_sensor_angle_deg) > 1e-3:
      self.accurate_steer_angle_seen = True

    if self.accurate_steer_angle_seen:
      # Offset seems to be invalid for large steering angles
      if abs(ret.steeringAngleDeg) < 90:
        self.angle_offset.update(torque_sensor_angle_deg - ret.steeringAngleDeg)

      if self.angle_offset.initialized:
        ret.steeringAngleOffsetDeg = self.angle_offset.x
        ret.steeringAngleDeg = torque_sensor_angle_deg - self.angle_offset.x

    ret.steeringRateDeg = cp.vl["STEER_ANGLE_SENSOR"]["STEER_RATE"]

    can_gear = int(cp.vl["GEAR_PACKET"]["GEAR"])
    ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(can_gear, None))
    ret.leftBlinker = cp.vl["BLINKERS_STATE"]["TURN_SIGNALS"] == 1
    ret.rightBlinker = cp.vl["BLINKERS_STATE"]["TURN_SIGNALS"] == 2

    ret.steeringTorque = cp.vl["STEER_TORQUE_SENSOR"]["STEER_TORQUE_DRIVER"]
    ret.steeringTorqueEps = cp.vl["STEER_TORQUE_SENSOR"]["STEER_TORQUE_EPS"]
    # we could use the override bit from dbc, but it's triggered at too high torque values
    ret.steeringPressed = abs(ret.steeringTorque) > STEER_THRESHOLD
    ret.steerWarning = cp.vl["EPS_STATUS"]["LKA_STATE"] not in (1, 5)

    if self.CP.carFingerprint in (CAR.LEXUS_IS, CAR.LEXUS_RC):
      ret.cruiseState.available = cp.vl["DSU_CRUISE"]["MAIN_ON"] != 0
      ret.cruiseState.speed = cp.vl["DSU_CRUISE"]["SET_SPEED"] * CV.KPH_TO_MS
    else:
      ret.cruiseState.available = cp.vl["PCM_CRUISE_2"]["MAIN_ON"] != 0
      ret.cruiseState.speed = cp.vl["PCM_CRUISE_2"]["SET_SPEED"] * CV.KPH_TO_MS

    if self.CP.carFingerprint in TSS2_CAR:
      self.acc_type = cp_cam.vl["ACC_CONTROL"]["ACC_TYPE"]

    # some TSS2 cars have low speed lockout permanently set, so ignore on those cars
    # these cars are identified by an ACC_TYPE value of 2.
    # TODO: it is possible to avoid the lockout and gain stop and go if you
    # send your own ACC_CONTROL msg on startup with ACC_TYPE set to 1
    if (self.CP.carFingerprint not in TSS2_CAR and self.CP.carFingerprint not in (CAR.LEXUS_IS, CAR.LEXUS_RC)) or \
       (self.CP.carFingerprint in TSS2_CAR and self.acc_type == 1):
      self.low_speed_lockout = cp.vl["PCM_CRUISE_2"]["LOW_SPEED_LOCKOUT"] == 2

    self.pcm_acc_status = cp.vl["PCM_CRUISE"]["CRUISE_STATE"]
    if self.CP.carFingerprint in NO_STOP_TIMER_CAR or self.CP.enableGasInterceptor:
      # ignore standstill in hybrid vehicles, since pcm allows to restart without
      # receiving any special command. Also if interceptor is detected
      ret.cruiseState.standstill = False
    else:
      ret.cruiseState.standstill = self.pcm_acc_status == 7
    ret.cruiseState.enabled = bool(cp.vl["PCM_CRUISE"]["CRUISE_ACTIVE"])
    ret.cruiseState.nonAdaptive = cp.vl["PCM_CRUISE"]["CRUISE_STATE"] in (1, 2, 3, 4, 5, 6)

    ret.genericToggle = bool(cp.vl["LIGHT_STALK"]["AUTO_HIGH_BEAM"])
    ret.stockAeb = bool(cp_cam.vl["PRE_COLLISION"]["PRECOLLISION_ACTIVE"] and cp_cam.vl["PRE_COLLISION"]["FORCE"] < -1e-5)

    ret.espDisabled = cp.vl["ESP_CONTROL"]["TC_DISABLED"] != 0
    # 2 is standby, 10 is active. TODO: check that everything else is really a faulty state
    self.steer_state = cp.vl["EPS_STATUS"]["LKA_STATE"]

    if self.CP.enableBsm:
      ret.leftBlindspot = (cp.vl["BSM"]["L_ADJACENT"] == 1) or (cp.vl["BSM"]["L_APPROACHING"] == 1)
      ret.rightBlindspot = (cp.vl["BSM"]["R_ADJACENT"] == 1) or (cp.vl["BSM"]["R_APPROACHING"] == 1)

    return ret

  @staticmethod
  def get_can_parser(CP):

    signals = [
      # sig_name, sig_address, default
      ("STEER_ANGLE", "STEER_ANGLE_SENSOR", 0),
      ("GEAR", "GEAR_PACKET", 0),
      ("BRAKE_PRESSED", "BRAKE_MODULE", 0),
      ("GAS_PEDAL", "GAS_PEDAL", 0),
      ("WHEEL_SPEED_FL", "WHEEL_SPEEDS", 0),
      ("WHEEL_SPEED_FR", "WHEEL_SPEEDS", 0),
      ("WHEEL_SPEED_RL", "WHEEL_SPEEDS", 0),
      ("WHEEL_SPEED_RR", "WHEEL_SPEEDS", 0),
      ("DOOR_OPEN_FL", "BODY_CONTROL_STATE", 1),
      ("DOOR_OPEN_FR", "BODY_CONTROL_STATE", 1),
      ("DOOR_OPEN_RL", "BODY_CONTROL_STATE", 1),
      ("DOOR_OPEN_RR", "BODY_CONTROL_STATE", 1),
      ("SEATBELT_DRIVER_UNLATCHED", "BODY_CONTROL_STATE", 1),
      ("TC_DISABLED", "ESP_CONTROL", 1),
      ("BRAKE_HOLD_ACTIVE", "ESP_CONTROL", 1),
      ("STEER_FRACTION", "STEER_ANGLE_SENSOR", 0),
      ("STEER_RATE", "STEER_ANGLE_SENSOR", 0),
      ("CRUISE_ACTIVE", "PCM_CRUISE", 0),
      ("CRUISE_STATE", "PCM_CRUISE", 0),
      ("GAS_RELEASED", "PCM_CRUISE", 1),
      ("STEER_TORQUE_DRIVER", "STEER_TORQUE_SENSOR", 0),
      ("STEER_TORQUE_EPS", "STEER_TORQUE_SENSOR", 0),
      ("STEER_ANGLE", "STEER_TORQUE_SENSOR", 0),
      ("TURN_SIGNALS", "BLINKERS_STATE", 3),   # 3 is no blinkers
      ("LKA_STATE", "EPS_STATUS", 0),
      ("AUTO_HIGH_BEAM", "LIGHT_STALK", 0),
    ]

    checks = [
      ("GEAR_PACKET", 1),
      ("LIGHT_STALK", 1),
      ("BLINKERS_STATE", 0.15),
      ("BODY_CONTROL_STATE", 3),
      ("ESP_CONTROL", 3),
      ("EPS_STATUS", 25),
      ("BRAKE_MODULE", 40),
      ("GAS_PEDAL", 33),
      ("WHEEL_SPEEDS", 80),
      ("STEER_ANGLE_SENSOR", 80),
      ("PCM_CRUISE", 33),
      ("STEER_TORQUE_SENSOR", 50),
    ]

    if CP.carFingerprint in (CAR.LEXUS_IS, CAR.LEXUS_RC):
      signals.append(("MAIN_ON", "DSU_CRUISE", 0))
      signals.append(("SET_SPEED", "DSU_CRUISE", 0))
      checks.append(("DSU_CRUISE", 5))
    else:
      signals.append(("MAIN_ON", "PCM_CRUISE_2", 0))
      signals.append(("SET_SPEED", "PCM_CRUISE_2", 0))
      signals.append(("LOW_SPEED_LOCKOUT", "PCM_CRUISE_2", 0))
      checks.append(("PCM_CRUISE_2", 33))

    # add gas interceptor reading if we are using it
    if CP.enableGasInterceptor:
      signals.append(("INTERCEPTOR_GAS", "GAS_SENSOR", 0))
      signals.append(("INTERCEPTOR_GAS2", "GAS_SENSOR", 0))
      checks.append(("GAS_SENSOR", 50))

    if CP.enableBsm:
      signals += [
        ("L_ADJACENT", "BSM", 0),
        ("L_APPROACHING", "BSM", 0),
        ("R_ADJACENT", "BSM", 0),
        ("R_APPROACHING", "BSM", 0),
      ]
      checks += [
        ("BSM", 1)
      ]

    return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 0)

  @staticmethod
  def get_cam_can_parser(CP):

    signals = [
      ("FORCE", "PRE_COLLISION", 0),
      ("PRECOLLISION_ACTIVE", "PRE_COLLISION", 0),
    ]

    # use steering message to check if panda is connected to frc
    checks = [
      ("STEERING_LKA", 42),
      ("PRE_COLLISION", 0), # TODO: figure out why freq is inconsistent
    ]

    if CP.carFingerprint in TSS2_CAR:
      signals.append(("ACC_TYPE", "ACC_CONTROL", 0))
      checks.append(("ACC_CONTROL", 33))

    return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 2)
Ejemplo n.º 7
0
class DriverStatus():
    def __init__(self, rhd=False, settings=DRIVER_MONITOR_SETTINGS()):
        # init policy settings
        self.settings = settings

        # init driver status
        self.is_rhd_region = rhd
        self.pose = DriverPose(self.settings._POSE_OFFSET_MAX_COUNT)
        self.pose_calibrated = False
        self.blink = DriverBlink()
        self.awareness = 1.
        self.awareness_active = 1.
        self.awareness_passive = 1.
        self.driver_distracted = False
        self.driver_distraction_filter = FirstOrderFilter(
            0., self.settings._DISTRACTED_FILTER_TS, self.settings._DT_DMON)
        self.face_detected = False
        self.face_partial = False
        self.terminal_alert_cnt = 0
        self.terminal_time = 0
        self.step_change = 0.
        self.active_monitoring_mode = True
        self.is_model_uncertain = False
        self.hi_stds = 0
        self.threshold_pre = self.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL / self.settings._DISTRACTED_TIME
        self.threshold_prompt = self.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL / self.settings._DISTRACTED_TIME

        self._set_timers(active_monitoring=True)

    def _set_timers(self, active_monitoring):
        if self.active_monitoring_mode and self.awareness <= self.threshold_prompt:
            if active_monitoring:
                self.step_change = self.settings._DT_DMON / self.settings._DISTRACTED_TIME
            else:
                self.step_change = 0.
            return  # no exploit after orange alert
        elif self.awareness <= 0.:
            return

        if active_monitoring:
            # when falling back from passive mode to active mode, reset awareness to avoid false alert
            if not self.active_monitoring_mode:
                self.awareness_passive = self.awareness
                self.awareness = self.awareness_active

            self.threshold_pre = self.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL / self.settings._DISTRACTED_TIME
            self.threshold_prompt = self.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL / self.settings._DISTRACTED_TIME
            self.step_change = self.settings._DT_DMON / self.settings._DISTRACTED_TIME
            self.active_monitoring_mode = True
        else:
            if self.active_monitoring_mode:
                self.awareness_active = self.awareness
                self.awareness = self.awareness_passive

            self.threshold_pre = self.settings._AWARENESS_PRE_TIME_TILL_TERMINAL / self.settings._AWARENESS_TIME
            self.threshold_prompt = self.settings._AWARENESS_PROMPT_TIME_TILL_TERMINAL / self.settings._AWARENESS_TIME
            self.step_change = self.settings._DT_DMON / self.settings._AWARENESS_TIME
            self.active_monitoring_mode = False

    def _is_driver_distracted(self, pose, blink):
        if not self.pose_calibrated:
            pitch_error = pose.pitch - self.settings._PITCH_NATURAL_OFFSET
            yaw_error = pose.yaw - self.settings._YAW_NATURAL_OFFSET
        else:
            pitch_error = pose.pitch - min(
                max(self.pose.pitch_offseter.filtered_stat.mean(),
                    self.settings._PITCH_MIN_OFFSET),
                self.settings._PITCH_MAX_OFFSET)
            yaw_error = pose.yaw - min(
                max(self.pose.yaw_offseter.filtered_stat.mean(),
                    self.settings._YAW_MIN_OFFSET),
                self.settings._YAW_MAX_OFFSET)

        pitch_error = 0 if pitch_error > 0 else abs(
            pitch_error)  # no positive pitch limit
        yaw_error = abs(yaw_error)

        if pitch_error > self.settings._POSE_PITCH_THRESHOLD*pose.cfactor_pitch or \
           yaw_error > self.settings._POSE_YAW_THRESHOLD*pose.cfactor_yaw:
            return DistractedType.BAD_POSE
        elif (blink.left_blink + blink.right_blink
              ) * 0.5 > self.settings._BLINK_THRESHOLD * blink.cfactor:
            return DistractedType.BAD_BLINK
        else:
            return DistractedType.NOT_DISTRACTED

    def set_policy(self, model_data, car_speed):
        ep = min(model_data.meta.engagedProb, 0.8) / 0.8  # engaged prob
        bp = model_data.meta.disengagePredictions.brakeDisengageProbs[
            0]  # brake disengage prob in next 2s
        # TODO: retune adaptive blink
        self.blink.cfactor = interp(ep, [0, 0.5, 1], [
            self.settings._BLINK_THRESHOLD_STRICT,
            self.settings._BLINK_THRESHOLD,
            self.settings._BLINK_THRESHOLD_SLACK
        ]) / self.settings._BLINK_THRESHOLD
        k1 = max(-0.00156 * ((car_speed - 16)**2) + 0.6, 0.2)
        bp_normal = max(min(bp / k1, 0.5), 0)
        self.pose.cfactor_pitch = interp(bp_normal, [0, 0.5], [
            self.settings._POSE_PITCH_THRESHOLD_SLACK,
            self.settings._POSE_PITCH_THRESHOLD_STRICT
        ]) / self.settings._POSE_PITCH_THRESHOLD
        self.pose.cfactor_yaw = interp(bp_normal, [0, 0.5], [
            self.settings._POSE_YAW_THRESHOLD_SLACK,
            self.settings._POSE_YAW_THRESHOLD_STRICT
        ]) / self.settings._POSE_YAW_THRESHOLD

    def get_pose(self, driver_state, cal_rpy, car_speed, op_engaged):
        if not all(
                len(x) > 0 for x in (driver_state.faceOrientation,
                                     driver_state.facePosition,
                                     driver_state.faceOrientationStd,
                                     driver_state.facePositionStd)):
            return

        self.face_partial = driver_state.partialFace > self.settings._PARTIAL_FACE_THRESHOLD
        self.face_detected = driver_state.faceProb > self.settings._FACE_THRESHOLD or self.face_partial
        self.pose.roll, self.pose.pitch, self.pose.yaw = face_orientation_from_net(
            driver_state.faceOrientation, driver_state.facePosition, cal_rpy,
            self.is_rhd_region)
        self.pose.pitch_std = driver_state.faceOrientationStd[0]
        self.pose.yaw_std = driver_state.faceOrientationStd[1]
        # self.pose.roll_std = driver_state.faceOrientationStd[2]
        model_std_max = max(self.pose.pitch_std, self.pose.yaw_std)
        self.pose.low_std = model_std_max < self.settings._POSESTD_THRESHOLD and not self.face_partial
        self.blink.left_blink = driver_state.leftBlinkProb * (
            driver_state.leftEyeProb > self.settings._EYE_THRESHOLD) * (
                driver_state.sunglassesProb < self.settings._SG_THRESHOLD)
        self.blink.right_blink = driver_state.rightBlinkProb * (
            driver_state.rightEyeProb > self.settings._EYE_THRESHOLD) * (
                driver_state.sunglassesProb < self.settings._SG_THRESHOLD)

        self.driver_distracted = self._is_driver_distracted(self.pose, self.blink) > 0 and \
                                       driver_state.faceProb > self.settings._FACE_THRESHOLD and self.pose.low_std
        self.driver_distraction_filter.update(self.driver_distracted)

        # update offseter
        # only update when driver is actively driving the car above a certain speed
        if self.face_detected and car_speed > self.settings._POSE_CALIB_MIN_SPEED and self.pose.low_std and (
                not op_engaged or not self.driver_distracted):
            self.pose.pitch_offseter.push_and_update(self.pose.pitch)
            self.pose.yaw_offseter.push_and_update(self.pose.yaw)

        self.pose_calibrated = self.pose.pitch_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT and \
                                           self.pose.yaw_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT

        self.is_model_uncertain = self.hi_stds > self.settings._HI_STD_FALLBACK_TIME
        self._set_timers(self.face_detected and not self.is_model_uncertain)
        if self.face_detected and not self.pose.low_std and not self.driver_distracted:
            self.hi_stds += 1
        elif self.face_detected and self.pose.low_std:
            self.hi_stds = 0

    def update(self, events, driver_engaged, ctrl_active, standstill):
        if (driver_engaged and self.awareness > 0) or not ctrl_active:
            # reset only when on disengagement if red reached
            self.awareness = 1.
            self.awareness_active = 1.
            self.awareness_passive = 1.
            return

        driver_attentive = self.driver_distraction_filter.x < 0.37
        awareness_prev = self.awareness

        if (driver_attentive and self.face_detected and self.pose.low_std
                and self.awareness > 0):
            # only restore awareness when paying attention and alert is not red
            self.awareness = min(
                self.awareness +
                ((self.settings._RECOVERY_FACTOR_MAX -
                  self.settings._RECOVERY_FACTOR_MIN) *
                 (1. - self.awareness) + self.settings._RECOVERY_FACTOR_MIN) *
                self.step_change, 1.)
            if self.awareness == 1.:
                self.awareness_passive = min(
                    self.awareness_passive + self.step_change, 1.)
            # don't display alert banner when awareness is recovering and has cleared orange
            if self.awareness > self.threshold_prompt:
                return

        standstill_exemption = standstill and self.awareness - self.step_change <= self.threshold_prompt
        certainly_distracted = self.driver_distraction_filter.x > 0.63 and self.driver_distracted and self.face_detected
        maybe_distracted = self.hi_stds > self.settings._HI_STD_FALLBACK_TIME or not self.face_detected
        if certainly_distracted or maybe_distracted:
            # should always be counting if distracted unless at standstill and reaching orange
            if not standstill_exemption:
                self.awareness = max(self.awareness - self.step_change, -0.1)

        alert = None
        if self.awareness <= 0.:
            # terminal red alert: disengagement required
            alert = EventName.driverDistracted if self.active_monitoring_mode else EventName.driverUnresponsive
            self.terminal_time += 1
            if awareness_prev > 0.:
                self.terminal_alert_cnt += 1
        elif self.awareness <= self.threshold_prompt:
            # prompt orange alert
            alert = EventName.promptDriverDistracted if self.active_monitoring_mode else EventName.promptDriverUnresponsive
        elif self.awareness <= self.threshold_pre:
            # pre green alert
            alert = EventName.preDriverDistracted if self.active_monitoring_mode else EventName.preDriverUnresponsive

        if alert is not None:
            events.add(alert)
Ejemplo n.º 8
0
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
    charging_disabled = False
    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

    ts_last_ip = 0
    ip_addr = '255.255.255.255'

    # sound trigger
    sound_trigger = 1
    opkrAutoShutdown = 0

    shutdown_trigger = 1
    is_openpilot_view_enabled = 0

    env = dict(os.environ)
    env['LD_LIBRARY_PATH'] = mediaplayer

    getoff_alert = params.get('OpkrEnableGetoffAlert') == b'1'

    hotspot_on_boot = params.get('OpkrHotspotOnBoot') == b'1'
    hotspot_run = False

    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()
        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
                    shutdown_trigger = 1
            else:
                no_panda_cnt = 0
                startup_conditions[
                    "ignition"] = pandaState.pandaState.ignitionLine or pandaState.pandaState.ignitionCan
                sound_trigger == 1
            #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
        elif int(params.get("IsOpenpilotViewEnabled")) == 1 and int(
                params.get("IsDriverViewEnabled")
        ) == 0 and is_openpilot_view_enabled == 0:
            is_openpilot_view_enabled = 1
            startup_conditions["ignition"] = True
        elif int(params.get("IsOpenpilotViewEnabled")) == 0 and int(
                params.get("IsDriverViewEnabled")
        ) == 0 and is_openpilot_view_enabled == 1:
            shutdown_trigger = 0
            sound_trigger == 0
            is_openpilot_view_enabled = 0
            startup_conditions["ignition"] = False

        # 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

        # 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.deviceState.ipAddr = ip_addr

        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"] = True if (
            (now.year > 2020) or (now.year == 2020 and now.month >= 10)
        ) else True  # set True for battery less EON otherwise, set False.
        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()

            if shutdown_trigger == 1 and sound_trigger == 1 and msg.deviceState.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 shutdown_trigger == 1 and msg.deviceState.batteryStatus == "Discharging" and \
               started_seen and opkrAutoShutdown and (sec_since_boot() - off_ts) > opkrAutoShutdown and not os.path.isfile(pandaflash_ongoing):
                os.system('LD_LIBRARY_PATH="" svc power shutdown')

        charging_disabled = check_car_battery_voltage(should_start, pandaState,
                                                      charging_disabled, msg)

        if msg.deviceState.batteryCurrent > 0:
            msg.deviceState.batteryStatus = "Discharging"
        else:
            msg.deviceState.batteryStatus = "Charging"

        msg.deviceState.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")

        sshkeylet = Params().get('OpkrSSHLegacy') == b'1'
        if not os.path.isfile(sshkeyfile) and sshkeylet:
            os.system(
                "cp -f /data/openpilot/selfdrive/assets/addon/key/GithubSshKeys_legacy /data/params/d/GithubSshKeys; chmod 600 /data/params/d/GithubSshKeys; touch /data/public_key"
            )
        elif os.path.isfile(sshkeyfile) and not sshkeylet:
            os.system(
                "cp -f /data/openpilot/selfdrive/assets/addon/key/GithubSshKeys_new /data/params/d/GithubSshKeys; chmod 600 /data/params/d/GithubSshKeys; rm -f /data/public_key"
            )

        # opkr hotspot
        if hotspot_on_boot and not hotspot_run and sec_since_boot() > 60:
            os.system("service call wifi 37 i32 0 i32 1 &")
            hotspot_run = True

        # 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 per minute
        if (count % int(60. / 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
Ejemplo n.º 9
0
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
  charging_disabled = False
  should_start_prev = False
  handle_fan = None
  is_uno = False

  params = Params()
  pm = PowerMonitoring()
  no_panda_cnt = 0

  thermal_config = get_thermal_config()

  ts_last_ip = 0
  ip_addr = '255.255.255.255'

  # sound trigger
  sound_trigger = 1
  opkrAutoShutdown = 0

  shutdown_trigger = 1
  is_openpilot_view_enabled = 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
  
  lateral_control_method = int(params.get("LateralControlMethod"))
  lateral_control_method_prev = int(params.get("LateralControlMethod"))
  lateral_control_method_cnt = 0
  lateral_control_method_trigger = 0
  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
      lateral_control_method = int(params.get("LateralControlMethod"))
      if lateral_control_method != lateral_control_method_prev and lateral_control_method_trigger == 0:
        startup_conditions["ignition"] = False
        lateral_control_method_trigger = 1
      elif lateral_control_method != lateral_control_method_prev:
        lateral_control_method_cnt += 1
        if lateral_control_method_cnt > 1 / DT_TRML:
          lateral_control_method_prev = lateral_control_method
      elif 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
          shutdown_trigger = 1
      else:
        no_panda_cnt = 0
        startup_conditions["ignition"] = health.health.ignitionLine or health.health.ignitionCan
        sound_trigger == 1
        lateral_control_method_cnt = 0
        lateral_control_method_trigger = 0

      # 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

        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 int(params.get("IsOpenpilotViewEnabled")) == 1 and int(params.get("IsDriverViewEnabled")) == 0 and is_openpilot_view_enabled == 0:
      is_openpilot_view_enabled = 1
      startup_conditions["ignition"] = True
    elif int(params.get("IsOpenpilotViewEnabled")) == 0 and int(params.get("IsDriverViewEnabled")) == 0 and is_openpilot_view_enabled == 1:
      shutdown_trigger = 0
      sound_trigger == 0
      is_openpilot_view_enabled = 0
      startup_conditions["ignition"] = False

    # 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, 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 (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

    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"] = 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"]))
    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')

      if shutdown_trigger == 1 and 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 shutdown_trigger == 1 and msg.thermal.batteryStatus == "Discharging" and \
         started_seen and opkrAutoShutdown and (sec_since_boot() - off_ts) > opkrAutoShutdown and not os.path.isfile(pandaflash_ongoing):
        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())

    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
Ejemplo n.º 10
0
def thermald_thread():
    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

    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.

        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:
                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
        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
Ejemplo n.º 11
0
class LanePlanner:
    def __init__(self, wide_camera=False):
        self.ll_t = np.zeros((TRAJECTORY_SIZE, ))
        self.ll_x = np.zeros((TRAJECTORY_SIZE, ))
        self.lll_y = np.zeros((TRAJECTORY_SIZE, ))
        self.rll_y = np.zeros((TRAJECTORY_SIZE, ))
        self.lane_width_estimate = FirstOrderFilter(3.7, 9.95, DT_MDL)
        self.lane_width_certainty = FirstOrderFilter(1.0, 0.95, DT_MDL)
        self.lane_width = 3.7

        self.lll_prob = 0.
        self.rll_prob = 0.
        self.d_prob = 0.

        self.lll_std = 0.
        self.rll_std = 0.

        self.l_lane_change_prob = 0.
        self.r_lane_change_prob = 0.

        self.camera_offset = -CAMERA_OFFSET if wide_camera else CAMERA_OFFSET
        self.path_offset = -PATH_OFFSET if wide_camera else PATH_OFFSET

        self.readings = []
        self.frame = 0

        self.wide_camera = wide_camera

    def parse_model(self, md):
        lane_lines = md.laneLines
        if len(lane_lines) == 4 and len(lane_lines[0].t) == TRAJECTORY_SIZE:
            self.ll_t = (np.array(lane_lines[1].t) +
                         np.array(lane_lines[2].t)) / 2
            # left and right ll x is the same
            self.ll_x = lane_lines[1].x
            # only offset left and right lane lines; offsetting path does not make sense

            self.lll_y = np.array(lane_lines[1].y) + self.camera_offset
            self.rll_y = np.array(lane_lines[2].y) + self.camera_offset
            self.lll_prob = md.laneLineProbs[1]
            self.rll_prob = md.laneLineProbs[2]
            self.lll_std = md.laneLineStds[1]
            self.rll_std = md.laneLineStds[2]

        desire_state = md.meta.desireState
        if len(desire_state):
            self.l_lane_change_prob = desire_state[
                log.LateralPlan.Desire.laneChangeLeft]
            self.r_lane_change_prob = desire_state[
                log.LateralPlan.Desire.laneChangeRight]

    def get_d_path(self, v_ego, path_t, path_xyz):
        # Reduce reliance on lanelines that are too far apart or
        # will be in a few seconds
        path_xyz[:, 1] += self.path_offset
        l_prob, r_prob = self.lll_prob, self.rll_prob
        width_pts = self.rll_y - self.lll_y
        prob_mods = []
        for t_check in (0.0, 1.5, 3.0):
            width_at_t = interp(t_check * (v_ego + 7), self.ll_x, width_pts)
            prob_mods.append(interp(width_at_t, [4.0, 5.0], [1.0, 0.0]))
        mod = min(prob_mods)
        l_prob *= mod
        r_prob *= mod

        # Reduce reliance on uncertain lanelines
        l_std_mod = interp(self.lll_std, [.15, .3], [1.0, 0.0])
        r_std_mod = interp(self.rll_std, [.15, .3], [1.0, 0.0])
        l_prob *= l_std_mod
        r_prob *= r_std_mod

        if ENABLE_ZORROBYTE:
            # zorrobyte code
            if l_prob > 0.5 and r_prob > 0.5:
                self.frame += 1
                if self.frame > 20:
                    self.frame = 0
                    current_lane_width = clip(
                        abs(self.rll_y[0] - self.lll_y[0]), 2.5, 3.5)
                    self.readings.append(current_lane_width)
                    self.lane_width = mean(self.readings)
                    if len(self.readings) >= 30:
                        self.readings.pop(0)

            # zorrobyte
            # Don't exit dive
            if abs(self.rll_y[0] - self.lll_y[0]) > self.lane_width:
                r_prob = r_prob / interp(l_prob, [0, 1], [1, 3])

        else:
            # Find current lanewidth
            self.lane_width_certainty.update(l_prob * r_prob)
            current_lane_width = abs(self.rll_y[0] - self.lll_y[0])
            self.lane_width_estimate.update(current_lane_width)
            speed_lane_width = interp(v_ego, [0., 31.], [2.8, 3.5])
            self.lane_width = self.lane_width_certainty.x * self.lane_width_estimate.x + \
                              (1 - self.lane_width_certainty.x) * speed_lane_width

        clipped_lane_width = min(4.0, self.lane_width)
        path_from_left_lane = self.lll_y + clipped_lane_width / 2.0
        path_from_right_lane = self.rll_y - clipped_lane_width / 2.0

        self.d_prob = l_prob + r_prob - l_prob * r_prob

        # neokii
        if ENABLE_INC_LANE_PROB and self.d_prob > 0.65:
            self.d_prob = min(self.d_prob * 1.3, 1.0)

        lane_path_y = (l_prob * path_from_left_lane + r_prob *
                       path_from_right_lane) / (l_prob + r_prob + 0.0001)
        safe_idxs = np.isfinite(self.ll_t)
        if safe_idxs[0]:
            lane_path_y_interp = np.interp(path_t, self.ll_t[safe_idxs],
                                           lane_path_y[safe_idxs])
            path_xyz[:, 1] = self.d_prob * lane_path_y_interp + (
                1.0 - self.d_prob) * path_xyz[:, 1]
        else:
            cloudlog.warning("Lateral mpc - NaNs in laneline times, ignoring")
        return path_xyz
Ejemplo n.º 12
0
def thermald_thread():
    setup_eon_fan()

    # prevent LEECO from undervoltage
    BATT_PERC_OFF = 10 if LEON else 15

    # now loop
    context = zmq.Context()
    thermal_sock = messaging.pub_sock(context, service_list['thermal'].port)
    health_sock = messaging.sub_sock(context, service_list['health'].port)
    location_sock = messaging.sub_sock(context,
                                       service_list['gpsLocation'].port)
    fan_speed = 0
    count = 0

    off_ts = None
    started_ts = None
    ignition_seen = False
    started_seen = False
    passive_starter = LocationStarter()
    thermal_status = ThermalStatus.green
    health_sock.RCVTIMEO = 1500
    current_filter = FirstOrderFilter(0., CURRENT_TAU, 1.)

    # 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()

        # loggerd is gated based on free space
        statvfs = os.statvfs(ROOT)
        avail = (statvfs.f_bavail * 1.0) / statvfs.f_blocks

        # 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/online") 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 > 95. 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 > 90.0:
            # hysteresis between engage not allowed and uploader not allowed
            thermal_status = clip(thermal_status, ThermalStatus.yellow,
                                  ThermalStatus.red)
        elif max_cpu_temp > 85.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 ****

        # start constellation of processes when the car starts
        ignition = health is not None and health.health.started
        ignition_seen = ignition_seen or ignition

        # Oldcar camry ignition wire is always hot, so instead sense ignition when voltage greater than 13V
        #ignition on ~ 11.7-12.5V
        #ignition off ~ 13.2-13.7V
        # add voltage check for ignition
        if not ignition_seen and health is not None and health.health.voltage > 13000:
            ignition = True

        do_uninstall = params.get("DoUninstall") == "1"
        accepted_terms = params.get("HasAcceptedTerms") == "1"
        completed_training = params.get(
            "CompletedTrainingVersion") == training_version

        should_start = ignition

        # have we seen a panda?
        passive = (params.get("Passive") == "1")

        # start on gps movement if we haven't seen ignition and are in passive mode
        should_start = should_start or (not (
            ignition_seen and health)  # seen ignition and panda is connected
                                        and passive and passive_starter.update(
                                            started_ts, location))

        # 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

        # require usb power in passive mode
        should_start = should_start and (not passive or msg.thermal.usbOnline)

        # 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:
            # TODO: Add a better warning when this is happening
            should_start = False

        if should_start:
            off_ts = None
            if started_ts is None:
                params.car_start()
                started_ts = sec_since_boot()
                started_seen = True
        else:
            started_ts = None
            if off_ts is None:
                off_ts = sec_since_boot()

            # 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)

        msg.thermal.chargingDisabled = charging_disabled
        msg.thermal.chargingError = current_filter.x > 1.0  # 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())
        print msg

        # report to server once per minute
        if (count % 60) == 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
Ejemplo n.º 13
0
def thermald_thread():
  # prevent LEECO from undervoltage
  BATT_PERC_OFF = int(kegman.conf['battPercOff'])
  
  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
  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_connectivity_alert = None
  charging_disabled = False
  time_valid_prev = True
  should_start_prev = False
  handle_fan = None
  is_uno = False

  params = Params()
  pm = PowerMonitoring()

  # ip addr
  ts_last_ip = None
  ts_last_update_vars = 0
  ts_last_charging_ctrl = None
  dp_last_modified = None
  ip_addr = '255.255.255.255'
  
  # sound trigger
  sound_trigger = 1

  env = dict(os.environ)
  env['LD_LIBRARY_PATH'] = mediaplayer

  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()

    if health is not None:
      usb_power = health.health.usbPowerMode != log.HealthData.UsbPowerMode.client
      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

        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"

    # update ip every 10 seconds
    ts = sec_since_boot()
    if ts_last_ip is None or 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.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

    # 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.utcnow()

    # 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:
      put_nonblocking("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")

    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:
      params.delete("Offroad_PandaFirmwareMismatch")
    if not fw_version_match and fw_version_match_prev:
      put_nonblocking("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:
        put_nonblocking("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):
        put_nonblocking("IsOffroad", "1")

      started_ts = None
      if off_ts is None:
        off_ts = sec_since_boot()
        sound_trigger = 1
        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) > 2:
        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.batteryPercent < BATT_PERC_OFF and msg.thermal.batteryStatus == "Discharging" and \
         started_seen and (sec_since_boot() - off_ts) > 38:
        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
    # 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:
      put_nonblocking("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
Ejemplo n.º 14
0
class LanePlanner:
    def __init__(self, wide_camera=False):
        self.ll_t = np.zeros((TRAJECTORY_SIZE, ))
        self.ll_x = np.zeros((TRAJECTORY_SIZE, ))
        self.lll_y = np.zeros((TRAJECTORY_SIZE, ))
        self.rll_y = np.zeros((TRAJECTORY_SIZE, ))
        self.lane_width_estimate = FirstOrderFilter(3.7, 9.95, DT_MDL)
        self.lane_width_certainty = FirstOrderFilter(1.0, 0.95, DT_MDL)
        self.lane_width = 3.7

        self.lll_prob = 0.
        self.rll_prob = 0.
        self.d_prob = 0.

        self.lll_std = 0.
        self.rll_std = 0.

        self.l_lane_change_prob = 0.
        self.r_lane_change_prob = 0.

        self.camera_offset = -CAMERA_OFFSET if wide_camera else CAMERA_OFFSET
        self.path_offset = -PATH_OFFSET if wide_camera else PATH_OFFSET

    def parse_model(self, md):
        if len(md.laneLines) == 4 and len(
                md.laneLines[0].t) == TRAJECTORY_SIZE:
            self.ll_t = (np.array(md.laneLines[1].t) +
                         np.array(md.laneLines[2].t)) / 2
            # left and right ll x is the same
            self.ll_x = md.laneLines[1].x
            # only offset left and right lane lines; offsetting path does not make sense
            self.lll_y = np.array(md.laneLines[1].y) - self.camera_offset
            self.rll_y = np.array(md.laneLines[2].y) - self.camera_offset
            self.lll_prob = md.laneLineProbs[1]
            self.rll_prob = md.laneLineProbs[2]
            self.lll_std = md.laneLineStds[1]
            self.rll_std = md.laneLineStds[2]

        if len(md.meta.desireState):
            self.l_lane_change_prob = md.meta.desireState[
                log.LateralPlan.Desire.laneChangeLeft]
            self.r_lane_change_prob = md.meta.desireState[
                log.LateralPlan.Desire.laneChangeRight]

    def get_d_path(self, v_ego, path_t, path_xyz):
        # Reduce reliance on lanelines that are too far apart or
        # will be in a few seconds
        path_xyz[:, 1] -= self.path_offset
        l_prob, r_prob = self.lll_prob, self.rll_prob
        width_pts = self.rll_y - self.lll_y
        prob_mods = []
        for t_check in [0.0, 1.5, 3.0]:
            width_at_t = interp(t_check * (v_ego + 7), self.ll_x, width_pts)
            prob_mods.append(interp(width_at_t, [4.0, 5.0], [1.0, 0.0]))
        mod = min(prob_mods)
        l_prob *= mod
        r_prob *= mod

        # Reduce reliance on uncertain lanelines
        l_std_mod = interp(self.lll_std, [.15, .3], [1.0, 0.0])
        r_std_mod = interp(self.rll_std, [.15, .3], [1.0, 0.0])
        l_prob *= l_std_mod
        r_prob *= r_std_mod

        # Find current lanewidth
        self.lane_width_certainty.update(l_prob * r_prob)
        current_lane_width = abs(self.rll_y[0] - self.lll_y[0])
        self.lane_width_estimate.update(current_lane_width)
        speed_lane_width = interp(v_ego, [0., 31.], [2.5, 3.5])
        self.lane_width = self.lane_width_certainty.x * self.lane_width_estimate.x + \
                          (1 - self.lane_width_certainty.x) * speed_lane_width

        clipped_lane_width = min(4.0, self.lane_width)
        path_from_left_lane = self.lll_y + clipped_lane_width / 2.0
        path_from_right_lane = self.rll_y - clipped_lane_width / 2.0

        self.d_prob = l_prob + r_prob - l_prob * r_prob
        lane_path_y = (l_prob * path_from_left_lane + r_prob *
                       path_from_right_lane) / (l_prob + r_prob + 0.0001)
        safe_idxs = np.isfinite(self.ll_t)
        if safe_idxs[0]:
            lane_path_y_interp = np.interp(path_t, self.ll_t[safe_idxs],
                                           lane_path_y[safe_idxs])
            path_xyz[:, 1] = self.d_prob * lane_path_y_interp + (
                1.0 - self.d_prob) * path_xyz[:, 1]
        else:
            cloudlog.warning("Lateral mpc - NaNs in laneline times, ignoring")
        return path_xyz
Ejemplo n.º 15
0
def thermald_thread():
    setup_eon_fan()

    # prevent LEECO from undervoltage
    BATT_PERC_OFF = int(kegman.conf['battPercOff'])

    # 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
    shutdown_count = 0

    off_ts = None
    started_ts = None
    ignition_seen = False
    #started_seen = False
    thermal_status = ThermalStatus.green
    health_sock.RCVTIMEO = 1500
    current_filter = FirstOrderFilter(0., CURRENT_TAU, 1.)
    services_killed = False
    health_prev = None

    # 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

        # 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()))
            usb_online = msg.thermal.usbOnline

        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 > 95. 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 > 90.0:
            # hysteresis between engage not allowed and uploader not allowed
            thermal_status = clip(thermal_status, ThermalStatus.yellow,
                                  ThermalStatus.red)
        elif max_cpu_temp > 85.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 ****

        # start constellation of processes when the car starts
        ignition = health is not None and health.health.started
        ignition_seen = ignition_seen or ignition

        # add voltage check for ignition
        if not ignition_seen and health is not None and health.health.voltage > 13500:
            ignition = True

        do_uninstall = params.get("DoUninstall") == "1"
        accepted_terms = params.get("HasAcceptedTerms") == "1"
        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:
            # TODO: Add a better warning when this is happening
            should_start = False

        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.batteryCurrent > 0 and \
               sec_since_boot() > 180:
                #started_seen and (sec_since_boot() - off_ts) > 60:
                if msg.thermal.usbOnline:
                    # if there is power through the USB then shutting down just results in an immediate restart so kill services instead (E.g. Nidec)
                    kill_list = [
                        "updated", "gpsd", "logcatd", "pandad", "ui",
                        "uploader", "tombstoned", "logmessaged", "athena",
                        "ai.comma", "boardd"
                    ]
                    # Kill processes to save battery cannot shutdown if plugged in because it will just restart after shutdown
                    for process_name in kill_list:
                        proc = subprocess.Popen(["pgrep", process_name],
                                                stdout=subprocess.PIPE)
                        for pid in proc.stdout:
                            os.kill(int(pid), signal.SIGTERM)
                else:
                    # if not just shut it down completely (E.g. Bosch or disconnected)
                    os.system('LD_LIBRARY_PATH="" svc power shutdown')

                services_killed = True

        if services_killed:
            charging_disabled = True
        else:
            charging_disabled = check_car_battery_voltage(
                should_start, health, charging_disabled, msg)

        # need to force batteryStatus because after NEOS update for 0.5.7 this doesn't work properly
        if msg.thermal.batteryCurrent > 0:
            msg.thermal.batteryStatus = "Discharging"
        else:
            msg.thermal.batteryStatus = "Charging"

        msg.thermal.chargingDisabled = charging_disabled
        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())
        print(msg)

        # report to server once per minute
        if (count % 60) == 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
        # shut down EON if usb is not present after certain time
        if not usb_online:
            shutdown_count += 1
        else:
            shutdown_count = 0

        if shutdown_count >= _SHUTDOWN_AT:
            os.system('LD_LIBRARY_PATH="" svc power shutdown')
Ejemplo n.º 16
0
def thermald_thread(end_event, hw_queue):
    pm = messaging.PubMaster(['deviceState'])
    sm = messaging.SubMaster([
        "peripheralState", "gpsLocationExternal", "controlsState",
        "pandaStates"
    ],
                             poll=["pandaStates"])

    count = 0

    onroad_conditions: Dict[str, bool] = {
        "ignition": False,
    }
    startup_conditions: Dict[str, bool] = {}
    startup_conditions_prev: Dict[str, bool] = {}

    off_ts = None
    started_ts = None
    started_seen = False
    thermal_status = ThermalStatus.green

    last_hw_state = HardwareState(
        network_type=NetworkType.none,
        network_metered=False,
        network_strength=NetworkStrength.unknown,
        network_info=None,
        nvme_temps=[],
        modem_temps=[],
    )

    current_filter = FirstOrderFilter(0., CURRENT_TAU, DT_TRML)
    temp_filter = FirstOrderFilter(0., TEMP_TAU, DT_TRML)
    should_start_prev = False
    in_car = False
    engaged_prev = False

    params = Params()
    power_monitor = PowerMonitoring()

    HARDWARE.initialize_hardware()
    thermal_config = HARDWARE.get_thermal_config()

    fan_controller = None

    while not end_event.is_set():
        sm.update(PANDA_STATES_TIMEOUT)

        pandaStates = sm['pandaStates']
        peripheralState = sm['peripheralState']

        msg = read_thermal(thermal_config)

        if sm.updated['pandaStates'] and len(pandaStates) > 0:

            # Set ignition based on any panda connected
            onroad_conditions["ignition"] = any(
                ps.ignitionLine or ps.ignitionCan for ps in pandaStates
                if ps.pandaType != log.PandaState.PandaType.unknown)

            pandaState = pandaStates[0]

            in_car = pandaState.harnessStatus != log.PandaState.HarnessStatus.notConnected

            # Setup fan handler on first connect to panda
            if fan_controller is None and peripheralState.pandaType != log.PandaState.PandaType.unknown:
                if TICI:
                    fan_controller = TiciFanController()

        elif (sec_since_boot() -
              sm.rcv_time['pandaStates']) > DISCONNECT_TIMEOUT:
            if onroad_conditions["ignition"]:
                onroad_conditions["ignition"] = False
                cloudlog.error("panda timed out onroad")

        try:
            last_hw_state = hw_queue.get_nowait()
        except queue.Empty:
            pass

        msg.deviceState.freeSpacePercent = get_available_percent(default=100.0)
        msg.deviceState.memoryUsagePercent = int(
            round(psutil.virtual_memory().percent))
        msg.deviceState.cpuUsagePercent = [
            int(round(n)) for n in psutil.cpu_percent(percpu=True)
        ]
        msg.deviceState.gpuUsagePercent = int(
            round(HARDWARE.get_gpu_usage_percent()))

        msg.deviceState.networkType = last_hw_state.network_type
        msg.deviceState.networkMetered = last_hw_state.network_metered
        msg.deviceState.networkStrength = last_hw_state.network_strength
        if last_hw_state.network_info is not None:
            msg.deviceState.networkInfo = last_hw_state.network_info

        msg.deviceState.nvmeTempC = last_hw_state.nvme_temps
        msg.deviceState.modemTempC = last_hw_state.modem_temps

        msg.deviceState.screenBrightnessPercent = HARDWARE.get_screen_brightness(
        )
        msg.deviceState.usbOnline = HARDWARE.get_usb_present()
        current_filter.update(msg.deviceState.batteryCurrent / 1e6)

        max_comp_temp = temp_filter.update(
            max(max(msg.deviceState.cpuTempC), msg.deviceState.memoryTempC,
                max(msg.deviceState.gpuTempC)))

        if fan_controller is not None:
            msg.deviceState.fanSpeedPercentDesired = fan_controller.update(
                max_comp_temp, onroad_conditions["ignition"])

        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 is_offroad_for_5_min and max_comp_temp > OFFROAD_DANGER_TEMP:
            # If device is offroad we want to cool down before going onroad
            # since going onroad increases load and can make temps go over 107
            thermal_status = ThermalStatus.danger
        else:
            current_band = THERMAL_BANDS[thermal_status]
            band_idx = list(THERMAL_BANDS.keys()).index(thermal_status)
            if current_band.min_temp is not None and max_comp_temp < current_band.min_temp:
                thermal_status = list(THERMAL_BANDS.keys())[band_idx - 1]
            elif current_band.max_temp is not None and max_comp_temp > current_band.max_temp:
                thermal_status = list(THERMAL_BANDS.keys())[band_idx + 1]

        # **** starting logic ****

        # Ensure date/time are valid
        now = datetime.datetime.utcnow()
        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"]))

        startup_conditions["up_to_date"] = params.get(
            "Offroad_ConnectivityNeeded") is None or params.get_bool(
                "DisableUpdates") or params.get_bool("SnoozeUpdate")
        startup_conditions["not_uninstalling"] = not params.get_bool(
            "DoUninstall")
        startup_conditions["accepted_terms"] = params.get(
            "HasAcceptedTerms") == terms_version

        # 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 \
                                                   params.get_bool("Passive")
        startup_conditions["not_driver_view"] = not params.get_bool(
            "IsDriverViewEnabled")
        startup_conditions["not_taking_snapshot"] = not params.get_bool(
            "IsTakingSnapshot")
        # 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
        onroad_conditions[
            "device_temp_good"] = thermal_status < ThermalStatus.danger
        set_offroad_alert_if_changed(
            "Offroad_TemperatureTooHigh",
            (not onroad_conditions["device_temp_good"]))

        # TODO: this should move to TICI.initialize_hardware, but we currently can't import params there
        if TICI:
            if not os.path.isfile("/persist/comma/living-in-the-moment"):
                if not Path("/data/media").is_mount():
                    set_offroad_alert_if_changed("Offroad_StorageMissing",
                                                 True)
                else:
                    # check for bad NVMe
                    try:
                        with open("/sys/block/nvme0n1/device/model") as f:
                            model = f.read().strip()
                        if not model.startswith(
                                "Samsung SSD 980") and params.get(
                                    "Offroad_BadNvme") is None:
                            set_offroad_alert_if_changed(
                                "Offroad_BadNvme", True)
                            cloudlog.event("Unsupported NVMe",
                                           model=model,
                                           error=True)
                    except Exception:
                        pass

        # Handle offroad/onroad transition
        should_start = all(onroad_conditions.values())
        if started_ts is None:
            should_start = should_start and all(startup_conditions.values())

        if should_start != should_start_prev or (count == 0):
            params.put_bool("IsOnroad", should_start)
            params.put_bool("IsOffroad", not should_start)

            params.put_bool("IsEngaged", False)
            engaged_prev = False
            HARDWARE.set_power_save(not should_start)

        if sm.updated['controlsState']:
            engaged = sm['controlsState'].enabled
            if engaged != engaged_prev:
                params.put_bool("IsEngaged", engaged)
                engaged_prev = engaged

            try:
                with open('/dev/kmsg', 'w') as kmsg:
                    kmsg.write(f"<3>[thermald] engaged: {engaged}\n")
            except Exception:
                pass

        if should_start:
            off_ts = None
            if started_ts is None:
                started_ts = sec_since_boot()
                started_seen = True
        else:
            if onroad_conditions["ignition"] and (startup_conditions !=
                                                  startup_conditions_prev):
                cloudlog.event("Startup blocked",
                               startup_conditions=startup_conditions,
                               onroad_conditions=onroad_conditions)

            started_ts = None
            if off_ts is None:
                off_ts = sec_since_boot()

        # Offroad power monitoring
        power_monitor.calculate(peripheralState, onroad_conditions["ignition"])
        msg.deviceState.offroadPowerUsageUwh = power_monitor.get_power_used()
        msg.deviceState.carBatteryCapacityUwh = max(
            0, power_monitor.get_car_battery_capacity())
        current_power_draw = HARDWARE.get_current_power_draw()  # pylint: disable=assignment-from-none
        if current_power_draw is not None:
            statlog.sample("power_draw", current_power_draw)
            msg.deviceState.powerDrawW = current_power_draw
        else:
            msg.deviceState.powerDrawW = 0

        # Check if we need to disable charging (handled by boardd)
        msg.deviceState.chargingDisabled = power_monitor.should_disable_charging(
            onroad_conditions["ignition"], in_car, off_ts)

        # Check if we need to shut down
        if power_monitor.should_shutdown(peripheralState,
                                         onroad_conditions["ignition"], in_car,
                                         off_ts, started_seen):
            cloudlog.warning(f"shutting device down, offroad since {off_ts}")
            params.put_bool("DoShutdown", True)

        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))

        last_ping = params.get("LastAthenaPingTime")
        if last_ping is not None:
            msg.deviceState.lastAthenaPingTime = int(last_ping)

        msg.deviceState.thermalStatus = thermal_status
        pm.send("deviceState", msg)

        should_start_prev = should_start
        startup_conditions_prev = startup_conditions.copy()

        # Log to statsd
        statlog.gauge("free_space_percent", msg.deviceState.freeSpacePercent)
        statlog.gauge("gpu_usage_percent", msg.deviceState.gpuUsagePercent)
        statlog.gauge("memory_usage_percent",
                      msg.deviceState.memoryUsagePercent)
        for i, usage in enumerate(msg.deviceState.cpuUsagePercent):
            statlog.gauge(f"cpu{i}_usage_percent", usage)
        for i, temp in enumerate(msg.deviceState.cpuTempC):
            statlog.gauge(f"cpu{i}_temperature", temp)
        for i, temp in enumerate(msg.deviceState.gpuTempC):
            statlog.gauge(f"gpu{i}_temperature", temp)
        statlog.gauge("memory_temperature", msg.deviceState.memoryTempC)
        statlog.gauge("ambient_temperature", msg.deviceState.ambientTempC)
        for i, temp in enumerate(msg.deviceState.pmicTempC):
            statlog.gauge(f"pmic{i}_temperature", temp)
        for i, temp in enumerate(last_hw_state.nvme_temps):
            statlog.gauge(f"nvme_temperature{i}", temp)
        for i, temp in enumerate(last_hw_state.modem_temps):
            statlog.gauge(f"modem_temperature{i}", temp)
        statlog.gauge("fan_speed_percent_desired",
                      msg.deviceState.fanSpeedPercentDesired)
        statlog.gauge("screen_brightness_percent",
                      msg.deviceState.screenBrightnessPercent)

        # report to server once every 10 minutes
        if (count % int(600. / DT_TRML)) == 0:
            cloudlog.event(
                "STATUS_PACKET",
                count=count,
                pandaStates=[
                    strip_deprecated_keys(p.to_dict()) for p in pandaStates
                ],
                peripheralState=strip_deprecated_keys(
                    peripheralState.to_dict()),
                location=(strip_deprecated_keys(
                    sm["gpsLocationExternal"].to_dict())
                          if sm.alive["gpsLocationExternal"] else None),
                deviceState=strip_deprecated_keys(msg.to_dict()))

        count += 1
Ejemplo n.º 17
0
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_bool(
                "DisableUpdates")
        startup_conditions["not_uninstalling"] = not params.get_bool(
            "DoUninstall")
        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_bool(
            "IsDriverViewEnabled")
        startup_conditions["not_taking_snapshot"] = not params.get_bool(
            "IsTakingSnapshot")
        # 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_bool("IsOffroad", True)
                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 per minute
        if (count % int(60. / 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
Ejemplo n.º 18
0
class DriverStatus():
  def __init__(self):
    self.pose = DriverPose()
    self.pose_calibrated = self.pose.pitch_offseter.filtered_stat.n > _POSE_OFFSET_MIN_COUNT and \
                            self.pose.yaw_offseter.filtered_stat.n > _POSE_OFFSET_MIN_COUNT
    self.blink = DriverBlink()
    self.awareness = 1.
    self.awareness_active = 1.
    self.driver_distracted = False
    self.driver_distraction_filter = FirstOrderFilter(0., _DISTRACTED_FILTER_TS, DT_DMON)
    self.face_detected = False
    self.terminal_alert_cnt = 0
    self.step_change = 0.
    self.active_monitoring_mode = True
    self.threshold_prompt = _DISTRACTED_PROMPT_TIME_TILL_TERMINAL / _DISTRACTED_TIME

    self.is_rhd_region = False
    self.is_rhd_region_checked = False

    self._set_timers(active_monitoring=True)

  def _set_timers(self, active_monitoring):
    if self.active_monitoring_mode and self.awareness <= self.threshold_prompt:
      if active_monitoring:
        self.step_change = DT_CTRL / _DISTRACTED_TIME
      else:
        self.step_change = 0.
      return # no exploit after orange alert
    elif self.awareness <= 0.:
      return

    if active_monitoring:
      # when falling back from passive mode to active mode, reset awareness to avoid false alert
      if not self.active_monitoring_mode:
        self.awareness = self.awareness_active

      self.threshold_pre = _DISTRACTED_PRE_TIME_TILL_TERMINAL / _DISTRACTED_TIME
      self.threshold_prompt = _DISTRACTED_PROMPT_TIME_TILL_TERMINAL / _DISTRACTED_TIME
      self.step_change = DT_CTRL / _DISTRACTED_TIME
      self.active_monitoring_mode = True
    else:
      if self.active_monitoring_mode:
        self.awareness_active = self.awareness

      self.threshold_pre = _AWARENESS_PRE_TIME_TILL_TERMINAL / _AWARENESS_TIME
      self.threshold_prompt = _AWARENESS_PROMPT_TIME_TILL_TERMINAL / _AWARENESS_TIME
      self.step_change = DT_CTRL / _AWARENESS_TIME
      self.active_monitoring_mode = False

  def _is_driver_distracted(self, pose, blink):
    if not self.pose_calibrated:
      pitch_error = pose.pitch - _PITCH_NATURAL_OFFSET
      yaw_error = pose.yaw - _YAW_NATURAL_OFFSET
      # add positive pitch allowance
      if pitch_error > 0.:
        pitch_error = max(pitch_error - _PITCH_POS_ALLOWANCE, 0.)
    else:
      pitch_error = pose.pitch - self.pose.pitch_offseter.filtered_stat.mean()
      yaw_error = pose.yaw - self.pose.yaw_offseter.filtered_stat.mean()

    pitch_error *= _PITCH_WEIGHT
    pose_metric = np.sqrt(yaw_error**2 + pitch_error**2)

    if pose_metric > _METRIC_THRESHOLD:
      return DistractedType.BAD_POSE
    elif blink.left_blink>_BLINK_THRESHOLD and blink.right_blink>_BLINK_THRESHOLD:
      return DistractedType.BAD_BLINK
    else:
      return DistractedType.NOT_DISTRACTED

  def get_pose(self, driver_monitoring, cal_rpy, car_speed, op_engaged):
    # 10 Hz
    if len(driver_monitoring.faceOrientation) == 0 or len(driver_monitoring.facePosition) == 0:
      return

    self.pose.roll, self.pose.pitch, self.pose.yaw = head_orientation_from_descriptor(driver_monitoring.faceOrientation, driver_monitoring.facePosition, cal_rpy)
    self.blink.left_blink = driver_monitoring.leftBlinkProb * (driver_monitoring.leftEyeProb>_EYE_THRESHOLD)
    self.blink.right_blink = driver_monitoring.rightBlinkProb * (driver_monitoring.rightEyeProb>_EYE_THRESHOLD)
    self.face_detected = driver_monitoring.faceProb > _FACE_THRESHOLD and not self.is_rhd_region

    self.driver_distracted = self._is_driver_distracted(self.pose, self.blink)>0
    # first order filters
    self.driver_distraction_filter.update(self.driver_distracted)

    # update offseter
    # only update when driver is actively driving the car above a certain speed
    if self.face_detected and car_speed>_POSE_CALIB_MIN_SPEED and not op_engaged:
      self.pose.pitch_offseter.push_and_update(self.pose.pitch)
      self.pose.yaw_offseter.push_and_update(self.pose.yaw)

    self.pose_calibrated = self.pose.pitch_offseter.filtered_stat.n > _POSE_OFFSET_MIN_COUNT and \
                            self.pose.yaw_offseter.filtered_stat.n > _POSE_OFFSET_MIN_COUNT

    self._set_timers(self.face_detected)

  def update(self, events, driver_engaged, ctrl_active, standstill):
    if (driver_engaged and self.awareness > 0) or not ctrl_active:
      # reset only when on disengagement if red reached
      self.awareness = 1.
      self.awareness_active = 1.
      return events

    driver_attentive = self.driver_distraction_filter.x < 0.37
    awareness_prev = self.awareness

    if (driver_attentive and self.face_detected and self.awareness > 0):
      # only restore awareness when paying attention and alert is not red
      self.awareness = min(self.awareness + ((_RECOVERY_FACTOR_MAX-_RECOVERY_FACTOR_MIN)*(1.-self.awareness)+_RECOVERY_FACTOR_MIN)*self.step_change, 1.)
      # don't display alert banner when awareness is recovering and has cleared orange
      if self.awareness > self.threshold_prompt:
        return events

    # should always be counting if distracted unless at standstill and reaching orange
    if (not self.face_detected or (self.driver_distraction_filter.x > 0.63 and self.driver_distracted and self.face_detected)) and \
       not (standstill and self.awareness - self.step_change <= self.threshold_prompt):
      self.awareness = max(self.awareness - self.step_change, -0.1)

    alert = None
    if self.awareness <= 0.:
      # terminal red alert: disengagement required
      alert = 'driverDistracted' if self.active_monitoring_mode else 'driverUnresponsive'
      if awareness_prev > 0.:
        self.terminal_alert_cnt += 1
    elif self.awareness <= self.threshold_prompt:
      # prompt orange alert
      alert = 'promptDriverDistracted' if self.active_monitoring_mode else 'promptDriverUnresponsive'
    elif self.awareness <= self.threshold_pre:
      # pre green alert
      alert = 'preDriverDistracted' if self.active_monitoring_mode else 'preDriverUnresponsive'

    if alert is not None:
      events.append(create_event(alert, [ET.WARNING]))

    return events
Ejemplo n.º 19
0
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
    wifiIpAddress = 'N/A'

    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

    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

                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)
                wifiIpAddress = HARDWARE.get_ip_address()
            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.wifiIpAddress = wifiIpAddress
        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 (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"] = True  #now.year >= 2019
        set_offroad_alert_if_changed("Offroad_InvalidTime",
                                     (not startup_conditions["time_valid"]))

        # Show update prompt
        try:
            last_update = now  #datetime.datetime.fromisoformat(params.get("LastUpdateTime", encoding='utf8'))
        except (TypeError, ValueError):
            last_update = now
        dt = now - last_update

        update_failed_count = 0  #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

        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"] = 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"]))
        should_start = all(startup_conditions.values())

        startup_conditions[
            "hardware_supported"] = True  #health is not None and health.health.hwType not in [log.HealthData.HwType.whitePanda,
        #log.HealthData.HwType.greyPanda]
        set_offroad_alert_if_changed(
            "Offroad_HardwareUnsupported", health is not None
            and not startup_conditions["hardware_supported"])

        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
        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()

        # 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
Ejemplo n.º 20
0
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('pandaStates',
                                         timeout=pandaState_timeout)
    sm = messaging.SubMaster(
        ["peripheralState", "gpsLocationExternal", "managerState"])

    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

    network_type = NetworkType.none
    network_strength = NetworkStrength.unknown
    network_info = None
    modem_version = None
    registered_count = 0
    nvme_temps = None
    modem_temps = None

    current_filter = FirstOrderFilter(0., CURRENT_TAU, DT_TRML)
    temp_filter = FirstOrderFilter(0., TEMP_TAU, DT_TRML)
    pandaState_prev = None
    should_start_prev = False
    in_car = False
    handle_fan = None
    is_uno = False
    ui_running_prev = False

    params = Params()
    power_monitor = PowerMonitoring()
    no_panda_cnt = 0

    HARDWARE.initialize_hardware()
    thermal_config = HARDWARE.get_thermal_config()

    # TODO: use PI controller for UNO
    controller = PIController(k_p=0,
                              k_i=2e-3,
                              neg_limit=-80,
                              pos_limit=0,
                              rate=(1 / DT_TRML))

    # Leave flag for loggerd to indicate device was left onroad
    if params.get_bool("IsOnroad"):
        params.put_bool("BootedOnroad", True)

    while True:
        pandaStates = messaging.recv_sock(pandaState_sock, wait=True)

        sm.update(0)
        peripheralState = sm['peripheralState']

        msg = read_thermal(thermal_config)

        if pandaStates is not None and len(pandaStates.pandaStates) > 0:
            pandaState = pandaStates.pandaStates[0]

            # If we lose connection to the panda, wait 5 seconds before going offroad
            if 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.ignitionLine or pandaState.ignitionCan

            in_car = pandaState.harnessStatus != log.PandaState.HarnessStatus.notConnected
            usb_power = peripheralState.usbPowerMode != log.PeripheralState.UsbPowerMode.client

            # Setup fan handler on first connect to panda
            if handle_fan is None and peripheralState.pandaType != log.PandaState.PandaType.unknown:
                is_uno = peripheralState.pandaType == log.PandaState.PandaType.uno

                if TICI:
                    cloudlog.info("Setting up TICI fan handler")
                    handle_fan = handle_fan_tici
                elif is_uno or PC:
                    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.pandaType == log.PandaState.PandaType.unknown and \
                  pandaState_prev.pandaType != log.PandaState.PandaType.unknown:
                    params.clear_all(ParamKeyType.CLEAR_ON_PANDA_DISCONNECT)
            pandaState_prev = pandaState

        # these are expensive calls. 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)
                network_info = HARDWARE.get_network_info()  # pylint: disable=assignment-from-none
                nvme_temps = HARDWARE.get_nvme_temperatures()
                modem_temps = HARDWARE.get_modem_temperatures()

                # Log modem version once
                if modem_version is None:
                    modem_version = HARDWARE.get_modem_version()  # pylint: disable=assignment-from-none
                    if modem_version is not None:
                        cloudlog.warning(f"Modem version: {modem_version}")

                if TICI and (network_info.get('state', None) == "REGISTERED"):
                    registered_count += 1
                else:
                    registered_count = 0

                if registered_count > 10:
                    cloudlog.warning(
                        f"Modem stuck in registered state {network_info}. nmcli conn up lte"
                    )
                    os.system("nmcli conn up lte")
                    registered_count = 0

            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(n)) for n in psutil.cpu_percent(percpu=True)
        ]
        msg.deviceState.gpuUsagePercent = int(
            round(HARDWARE.get_gpu_usage_percent()))
        msg.deviceState.networkType = network_type
        msg.deviceState.networkStrength = network_strength
        if network_info is not None:
            msg.deviceState.networkInfo = network_info
        if nvme_temps is not None:
            msg.deviceState.nvmeTempC = nvme_temps
        if modem_temps is not None:
            msg.deviceState.modemTempC = modem_temps

        msg.deviceState.screenBrightnessPercent = HARDWARE.get_screen_brightness(
        )
        msg.deviceState.batteryPercent = HARDWARE.get_battery_capacity()
        msg.deviceState.batteryCurrent = HARDWARE.get_battery_current()
        msg.deviceState.usbOnline = HARDWARE.get_usb_present()
        current_filter.update(msg.deviceState.batteryCurrent / 1e6)

        max_comp_temp = temp_filter.update(
            max(max(msg.deviceState.cpuTempC), msg.deviceState.memoryTempC,
                max(msg.deviceState.gpuTempC)))

        if handle_fan is not None:
            fan_speed = handle_fan(controller, max_comp_temp, fan_speed,
                                   startup_conditions["ignition"])
            msg.deviceState.fanSpeedPercentDesired = fan_speed

        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 is_offroad_for_5_min and max_comp_temp > OFFROAD_DANGER_TEMP:
            # If device is offroad we want to cool down before going onroad
            # since going onroad increases load and can make temps go over 107
            thermal_status = ThermalStatus.danger
        else:
            current_band = THERMAL_BANDS[thermal_status]
            band_idx = list(THERMAL_BANDS.keys()).index(thermal_status)
            if current_band.min_temp is not None and max_comp_temp < current_band.min_temp:
                thermal_status = list(THERMAL_BANDS.keys())[band_idx - 1]
            elif current_band.max_temp is not None and max_comp_temp > current_band.max_temp:
                thermal_status = list(THERMAL_BANDS.keys())[band_idx + 1]

        # **** 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 tested_branch:
                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_bool(
                "DisableUpdates") or params.get_bool("SnoozeUpdate")
        startup_conditions["not_uninstalling"] = not params.get_bool(
            "DoUninstall")
        startup_conditions["accepted_terms"] = params.get(
            "HasAcceptedTerms") == terms_version

        # 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 \
                                                   params.get_bool("Passive")
        startup_conditions["not_driver_view"] = not params.get_bool(
            "IsDriverViewEnabled")
        startup_conditions["not_taking_snapshot"] = not params.get_bool(
            "IsTakingSnapshot")
        # 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"]))

        if TICI:
            set_offroad_alert_if_changed("Offroad_StorageMissing",
                                         (not Path("/data/media").is_mount()))

        # Handle offroad/onroad transition
        should_start = all(startup_conditions.values())
        if should_start != should_start_prev or (count == 0):
            params.put_bool("IsOnroad", should_start)
            params.put_bool("IsOffroad", not should_start)
            HARDWARE.set_power_save(not should_start)

        if should_start:
            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)

            started_ts = None
            if off_ts is None:
                off_ts = sec_since_boot()

        # Offroad power monitoring
        power_monitor.calculate(peripheralState,
                                startup_conditions["ignition"])
        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(
            startup_conditions["ignition"], in_car, off_ts)

        # Check if we need to shut down
        if power_monitor.should_shutdown(peripheralState,
                                         startup_conditions["ignition"],
                                         in_car, 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
        ui_running = "ui" in (p.name for p in sm["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))

        last_ping = params.get("LastAthenaPingTime")
        if last_ping is not None:
            msg.deviceState.lastAthenaPingTime = int(last_ping)

        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:
            if EON and started_ts is None and msg.deviceState.memoryUsagePercent > 40:
                cloudlog.event("High offroad memory usage",
                               mem=msg.deviceState.memoryUsagePercent)

            cloudlog.event(
                "STATUS_PACKET",
                count=count,
                pandaStates=(strip_deprecated_keys(pandaStates.to_dict())
                             if pandaStates else None),
                peripheralState=strip_deprecated_keys(
                    peripheralState.to_dict()),
                location=(strip_deprecated_keys(
                    sm["gpsLocationExternal"].to_dict())
                          if sm.alive["gpsLocationExternal"] else None),
                deviceState=strip_deprecated_keys(msg.to_dict()))

        count += 1
Ejemplo n.º 21
0
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

    network_type = NetworkType.none
    network_strength = NetworkStrength.unknown
    network_info = None
    modem_version = None
    registered_count = 0

    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

    power_monitor = PowerMonitoring()
    no_panda_cnt = 0

    HARDWARE.initialize_hardware()
    thermal_config = HARDWARE.get_thermal_config()

    if params.get_bool("IsOnroad"):
        cloudlog.event("onroad flag not cleared")

    # dp
    dp_no_batt = params.get_bool("dp_no_batt")
    dp_temp_monitor = True
    dp_last_modified_temp_monitor = None

    dp_auto_shutdown = False
    dp_last_modified_auto_shutdown = None
    dp_auto_shutdown_last = False

    dp_auto_shutdown_in = 90
    dp_last_modified_auto_shutdown_in = None
    dp_auto_shutdown_in_last = 90

    dp_fan_mode = 0
    dp_fan_mode_last = None

    modified = None
    last_modified = None
    last_modified_check = None

    #dp
    prebuilt_file = '/data/openpilot/prebuilt'
    dp_prebuilt = params.get_bool("dp_prebuilt")
    if not os.path.isfile(prebuilt_file) and dp_prebuilt:
        os.system("touch /data/openpilot/prebuilt")
    elif os.path.isfile(prebuilt_file) and not dp_prebuilt:
        os.system("rm -fr /data/openpilot/prebuilt")

    # 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_files = ["/sys/kernel/debug/regulator/pm8994_s11/voltage"
                     ] + cpr_files
        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)

    # dp - light up = not started
    if JETSON:
        HARDWARE.led(True)

    while 1:

        # dp - load temp monitor conf
        last_modified_check, modified = get_last_modified(
            LAST_MODIFIED_THERMALD, last_modified_check, modified)
        if last_modified != modified:
            dp_temp_monitor, dp_last_modified_temp_monitor = param_get_if_updated(
                "dp_temp_monitor", "bool", dp_temp_monitor,
                dp_last_modified_temp_monitor)
            dp_auto_shutdown, dp_last_modified_auto_shutdown = param_get_if_updated(
                "dp_auto_shutdown", "bool", dp_auto_shutdown,
                dp_last_modified_auto_shutdown)
            dp_auto_shutdown_in, dp_last_modified_auto_shutdown_in = param_get_if_updated(
                "dp_auto_shutdown_in", "int", dp_auto_shutdown_in,
                dp_last_modified_auto_shutdown_in)
            dp_fan_mode, dp_fan_mode_last = param_get_if_updated(
                "dp_fan_mode", "int", dp_fan_mode, dp_fan_mode_last)
            last_modified = modified

        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 not JETSON and 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.clear_all(ParamKeyType.CLEAR_ON_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)
                network_info = HARDWARE.get_network_info()  # pylint: disable=assignment-from-none

                # Log modem version once
                if modem_version is None:
                    modem_version = HARDWARE.get_modem_version()  # pylint: disable=assignment-from-none
                    if modem_version is not None:
                        cloudlog.warning(f"Modem version: {modem_version}")

                if TICI and (network_info.get('state', None) == "REGISTERED"):
                    registered_count += 1
                else:
                    registered_count = 0

                if registered_count > 10:
                    cloudlog.warning(
                        f"Modem stuck in registered state {network_info}. nmcli conn up lte"
                    )
                    os.system("nmcli conn up lte")
                    registered_count = 0

            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.gpuUsagePercent = int(
            round(HARDWARE.get_gpu_usage_percent()))
        msg.deviceState.networkType = network_type
        msg.deviceState.networkStrength = network_strength
        if network_info is not None:
            msg.deviceState.networkInfo = network_info

        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 or dp_no_batt:
            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(dp_fan_mode, 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

        if not dp_temp_monitor and thermal_status in [
                ThermalStatus.red, ThermalStatus.danger
        ]:
            thermal_status = ThermalStatus.yellow
        # **** 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 tested_branch:
        #     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_bool(
                "DisableUpdates")
        startup_conditions["not_uninstalling"] = not params.get_bool(
            "DoUninstall")
        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 \
                                                   params.get_bool("Passive")
        startup_conditions["not_driver_view"] = not params.get_bool(
            "IsDriverViewEnabled")
        startup_conditions["not_taking_snapshot"] = not params.get_bool(
            "IsTakingSnapshot")
        # 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"]))

        if TICI:
            set_offroad_alert_if_changed("Offroad_NvmeMissing",
                                         (not Path("/data/media").is_mount()))

        # Handle offroad/onroad transition
        should_start = all(startup_conditions.values())
        if should_start != should_start_prev or (count == 0):
            params.put_bool("IsOnroad", should_start)
            params.put_bool("IsOffroad", not should_start)
            HARDWARE.set_power_save(not should_start)

        if should_start:
            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)

            started_ts = None
            if off_ts is None:
                off_ts = sec_since_boot()

        # dp - show led when off and vice versa
        if should_start != should_start_prev:
            HARDWARE.led(False if should_start else True)

        # Offroad power monitoring
        if not dp_no_batt:
            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,
                                         LEON):
            cloudlog.info(f"shutting device down, offroad since {off_ts}")
            # TODO: add function for blocking cloudlog instead of sleep
            time.sleep(10)
            HARDWARE.shutdown()

        # dp - auto shutdown
        # reset off_ts if we change auto shutdown related params
        if off_ts is not None:
            if dp_auto_shutdown:
                shutdown_sec = dp_auto_shutdown_in * 60
                sec_now = sec_since_boot() - off_ts
                if (shutdown_sec - 5) < sec_now:
                    msg.deviceState.chargingDisabled = True
                if shutdown_sec < sec_now:
                    time.sleep(10)
                    HARDWARE.shutdown()

            if dp_auto_shutdown_in_last != dp_auto_shutdown_in or dp_auto_shutdown_last != dp_auto_shutdown:
                off_ts = sec_since_boot()
            dp_auto_shutdown_last = dp_auto_shutdown
            dp_auto_shutdown_in_last = dp_auto_shutdown_in

        # 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))

        last_ping = params.get("LastAthenaPingTime")
        if last_ping is not None:
            msg.deviceState.lastAthenaPingTime = int(last_ping)

        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:
        #   if EON and started_ts is None and msg.deviceState.memoryUsagePercent > 40:
        #     cloudlog.event("High offroad memory usage", mem=msg.deviceState.memoryUsagePercent)
        #
        #   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
Ejemplo n.º 22
0
class DriverStatus():
  def __init__(self, monitor_on=False):
    self.pose = _DriverPose()
    self.monitor_on = monitor_on
    self.monitor_param_on = monitor_on
    self.monitor_valid = True   # variance needs to be low
    self.awareness = 1.
    self.driver_distracted = False
    self.driver_distraction_filter = FirstOrderFilter(0., _DISTRACTED_FILTER_TS, DT_DMON)
    self.variance_high = False
    self.variance_filter = FirstOrderFilter(0., _VARIANCE_FILTER_TS, DT_DMON)
    self.ts_last_check = 0.
    self.face_detected = False
    self.terminal_alert_cnt = 0
    self._set_timers()

  def _reset_filters(self):
    self.driver_distraction_filter.x = 0.
    self.variance_filter.x = 0.
    self.monitor_valid = True

  def _set_timers(self):
    if self.monitor_on:
      self.threshold_pre = _DISTRACTED_PRE_TIME / _DISTRACTED_TIME
      self.threshold_prompt = _DISTRACTED_PROMPT_TIME / _DISTRACTED_TIME
      self.step_change = DT_CTRL / _DISTRACTED_TIME
    else:
      self.threshold_pre = _AWARENESS_PRE_TIME / _AWARENESS_TIME
      self.threshold_prompt = _AWARENESS_PROMPT_TIME / _AWARENESS_TIME
      self.step_change = DT_CTRL / _AWARENESS_TIME

  def _is_driver_distracted(self, pose):
    # to be tuned and to learn the driver's normal pose
    pitch_error = pose.pitch - _PITCH_NATURAL_OFFSET
    yaw_error = pose.yaw - _YAW_NATURAL_OFFSET
    # add positive pitch allowance
    if pitch_error > 0.:
      pitch_error = max(pitch_error - _PITCH_POS_ALLOWANCE, 0.)
    pitch_error *= _PITCH_WEIGHT
    metric = np.sqrt(yaw_error**2 + pitch_error**2)
    #print "%02.4f" % np.degrees(pose.pitch), "%02.4f" % np.degrees(pitch_error), "%03.4f" % np.degrees(pose.pitch_offset), metric
    return 1 if metric > _METRIC_THRESHOLD else 0


  def get_pose(self, driver_monitoring, params):

    self.pose.roll, self.pose.pitch, self.pose.yaw = head_orientation_from_descriptor(driver_monitoring.descriptor)

    # TODO: DM data should not be in a list if they are not homogeneous
    if len(driver_monitoring.descriptor) > 6:
      self.face_detected = driver_monitoring.descriptor[6] > 0.
    else:
      self.face_detected = True

    self.driver_distracted = self._is_driver_distracted(self.pose)
    # first order filters
    self.driver_distraction_filter.update(self.driver_distracted)
    self.variance_high = driver_monitoring.std > _STD_THRESHOLD
    self.variance_filter.update(self.variance_high)

    monitor_param_on_prev = self.monitor_param_on
    monitor_valid_prev = self.monitor_valid

    # don't check for param too often as it's a kernel call
    ts = sec_since_boot()
    if ts - self.ts_last_check > 1.:
      self.monitor_param_on = params.get("IsDriverMonitoringEnabled") == "1"
      self.ts_last_check = ts

    self.monitor_valid = _monitor_hysteresis(self.variance_filter.x, monitor_valid_prev)
    self.monitor_on = self.monitor_valid and self.monitor_param_on
    if monitor_param_on_prev != self.monitor_param_on:
      self._reset_filters()
    self._set_timers()


  def update(self, events, driver_engaged, ctrl_active, standstill):

    driver_engaged |= (self.driver_distraction_filter.x < 0.37 and self.monitor_on)
    awareness_prev = self.awareness

    if (driver_engaged and self.awareness > 0.) or not ctrl_active:
      # always reset if driver is in control (unless we are in red alert state) or op isn't active
      self.awareness = 1.

    # only update if face is detected, driver is distracted and distraction filter is high
    if (not self.monitor_on or (self.driver_distraction_filter.x > 0.63 and self.driver_distracted and self.face_detected)) and \
       not (standstill and self.awareness - self.step_change <= self.threshold_prompt):
      self.awareness = max(self.awareness - self.step_change, -0.1)

    alert = None
    if self.awareness < 0.:
      # terminal red alert: disengagement required
      alert = 'driverDistracted' if self.monitor_on else 'driverUnresponsive'
      if awareness_prev >= 0.:
        self.terminal_alert_cnt += 1
    elif self.awareness <= self.threshold_prompt:
      # prompt orange alert
      alert = 'promptDriverDistracted' if self.monitor_on else 'promptDriverUnresponsive'
    elif self.awareness <= self.threshold_pre:
      # pre green alert
      alert = 'preDriverDistracted' if self.monitor_on else 'preDriverUnresponsive'

    if alert is not None:
      events.append(create_event(alert, [ET.WARNING]))

    return events
Ejemplo n.º 23
0
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
    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_connectivity_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"

        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:
            params.delete("Offroad_InvalidTime")
        if not time_valid and time_valid_prev:
            put_nonblocking("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")
                put_nonblocking(
                    "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")
                put_nonblocking("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")

        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:
            params.delete("Offroad_PandaFirmwareMismatch")
        if not fw_version_match and fw_version_match_prev:
            put_nonblocking(
                "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:
                put_nonblocking(
                    "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):
                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:
            put_nonblocking(
                "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

        # 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
Ejemplo n.º 24
0
class LatControlINDI():
  def __init__(self, CP):
    self.angle_steers_des = 0.

    A = np.array([[1.0, DT_CTRL, 0.0],
                  [0.0, 1.0, DT_CTRL],
                  [0.0, 0.0, 1.0]])
    C = np.array([[1.0, 0.0, 0.0],
                  [0.0, 1.0, 0.0]])

    # Q = np.matrix([[1e-2, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 10.0]])
    # R = np.matrix([[1e-2, 0.0], [0.0, 1e3]])

    # (x, l, K) = control.dare(np.transpose(A), np.transpose(C), Q, R)
    # K = np.transpose(K)
    K = np.array([[7.30262179e-01, 2.07003658e-04],
                  [7.29394177e+00, 1.39159419e-02],
                  [1.71022442e+01, 3.38495381e-02]])

    self.speed = 0.

    self.K = K
    self.A_K = A - np.dot(K, C)
    self.x = np.array([[0.], [0.], [0.]])

    self.enforce_rate_limit = CP.carName == "toyota"

    self._RC = (CP.lateralTuning.indi.timeConstantBP, CP.lateralTuning.indi.timeConstantV)
    self._G = (CP.lateralTuning.indi.actuatorEffectivenessBP, CP.lateralTuning.indi.actuatorEffectivenessV)
    self._outer_loop_gain = (CP.lateralTuning.indi.outerLoopGainBP, CP.lateralTuning.indi.outerLoopGainV)
    self._inner_loop_gain = (CP.lateralTuning.indi.innerLoopGainBP, CP.lateralTuning.indi.innerLoopGainV)

    self.sat_count_rate = 1.0 * DT_CTRL
    self.sat_limit = CP.steerLimitTimer
    self.steer_filter = FirstOrderFilter(0., self.RC, DT_CTRL)

    self.reset()

  @property
  def RC(self):
    return interp(self.speed, self._RC[0], self._RC[1])

  @property
  def G(self):
    return interp(self.speed, self._G[0], self._G[1])

  @property
  def outer_loop_gain(self):
    return interp(self.speed, self._outer_loop_gain[0], self._outer_loop_gain[1])

  @property
  def inner_loop_gain(self):
    return interp(self.speed, self._inner_loop_gain[0], self._inner_loop_gain[1])

  def reset(self):
    self.steer_filter.x = 0.
    self.output_steer = 0.
    self.sat_count = 0.
    self.speed = 0.

  def _check_saturation(self, control, check_saturation, limit):
    saturated = abs(control) == limit

    if saturated and check_saturation:
      self.sat_count += self.sat_count_rate
    else:
      self.sat_count -= self.sat_count_rate

    self.sat_count = clip(self.sat_count, 0.0, 1.0)

    return self.sat_count > self.sat_limit

  def update(self, active, CS, CP, VM, params, last_actuators, curvature, curvature_rate):
    self.speed = CS.vEgo
    # Update Kalman filter
    y = np.array([[math.radians(CS.steeringAngleDeg)], [math.radians(CS.steeringRateDeg)]])
    self.x = np.dot(self.A_K, self.x) + np.dot(self.K, y)

    indi_log = log.ControlsState.LateralINDIState.new_message()
    indi_log.steeringAngleDeg = math.degrees(self.x[0])
    indi_log.steeringRateDeg = math.degrees(self.x[1])
    indi_log.steeringAccelDeg = math.degrees(self.x[2])

    steers_des = VM.get_steer_from_curvature(-curvature, CS.vEgo, params.roll)
    steers_des += math.radians(params.angleOffsetDeg)
    indi_log.steeringAngleDesiredDeg = math.degrees(steers_des)

    rate_des = VM.get_steer_from_curvature(-curvature_rate, CS.vEgo, 0)
    indi_log.steeringRateDesiredDeg = math.degrees(rate_des)

    if CS.vEgo < 0.3 or not active:
      indi_log.active = False
      self.output_steer = 0.0
      self.steer_filter.x = 0.0
    else:
      # Expected actuator value
      self.steer_filter.update_alpha(self.RC)
      self.steer_filter.update(self.output_steer)

      # Compute acceleration error
      rate_sp = self.outer_loop_gain * (steers_des - self.x[0]) + rate_des
      accel_sp = self.inner_loop_gain * (rate_sp - self.x[1])
      accel_error = accel_sp - self.x[2]

      # Compute change in actuator
      g_inv = 1. / self.G
      delta_u = g_inv * accel_error

      # If steering pressed, only allow wind down
      if CS.steeringPressed and (delta_u * self.output_steer > 0):
        delta_u = 0

      # Enforce rate limit
      if self.enforce_rate_limit:
        steer_max = float(CarControllerParams.STEER_MAX)
        new_output_steer_cmd = steer_max * (self.steer_filter.x + delta_u)
        prev_output_steer_cmd = steer_max * self.output_steer
        new_output_steer_cmd = apply_toyota_steer_torque_limits(new_output_steer_cmd, prev_output_steer_cmd, prev_output_steer_cmd, CarControllerParams)
        self.output_steer = new_output_steer_cmd / steer_max
      else:
        self.output_steer = self.steer_filter.x + delta_u

      steers_max = get_steer_max(CP, CS.vEgo)
      self.output_steer = clip(self.output_steer, -steers_max, steers_max)

      indi_log.active = True
      indi_log.rateSetPoint = float(rate_sp)
      indi_log.accelSetPoint = float(accel_sp)
      indi_log.accelError = float(accel_error)
      indi_log.delayedOutput = float(self.steer_filter.x)
      indi_log.delta = float(delta_u)
      indi_log.output = float(self.output_steer)

      check_saturation = (CS.vEgo > 10.) and not CS.steeringRateLimited and not CS.steeringPressed
      indi_log.saturated = self._check_saturation(self.output_steer, check_saturation, steers_max)

    return float(self.output_steer), float(steers_des), indi_log