Example #1
0
    def update(self, active, v_ego, angle_steers, steer_override, d_poly,
               angle_offset, VM, PL):
        cur_time = sec_since_boot()
        self.mpc_updated = False
        # TODO: this creates issues in replay when rewinding time: mpc won't run
        if self.last_mpc_ts < PL.last_md_ts:
            self.last_mpc_ts = PL.last_md_ts
            self.angle_steers_des_prev = self.angle_steers_des_mpc

            curvature_factor = VM.curvature_factor(v_ego)

            l_poly = libmpc_py.ffi.new("double[4]", list(PL.PP.l_poly))
            r_poly = libmpc_py.ffi.new("double[4]", list(PL.PP.r_poly))
            p_poly = libmpc_py.ffi.new("double[4]", list(PL.PP.p_poly))

            # account for actuation delay
            self.cur_state = calc_states_after_delay(self.cur_state, v_ego,
                                                     angle_steers,
                                                     curvature_factor,
                                                     VM.CP.steerRatio,
                                                     VM.CP.steerActuatorDelay)

            v_ego_mpc = max(v_ego, 5.0)  # avoid mpc roughness due to low speed
            self.libmpc.run_mpc(self.cur_state, self.mpc_solution, l_poly,
                                r_poly, p_poly, PL.PP.l_prob, PL.PP.r_prob,
                                PL.PP.p_prob, curvature_factor, v_ego_mpc,
                                PL.PP.lane_width)

            # reset to current steer angle if not active or overriding
            if active:
                delta_desired = self.mpc_solution[0].delta[1]
            else:
                delta_desired = math.radians(angle_steers -
                                             angle_offset) / VM.CP.steerRatio

            self.cur_state[0].delta = delta_desired

            self.angle_steers_des_mpc = float(
                math.degrees(delta_desired * VM.CP.steerRatio) + angle_offset)
            self.angle_steers_des_time = cur_time
            self.mpc_updated = True

            # Real-Time Tuning:  Reset MPC if steerRateCost changed
            # TODO:  Figure out if this is the best way to accomplish the real-time change to steerRateCost
            if self.rtt_reset_mpc:
                self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE,
                                 MPC_COST_LAT.HEADING, VM.CP.steerRateCost)
                self.cur_state[0].delta = math.radians(
                    angle_steers) / VM.CP.steerRatio
                self.rtt_reset_mpc = False

            #  Check for infeasable MPC solution
            self.mpc_nans = np.any(np.isnan(list(self.mpc_solution[0].delta)))
            t = sec_since_boot()
            if self.mpc_nans:
                self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE,
                                 MPC_COST_LAT.HEADING, VM.CP.steerRateCost)
                self.cur_state[0].delta = math.radians(
                    angle_steers) / VM.CP.steerRatio

                if t > self.last_cloudlog_t + 5.0:
                    self.last_cloudlog_t = t
                    cloudlog.warning("Lateral mpc - nan: True")

        if v_ego < 0.3 or not active:
            output_steer = 0.0
            self.pid.reset()
        else:
            # TODO: ideally we should interp, but for tuning reasons we keep the mpc solution
            # constant for 0.05s.
            #dt = min(cur_time - self.angle_steers_des_time, _DT_MPC + _DT) + _DT  # no greater than dt mpc + dt, to prevent too high extraps
            #self.angle_steers_des = self.angle_steers_des_prev + (dt / _DT_MPC) * (self.angle_steers_des_mpc - self.angle_steers_des_prev)
            self.angle_steers_des = self.angle_steers_des_mpc
            steers_max = get_steer_max(VM.CP, v_ego)
            self.pid.pos_limit = steers_max
            self.pid.neg_limit = -steers_max
            steer_feedforward = self.angle_steers_des  # feedforward desired angle
            if VM.CP.steerControlType == car.CarParams.SteerControlType.torque:
                steer_feedforward *= v_ego**2  # proportional to realigning tire momentum (~ lateral accel)
            #deadzone = 0.0
            output_steer = self.pid.update(self.angle_steers_des,
                                           angle_steers,
                                           check_saturation=(v_ego > 10),
                                           override=steer_override,
                                           feedforward=steer_feedforward,
                                           speed=v_ego,
                                           deadzone=self.deadzone)

        self.sat_flag = self.pid.saturated
        return output_steer, float(self.angle_steers_des)
Example #2
0
    def calculate(self, health):
        try:
            now = sec_since_boot()

            # If health is None, we're probably not in a car, so we don't care
            if health is None or health.health.hwType == log.HealthData.HwType.unknown:
                with self.integration_lock:
                    self.last_measurement_time = None
                    self.next_pulsed_measurement_time = None
                    self.power_used_uWh = 0
                return

            # Low-pass battery voltage
            self.car_voltage_mV = (
                (health.health.voltage * CAR_VOLTAGE_LOW_PASS_K) +
                (self.car_voltage_mV * (1 - CAR_VOLTAGE_LOW_PASS_K)))

            # Cap the car battery power and save it in a param every 10-ish seconds
            self.car_battery_capacity_uWh = max(self.car_battery_capacity_uWh,
                                                0)
            self.car_battery_capacity_uWh = min(self.car_battery_capacity_uWh,
                                                CAR_BATTERY_CAPACITY_uWh)
            if now - self.last_save_time >= 10:
                put_nonblocking("CarBatteryCapacity",
                                str(int(self.car_battery_capacity_uWh)))
                self.last_save_time = now

            # First measurement, set integration time
            with self.integration_lock:
                if self.last_measurement_time is None:
                    self.last_measurement_time = now
                    return

            if (health.health.ignitionLine or health.health.ignitionCan):
                # If there is ignition, we integrate the charging rate of the car
                with self.integration_lock:
                    self.power_used_uWh = 0
                    integration_time_h = (now -
                                          self.last_measurement_time) / 3600
                    if integration_time_h < 0:
                        raise ValueError(
                            f"Negative integration time: {integration_time_h}h"
                        )
                    self.car_battery_capacity_uWh += (CAR_CHARGING_RATE_W *
                                                      1e6 * integration_time_h)
                    self.last_measurement_time = now
            else:
                # No ignition, we integrate the offroad power used by the device
                is_uno = health.health.hwType == log.HealthData.HwType.uno
                # Get current power draw somehow
                current_power = 0
                if get_battery_status() == 'Discharging':
                    # If the battery is discharging, we can use this measurement
                    # On C2: this is low by about 10-15%, probably mostly due to UNO draw not being factored in
                    current_power = ((get_battery_voltage() / 1000000) *
                                     (get_battery_current() / 1000000))
                elif (health.health.hwType in [
                        log.HealthData.HwType.whitePanda,
                        log.HealthData.HwType.greyPanda
                ]) and (health.health.current > 1):
                    # If white/grey panda, use the integrated current measurements if the measurement is not 0
                    # If the measurement is 0, the current is 400mA or greater, and out of the measurement range of the panda
                    # This seems to be accurate to about 5%
                    current_power = (
                        PANDA_OUTPUT_VOLTAGE *
                        panda_current_to_actual_current(health.health.current))
                elif (self.next_pulsed_measurement_time is not None) and (
                        self.next_pulsed_measurement_time <= now):
                    # TODO: Figure out why this is off by a factor of 3/4???
                    FUDGE_FACTOR = 1.33

                    # Turn off charging for about 10 sec in a thread that does not get killed on SIGINT, and perform measurement here to avoid blocking thermal
                    def perform_pulse_measurement(now):
                        try:
                            set_battery_charging(False)
                            time.sleep(5)

                            # Measure for a few sec to get a good average
                            voltages = []
                            currents = []
                            for _ in range(6):
                                voltages.append(get_battery_voltage())
                                currents.append(get_battery_current())
                                time.sleep(1)
                            current_power = ((mean(voltages) / 1000000) *
                                             (mean(currents) / 1000000))

                            self._perform_integration(
                                now, current_power * FUDGE_FACTOR)

                            # Enable charging again
                            set_battery_charging(True)
                        except Exception:
                            cloudlog.exception(
                                "Pulsed power measurement failed")

                    # Start pulsed measurement and return
                    threading.Thread(target=perform_pulse_measurement,
                                     args=(now, )).start()
                    self.next_pulsed_measurement_time = None
                    return

                elif self.next_pulsed_measurement_time is None and not is_uno:
                    # On a charging EON with black panda, or drawing more than 400mA out of a white/grey one
                    # Only way to get the power draw is to turn off charging for a few sec and check what the discharging rate is
                    # We shouldn't do this very often, so make sure it has been some long-ish random time interval
                    self.next_pulsed_measurement_time = now + random.randint(
                        120, 180)
                    return
                else:
                    # Do nothing
                    return

                # Do the integration
                self._perform_integration(now, current_power)
        except Exception:
            cloudlog.exception("Power monitoring calculation failed")
Example #3
0
def thermald_thread():
    setup_eon_fan()

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

    # 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.)
    services_killed = False
    # 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
        try:
            statvfs = os.statvfs(ROOT)
        except OSError:
            os.mkdir(ROOT)
            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/present") as f:
            msg.thermal.usbOnline = bool(int(f.read()))

        current_filter.update(msg.thermal.batteryCurrent / 1e6)

        # TODO: add car battery voltage check
        max_cpu_temp = max(msg.thermal.cpu0, msg.thermal.cpu1,
                           msg.thermal.cpu2, msg.thermal.cpu3) / 10.0
        max_comp_temp = max(max_cpu_temp, msg.thermal.mem / 10.,
                            msg.thermal.gpu / 10.)
        bat_temp = msg.thermal.bat / 1000.
        fan_speed = handle_fan(max_cpu_temp, bat_temp, fan_speed)
        msg.thermal.fanSpeed = fan_speed

        # thermal logic with hysterisis
        if max_cpu_temp > 107. or bat_temp >= 63.:
            # onroad not allowed
            thermal_status = ThermalStatus.danger
        elif max_comp_temp > 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")

        # 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
                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.  # 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
Example #4
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_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)

  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.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)
      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 != should_start_prev or (count == 0):
      params.put_bool("IsOffroad", not should_start)
      HARDWARE.set_power_save(not should_start)
      if TICI and not params.get_bool("EnableLteOnroad"):
        fxn = "off" if should_start else "on"
        os.system(f"nmcli radio wwan {fxn}")

    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(pandaState)
    msg.deviceState.offroadPowerUsageUwh = power_monitor.get_power_used()
    msg.deviceState.carBatteryCapacityUwh = max(0, power_monitor.get_car_battery_capacity())

    # Check if we need to disable charging (handled by boardd)
    msg.deviceState.chargingDisabled = power_monitor.should_disable_charging(pandaState, off_ts)

    # Check if we need to shut down
    if power_monitor.should_shutdown(pandaState, off_ts, started_seen):
      cloudlog.info(f"shutting device down, offroad since {off_ts}")
      # TODO: add function for blocking cloudlog instead of sleep
      time.sleep(10)
      HARDWARE.shutdown()

    # If UI has crashed, set the brightness to reasonable non-zero value
    manager_state = messaging.recv_one_or_none(managerState_sock)
    if manager_state is not None:
      ui_running = "ui" in (p.name for p in manager_state.managerState.processes if p.running)
      if ui_running_prev and not ui_running:
        HARDWARE.set_screen_brightness(20)
      ui_running_prev = ui_running

    msg.deviceState.chargingError = current_filter.x > 0. and msg.deviceState.batteryPercent < 90  # if current is positive, then battery is being discharged
    msg.deviceState.started = started_ts is not None
    msg.deviceState.startedMonoTime = int(1e9*(started_ts or 0))

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

    if EON and not is_uno:
      set_offroad_alert_if_changed("Offroad_ChargeDisabled", (not usb_power))

    should_start_prev = should_start
    startup_conditions_prev = startup_conditions.copy()

    # report to server once every 10 minutes
    if (count % int(600. / DT_TRML)) == 0:
      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
Example #5
0
    def update(self, c):
        # ******************* do can recv *******************
        canMonoTimes = []

        self.cp.update(int(sec_since_boot() * 1e9), False)
        self.epas_cp.update(int(sec_since_boot() * 1e9), False)

        self.CS.update(self.cp, self.epas_cp)

        # create message
        ret = car.CarState.new_message()

        # speeds
        ret.vEgo = self.CS.v_ego
        ret.aEgo = self.CS.a_ego
        ret.vEgoRaw = self.CS.v_ego_raw
        ret.yawRate = self.VM.yaw_rate(self.CS.angle_steers * CV.DEG_TO_RAD,
                                       self.CS.v_ego)
        ret.standstill = self.CS.standstill
        ret.wheelSpeeds.fl = self.CS.v_wheel_fl
        ret.wheelSpeeds.fr = self.CS.v_wheel_fr
        ret.wheelSpeeds.rl = self.CS.v_wheel_rl
        ret.wheelSpeeds.rr = self.CS.v_wheel_rr

        # gas pedal, we don't use with with interceptor so it's always 0/False
        ret.gas = self.CS.user_gas
        if not self.CP.enableGasInterceptor:
            ret.gasPressed = self.CS.user_gas_pressed
        else:
            ret.gasPressed = self.CS.user_gas_pressed

        # brake pedal
        ret.brakePressed = (self.CS.brake_pressed != 0) and (
            self.CS.cstm_btns.get_button_status("brake") == 0)
        # FIXME: read sendcan for brakelights
        brakelights_threshold = 0.1
        ret.brakeLights = bool(self.CS.brake_switch
                               or c.actuators.brake > brakelights_threshold)

        # steering wheel
        ret.steeringAngle = self.CS.angle_steers
        ret.steeringRate = self.CS.angle_steers_rate

        # gear shifter lever
        ret.gearShifter = self.CS.gear_shifter

        ret.steeringTorque = self.CS.steer_torque_driver
        ret.steeringPressed = self.CS.steer_override

        # cruise state
        ret.cruiseState.enabled = True  #self.CS.pcm_acc_status != 0
        ret.cruiseState.speed = self.CS.v_cruise_pcm * CV.KPH_TO_MS
        ret.cruiseState.available = bool(self.CS.main_on)
        ret.cruiseState.speedOffset = self.CS.cruise_speed_offset
        ret.cruiseState.standstill = False

        # TODO: button presses
        buttonEvents = []
        ret.leftBlinker = bool(self.CS.left_blinker_on)
        ret.rightBlinker = bool(self.CS.right_blinker_on)

        ret.doorOpen = not self.CS.door_all_closed
        ret.seatbeltUnlatched = not self.CS.seatbelt

        if self.CS.left_blinker_on != self.CS.prev_left_blinker_on:
            be = car.CarState.ButtonEvent.new_message()
            be.type = 'leftBlinker'
            be.pressed = self.CS.left_blinker_on != 0
            buttonEvents.append(be)

        if self.CS.right_blinker_on != self.CS.prev_right_blinker_on:
            be = car.CarState.ButtonEvent.new_message()
            be.type = 'rightBlinker'
            be.pressed = self.CS.right_blinker_on != 0
            buttonEvents.append(be)

        if self.CS.cruise_buttons != self.CS.prev_cruise_buttons:
            be = car.CarState.ButtonEvent.new_message()
            be.type = 'unknown'
            if self.CS.cruise_buttons != 0:
                be.pressed = True
                but = self.CS.cruise_buttons
            else:
                be.pressed = False
                but = self.CS.prev_cruise_buttons
            if but == CruiseButtons.RES_ACCEL:
                be.type = 'accelCruise'
            elif but == CruiseButtons.DECEL_SET:
                be.type = 'decelCruise'
            elif but == CruiseButtons.CANCEL:
                be.type = 'cancel'
            elif but == CruiseButtons.MAIN:
                be.type = 'altButton3'
            buttonEvents.append(be)

        if self.CS.cruise_buttons != self.CS.prev_cruise_buttons:
            be = car.CarState.ButtonEvent.new_message()
            be.type = 'unknown'
            be.pressed = bool(self.CS.cruise_buttons)
            buttonEvents.append(be)
        ret.buttonEvents = buttonEvents

        # events
        # TODO: I don't like the way capnp does enums
        # These strings aren't checked at compile time
        events = []
        if not self.CS.can_valid:
            self.can_invalid_count += 1
            if self.can_invalid_count >= 5:
                events.append(
                    create_event('commIssue',
                                 [ET.NO_ENTRY, ET.IMMEDIATE_DISABLE]))
        else:
            self.can_invalid_count = 0
        if self.CS.steer_error:
            if self.CS.cstm_btns.get_button_status("steer") == 0:
                events.append(
                    create_event('steerUnavailable',
                                 [ET.NO_ENTRY, ET.WARNING]))
        elif self.CS.steer_warning:
            if self.CS.cstm_btns.get_button_status("steer") == 0:
                events.append(
                    create_event('steerTempUnavailable',
                                 [ET.NO_ENTRY, ET.IMMEDIATE_DISABLE]))
        if self.CS.brake_error:
            events.append(
                create_event(
                    'brakeUnavailable',
                    [ET.NO_ENTRY, ET.IMMEDIATE_DISABLE, ET.PERMANENT]))
        if not ret.gearShifter == 'drive':
            events.append(
                create_event('wrongGear', [ET.NO_ENTRY, ET.SOFT_DISABLE]))
        if ret.doorOpen:
            events.append(
                create_event('doorOpen', [ET.NO_ENTRY, ET.SOFT_DISABLE]))
        if ret.seatbeltUnlatched:
            events.append(
                create_event('seatbeltNotLatched',
                             [ET.NO_ENTRY, ET.SOFT_DISABLE]))
        if self.CS.esp_disabled:
            events.append(
                create_event('espDisabled', [ET.NO_ENTRY, ET.SOFT_DISABLE]))
        if not self.CS.main_on:
            events.append(
                create_event('wrongCarMode', [ET.NO_ENTRY, ET.USER_DISABLE]))
        if ret.gearShifter == 'reverse':
            events.append(
                create_event('reverseGear',
                             [ET.NO_ENTRY, ET.IMMEDIATE_DISABLE]))
        if self.CS.brake_hold:
            events.append(
                create_event('brakeHold', [ET.NO_ENTRY, ET.USER_DISABLE]))
        if self.CS.park_brake:
            events.append(
                create_event('parkBrake', [ET.NO_ENTRY, ET.USER_DISABLE]))

        if self.CP.enableCruise and ret.vEgo < self.CP.minEnableSpeed:
            events.append(create_event('speedTooLow', [ET.NO_ENTRY]))


# Standard OP method to disengage:
# disable on pedals rising edge or when brake is pressed and speed isn't zero
#    if (ret.gasPressed and not self.gas_pressed_prev) or \
#       (ret.brakePressed and (not self.brake_pressed_prev or ret.vEgo > 0.001)):
#      events.append(create_event('steerTempUnavailable', [ET.NO_ENTRY, ET.IMMEDIATE_DISABLE]))

        if (self.CS.cstm_btns.get_button_status("brake") > 0):
            if ((self.CS.brake_pressed != 0) != self.brake_pressed_prev
                ):  #break not canceling when pressed
                self.CS.cstm_btns.set_button_status(
                    "brake", 2 if self.CS.brake_pressed != 0 else 1)
        else:
            if ret.brakePressed:
                events.append(
                    create_event('pedalPressed',
                                 [ET.NO_ENTRY, ET.USER_DISABLE]))
        if ret.gasPressed:
            events.append(create_event('pedalPressed', [ET.PRE_ENABLE]))

        # it can happen that car cruise disables while comma system is enabled: need to
        # keep braking if needed or if the speed is very low
        if self.CP.enableCruise and not ret.cruiseState.enabled and c.actuators.brake <= 0.:
            # non loud alert if cruise disbales below 25mph as expected (+ a little margin)
            if ret.vEgo < self.CP.minEnableSpeed + 2.:
                events.append(
                    create_event('speedTooLow', [ET.IMMEDIATE_DISABLE]))
            else:
                events.append(
                    create_event("cruiseDisabled", [ET.IMMEDIATE_DISABLE]))
        if self.CS.CP.minEnableSpeed > 0 and ret.vEgo < 0.001:
            events.append(create_event('manualRestart', [ET.WARNING]))

        cur_time = sec_since_boot()
        enable_pressed = False
        # handle button presses
        for b in ret.buttonEvents:

            # do enable on both accel and decel buttons
            if b.type == "altButton3" and not b.pressed:
                print "enabled pressed at", cur_time
                self.last_enable_pressed = cur_time
                enable_pressed = True

            # do disable on button down
            if b.type == "cancel" and b.pressed:
                events.append(create_event('buttonCancel', [ET.USER_DISABLE]))

        if self.CP.enableCruise:
            # KEEP THIS EVENT LAST! send enable event if button is pressed and there are
            # NO_ENTRY events, so controlsd will display alerts. Also not send enable events
            # too close in time, so a no_entry will not be followed by another one.
            # TODO: button press should be the only thing that triggers enble
            if ((cur_time - self.last_enable_pressed) < 0.2 and
                (cur_time - self.last_enable_sent) > 0.2 and
                ret.cruiseState.enabled) or \
               (enable_pressed and get_events(events, [ET.NO_ENTRY])):
                events.append(create_event('buttonEnable', [ET.ENABLE]))
                self.last_enable_sent = cur_time
        elif enable_pressed:
            events.append(create_event('buttonEnable', [ET.ENABLE]))

        ret.events = events
        ret.canMonoTimes = canMonoTimes

        # update previous brake/gas pressed
        self.gas_pressed_prev = ret.gasPressed
        self.brake_pressed_prev = self.CS.brake_pressed != 0

        # cast to reader so it can't be modified
        return ret.as_reader()
Example #6
0
def thermald_thread():
    setup_eon_fan()

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

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

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

    # Make sure charging is enabled
    charging_disabled = False
    os.system('echo "1" > /sys/class/power_supply/battery/charging_enabled')

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

        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") == terms_version
        completed_training = params.get(
            "CompletedTrainingVersion") == training_version

        should_start = ignition

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

        # with 2% left, we killall, otherwise the phone will take a long time to boot
        should_start = should_start and msg.thermal.freeSpace > 0.02

        # confirm we have completed training and aren't uninstalling
        should_start = should_start and accepted_terms and (
            passive or completed_training) and (not do_uninstall)

        # if any CPU gets above 107 or the battery gets above 63, kill all processes
        # controls will warn with CPU above 95 or battery above 60
        if thermal_status >= ThermalStatus.danger:
            # 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.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.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
Example #7
0
 def update(self, timeout=-1):
     msgs = []
     for sock in self.poller.poll(timeout):
         msgs.append(recv_one(sock))
     self.update_msgs(sec_since_boot(), msgs)
Example #8
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')

    ignition = False
    fan_speed = 0
    count = 0

    off_ts = None
    started_ts = None
    started_seen = False
    thermal_status = ThermalStatus.green
    thermal_status_prev = ThermalStatus.green
    usb_power = True
    usb_power_prev = True
    current_branch = get_git_branch()

    network_type = NetworkType.none
    network_strength = NetworkStrength.unknown

    current_filter = FirstOrderFilter(0., CURRENT_TAU, DT_TRML)
    cpu_temp_filter = FirstOrderFilter(0., CPU_TEMP_TAU, DT_TRML)
    health_prev = None
    fw_version_match_prev = True
    current_update_alert = None
    time_valid_prev = True
    should_start_prev = False
    handle_fan = None
    is_uno = False
    has_relay = False

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

    while 1:
        health = messaging.recv_sock(health_sock, wait=True)
        location = messaging.recv_sock(location_sock)
        location = location.gpsLocation if location else None
        msg = read_thermal()

        if health is not None:
            usb_power = health.health.usbPowerMode != log.HealthData.UsbPowerMode.client

            # If we lose connection to the panda, wait 5 seconds before going offroad
            if health.health.hwType == log.HealthData.HwType.unknown:
                no_panda_cnt += 1
                if no_panda_cnt > DISCONNECT_TIMEOUT / DT_TRML:
                    if ignition:
                        cloudlog.error("Lost panda connection while onroad")
                    ignition = False
            else:
                no_panda_cnt = 0
                ignition = health.health.ignitionLine or health.health.ignitionCan

            # Setup fan handler on first connect to panda
            if handle_fan is None and health.health.hwType != log.HealthData.HwType.unknown:
                is_uno = health.health.hwType == log.HealthData.HwType.uno
                has_relay = health.health.hwType in [
                    log.HealthData.HwType.blackPanda,
                    log.HealthData.HwType.uno, log.HealthData.HwType.dos
                ]

                if is_uno or not ANDROID:
                    cloudlog.info("Setting up UNO fan handler")
                    handle_fan = handle_fan_uno
                else:
                    cloudlog.info("Setting up EON fan handler")
                    setup_eon_fan()
                    handle_fan = handle_fan_eon

            # Handle disconnect
            if health_prev is not None:
                if health.health.hwType == log.HealthData.HwType.unknown and \
                  health_prev.health.hwType != log.HealthData.HwType.unknown:
                    params.panda_disconnect()
            health_prev = health

        # get_network_type is an expensive call. update every 10s
        if (count % int(10. / DT_TRML)) == 0:
            try:
                network_type = get_network_type()
                network_strength = get_network_strength(network_type)
            except Exception:
                cloudlog.exception("Error getting network status")

        msg.thermal.freeSpace = get_available_percent(default=100.0) / 100.0
        msg.thermal.memUsedPercent = int(round(
            psutil.virtual_memory().percent))
        msg.thermal.cpuPerc = int(round(psutil.cpu_percent()))
        msg.thermal.networkType = network_type
        msg.thermal.networkStrength = network_strength
        msg.thermal.batteryPercent = get_battery_capacity()
        msg.thermal.batteryStatus = get_battery_status()
        msg.thermal.batteryCurrent = get_battery_current()
        msg.thermal.batteryVoltage = get_battery_voltage()
        msg.thermal.usbOnline = get_usb_present()

        # Fake battery levels on uno for frame
        if is_uno:
            msg.thermal.batteryPercent = 100
            msg.thermal.batteryStatus = "Charging"
            msg.thermal.bat = 0

        current_filter.update(msg.thermal.batteryCurrent / 1e6)

        # TODO: add car battery voltage check
        max_cpu_temp = cpu_temp_filter.update(
            max(msg.thermal.cpu0, msg.thermal.cpu1, msg.thermal.cpu2,
                msg.thermal.cpu3) / 10.0)

        max_comp_temp = max(max_cpu_temp, msg.thermal.mem / 10.,
                            msg.thermal.gpu / 10.)
        bat_temp = msg.thermal.bat / 1000.

        if handle_fan is not None:
            fan_speed = handle_fan(max_cpu_temp, bat_temp, fan_speed, ignition)
            msg.thermal.fanSpeed = fan_speed

        # If device is offroad we want to cool down before going onroad
        # since going onroad increases load and can make temps go over 107
        # We only do this if there is a relay that prevents the car from faulting
        if max_cpu_temp > 107. or bat_temp >= 63. or (has_relay and
                                                      (started_ts is None)
                                                      and max_cpu_temp > 70.0):
            # onroad not allowed
            thermal_status = ThermalStatus.danger
        elif max_comp_temp > 96.0 or bat_temp > 60.:
            # hysteresis between onroad not allowed and engage not allowed
            thermal_status = clip(thermal_status, ThermalStatus.red,
                                  ThermalStatus.danger)
        elif max_cpu_temp > 94.0:
            # hysteresis between engage not allowed and uploader not allowed
            thermal_status = clip(thermal_status, ThermalStatus.yellow,
                                  ThermalStatus.red)
        elif max_cpu_temp > 80.0:
            # uploader not allowed
            thermal_status = ThermalStatus.yellow
        elif max_cpu_temp > 75.0:
            # hysteresis between uploader not allowed and all good
            thermal_status = clip(thermal_status, ThermalStatus.green,
                                  ThermalStatus.yellow)
        else:
            # all good
            thermal_status = ThermalStatus.green

        # **** starting logic ****

        # Check for last update time and display alerts if needed
        now = datetime.datetime.utcnow()

        # show invalid date/time alert
        time_valid = now.year >= 2019
        if time_valid and not time_valid_prev:
            set_offroad_alert("Offroad_InvalidTime", False)
        if not time_valid and time_valid_prev:
            set_offroad_alert("Offroad_InvalidTime", True)
        time_valid_prev = time_valid

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

        update_failed_count = params.get("UpdateFailedCount")
        update_failed_count = 0 if update_failed_count is None else int(
            update_failed_count)
        last_update_exception = params.get("LastUpdateException",
                                           encoding='utf8')

        if update_failed_count > 15 and last_update_exception is not None:
            if current_branch in ["release2", "dashcam"]:
                extra_text = "Ensure the software is correctly installed"
            else:
                extra_text = last_update_exception

            if current_update_alert != "update" + extra_text:
                current_update_alert = "update" + extra_text
                set_offroad_alert("Offroad_ConnectivityNeeded", False)
                set_offroad_alert("Offroad_ConnectivityNeededPrompt", False)
                set_offroad_alert("Offroad_UpdateFailed",
                                  True,
                                  extra_text=extra_text)
        elif dt.days > DAYS_NO_CONNECTIVITY_MAX and update_failed_count > 1:
            if current_update_alert != "expired":
                current_update_alert = "expired"
                set_offroad_alert("Offroad_UpdateFailed", False)
                set_offroad_alert("Offroad_ConnectivityNeededPrompt", False)
                set_offroad_alert("Offroad_ConnectivityNeeded", True)
        elif dt.days > DAYS_NO_CONNECTIVITY_PROMPT:
            remaining_time = str(max(DAYS_NO_CONNECTIVITY_MAX - dt.days, 0))
            if current_update_alert != "prompt" + remaining_time:
                current_update_alert = "prompt" + remaining_time
                set_offroad_alert("Offroad_UpdateFailed", False)
                set_offroad_alert("Offroad_ConnectivityNeeded", False)
                set_offroad_alert("Offroad_ConnectivityNeededPrompt",
                                  True,
                                  extra_text=f"{remaining_time} days.")
        elif current_update_alert is not None:
            current_update_alert = None
            set_offroad_alert("Offroad_UpdateFailed", False)
            set_offroad_alert("Offroad_ConnectivityNeeded", False)
            set_offroad_alert("Offroad_ConnectivityNeededPrompt", False)

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

        panda_signature = params.get("PandaFirmware")
        fw_version_match = (panda_signature is None) or (
            panda_signature == FW_SIGNATURE
        )  # don't show alert is no panda is connected (None)

        should_start = ignition

        # with 2% left, we killall, otherwise the phone will take a long time to boot
        should_start = should_start and msg.thermal.freeSpace > 0.02

        # confirm we have completed training and aren't uninstalling
        should_start = should_start and accepted_terms and completed_training and (
            not do_uninstall)

        # check for firmware mismatch
        should_start = should_start and fw_version_match

        # check if system time is valid
        should_start = should_start and time_valid

        # don't start while taking snapshot
        if not should_start_prev:
            is_viewing_driver = params.get("IsDriverViewEnabled") == b"1"
            is_taking_snapshot = params.get("IsTakingSnapshot") == b"1"
            should_start = should_start and (not is_taking_snapshot) and (
                not is_viewing_driver)

        if fw_version_match and not fw_version_match_prev:
            set_offroad_alert("Offroad_PandaFirmwareMismatch", False)
        if not fw_version_match and fw_version_match_prev:
            set_offroad_alert("Offroad_PandaFirmwareMismatch", True)

        # if any CPU gets above 107 or the battery gets above 63, kill all processes
        # controls will warn with CPU above 95 or battery above 60
        if thermal_status >= ThermalStatus.danger:
            should_start = False
            if thermal_status_prev < ThermalStatus.danger:
                set_offroad_alert("Offroad_TemperatureTooHigh", True)
        else:
            if thermal_status_prev >= ThermalStatus.danger:
                set_offroad_alert("Offroad_TemperatureTooHigh", False)

        if should_start:
            if not should_start_prev:
                params.delete("IsOffroad")

            off_ts = None
            if started_ts is None:
                started_ts = sec_since_boot()
                started_seen = True
                os.system(
                    'echo performance > /sys/class/devfreq/soc:qcom,cpubw/governor'
                )
        else:
            if should_start_prev or (count == 0):
                put_nonblocking("IsOffroad", "1")

            started_ts = None
            if off_ts is None:
                off_ts = sec_since_boot()
                os.system(
                    'echo powersave > /sys/class/devfreq/soc:qcom,cpubw/governor'
                )

        # Offroad power monitoring
        pm.calculate(health)
        msg.thermal.offroadPowerUsage = pm.get_power_used()
        msg.thermal.carBatteryCapacity = pm.get_car_battery_capacity()

        # Check if we need to disable charging (handled by boardd)
        msg.thermal.chargingDisabled = pm.should_disable_charging(
            health, off_ts)

        # Check if we need to shut down
        if pm.should_shutdown(health, off_ts, started_seen, LEON):
            cloudlog.info(f"shutting device down, offroad since {off_ts}")
            # TODO: add function for blocking cloudlog instead of sleep
            time.sleep(10)
            os.system('LD_LIBRARY_PATH="" svc power shutdown')

        msg.thermal.chargingError = current_filter.x > 0. and msg.thermal.batteryPercent < 90  # if current is positive, then battery is being discharged
        msg.thermal.started = started_ts is not None
        msg.thermal.startedTs = int(1e9 * (started_ts or 0))

        msg.thermal.thermalStatus = thermal_status
        thermal_sock.send(msg.to_bytes())

        if usb_power_prev and not usb_power:
            set_offroad_alert("Offroad_ChargeDisabled", True)
        elif usb_power and not usb_power_prev:
            set_offroad_alert("Offroad_ChargeDisabled", False)

        thermal_status_prev = thermal_status
        usb_power_prev = usb_power
        fw_version_match_prev = fw_version_match
        should_start_prev = should_start

        # report to server once per minute
        if (count % int(60. / DT_TRML)) == 0:
            cloudlog.event("STATUS_PACKET",
                           count=count,
                           health=(health.to_dict() if health else None),
                           location=(location.to_dict() if location else None),
                           thermal=msg.to_dict())

        count += 1
Example #9
0
  def update(self, c):
    # ******************* do can recv *******************
    canMonoTimes = []

    self.cp.update(int(sec_since_boot() * 1e9), False)

    self.CS.update(self.cp)

    # create message
    ret = car.CarState.new_message()

    # speeds
    ret.vEgo = self.CS.v_ego
    ret.vEgoRaw = self.CS.v_ego_raw
    ret.aEgo = self.CS.a_ego
    ret.yawRate = self.VM.yaw_rate(self.CS.angle_steers * CV.DEG_TO_RAD, self.CS.v_ego)
    ret.standstill = self.CS.standstill
    ret.wheelSpeeds.fl = self.CS.v_wheel_fl
    ret.wheelSpeeds.fr = self.CS.v_wheel_fr
    ret.wheelSpeeds.rl = self.CS.v_wheel_rl
    ret.wheelSpeeds.rr = self.CS.v_wheel_rr

    # gear shifter
    ret.gearShifter = self.CS.gear_shifter

    # gas pedal
    ret.gas = self.CS.car_gas
    ret.gasPressed = self.CS.pedal_gas > 0

    # brake pedal
    ret.brake = self.CS.user_brake
    ret.brakePressed = self.CS.brake_pressed
    ret.brakeLights = self.CS.brake_lights

    # steering wheel
    ret.steeringAngle = self.CS.angle_steers
    ret.steeringRate = self.CS.angle_steers_rate

    ret.steeringTorque = self.CS.steer_torque_driver
    ret.steeringPressed = self.CS.steer_override

    # cruise state
    ret.cruiseState.enabled = self.CS.pcm_acc_status  # same as main_on
    ret.cruiseState.speed = self.CS.v_cruise_pcm * CV.KPH_TO_MS
    ret.cruiseState.available = self.CS.main_on
    ret.cruiseState.speedOffset = 0.
    # ignore standstill in hybrid rav4, since pcm allows to restart without
    # receiving any special command
    ret.cruiseState.standstill = False

    # TODO: button presses
    buttonEvents = []

    if self.CS.left_blinker_on != self.CS.prev_left_blinker_on:
      be = car.CarState.ButtonEvent.new_message()
      be.type = 'leftBlinker'
      be.pressed = self.CS.left_blinker_on != 0
      buttonEvents.append(be)

    if self.CS.right_blinker_on != self.CS.prev_right_blinker_on:
      be = car.CarState.ButtonEvent.new_message()
      be.type = 'rightBlinker'
      be.pressed = self.CS.right_blinker_on != 0
      buttonEvents.append(be)

    ret.buttonEvents = buttonEvents
    ret.leftBlinker = bool(self.CS.left_blinker_on)
    ret.rightBlinker = bool(self.CS.right_blinker_on)

    ret.doorOpen = not self.CS.door_all_closed
    ret.seatbeltUnlatched = not self.CS.seatbelt
    self.low_speed_alert = (ret.vEgo < self.CP.minSteerSpeed)

    ret.genericToggle = self.CS.generic_toggle

    # events
    events = []
    if not self.CS.can_valid:
      self.can_invalid_count += 1
      if self.can_invalid_count >= 5:
        events.append(create_event('commIssue', [ET.NO_ENTRY, ET.IMMEDIATE_DISABLE]))
    else:
      self.can_invalid_count = 0
    if not ret.gearShifter == 'drive':
      events.append(create_event('wrongGear', [ET.NO_ENTRY, ET.SOFT_DISABLE]))
    if ret.doorOpen:
      events.append(create_event('doorOpen', [ET.NO_ENTRY, ET.SOFT_DISABLE]))
    if ret.seatbeltUnlatched:
      events.append(create_event('seatbeltNotLatched', [ET.NO_ENTRY, ET.SOFT_DISABLE]))
    if self.CS.esp_disabled:
      events.append(create_event('espDisabled', [ET.NO_ENTRY, ET.SOFT_DISABLE]))
    if not self.CS.main_on:
      events.append(create_event('wrongCarMode', [ET.NO_ENTRY, ET.USER_DISABLE]))
    if ret.gearShifter == 'reverse':
      events.append(create_event('reverseGear', [ET.NO_ENTRY, ET.IMMEDIATE_DISABLE]))
    if self.CS.steer_error:
      events.append(create_event('steerUnavailable', [ET.NO_ENTRY, ET.IMMEDIATE_DISABLE, ET.PERMANENT]))

    if ret.cruiseState.enabled and not self.cruise_enabled_prev:
      events.append(create_event('pcmEnable', [ET.ENABLE]))
    elif not ret.cruiseState.enabled:
      events.append(create_event('pcmDisable', [ET.USER_DISABLE]))

    # disable on gas pedal and speed isn't zero. Gas pedal is used to resume ACC
    # from a 3+ second stop.
    if (ret.gasPressed and (not self.gas_pressed_prev) and ret.vEgo > 2.0):
      events.append(create_event('pedalPressed', [ET.NO_ENTRY, ET.USER_DISABLE]))

    if self.low_speed_alert:
      events.append(create_event('belowSteerSpeed', [ET.WARNING]))

    ret.events = events
    ret.canMonoTimes = canMonoTimes

    self.gas_pressed_prev = ret.gasPressed
    self.brake_pressed_prev = ret.brakePressed
    self.cruise_enabled_prev = ret.cruiseState.enabled

    return ret.as_reader()
Example #10
0
  def update(self, sm, pm, CP, VM):

    v_ego = sm['carState'].vEgo
    angle_steers = sm['carState'].steeringAngle
    active = sm['controlsState'].active

    angle_offset = sm['liveParameters'].angleOffset


    if not self.param_OpkrEnableLearner:
      kyd = kyd_conf()
      #self.steer_rate_cost = float(kyd.conf['steerRateCost'])
      self.sRBP = kyd.conf['sR_BP']
      self.sRBoost = kyd.conf['sR_Boost']
      boost_rate = interp(abs(angle_steers), self.sRBP, self.sRBoost)
      self.kyd_steerRatio = self.steerRatio + boost_rate

      self.sR_Cost = [1.5,1.32,1.15,0.99,0.84,0.72,0.62,0.53,0.36,0.275,0.20,0.175,0.15,0.11,0.05]
      #self.sR_Cost = [1.0,0.90,0.81,0.73,0.66,0.60,0.54,0.48,0.36,0.275,0.20,0.175,0.15,0.11,0.05] 
      #self.sR_Cost = [0.75,0.67,0.60,0.54,0.48,0.425,0.37,0.32,0.24,0.19,0.15,0.14,0.13,0.11,0.05]
      #self.sR_Cost = [0.50,0.46,0.425,0.395,0.37,0.34,0.315,0.29,0.23,0.185,0.15,0.14,0.13,0.11,0.05]
      #steerRatio = 10.0
      #"sR_BP": [0.0,2.0,4.0,6.0,8.0,10.0,12.0,14.0,20.0,27.0,35.0,40.0,45.0,60.0,100],
      #"sR_Boost": [0.0,0.7,1.3,1.9,2.5,3.05,3.55,4.0,5.0,5.7,6.2,6.35,6.4,6.5,8.0],
      self.steer_rate_cost = interp(abs(angle_steers), self.sRBP, self.sR_Cost)


    # Run MPC
    self.angle_steers_des_prev = self.angle_steers_des_mpc

    # Update vehicle model
    x = max(sm['liveParameters'].stiffnessFactor, 0.1)
    sr = max(sm['liveParameters'].steerRatio, 0.1)
    #VM.update_params(0.8, CP.steerRatio) # 타이어 강성계수 고정,조향비율 고정 계산
    VM.update_params(x, sr)

    curvature_factor = VM.curvature_factor(v_ego)

    self.LP.parse_model(sm['model'])

    # Lane change logic
    one_blinker = sm['carState'].leftBlinker != sm['carState'].rightBlinker
    below_lane_change_speed = v_ego < LANE_CHANGE_SPEED_MIN

    if sm['carState'].leftBlinker:
      self.lane_change_direction = LaneChangeDirection.left
    elif sm['carState'].rightBlinker:
      self.lane_change_direction = LaneChangeDirection.right

    if (not active) or (self.lane_change_run_timer > LANE_CHANGE_TIME_MAX) or (not one_blinker) or (not self.lane_change_enabled):
      self.lane_change_state = LaneChangeState.off
      self.lane_change_direction = LaneChangeDirection.none
    else:
      torque_applied = sm['carState'].steeringPressed and \
                       ((sm['carState'].steeringTorque > 0 and self.lane_change_direction == LaneChangeDirection.left) or
                        (sm['carState'].steeringTorque < 0 and self.lane_change_direction == LaneChangeDirection.right))

      blindspot_detected = ((sm['carState'].leftBlindspot and self.lane_change_direction == LaneChangeDirection.left) or
                            (sm['carState'].rightBlindspot and self.lane_change_direction == LaneChangeDirection.right))

      lane_change_prob = self.LP.l_lane_change_prob + self.LP.r_lane_change_prob

      # State transitions
      # off
      if self.lane_change_state == LaneChangeState.off and one_blinker and not self.prev_one_blinker and not below_lane_change_speed:
        self.lane_change_state = LaneChangeState.preLaneChange
        self.lane_change_ll_prob = 1.0
        self.lane_change_wait_timer = 0

      # pre
      elif self.lane_change_state == LaneChangeState.preLaneChange:
        self.lane_change_wait_timer += DT_MDL

        if not one_blinker or below_lane_change_speed:
          self.lane_change_state = LaneChangeState.off
        elif not blindspot_detected and (torque_applied or (self.lane_change_auto_delay and self.lane_change_wait_timer > self.lane_change_auto_delay)):
          self.lane_change_state = LaneChangeState.laneChangeStarting

      # starting
      elif self.lane_change_state == LaneChangeState.laneChangeStarting:
        # fade out over .5s
        self.lane_change_ll_prob = max(self.lane_change_ll_prob - 1.5*DT_MDL, 0.0)
        # 98% certainty
        if lane_change_prob < 0.02 and self.lane_change_ll_prob < 0.01:
          self.lane_change_state = LaneChangeState.laneChangeFinishing

      # finishing
      elif self.lane_change_state == LaneChangeState.laneChangeFinishing:
        # fade in laneline over 1s
        self.lane_change_ll_prob = min(self.lane_change_ll_prob + DT_MDL, 1.0)
        if one_blinker and self.lane_change_ll_prob > 0.99:
          self.lane_change_state = LaneChangeState.laneChangeDone

      # done
      elif self.lane_change_state == LaneChangeState.laneChangeDone:
        if not one_blinker:
          self.lane_change_state = LaneChangeState.off

    if self.lane_change_state in [LaneChangeState.off, LaneChangeState.preLaneChange]:
      self.lane_change_run_timer = 0.0
    else:
      self.lane_change_run_timer += DT_MDL

    self.prev_one_blinker = one_blinker

    desire = DESIRES[self.lane_change_direction][self.lane_change_state]

    # Turn off lanes during lane change
    if desire == log.PathPlan.Desire.laneChangeRight or desire == log.PathPlan.Desire.laneChangeLeft:
      self.LP.l_prob *= self.lane_change_ll_prob
      self.LP.r_prob *= self.lane_change_ll_prob
    self.LP.update_d_poly(v_ego)

    # account for actuation delay
    if self.param_OpkrEnableLearner:
      self.cur_state = calc_states_after_delay(self.cur_state, v_ego, angle_steers - angle_offset, curvature_factor, VM.sR, CP.steerActuatorDelay)
    else:
      self.cur_state = calc_states_after_delay(self.cur_state, v_ego, angle_steers - angle_offset, curvature_factor, self.kyd_steerRatio, CP.steerActuatorDelay)
    
    v_ego_mpc = max(v_ego, 5.0)  # avoid mpc roughness due to low speed
    self.libmpc.run_mpc(self.cur_state, self.mpc_solution,
                        list(self.LP.l_poly), list(self.LP.r_poly), list(self.LP.d_poly),
                        self.LP.l_prob, self.LP.r_prob, curvature_factor, v_ego_mpc, self.LP.lane_width)

    # reset to current steer angle if not active or overriding
    if active:
      delta_desired = self.mpc_solution[0].delta[1]
      if self.param_OpkrEnableLearner:
        rate_desired = math.degrees(self.mpc_solution[0].rate[0] * VM.sR)
      else:
        rate_desired = math.degrees(self.mpc_solution[0].rate[0] * self.kyd_steerRatio)
    else:
      if self.param_OpkrEnableLearner:
        delta_desired = math.radians(angle_steers - angle_offset) / VM.sR
      else:
        delta_desired = math.radians(angle_steers - angle_offset) / self.kyd_steerRatio
      rate_desired = 0.0

    self.cur_state[0].delta = delta_desired
    if self.param_OpkrEnableLearner:
      self.angle_steers_des_mpc = float(math.degrees(delta_desired * VM.sR) + angle_offset)
    else:
      self.angle_steers_des_mpc = float(math.degrees(delta_desired * self.kyd_steerRatio) + angle_offset)
    #  Check for infeasable MPC solution
    mpc_nans = any(math.isnan(x) for x in self.mpc_solution[0].delta)
    t = sec_since_boot()
    if mpc_nans:
      self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE, MPC_COST_LAT.HEADING, self.steer_rate_cost)
      if self.param_OpkrEnableLearner:
        self.cur_state[0].delta = math.radians(angle_steers - angle_offset) / VM.sR
      else:
        self.cur_state[0].delta = math.radians(angle_steers - angle_offset) / self.kyd_steerRatio

      if t > self.last_cloudlog_t + 5.0:
        self.last_cloudlog_t = t
        cloudlog.warning("Lateral mpc - nan: True")

    if self.mpc_solution[0].cost > 20000. or mpc_nans:   # TODO: find a better way to detect when MPC did not converge
      self.solution_invalid_cnt += 1
    else:
      self.solution_invalid_cnt = 0
    plan_solution_valid = self.solution_invalid_cnt < 3

    plan_send = messaging.new_message('pathPlan')
    plan_send.valid = sm.all_alive_and_valid(service_list=['carState', 'controlsState', 'liveParameters', 'model'])
    plan_send.pathPlan.laneWidth = float(self.LP.lane_width)
    plan_send.pathPlan.dPoly = [float(x) for x in self.LP.d_poly]
    plan_send.pathPlan.lPoly = [float(x) for x in self.LP.l_poly]
    plan_send.pathPlan.lProb = float(self.LP.l_prob)
    plan_send.pathPlan.rPoly = [float(x) for x in self.LP.r_poly]
    plan_send.pathPlan.rProb = float(self.LP.r_prob)

    plan_send.pathPlan.angleSteers = float(self.angle_steers_des_mpc)
    plan_send.pathPlan.rateSteers = float(rate_desired)
    plan_send.pathPlan.angleOffset = float(sm['liveParameters'].angleOffset)
    plan_send.pathPlan.mpcSolutionValid = bool(plan_solution_valid)
    plan_send.pathPlan.paramsValid = bool(sm['liveParameters'].valid)

    plan_send.pathPlan.desire = desire
    plan_send.pathPlan.laneChangeState = self.lane_change_state
    plan_send.pathPlan.laneChangeDirection = self.lane_change_direction

    if not self.param_OpkrEnableLearner:
      plan_send.pathPlan.steerRatio = float(self.kyd_steerRatio)

    pm.send('pathPlan', plan_send)

    if LOG_MPC:
      dat = messaging.new_message('liveMpc')
      dat.liveMpc.x = list(self.mpc_solution[0].x)
      dat.liveMpc.y = list(self.mpc_solution[0].y)
      dat.liveMpc.psi = list(self.mpc_solution[0].psi)
      dat.liveMpc.delta = list(self.mpc_solution[0].delta)
      dat.liveMpc.cost = self.mpc_solution[0].cost
      pm.send('liveMpc', dat)
Example #11
0
        ("ACC_STATUS", 0x17c, 0),
        ("PEDAL_GAS", 0x17c, 0),
        ("CRUISE_SETTING", 0x296, 0),
        ("LEFT_BLINKER", 0x326, 0),
        ("RIGHT_BLINKER", 0x326, 0),
        ("COUNTER", 0x324, 0),
        ("ENGINE_RPM", 0x17C, 0)
    ]
    checks = [
        (0x14a, 100),  # address, frequency
        (0x158, 100),
        (0x17c, 100),
        (0x191, 100),
        (0x1a4, 50),
        (0x326, 10),
        (0x1b0, 50),
        (0x1d0, 50),
        (0x305, 10),
        (0x324, 10),
        (0x405, 3),
    ]

    cp = CANParser("honda_civic_touring_2016_can", signals, checks, 0)
    print cp.vl

    while True:
        cp.update(int(sec_since_boot() * 1e9), True)
        print cp.vl
        print cp.can_valid
        time.sleep(0.01)
Example #12
0
    def update(self, sm, pm, CP, VM):
        v_ego = sm['carState'].vEgo
        angle_steers = sm['carState'].steeringAngle
        active = sm['controlsState'].active
        angle_offset = sm['liveParameters'].angleOffset

        steerRateCost = ntune_get('steerRateCost')

        if self.steer_rate_cost_prev != steerRateCost:
            self.steer_rate_cost_prev = steerRateCost

            self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE,
                             MPC_COST_LAT.HEADING, steerRateCost)
            self.cur_state[0].delta = math.radians(angle_steers -
                                                   angle_offset) / VM.sR

        # Run MPC
        self.angle_steers_des_prev = self.angle_steers_des_mpc

        # Update vehicle model
        x = max(sm['liveParameters'].stiffnessFactor, 0.1)

        if self.use_dynamic_sr:
            sr = interp(abs(self.angle_steers_des_mpc), [5., 15.],
                        [11.8, 16.2])
        else:
            if ntune_isEnabled('useLiveSteerRatio'):
                sr = max(sm['liveParameters'].steerRatio, 0.1)
            else:
                sr = max(ntune_get('steerRatio'), 0.1)

        VM.update_params(x, sr)

        curvature_factor = VM.curvature_factor(v_ego)

        self.LP.parse_model(sm['model'])

        # Lane change logic
        one_blinker = sm['carState'].leftBlinker != sm['carState'].rightBlinker
        below_lane_change_speed = v_ego < LANE_CHANGE_SPEED_MIN

        if sm['carState'].leftBlinker:
            self.lane_change_direction = LaneChangeDirection.left
        elif sm['carState'].rightBlinker:
            self.lane_change_direction = LaneChangeDirection.right

        if (not active) or (self.lane_change_timer > LANE_CHANGE_TIME_MAX) or (
                not one_blinker) or (not self.lane_change_enabled):
            self.lane_change_state = LaneChangeState.off
            self.lane_change_direction = LaneChangeDirection.none
        else:
            torque_applied = sm['carState'].steeringPressed and \
                             ((sm['carState'].steeringTorque > 0 and self.lane_change_direction == LaneChangeDirection.left) or
                              (sm['carState'].steeringTorque < 0 and self.lane_change_direction == LaneChangeDirection.right)) or \
                              self.auto_lane_change_enabled and \
                             (AUTO_LCA_START_TIME+1.0) > self.auto_lane_change_timer > AUTO_LCA_START_TIME

            blindspot_detected = (
                (sm['carState'].leftBlindspot
                 and self.lane_change_direction == LaneChangeDirection.left) or
                (sm['carState'].rightBlindspot
                 and self.lane_change_direction == LaneChangeDirection.right))

            lane_change_prob = self.LP.l_lane_change_prob + self.LP.r_lane_change_prob

            # State transitions
            # off
            if self.lane_change_state == LaneChangeState.off and one_blinker and not self.prev_one_blinker and not below_lane_change_speed:
                self.lane_change_state = LaneChangeState.preLaneChange
                self.lane_change_ll_prob = 1.0

            # pre
            elif self.lane_change_state == LaneChangeState.preLaneChange:
                if not one_blinker or below_lane_change_speed:
                    self.lane_change_state = LaneChangeState.off
                elif torque_applied and (not blindspot_detected
                                         or self.prev_torque_applied):
                    self.lane_change_state = LaneChangeState.laneChangeStarting
                elif torque_applied and blindspot_detected and self.auto_lane_change_timer != 10.0:
                    self.auto_lane_change_timer = 10.0
                elif not torque_applied and self.auto_lane_change_timer == 10.0 and not self.prev_torque_applied:
                    self.prev_torque_applied = True

            # starting
            #elif self.lane_change_state == LaneChangeState.laneChangeStarting:
            #  # fade out over .5s
            #  self.lane_change_ll_prob = max(self.lane_change_ll_prob - 2*DT_MDL, 0.0)
            #  # 98% certainty
            #  if lane_change_prob < 0.02 and self.lane_change_ll_prob < 0.01:
            #    self.lane_change_state = LaneChangeState.laneChangeFinishing

            # finishing
            elif self.lane_change_state == LaneChangeState.laneChangeFinishing:
                # fade in laneline over 1s
                self.lane_change_ll_prob = min(
                    self.lane_change_ll_prob + DT_MDL, 1.0)
                if one_blinker and self.lane_change_ll_prob > 0.99:
                    self.lane_change_state = LaneChangeState.preLaneChange
                elif self.lane_change_ll_prob > 0.99:
                    self.lane_change_state = LaneChangeState.off

        if self.lane_change_state in [
                LaneChangeState.off, LaneChangeState.preLaneChange
        ]:
            self.lane_change_timer = 0.0
        else:
            self.lane_change_timer += DT_MDL

        if self.lane_change_state == LaneChangeState.off:
            self.auto_lane_change_timer = 0.0
            self.prev_torque_applied = False
        elif self.auto_lane_change_timer < (
                AUTO_LCA_START_TIME +
                1.0):  # stop afer 3 sec resume from 10 when torque applied
            self.auto_lane_change_timer += DT_MDL

        self.prev_one_blinker = one_blinker

        desire = DESIRES[self.lane_change_direction][self.lane_change_state]

        # Turn off lanes during lane change
        if desire == log.PathPlan.Desire.laneChangeRight or desire == log.PathPlan.Desire.laneChangeLeft:
            self.LP.l_prob *= self.lane_change_ll_prob
            self.LP.r_prob *= self.lane_change_ll_prob

        self.LP.update_d_poly(v_ego)

        steerActuatorDelay = ntune_get('steerActuatorDelay')

        # account for actuation delay
        self.cur_state = calc_states_after_delay(self.cur_state, v_ego,
                                                 angle_steers - angle_offset,
                                                 curvature_factor, VM.sR,
                                                 steerActuatorDelay)

        v_ego_mpc = max(v_ego, 5.0)  # avoid mpc roughness due to low speed
        self.libmpc.run_mpc(self.cur_state, self.mpc_solution,
                            list(self.LP.l_poly), list(self.LP.r_poly),
                            list(self.LP.d_poly), self.LP.l_prob,
                            self.LP.r_prob, curvature_factor, v_ego_mpc,
                            self.LP.lane_width)

        # reset to current steer angle if not active or overriding
        if active:
            delta_desired = self.mpc_solution[0].delta[1]
            rate_desired = math.degrees(self.mpc_solution[0].rate[0] * VM.sR)
        else:
            delta_desired = math.radians(angle_steers - angle_offset) / VM.sR
            rate_desired = 0.0

        self.cur_state[0].delta = delta_desired

        self.angle_steers_des_mpc = float(
            math.degrees(delta_desired * VM.sR) + angle_offset)

        #  Check for infeasable MPC solution
        mpc_nans = any(math.isnan(x) for x in self.mpc_solution[0].delta)
        t = sec_since_boot()
        if mpc_nans:
            self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE,
                             MPC_COST_LAT.HEADING, steerRateCost)
            self.cur_state[0].delta = math.radians(angle_steers -
                                                   angle_offset) / VM.sR

            if t > self.last_cloudlog_t + 5.0:
                self.last_cloudlog_t = t
                cloudlog.warning("Lateral mpc - nan: True")

        if self.mpc_solution[
                0].cost > 20000. or mpc_nans:  # TODO: find a better way to detect when MPC did not converge
            self.solution_invalid_cnt += 1
        else:
            self.solution_invalid_cnt = 0
        plan_solution_valid = self.solution_invalid_cnt < 3

        plan_send = messaging.new_message('pathPlan')
        plan_send.valid = sm.all_alive_and_valid(service_list=[
            'carState', 'controlsState', 'liveParameters', 'model'
        ])
        plan_send.pathPlan.laneWidth = float(self.LP.lane_width)
        plan_send.pathPlan.dPoly = [float(x) for x in self.LP.d_poly]
        plan_send.pathPlan.lPoly = [float(x) for x in self.LP.l_poly]
        plan_send.pathPlan.lProb = float(self.LP.l_prob)
        plan_send.pathPlan.rPoly = [float(x) for x in self.LP.r_poly]
        plan_send.pathPlan.rProb = float(self.LP.r_prob)

        plan_send.pathPlan.angleSteers = float(self.angle_steers_des_mpc)
        plan_send.pathPlan.rateSteers = float(rate_desired)
        plan_send.pathPlan.angleOffset = float(
            sm['liveParameters'].angleOffsetAverage)
        plan_send.pathPlan.mpcSolutionValid = bool(plan_solution_valid)
        plan_send.pathPlan.paramsValid = bool(sm['liveParameters'].valid)

        plan_send.pathPlan.desire = desire
        plan_send.pathPlan.laneChangeState = self.lane_change_state
        plan_send.pathPlan.laneChangeDirection = self.lane_change_direction
        plan_send.pathPlan.autoLaneChangeEnabled = self.auto_lane_change_enabled
        plan_send.pathPlan.autoLaneChangeTimer = int(
            AUTO_LCA_START_TIME) - int(self.auto_lane_change_timer)

        plan_send.pathPlan.steerRatio = VM.sR
        plan_send.pathPlan.steerRateCost = steerRateCost
        plan_send.pathPlan.steerActuatorDelay = steerActuatorDelay

        pm.send('pathPlan', plan_send)

        if LOG_MPC:
            dat = messaging.new_message('liveMpc')
            dat.liveMpc.x = list(self.mpc_solution[0].x)
            dat.liveMpc.y = list(self.mpc_solution[0].y)
            dat.liveMpc.psi = list(self.mpc_solution[0].psi)
            dat.liveMpc.delta = list(self.mpc_solution[0].delta)
            dat.liveMpc.cost = self.mpc_solution[0].cost
            pm.send('liveMpc', dat)
Example #13
0
def log_handler(end_event):
    if PC:
        return

    log_files = []
    last_scan = 0
    while not end_event.is_set():
        try:
            curr_scan = sec_since_boot()
            if curr_scan - last_scan > 10:
                log_files = get_logs_to_send_sorted()
                last_scan = curr_scan

            # send one log
            curr_log = None
            if len(log_files) > 0:
                log_entry = log_files.pop()  # newest log file
                cloudlog.debug(
                    f"athena.log_handler.forward_request {log_entry}")
                try:
                    curr_time = int(time.time())
                    log_path = os.path.join(SWAGLOG_DIR, log_entry)
                    setxattr(log_path, LOG_ATTR_NAME,
                             int.to_bytes(curr_time, 4, sys.byteorder))
                    with open(log_path) as f:
                        jsonrpc = {
                            "method": "forwardLogs",
                            "params": {
                                "logs": f.read()
                            },
                            "jsonrpc": "2.0",
                            "id": log_entry
                        }
                        low_priority_send_queue.put_nowait(json.dumps(jsonrpc))
                        curr_log = log_entry
                except OSError:
                    pass  # file could be deleted by log rotation

            # wait for response up to ~100 seconds
            # always read queue at least once to process any old responses that arrive
            for _ in range(100):
                if end_event.is_set():
                    break
                try:
                    log_resp = json.loads(log_recv_queue.get(timeout=1))
                    log_entry = log_resp.get("id")
                    log_success = "result" in log_resp and log_resp[
                        "result"].get("success")
                    cloudlog.debug(
                        f"athena.log_handler.forward_response {log_entry} {log_success}"
                    )
                    if log_entry and log_success:
                        log_path = os.path.join(SWAGLOG_DIR, log_entry)
                        try:
                            setxattr(log_path, LOG_ATTR_NAME,
                                     LOG_ATTR_VALUE_MAX_UNIX_TIME)
                        except OSError:
                            pass  # file could be deleted by log rotation
                    if curr_log == log_entry:
                        break
                except queue.Empty:
                    if curr_log is None:
                        break

        except Exception:
            cloudlog.exception("athena.log_handler.exception")
Example #14
0
    def update(self, sendcan, enabled, CS, frame, final_gas, final_brake, final_steer, \
               pcm_speed, pcm_override, pcm_cancel_cmd, pcm_accel, \
               hud_v_cruise, hud_show_lanes, hud_show_car, hud_alert, \
               snd_beep, snd_chime):
        """ Controls thread """

        # TODO: Make the accord work.
        if CS.accord:
            return

        # *** apply brake hysteresis ***
        final_brake, self.braking, self.brake_steady = actuator_hystereses(
            final_brake, self.braking, self.brake_steady, CS.v_ego, CS.civic)

        # *** no output if not enabled ***
        if not enabled:
            final_gas = 0.
            final_brake = 0.
            final_steer = 0.
            # send pcm acc cancel cmd if drive is disabled but pcm is still on, or if the system can't be activated
            if CS.pcm_acc_status:
                pcm_cancel_cmd = True

        # *** rate limit after the enable check ***
        final_brake = rate_limit(final_brake, self.final_brake_last, -2.,
                                 1. / 100)
        self.final_brake_last = final_brake

        # vehicle hud display, wait for one update from 10Hz 0x304 msg
        #TODO: use enum!!
        if hud_show_lanes:
            hud_lanes = 0x04
        else:
            hud_lanes = 0x00

        # TODO: factor this out better
        if enabled:
            if hud_show_car:
                hud_car = 0xe0
            else:
                hud_car = 0xd0
        else:
            hud_car = 0xc0

        #print chime, alert_id, hud_alert
        fcw_display, steer_required, acc_alert = process_hud_alert(hud_alert)

        hud = HUDData(int(pcm_accel), int(hud_v_cruise), 0x01,
                      hud_car, 0xc1, 0x41, hud_lanes + steer_required,
                      int(snd_beep), 0x48, (snd_chime << 5) + fcw_display,
                      acc_alert)

        if not all(isinstance(x, int) and 0 <= x < 256 for x in hud):
            print "INVALID HUD", hud
            hud = HUDData(0xc6, 255, 64, 0xc0, 209, 0x41, 0x40, 0, 0x48, 0, 0)

        # **** process the car messages ****

        # *** compute control surfaces ***
        tt = sec_since_boot()
        GAS_MAX = 1004
        BRAKE_MAX = 1024 / 4
        STEER_MAX = 0xF00
        GAS_OFFSET = 328

        # steer torque is converted back to CAN reference (positive when steering right)
        apply_gas = int(clip(final_gas * GAS_MAX, 0, GAS_MAX - 1))
        apply_brake = int(clip(final_brake * BRAKE_MAX, 0, BRAKE_MAX - 1))
        apply_steer = int(clip(-final_steer * STEER_MAX, -STEER_MAX,
                               STEER_MAX))

        # no gas if you are hitting the brake or the user is
        if apply_gas > 0 and (apply_brake != 0 or CS.brake_pressed):
            print "CANCELLING GAS", apply_brake
            apply_gas = 0

        # no computer brake if the gas is being pressed
        if CS.car_gas > 0 and apply_brake != 0:
            print "CANCELLING BRAKE"
            apply_brake = 0

        # any other cp.vl[0x18F]['STEER_STATUS'] is common and can happen during user override. sending 0 torque to avoid EPS sending error 5
        if CS.steer_not_allowed:
            print "STEER ALERT, TORQUE INHIBITED"
            apply_steer = 0

        # *** entry into controls state ***
        if (CS.prev_cruise_buttons == CruiseButtons.DECEL_SET or CS.prev_cruise_buttons == CruiseButtons.RES_ACCEL) and \
            CS.cruise_buttons == 0 and not self.controls_allowed:
            print "CONTROLS ARE LIVE"
            self.controls_allowed = True

        # *** exit from controls state on cancel, gas, or brake ***
        if (CS.cruise_buttons == CruiseButtons.CANCEL or CS.brake_pressed
                or CS.user_gas_pressed or
            (CS.pedal_gas > 0 and CS.brake_only)) and self.controls_allowed:
            print "CONTROLS ARE DEAD"
            self.controls_allowed = False

        # *** controls fail on steer error, brake error, or invalid can ***
        if CS.steer_error:
            print "STEER ERROR"
            self.controls_allowed = False

        if CS.brake_error:
            print "BRAKE ERROR"
            self.controls_allowed = False

        if not CS.can_valid and self.controls_allowed:  # 200 ms
            print "CAN INVALID"
            self.controls_allowed = False

        # Send CAN commands.
        can_sends = []

        # Send steering command.
        if CS.accord:
            idx = frame % 2
            can_sends.append(
                hondacan.create_accord_steering_control(apply_steer, idx))
        else:
            idx = frame % 4
            can_sends.append(hondacan.create_steering_control(
                apply_steer, idx))

        # Send gas and brake commands.
        if (frame % 2) == 0:
            idx = (frame / 2) % 4
            can_sends.append(
                hondacan.create_brake_command(apply_brake, pcm_override,
                                              pcm_cancel_cmd, hud.chime, idx))
            if not CS.brake_only:
                # send exactly zero if apply_gas is zero. Interceptor will send the max between read value and apply_gas.
                # This prevents unexpected pedal range rescaling
                gas_amount = (apply_gas + GAS_OFFSET) * (apply_gas > 0)
                can_sends.append(hondacan.create_gas_command(gas_amount, idx))

        # Send dashboard UI commands.
        if (frame % 10) == 0:
            idx = (frame / 10) % 4
            can_sends.extend(
                hondacan.create_ui_commands(pcm_speed, hud, CS.civic,
                                            CS.accord, idx))

        # radar at 20Hz, but these msgs need to be sent at 50Hz on ilx (seems like an Acura bug)
        if CS.civic or CS.accord:
            radar_send_step = 5
        else:
            radar_send_step = 2

        if (frame % radar_send_step) == 0:
            idx = (frame / radar_send_step) % 4
            can_sends.extend(
                hondacan.create_radar_commands(CS.v_ego, CS.civic, CS.accord,
                                               idx))

        sendcan.send(
            can_list_to_can_capnp(can_sends, msgtype='sendcan').to_bytes())
Example #15
0
def controlsd_thread(gctx=None, rate=100, default_bias=0.):
    gc.disable()

    # start the loop
    set_realtime_priority(3)

    context = zmq.Context()
    params = Params()

    # Pub Sockets
    live100 = messaging.pub_sock(context, service_list['live100'].port)
    carstate = messaging.pub_sock(context, service_list['carState'].port)
    carcontrol = messaging.pub_sock(context, service_list['carControl'].port)
    livempc = messaging.pub_sock(context, service_list['liveMpc'].port)

    is_metric = params.get("IsMetric") == "1"
    passive = params.get("Passive") != "0"

    # No sendcan if passive
    if not passive:
        sendcan = messaging.pub_sock(context, service_list['sendcan'].port)
    else:
        sendcan = None

    # Sub sockets
    poller = zmq.Poller()
    thermal = messaging.sub_sock(context,
                                 service_list['thermal'].port,
                                 conflate=True,
                                 poller=poller)
    health = messaging.sub_sock(context,
                                service_list['health'].port,
                                conflate=True,
                                poller=poller)
    cal = messaging.sub_sock(context,
                             service_list['liveCalibration'].port,
                             conflate=True,
                             poller=poller)
    driver_monitor = messaging.sub_sock(context,
                                        service_list['driverMonitoring'].port,
                                        conflate=True,
                                        poller=poller)
    gps_location = messaging.sub_sock(context,
                                      service_list['gpsLocationExternal'].port,
                                      conflate=True,
                                      poller=poller)
    logcan = messaging.sub_sock(context, service_list['can'].port)

    CC = car.CarControl.new_message()
    CI, CP = get_car(logcan, sendcan, 1.0 if passive else None)

    if CI is None:
        raise Exception("unsupported car")

    # if stock camera is connected, then force passive behavior
    if not CP.enableCamera:
        passive = True
        sendcan = None

    if passive:
        CP.safetyModel = car.CarParams.SafetyModels.noOutput

    # Get FCW toggle from settings
    fcw_enabled = params.get("IsFcwEnabled") == "1"
    geofence = None

    PL = Planner(CP, fcw_enabled)
    LoC = LongControl(CP, CI.compute_gb)
    VM = VehicleModel(CP)
    LaC = LatControl(CP)
    AM = AlertManager()
    driver_status = DriverStatus()

    if not passive:
        AM.add("startup", False)

    # Write CarParams for radard and boardd safety mode
    params.put("CarParams", CP.to_bytes())
    params.put("LongitudinalControl",
               "1" if CP.openpilotLongitudinalControl else "0")

    state = State.disabled
    soft_disable_timer = 0
    v_cruise_kph = 255
    v_cruise_kph_last = 0
    overtemp = False
    free_space = False
    cal_status = Calibration.INVALID
    cal_perc = 0
    mismatch_counter = 0
    low_battery = False

    rk = Ratekeeper(rate, print_delay_threshold=2. / 1000)

    # Read angle offset from previous drive, fallback to default
    angle_offset = default_bias
    calibration_params = params.get("CalibrationParams")
    if calibration_params:
        try:
            calibration_params = json.loads(calibration_params)
            angle_offset = calibration_params["angle_offset2"]
        except (ValueError, KeyError):
            pass

    prof = Profiler(False)  # off by default

    while True:
        start_time = int(sec_since_boot() * 1e9)
        prof.checkpoint("Ratekeeper", ignore=True)

        # Sample data and compute car events
        CS, events, cal_status, cal_perc, overtemp, free_space, low_battery, mismatch_counter = data_sample(
            CI, CC, thermal, cal, health, driver_monitor, gps_location, poller,
            cal_status, cal_perc, overtemp, free_space, low_battery,
            driver_status, geofence, state, mismatch_counter, params)
        prof.checkpoint("Sample")

        # Define longitudinal plan (MPC)
        plan, plan_ts = calc_plan(CS, CP, VM, events, PL, LaC, LoC,
                                  v_cruise_kph, driver_status, geofence)
        prof.checkpoint("Plan")

        if not passive:
            # update control state
            state, soft_disable_timer, v_cruise_kph, v_cruise_kph_last = \
              state_transition(CS, CP, state, events, soft_disable_timer, v_cruise_kph, AM)
            prof.checkpoint("State transition")

        # Compute actuators (runs PID loops and lateral MPC)
        actuators, v_cruise_kph, driver_status, angle_offset = state_control(
            plan, CS, CP, state, events, v_cruise_kph, v_cruise_kph_last, AM,
            rk, driver_status, PL, LaC, LoC, VM, angle_offset, passive,
            is_metric, cal_perc)
        prof.checkpoint("State Control")

        # Publish data
        CC = data_send(PL.perception_state, plan, plan_ts, CS, CI, CP, VM,
                       state, events, actuators, v_cruise_kph, rk, carstate,
                       carcontrol, live100, livempc, AM, driver_status, LaC,
                       LoC, angle_offset, passive, start_time)
        prof.checkpoint("Sent")

        rk.keep_time()  # Run at 100Hz
        prof.display()
Example #16
0
def thermald_thread():
    # prevent LEECO from undervoltage
    BATT_PERC_OFF = 100  #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

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

    IsOpenpilotViewEnabled = 0

    OpkrLoadStep = 0
    OpkrAutoShutdown = 0
    do_uninstall = 0
    accepted_terms = 0
    completed_training = 0
    panda_signature = 0

    ts_last_ip = 0
    ip_addr = '255.255.255.255'

    # sound trigger
    sound_trigger = 1

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

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

    while 1:
        OpkrLoadStep += 1
        if OpkrLoadStep == 1:
            OpkrAutoShutdown = params.get_OpkrAutoShutdown()
        elif OpkrLoadStep == 2:
            do_uninstall = params.get("DoUninstall") == b"1"
        elif OpkrLoadStep == 3:
            accepted_terms = params.get("HasAcceptedTerms") == terms_version
        elif OpkrLoadStep == 4:
            completed_training = params.get(
                "CompletedTrainingVersion") == training_version
        elif OpkrLoadStep == 5:
            panda_signature = params.get("PandaFirmware")
        else:
            OpkrLoadStep = 0

        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

            # 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

                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
        elif ignition == False or IsOpenpilotViewEnabled:
            IsOpenpilotViewEnabled = int(params.get("IsOpenpilotViewEnabled"))
            ignition = IsOpenpilotViewEnabled

        # get_network_type is an expensive call. update every 10s
        if (count % int(10. / DT_TRML)) == 0:
            try:
                network_type = 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 - 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")
        #        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")

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

        current_ts = sec_since_boot()
        if should_start:
            if not should_start_prev:
                params.delete("IsOffroad")

            off_ts = None
            if started_ts is None:
                started_ts = current_ts
                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 = current_ts
                os.system(
                    'echo powersave > /sys/class/devfreq/soc:qcom,cpubw/governor'
                )

            if sound_trigger == 1 and msg.thermal.batteryStatus == "Discharging" and started_seen and (
                    sec_since_boot() - off_ts) > 1 and getoff_alert:
                subprocess.Popen([
                    mediaplayer + 'mediaplayer',
                    '/data/openpilot/selfdrive/assets/sounds/eondetach.wav'
                ],
                                 shell=False,
                                 stdin=None,
                                 stdout=None,
                                 stderr=None,
                                 env=env,
                                 close_fds=True)
                sound_trigger = 0

            # shutdown if the battery gets lower than 3%, it's discharging, we aren't running for
            # more than a minute but we were running
            power_shutdown = False
            if msg.thermal.batteryStatus == "Discharging":
                delta_ts = current_ts - off_ts

                if started_seen:
                    if msg.thermal.batteryPercent <= BATT_PERC_OFF and (
                            OpkrAutoShutdown and delta_ts > OpkrAutoShutdown):
                        power_shutdown = True
                elif delta_ts > 240 and msg.thermal.batteryPercent < 10:
                    power_shutdown = True

                if power_shutdown:
                    os.system('LD_LIBRARY_PATH="" svc power shutdown')
                    print('power_shutdown batterypercent={} should_start={}'.
                          format(msg.thermal.batteryPercent, should_start))

            else:
                off_ts = current_ts

            #print( 'OpkrAutoShutdown = {}'.format( OpkrAutoShutdown ) )
            #if msg.thermal.batteryPercent < BATT_PERC_OFF and msg.thermal.batteryStatus == "Discharging" and \
            #   started_seen and (current_ts - off_ts) > 60:
            #  os.system('LD_LIBRARY_PATH="" svc power shutdown')

        # print( 'batterypercent={} should_start={}'.format(msg.thermal.batteryPercent, should_start) )
        # Offroad power monitoring
        pm.calculate(health, msg)
        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

        if usb_power:
            pm.charging_ctrl(msg, ts, 80, 70)

        # 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
Example #17
0
    def update(self, sm):
        v_ego = sm['carState'].vEgo
        measured_curvature = sm['controlsState'].curvature

        # Parse model predictions
        md = sm['modelV2']
        self.LP.parse_model(md)
        if len(md.position.x) == TRAJECTORY_SIZE and len(
                md.orientation.x) == TRAJECTORY_SIZE:
            self.path_xyz = np.column_stack(
                [md.position.x, md.position.y, md.position.z])
            self.t_idxs = np.array(md.position.t)
            self.plan_yaw = list(md.orientation.z)
        if len(md.position.xStd) == TRAJECTORY_SIZE:
            self.path_xyz_stds = np.column_stack(
                [md.position.xStd, md.position.yStd, md.position.zStd])

        # Lane change logic
        lane_change_prob = self.LP.l_lane_change_prob + self.LP.r_lane_change_prob
        self.DH.update(sm['carState'], sm['controlsState'].active,
                       lane_change_prob)

        # Turn off lanes during lane change
        if self.DH.desire == log.LateralPlan.Desire.laneChangeRight or self.DH.desire == log.LateralPlan.Desire.laneChangeLeft:
            self.LP.lll_prob *= self.DH.lane_change_ll_prob
            self.LP.rll_prob *= self.DH.lane_change_ll_prob

        # Calculate final driving path and set MPC costs
        if self.use_lanelines:
            d_path_xyz = self.LP.get_d_path(v_ego, self.t_idxs, self.path_xyz)
            self.lat_mpc.set_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.HEADING,
                                     self.steer_rate_cost)
        else:
            d_path_xyz = self.path_xyz
            path_cost = np.clip(
                abs(self.path_xyz[0, 1] / self.path_xyz_stds[0, 1]), 0.5,
                1.5) * MPC_COST_LAT.PATH
            # Heading cost is useful at low speed, otherwise end of plan can be off-heading
            heading_cost = interp(v_ego, [5.0, 10.0],
                                  [MPC_COST_LAT.HEADING, 0.0])
            self.lat_mpc.set_weights(path_cost, heading_cost,
                                     self.steer_rate_cost)

        y_pts = np.interp(v_ego * self.t_idxs[:LAT_MPC_N + 1],
                          np.linalg.norm(d_path_xyz, axis=1), d_path_xyz[:, 1])
        heading_pts = np.interp(v_ego * self.t_idxs[:LAT_MPC_N + 1],
                                np.linalg.norm(self.path_xyz, axis=1),
                                self.plan_yaw)
        self.y_pts = y_pts

        assert len(y_pts) == LAT_MPC_N + 1
        assert len(heading_pts) == LAT_MPC_N + 1
        # self.x0[4] = v_ego
        p = np.array([v_ego, CAR_ROTATION_RADIUS])
        self.lat_mpc.run(self.x0, p, y_pts, heading_pts)
        # init state for next
        self.x0[3] = interp(DT_MDL, self.t_idxs[:LAT_MPC_N + 1],
                            self.lat_mpc.x_sol[:, 3])

        #  Check for infeasible MPC solution
        mpc_nans = np.isnan(self.lat_mpc.x_sol[:, 3]).any()
        t = sec_since_boot()
        if mpc_nans or self.lat_mpc.solution_status != 0:
            self.reset_mpc()
            self.x0[3] = measured_curvature
            if t > self.last_cloudlog_t + 5.0:
                self.last_cloudlog_t = t
                cloudlog.warning("Lateral mpc - nan: True")

        if self.lat_mpc.cost > 20000. or mpc_nans:
            self.solution_invalid_cnt += 1
        else:
            self.solution_invalid_cnt = 0
Example #18
0
  def update(self, sendcan, enabled, CS, frame, actuators,
             pcm_cancel_cmd, hud_alert, audible_alert):

    # *** compute control surfaces ***
    ts = sec_since_boot()

    # steer torque is converted back to CAN reference (positive when steering right)
    apply_accel = actuators.gas - actuators.brake
    apply_accel, self.accel_steady = accel_hysteresis(apply_accel, self.accel_steady, enabled)
    apply_accel = int(round(clip(apply_accel * ACCEL_SCALE, ACCEL_MIN, ACCEL_MAX)))

    # steer torque is converted back to CAN reference (positive when steering right)
    apply_steer = int(round(actuators.steer * STEER_MAX))

    max_lim = min(max(CS.steer_torque_motor + STEER_ERROR_MAX, STEER_ERROR_MAX), STEER_MAX)
    min_lim = max(min(CS.steer_torque_motor - STEER_ERROR_MAX, -STEER_ERROR_MAX), -STEER_MAX)

    apply_steer = clip(apply_steer, min_lim, max_lim)

    # slow rate if steer torque increases in magnitude
    if self.last_steer > 0:
      apply_steer = clip(apply_steer, max(self.last_steer - STEER_DELTA_DOWN, - STEER_DELTA_UP), self.last_steer + STEER_DELTA_UP)
    else:
      apply_steer = clip(apply_steer, self.last_steer - STEER_DELTA_UP, min(self.last_steer + STEER_DELTA_DOWN, STEER_DELTA_UP))

    if not enabled and CS.pcm_acc_status:
      # send pcm acc cancel cmd if drive is disabled but pcm is still on, or if the system can't be activated
      pcm_cancel_cmd = 1

    # dropping torque immediately might cause eps to temp fault. On the other hand, safety_toyota
    # cuts steer torque immediately anyway TODO: monitor if this is a real issue
    if not enabled or CS.steer_error:
      apply_steer = 0

    # on entering standstill, send standstill request
    if CS.standstill and not self.last_standstill:
      self.standstill_req = True
    if CS.pcm_acc_status != 8:
      # pcm entered standstill or it's disabled
      self.standstill_req = False

    self.last_steer = apply_steer
    self.last_accel = apply_accel
    self.last_standstill = CS.standstill

    can_sends = []

    #*** control msgs ***
    #print "steer", apply_steer, min_lim, max_lim, CS.steer_torque_motor

    # toyota can trace shows this message at 42Hz, with counter adding alternatively 1 and 2;
    # sending it at 100Hz seem to allow a higher rate limit, as the rate limit seems imposed
    # on consecutive messages
    if ECU.CAM in self.fake_ecus:
      can_sends.append(create_steer_command(apply_steer, frame))

    if ECU.APGS in self.fake_ecus:
      can_sends.append(create_ipas_steer_command(apply_steer))

    # accel cmd comes from DSU, but we can spam can to cancel the system even if we are using lat only control
    if (frame % 3 == 0 and ECU.DSU in self.fake_ecus) or (pcm_cancel_cmd and ECU.CAM in self.fake_ecus):
      if ECU.DSU in self.fake_ecus:
        can_sends.append(create_accel_command(apply_accel, pcm_cancel_cmd, self.standstill_req))
      else:
        can_sends.append(create_accel_command(0, pcm_cancel_cmd, False))

    if frame % 10 == 0 and ECU.CAM in self.fake_ecus:
      for addr in TARGET_IDS:
        can_sends.append(create_video_target(frame/10, addr))

    # ui mesg is at 100Hz but we send asap if:
    # - there is something to display
    # - there is something to stop displaying
    alert_out = process_hud_alert(hud_alert, audible_alert)
    steer, fcw, sound1, sound2 = alert_out

    if (any(alert_out) and not self.alert_active) or \
       (not any(alert_out) and self.alert_active):
      send_ui = True
      self.alert_active = not self.alert_active
    else:
      send_ui = False

    if (frame % 100 == 0 or send_ui) and ECU.CAM in self.fake_ecus:
      can_sends.append(create_ui_command(steer, sound1, sound2))
      can_sends.append(create_fcw_command(fcw))

    #*** static msgs ***

    for addr, (ecu, cars, bus, fr_step, vl) in STATIC_MSGS.iteritems():
      if frame % fr_step == 0 and ecu in self.fake_ecus and self.car_fingerprint in cars:
        # special cases
        if fr_step == 5 and ecu == ECU.CAM and bus == 1:
          cnt = (((frame / 5) % 7) + 1) << 5
          vl = chr(cnt) + vl
        elif addr in (0x489, 0x48a) and bus == 0:
          # add counter for those 2 messages (last 4 bits)
          cnt = ((frame/100)%0xf) + 1
          if addr == 0x48a:
            # 0x48a has a 8 preceding the counter
            cnt += 1 << 7
          vl += chr(cnt)

        can_sends.append(make_can_msg(addr, vl, bus, False))


    sendcan.send(can_list_to_can_capnp(can_sends, msgtype='sendcan').to_bytes())
Example #19
0
def new_message():
    dat = log.Event.new_message()
    dat.logMonoTime = int(sec_since_boot() * 1e9)
    dat.valid = True
    return dat
Example #20
0
    def update(self, sendcan, enabled, CS, frame, actuators, \
               pcm_speed, pcm_override, pcm_cancel_cmd, pcm_accel, \
               hud_v_cruise, hud_show_lanes, hud_show_car, hud_alert, \
               snd_beep, snd_chime):
        """ Controls thread """

        # TODO: Make the accord work.
        if CS.accord:
            return

        # *** apply brake hysteresis ***
        brake, self.braking, self.brake_steady = actuator_hystereses(
            actuators.brake, self.braking, self.brake_steady, CS.v_ego,
            CS.civic)

        # *** no output if not enabled ***
        if not enabled and CS.pcm_acc_status:
            # send pcm acc cancel cmd if drive is disabled but pcm is still on, or if the system can't be activated
            pcm_cancel_cmd = True

        # *** rate limit after the enable check ***
        brake = rate_limit(brake, self.brake_last, -2., 1. / 100)
        self.brake_last = brake

        # vehicle hud display, wait for one update from 10Hz 0x304 msg
        #TODO: use enum!!
        if hud_show_lanes:
            hud_lanes = 0x04
        else:
            hud_lanes = 0x00

        # TODO: factor this out better
        if enabled:
            if hud_show_car:
                hud_car = 0xe0
            else:
                hud_car = 0xd0
        else:
            hud_car = 0xc0

        #print chime, alert_id, hud_alert
        fcw_display, steer_required, acc_alert = process_hud_alert(hud_alert)

        hud = HUDData(int(pcm_accel), int(round(hud_v_cruise)), 0x01,
                      hud_car, 0xc1, 0x41, hud_lanes + steer_required,
                      int(snd_beep), 0x48, (snd_chime << 5) + fcw_display,
                      acc_alert)

        if not all(isinstance(x, int) and 0 <= x < 256 for x in hud):
            print "INVALID HUD", hud
            hud = HUDData(0xc6, 255, 64, 0xc0, 209, 0x41, 0x40, 0, 0x48, 0, 0)

        # **** process the car messages ****

        # *** compute control surfaces ***
        tt = sec_since_boot()
        GAS_MAX = 1004
        BRAKE_MAX = 1024 / 4
        if CS.civic:
            is_fw_modified = os.getenv("DONGLE_ID") in ['b0f5a01cf604185c']
            STEER_MAX = 0x1FFF if is_fw_modified else 0x1000
        elif CS.crv:
            STEER_MAX = 0x300  # CR-V only uses 12-bits and requires a lower value
        else:
            STEER_MAX = 0xF00
        GAS_OFFSET = 328

        # steer torque is converted back to CAN reference (positive when steering right)
        apply_gas = int(clip(actuators.gas * GAS_MAX, 0, GAS_MAX - 1))
        apply_brake = int(clip(brake * BRAKE_MAX, 0, BRAKE_MAX - 1))
        apply_steer = int(
            clip(-actuators.steer * STEER_MAX, -STEER_MAX, STEER_MAX))

        # no gas if you are hitting the brake or the user is
        if apply_gas > 0 and (apply_brake != 0 or CS.brake_pressed):
            apply_gas = 0

        # no computer brake if the gas is being pressed
        if CS.car_gas > 0 and apply_brake != 0:
            apply_brake = 0

        # any other cp.vl[0x18F]['STEER_STATUS'] is common and can happen during user override. sending 0 torque to avoid EPS sending error 5
        if CS.steer_not_allowed:
            apply_steer = 0

        # Send CAN commands.
        can_sends = []

        # Send steering command.
        if CS.accord:
            idx = frame % 2
            can_sends.append(
                hondacan.create_accord_steering_control(apply_steer, idx))
        else:
            idx = frame % 4
            can_sends.extend(
                hondacan.create_steering_control(apply_steer, CS.crv, idx))

        # Send gas and brake commands.
        if (frame % 2) == 0:
            idx = (frame / 2) % 4
            can_sends.append(
                hondacan.create_brake_command(apply_brake, pcm_override,
                                              pcm_cancel_cmd, hud.chime, idx))
            if not CS.brake_only:
                # send exactly zero if apply_gas is zero. Interceptor will send the max between read value and apply_gas.
                # This prevents unexpected pedal range rescaling
                gas_amount = (apply_gas + GAS_OFFSET) * (apply_gas > 0)
                can_sends.append(hondacan.create_gas_command(gas_amount, idx))

        # Send dashboard UI commands.
        if (frame % 10) == 0:
            idx = (frame / 10) % 4
            can_sends.extend(
                hondacan.create_ui_commands(pcm_speed, hud, CS.civic,
                                            CS.accord, CS.crv, idx))

        # radar at 20Hz, but these msgs need to be sent at 50Hz on ilx (seems like an Acura bug)
        if CS.civic or CS.accord or CS.crv:
            radar_send_step = 5
        else:
            radar_send_step = 2

        if (frame % radar_send_step) == 0:
            idx = (frame / radar_send_step) % 4
            can_sends.extend(
                hondacan.create_radar_commands(CS.v_ego, CS.civic, CS.accord,
                                               CS.crv, idx))

        sendcan.send(
            can_list_to_can_capnp(can_sends, msgtype='sendcan').to_bytes())
Example #21
0
  def update(self, sm, pm, CP, VM, PP):
    """Gets called when new radarState is available"""
    cur_time = sec_since_boot()
    v_ego = sm['carState'].vEgo

    long_control_state = sm['controlsState'].longControlState
    v_cruise_kph = sm['controlsState'].vCruise
    force_slow_decel = sm['controlsState'].forceDecel
    v_cruise_setpoint = v_cruise_kph * CV.KPH_TO_MS

    lead_1 = sm['radarState'].leadOne
    lead_2 = sm['radarState'].leadTwo

    enabled = (long_control_state == LongCtrlState.pid) or (long_control_state == LongCtrlState.stopping)
    following = lead_1.status and lead_1.dRel < 45.0 and lead_1.vLeadK > v_ego and lead_1.aLeadK > 0.0

    if len(sm['model'].path.poly):
      path = list(sm['model'].path.poly)

      # Curvature of polynomial https://en.wikipedia.org/wiki/Curvature#Curvature_of_the_graph_of_a_function
      # y = a x^3 + b x^2 + c x + d, y' = 3 a x^2 + 2 b x + c, y'' = 6 a x + 2 b
      # k = y'' / (1 + y'^2)^1.5
      # TODO: compute max speed without using a list of points and without numpy
      y_p = 3 * path[0] * self.path_x**2 + 2 * path[1] * self.path_x + path[2]
      y_pp = 6 * path[0] * self.path_x + 2 * path[1]
      curv = y_pp / (1. + y_p**2)**1.5

      a_y_max = 2.975 - v_ego * 0.0375  # ~1.85 @ 75mph, ~2.6 @ 25mph
      v_curvature = np.sqrt(a_y_max / np.clip(np.abs(curv), 1e-4, None))
      model_speed = np.min(v_curvature)
      model_speed = max(20.0 * CV.MPH_TO_MS, model_speed) # Don't slow down below 20mph
    else:
      model_speed = MAX_SPEED

    # Calculate speed for normal cruise control
    if enabled and not self.first_loop and not sm['carState'].brakePressed and not sm['carState'].gasPressed:
      accel_limits = [float(x) for x in calc_cruise_accel_limits(v_ego, following)]
      jerk_limits = [min(-0.1, accel_limits[0]), max(0.1, accel_limits[1])]  # TODO: make a separate lookup for jerk tuning
      accel_limits_turns = limit_accel_in_turns(v_ego, sm['carState'].steeringAngle, 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])

      self.v_cruise, self.a_cruise = speed_smoother(self.v_acc_start, self.a_acc_start,
                                                    v_cruise_setpoint,
                                                    accel_limits_turns[1], accel_limits_turns[0],
                                                    jerk_limits[1], jerk_limits[0],
                                                    LON_MPC_STEP)

      self.v_model, self.a_model = speed_smoother(self.v_acc_start, self.a_acc_start,
                                                    model_speed,
                                                    2*accel_limits[1], accel_limits[0],
                                                    2*jerk_limits[1], jerk_limits[0],
                                                    LON_MPC_STEP)

      # cruise speed can't be negative even is user is distracted
      self.v_cruise = max(self.v_cruise, 0.)
    else:
      starting = long_control_state == LongCtrlState.starting
      a_ego = min(sm['carState'].aEgo, 0.0)
      reset_speed = MIN_CAN_SPEED if starting else v_ego
      reset_accel = self.CP.startAccel if starting else a_ego
      self.v_acc = reset_speed
      self.a_acc = reset_accel
      self.v_acc_start = reset_speed
      self.a_acc_start = reset_accel
      self.v_cruise = reset_speed
      self.a_cruise = reset_accel

    self.mpc1.set_cur_state(self.v_acc_start, self.a_acc_start)
    self.mpc2.set_cur_state(self.v_acc_start, self.a_acc_start)

    self.mpc1.update(pm, sm['carState'], lead_1, v_cruise_setpoint)
    self.mpc2.update(pm, sm['carState'], lead_2, v_cruise_setpoint)

    self.choose_solution(v_cruise_setpoint, enabled)

    # determine fcw
    if self.mpc1.new_lead:
      self.fcw_checker.reset_lead(cur_time)

    blinkers = sm['carState'].leftBlinker or sm['carState'].rightBlinker
    fcw = self.fcw_checker.update(self.mpc1.mpc_solution, cur_time,
                                  sm['controlsState'].active,
                                  v_ego, sm['carState'].aEgo,
                                  lead_1.dRel, lead_1.vLead, lead_1.aLeadK,
                                  lead_1.yRel, lead_1.vLat,
                                  lead_1.fcw, blinkers) and not sm['carState'].brakePressed
    if fcw:
      cloudlog.info("FCW triggered %s", self.fcw_checker.counters)

    radar_dead = not sm.alive['radarState']

    radar_errors = list(sm['radarState'].radarErrors)
    radar_fault = car.RadarData.Error.fault in radar_errors
    radar_can_error = car.RadarData.Error.canError in radar_errors

    # **** send the plan ****
    plan_send = messaging.new_message()
    plan_send.init('plan')

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

    plan_send.plan.mdMonoTime = sm.logMonoTime['model']
    plan_send.plan.radarStateMonoTime = sm.logMonoTime['radarState']

    # longitudal plan
    plan_send.plan.vCruise = float(self.v_cruise)
    plan_send.plan.aCruise = float(self.a_cruise)
    plan_send.plan.vStart = float(self.v_acc_start)
    plan_send.plan.aStart = float(self.a_acc_start)
    plan_send.plan.vTarget = float(self.v_acc)
    plan_send.plan.aTarget = float(self.a_acc)
    plan_send.plan.vTargetFuture = float(self.v_acc_future)
    plan_send.plan.hasLead = self.mpc1.prev_lead_status
    plan_send.plan.longitudinalPlanSource = self.longitudinalPlanSource

    radar_valid = not (radar_dead or radar_fault)
    plan_send.plan.radarValid = bool(radar_valid)
    plan_send.plan.radarCanError = bool(radar_can_error)

    plan_send.plan.processingDelay = (plan_send.logMonoTime / 1e9) - sm.rcv_time['radarState']

    # Send out fcw
    plan_send.plan.fcw = fcw

    pm.send('plan', plan_send)

    # Interpolate 0.05 seconds and save as starting point for next iteration
    a_acc_sol = self.a_acc_start + (CP.radarTimeStep / LON_MPC_STEP) * (self.a_acc - self.a_acc_start)
    v_acc_sol = self.v_acc_start + CP.radarTimeStep * (a_acc_sol + self.a_acc_start) / 2.0
    self.v_acc_start = v_acc_sol
    self.a_acc_start = a_acc_sol

    self.first_loop = False
Example #22
0
    def update(self, CS, lead, v_cruise_setpoint):
        # Setup current mpc state
        self.cur_state[0].x_ego = 0.0

        if lead is not None and lead.status:
            x_lead = lead.dRel
            v_lead = max(0.0, lead.vLead)
            a_lead = lead.aLeadK

            if (v_lead < 0.1 or -a_lead / 2.0 > v_lead):
                v_lead = 0.0
                a_lead = 0.0

            self.a_lead_tau = max(lead.aLeadTau, (a_lead**2 * math.pi) /
                                  (2 * (v_lead + 0.01)**2))
            self.new_lead = False
            if not self.prev_lead_status or abs(x_lead -
                                                self.prev_lead_x) > 2.5:
                self.libmpc.init_with_simulation(self.v_mpc, x_lead, v_lead,
                                                 a_lead, self.a_lead_tau)
                self.new_lead = True

            self.prev_lead_status = True
            self.prev_lead_x = x_lead
            self.cur_state[0].x_l = x_lead
            self.cur_state[0].v_l = v_lead
        else:
            self.prev_lead_status = False
            # Fake a fast lead car, so mpc keeps running
            self.cur_state[0].x_l = 50.0
            self.cur_state[0].v_l = CS.vEgo + 10.0
            a_lead = 0.0
            self.a_lead_tau = _LEAD_ACCEL_TAU

        # Calculate mpc
        t = sec_since_boot()
        n_its = self.libmpc.run_mpc(self.cur_state, self.mpc_solution,
                                    self.a_lead_tau, a_lead)
        duration = int((sec_since_boot() - t) * 1e9)
        self.send_mpc_solution(n_its, duration)

        # Get solution. MPC timestep is 0.2 s, so interpolation to 0.05 s is needed
        self.v_mpc = self.mpc_solution[0].v_ego[1]
        self.a_mpc = self.mpc_solution[0].a_ego[1]
        self.v_mpc_future = self.mpc_solution[0].v_ego[10]

        # Reset if NaN or goes through lead car
        dls = np.array(list(self.mpc_solution[0].x_l)) - np.array(
            list(self.mpc_solution[0].x_ego))
        crashing = min(dls) < -50.0
        nans = np.any(np.isnan(list(self.mpc_solution[0].v_ego)))
        backwards = min(list(self.mpc_solution[0].v_ego)) < -0.01

        if ((backwards or crashing) and self.prev_lead_status) or nans:
            if t > self.last_cloudlog_t + 5.0:
                self.last_cloudlog_t = t
                cloudlog.warning(
                    "Longitudinal mpc %d reset - backwards: %s crashing: %s nan: %s"
                    % (self.mpc_id, backwards, crashing, nans))

            self.libmpc.init(MPC_COST_LONG.TTC, MPC_COST_LONG.DISTANCE,
                             MPC_COST_LONG.ACCELERATION, MPC_COST_LONG.JERK)
            self.cur_state[0].v_ego = CS.vEgo
            self.cur_state[0].a_ego = 0.0
            self.v_mpc = CS.vEgo
            self.a_mpc = CS.aEgo
            self.prev_lead_status = False
Example #23
0
 def can_show_alert(self):
     return sec_since_boot() - self.change_time > self.alert_duration
Example #24
0
    def __init__(self, CP, fcw_enabled):
        context = zmq.Context()
        self.CP = CP
        self.poller = zmq.Poller()

        self.live20 = messaging.sub_sock(context,
                                         service_list['live20'].port,
                                         conflate=True,
                                         poller=self.poller)
        self.model = messaging.sub_sock(context,
                                        service_list['model'].port,
                                        conflate=True,
                                        poller=self.poller)
        self.live_map_data = messaging.sub_sock(
            context,
            service_list['liveMapData'].port,
            conflate=True,
            poller=self.poller)

        if os.environ.get('GPS_PLANNER_ACTIVE', False):
            self.gps_planner_plan = messaging.sub_sock(
                context,
                service_list['gpsPlannerPlan'].port,
                conflate=True,
                poller=self.poller,
                addr=GPS_PLANNER_ADDR)
        else:
            self.gps_planner_plan = None

        self.plan = messaging.pub_sock(context, service_list['plan'].port)
        self.live_longitudinal_mpc = messaging.pub_sock(
            context, service_list['liveLongitudinalMpc'].port)

        self.last_md_ts = 0
        self.last_l20_ts = 0
        self.last_model = 0.
        self.last_l20 = 0.
        self.model_dead = True
        self.radar_dead = True
        self.radar_errors = []

        self.PP = PathPlanner()
        self.mpc1 = LongitudinalMpc(1, self.live_longitudinal_mpc)
        self.mpc2 = LongitudinalMpc(2, self.live_longitudinal_mpc)

        self.v_acc_start = 0.0
        self.a_acc_start = 0.0
        self.acc_start_time = sec_since_boot()
        self.v_acc = 0.0
        self.v_acc_sol = 0.0
        self.v_acc_future = 0.0
        self.a_acc = 0.0
        self.a_acc_sol = 0.0
        self.v_cruise = 0.0
        self.a_cruise = 0.0

        self.lead_1 = None
        self.lead_2 = None

        self.longitudinalPlanSource = 'cruise'
        self.fcw = False
        self.fcw_checker = FCWChecker()
        self.fcw_enabled = fcw_enabled

        self.last_gps_planner_plan = None
        self.gps_planner_active = False
        self.last_live_map_data = None
        self.perception_state = log.Live20Data.new_message()

        self.params = Params()
        self.v_curvature = NO_CURVATURE_SPEED
        self.v_speedlimit = NO_CURVATURE_SPEED
        self.decel_for_turn = False
        self.map_valid = False
Example #25
0
    def update(self, sm, pm, CP, VM):
        v_ego = sm['carState'].vEgo
        angle_steers = sm['carState'].steeringAngle
        active = sm['controlsState'].active

        angle_offset = sm['liveParameters'].angleOffset

        self.LP.update(v_ego, sm['model'])

        # Run MPC
        self.angle_steers_des_prev = self.angle_steers_des_mpc
        VM.update_params(sm['liveParameters'].stiffnessFactor,
                         sm['liveParameters'].steerRatio)
        curvature_factor = VM.curvature_factor(v_ego)

        # TODO: Check for active, override, and saturation
        # if active:
        #   self.path_offset_i += self.LP.d_poly[3] / (60.0 * 20.0)
        #   self.path_offset_i = clip(self.path_offset_i, -0.5,  0.5)
        #   self.LP.d_poly[3] += self.path_offset_i
        # else:
        #   self.path_offset_i = 0.0

        #if active:
        #  curvfac = self.curvature_offset.update(angle_steers - angle_offset, self.LP.d_poly, v_ego)
        #else:
        #  curvfac = 0.
        #curvature_factor = VM.curvature_factor(v_ego) + curvfac

        # account for actuation delay
        self.cur_state = calc_states_after_delay(self.cur_state, v_ego,
                                                 angle_steers - angle_offset,
                                                 curvature_factor, VM.sR,
                                                 CP.steerActuatorDelay)

        v_ego_mpc = max(v_ego, 5.0)  # avoid mpc roughness due to low speed
        self.libmpc.run_mpc(self.cur_state, self.mpc_solution,
                            list(self.LP.l_poly), list(self.LP.r_poly),
                            list(self.LP.d_poly), self.LP.l_prob,
                            self.LP.r_prob, curvature_factor, v_ego_mpc,
                            self.LP.lane_width)

        # reset to current steer angle if not active or overriding
        if active:
            delta_desired = self.mpc_solution[0].delta[1]
            rate_desired = math.degrees(self.mpc_solution[0].rate[0] * VM.sR)
        else:
            delta_desired = math.radians(angle_steers - angle_offset) / VM.sR
            rate_desired = 0.0

        self.cur_state[0].delta = delta_desired

        self.angle_steers_des_mpc = float(
            math.degrees(delta_desired * VM.sR) + angle_offset)

        #  Check for infeasable MPC solution
        mpc_nans = any(math.isnan(x) for x in self.mpc_solution[0].delta)
        t = sec_since_boot()
        if mpc_nans:
            self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE,
                             MPC_COST_LAT.HEADING, CP.steerRateCost)
            self.cur_state[0].delta = math.radians(angle_steers -
                                                   angle_offset) / VM.sR

            if t > self.last_cloudlog_t + 5.0:
                self.last_cloudlog_t = t
                cloudlog.warning("Lateral mpc - nan: True")

        if self.mpc_solution[
                0].cost > 20000. or mpc_nans:  # TODO: find a better way to detect when MPC did not converge
            self.solution_invalid_cnt += 1
        else:
            self.solution_invalid_cnt = 0
        plan_solution_valid = self.solution_invalid_cnt < 2

        plan_send = messaging.new_message()
        plan_send.init('pathPlan')
        plan_send.valid = sm.all_alive_and_valid(service_list=[
            'carState', 'controlsState', 'liveParameters', 'model'
        ])
        plan_send.pathPlan.laneWidth = float(self.LP.lane_width)
        plan_send.pathPlan.dPoly = [float(x) for x in self.LP.d_poly]
        plan_send.pathPlan.lPoly = [float(x) for x in self.LP.l_poly]
        plan_send.pathPlan.lProb = float(self.LP.l_prob)
        plan_send.pathPlan.rPoly = [float(x) for x in self.LP.r_poly]
        plan_send.pathPlan.rProb = float(self.LP.r_prob)

        plan_send.pathPlan.angleSteers = float(self.angle_steers_des_mpc)
        plan_send.pathPlan.rateSteers = float(rate_desired)
        plan_send.pathPlan.angleOffset = float(
            sm['liveParameters'].angleOffsetAverage)
        plan_send.pathPlan.mpcSolutionValid = bool(plan_solution_valid)
        plan_send.pathPlan.paramsValid = bool(sm['liveParameters'].valid)
        plan_send.pathPlan.sensorValid = bool(sm['liveParameters'].sensorValid)
        plan_send.pathPlan.posenetValid = bool(
            sm['liveParameters'].posenetValid)

        pm.send('pathPlan', plan_send)

        dat = messaging.new_message()
        dat.init('liveMpc')
        dat.liveMpc.x = list(self.mpc_solution[0].x)
        dat.liveMpc.y = list(self.mpc_solution[0].y)
        dat.liveMpc.psi = list(self.mpc_solution[0].psi)
        dat.liveMpc.delta = list(self.mpc_solution[0].delta)
        dat.liveMpc.cost = self.mpc_solution[0].cost
        pm.send('liveMpc', dat)

        msg = arne182.LatControl.new_message()
        msg.anglelater = math.degrees(list(self.mpc_solution[0].delta)[-1])
        if not travis:
            self.latControl_sock.send(msg.to_bytes())
Example #26
0
    def update(self, CS, CP, VM, LaC, LoC, v_cruise_kph, force_slow_decel):
        cur_time = sec_since_boot()
        v_cruise_setpoint = v_cruise_kph * CV.KPH_TO_MS

        md = None
        l20 = None
        gps_planner_plan = None

        for socket, event in self.poller.poll(0):
            if socket is self.model:
                md = messaging.recv_one(socket)
            elif socket is self.live20:
                l20 = messaging.recv_one(socket)
            elif socket is self.gps_planner_plan:
                gps_planner_plan = messaging.recv_one(socket)
            elif socket is self.live_map_data:
                self.last_live_map_data = messaging.recv_one(
                    socket).liveMapData

        if gps_planner_plan is not None:
            self.last_gps_planner_plan = gps_planner_plan

        if md is not None:
            self.last_md_ts = md.logMonoTime
            self.last_model = cur_time
            self.model_dead = False

            self.PP.update(CS.vEgo, md)

            if self.last_gps_planner_plan is not None:
                plan = self.last_gps_planner_plan.gpsPlannerPlan
                self.gps_planner_active = plan.valid
                if plan.valid:
                    self.PP.d_poly = plan.poly
                    self.PP.p_poly = plan.poly
                    self.PP.c_poly = plan.poly
                    self.PP.l_prob = 0.0
                    self.PP.r_prob = 0.0
                    self.PP.c_prob = 1.0

        if l20 is not None:
            self.perception_state = copy(l20.live20)
            self.last_l20_ts = l20.logMonoTime
            self.last_l20 = cur_time
            self.radar_dead = False
            self.radar_errors = list(l20.live20.radarErrors)

            self.v_acc_start = self.v_acc_sol
            self.a_acc_start = self.a_acc_sol
            self.acc_start_time = cur_time

            self.lead_1 = l20.live20.leadOne
            self.lead_2 = l20.live20.leadTwo

            enabled = (LoC.long_control_state
                       == LongCtrlState.pid) or (LoC.long_control_state
                                                 == LongCtrlState.stopping)
            following = self.lead_1.status and self.lead_1.dRel < 45.0 and self.lead_1.vLeadK > CS.vEgo and self.lead_1.aLeadK > 0.0

            if self.last_live_map_data:
                self.v_speedlimit = NO_CURVATURE_SPEED
                self.v_curvature = NO_CURVATURE_SPEED
                self.map_valid = self.last_live_map_data.mapValid

                # Speed limit
                if self.last_live_map_data.speedLimitValid:
                    speed_limit = self.last_live_map_data.speedLimit
                    set_speed_limit_active = self.params.get(
                        "LimitSetSpeed") == "1" and self.params.get(
                            "SpeedLimitOffset") is not None

                    if set_speed_limit_active:
                        offset = float(self.params.get("SpeedLimitOffset"))
                        self.v_speedlimit = speed_limit + offset

                        # Curvature
                        if self.last_live_map_data.curvatureValid:
                            curvature = abs(self.last_live_map_data.curvature)
                            v_curvature = math.sqrt(A_Y_MAX /
                                                    max(1e-4, curvature))
                            self.v_curvature = min(NO_CURVATURE_SPEED,
                                                   v_curvature)

            # leave 1m/s margin on vEgo to asses if turn is limiting our speed.
            self.decel_for_turn = bool(self.v_curvature < min(
                [v_cruise_setpoint, self.v_speedlimit, CS.vEgo + 1.]))
            v_cruise_setpoint = min(
                [v_cruise_setpoint, self.v_curvature, self.v_speedlimit])

            # Calculate speed for normal cruise control
            if enabled:
                accel_limits = map(
                    float, calc_cruise_accel_limits(CS.vEgo, following))
                # TODO: make a separate lookup for jerk tuning
                jerk_limits = [
                    min(-0.1, accel_limits[0]),
                    max(0.1, accel_limits[1])
                ]
                accel_limits = limit_accel_in_turns(CS.vEgo, CS.steeringAngle,
                                                    accel_limits, self.CP)

                if force_slow_decel:
                    # if required so, force a smooth deceleration
                    accel_limits[1] = min(accel_limits[1], AWARENESS_DECEL)
                    accel_limits[0] = min(accel_limits[0], accel_limits[1])

                # Change accel limits based on time remaining to turn
                if self.decel_for_turn:
                    time_to_turn = max(
                        1.0, self.last_live_map_data.distToTurn /
                        max(self.v_cruise, 1.))
                    required_decel = min(
                        0, (self.v_curvature - self.v_cruise) / time_to_turn)
                    accel_limits[0] = max(accel_limits[0], required_decel)

                self.v_cruise, self.a_cruise = speed_smoother(
                    self.v_acc_start, self.a_acc_start, v_cruise_setpoint,
                    accel_limits[1], accel_limits[0], jerk_limits[1],
                    jerk_limits[0], _DT_MPC)
                # cruise speed can't be negative even is user is distracted
                self.v_cruise = max(self.v_cruise, 0.)
            else:
                starting = LoC.long_control_state == LongCtrlState.starting
                a_ego = min(CS.aEgo, 0.0)
                reset_speed = MIN_CAN_SPEED if starting else CS.vEgo
                reset_accel = self.CP.startAccel if starting else a_ego
                self.v_acc = reset_speed
                self.a_acc = reset_accel
                self.v_acc_start = reset_speed
                self.a_acc_start = reset_accel
                self.v_cruise = reset_speed
                self.a_cruise = reset_accel
                self.v_acc_sol = reset_speed
                self.a_acc_sol = reset_accel

            self.mpc1.set_cur_state(self.v_acc_start, self.a_acc_start)
            self.mpc2.set_cur_state(self.v_acc_start, self.a_acc_start)

            self.mpc1.update(CS, self.lead_1, v_cruise_setpoint)
            self.mpc2.update(CS, self.lead_2, v_cruise_setpoint)

            self.choose_solution(v_cruise_setpoint, enabled)

            # determine fcw
            if self.mpc1.new_lead:
                self.fcw_checker.reset_lead(cur_time)

            blinkers = CS.leftBlinker or CS.rightBlinker
            self.fcw = self.fcw_checker.update(self.mpc1.mpc_solution, cur_time, CS.vEgo, CS.aEgo,
                                               self.lead_1.dRel, self.lead_1.vLead, self.lead_1.aLeadK,
                                               self.lead_1.yRel, self.lead_1.vLat,
                                               self.lead_1.fcw, blinkers) \
                       and not CS.brakePressed
            if self.fcw:
                cloudlog.info("FCW triggered %s", self.fcw_checker.counters)

        if cur_time - self.last_model > 0.5:
            self.model_dead = True

        if cur_time - self.last_l20 > 0.5:
            self.radar_dead = True
        # **** send the plan ****
        plan_send = messaging.new_message()
        plan_send.init('plan')

        events = []
        if self.model_dead:
            events.append(
                create_event('modelCommIssue',
                             [ET.NO_ENTRY, ET.IMMEDIATE_DISABLE]))
        if self.radar_dead or 'commIssue' in self.radar_errors:
            events.append(
                create_event('radarCommIssue', [ET.NO_ENTRY, ET.SOFT_DISABLE]))
        if 'fault' in self.radar_errors:
            events.append(
                create_event('radarFault', [ET.NO_ENTRY, ET.SOFT_DISABLE]))
        if LaC.mpc_solution[
                0].cost > 10000. or LaC.mpc_nans:  # TODO: find a better way to detect when MPC did not converge
            events.append(
                create_event('plannerError',
                             [ET.NO_ENTRY, ET.IMMEDIATE_DISABLE]))

        # Interpolation of trajectory
        dt = min(
            cur_time - self.acc_start_time, _DT_MPC + _DT
        ) + _DT  # no greater than dt mpc + dt, to prevent too high extraps
        self.a_acc_sol = self.a_acc_start + (dt / _DT_MPC) * (self.a_acc -
                                                              self.a_acc_start)
        self.v_acc_sol = self.v_acc_start + dt * (self.a_acc_sol +
                                                  self.a_acc_start) / 2.0

        plan_send.plan.events = events
        plan_send.plan.mdMonoTime = self.last_md_ts
        plan_send.plan.l20MonoTime = self.last_l20_ts

        # lateral plan
        plan_send.plan.lateralValid = not self.model_dead
        plan_send.plan.dPoly = map(float, self.PP.d_poly)
        plan_send.plan.laneWidth = float(self.PP.lane_width)

        # longitudal plan
        plan_send.plan.longitudinalValid = not self.radar_dead
        plan_send.plan.vCruise = self.v_cruise
        plan_send.plan.aCruise = self.a_cruise
        plan_send.plan.vTarget = self.v_acc_sol
        plan_send.plan.aTarget = self.a_acc_sol
        plan_send.plan.vTargetFuture = self.v_acc_future
        plan_send.plan.hasLead = self.mpc1.prev_lead_status
        plan_send.plan.hasLeftLane = bool(self.PP.l_prob > 0.5)
        plan_send.plan.hasRightLane = bool(self.PP.r_prob > 0.5)
        plan_send.plan.longitudinalPlanSource = self.longitudinalPlanSource

        plan_send.plan.gpsPlannerActive = self.gps_planner_active

        plan_send.plan.vCurvature = self.v_curvature
        plan_send.plan.decelForTurn = self.decel_for_turn
        plan_send.plan.mapValid = self.map_valid

        # Send out fcw
        fcw = self.fcw and (self.fcw_enabled
                            or LoC.long_control_state != LongCtrlState.off)
        plan_send.plan.fcw = fcw

        self.plan.send(plan_send.to_bytes())
        return plan_send
Example #27
0
    def update(self, c, can_strings):
        self.pt_cp.update_strings(int(sec_since_boot() * 1e9), can_strings)
        self.cam_cp.update_strings(int(sec_since_boot() * 1e9), can_strings)

        self.CS.update(self.pt_cp, self.cam_cp)

        # create message
        ret = car.CarState.new_message()

        ret.canValid = self.pt_cp.can_valid and self.cam_cp.can_valid

        # speeds
        ret.vEgo = self.CS.v_ego
        ret.aEgo = self.CS.a_ego
        ret.vEgoRaw = self.CS.v_ego_raw
        ret.yawRate = self.VM.yaw_rate(self.CS.angle_steers * CV.DEG_TO_RAD,
                                       self.CS.v_ego)
        ret.standstill = self.CS.standstill
        ret.wheelSpeeds.fl = self.CS.v_wheel_fl
        ret.wheelSpeeds.fr = self.CS.v_wheel_fr
        ret.wheelSpeeds.rl = self.CS.v_wheel_rl
        ret.wheelSpeeds.rr = self.CS.v_wheel_rr

        # steering wheel
        ret.steeringAngle = self.CS.angle_steers

        # torque and user override. Driver awareness
        # timer resets when the user uses the steering wheel.
        ret.steeringPressed = self.CS.steer_override
        ret.steeringTorque = self.CS.steer_torque_driver

        ret.gas = self.CS.pedal_gas / 255.
        ret.gasPressed = self.CS.user_gas_pressed

        # cruise state
        ret.cruiseState.enabled = bool(self.CS.acc_active)
        ret.cruiseState.speed = self.CS.v_cruise_pcm * CV.KPH_TO_MS
        ret.cruiseState.available = bool(self.CS.main_on)
        ret.cruiseState.speedOffset = 0.

        ret.leftBlinker = self.CS.left_blinker_on
        ret.rightBlinker = self.CS.right_blinker_on
        ret.seatbeltUnlatched = self.CS.seatbelt_unlatched
        ret.doorOpen = self.CS.door_open

        buttonEvents = []

        # blinkers
        if self.CS.left_blinker_on != self.CS.prev_left_blinker_on:
            be = car.CarState.ButtonEvent.new_message()
            be.type = 'leftBlinker'
            be.pressed = self.CS.left_blinker_on
            buttonEvents.append(be)

        if self.CS.right_blinker_on != self.CS.prev_right_blinker_on:
            be = car.CarState.ButtonEvent.new_message()
            be.type = 'rightBlinker'
            be.pressed = self.CS.right_blinker_on
            buttonEvents.append(be)

        be = car.CarState.ButtonEvent.new_message()
        be.type = 'accelCruise'
        buttonEvents.append(be)

        events = []
        if ret.seatbeltUnlatched:
            events.append(
                create_event('seatbeltNotLatched',
                             [ET.NO_ENTRY, ET.SOFT_DISABLE]))

        if ret.doorOpen:
            events.append(
                create_event('doorOpen', [ET.NO_ENTRY, ET.SOFT_DISABLE]))

        if self.CS.acc_active and not self.acc_active_prev:
            events.append(create_event('pcmEnable', [ET.ENABLE]))
        if not self.CS.acc_active:
            events.append(create_event('pcmDisable', [ET.USER_DISABLE]))

        # disable on gas pedal rising edge
        if (ret.gasPressed and not self.gas_pressed_prev):
            events.append(
                create_event('pedalPressed', [ET.NO_ENTRY, ET.USER_DISABLE]))

        if ret.gasPressed:
            events.append(create_event('pedalPressed', [ET.PRE_ENABLE]))

        ret.events = events

        # update previous brake/gas pressed
        self.gas_pressed_prev = ret.gasPressed
        self.acc_active_prev = self.CS.acc_active

        # cast to reader so it can't be modified
        return ret.as_reader()
Example #28
0
def state_control(plan, CS, CP, state, events, v_cruise_kph, v_cruise_kph_last,
                  AM, rk, driver_status, PL, LaC, LoC, VM, angle_offset,
                  passive, is_metric, cal_perc):
    """Given the state, this function returns an actuators packet"""

    actuators = car.CarControl.Actuators.new_message()

    enabled = isEnabled(state)
    active = isActive(state)

    # check if user has interacted with the car
    driver_engaged = len(CS.buttonEvents) > 0 or \
                     v_cruise_kph != v_cruise_kph_last or \
                     CS.steeringPressed

    # add eventual driver distracted events
    events = driver_status.update(events, driver_engaged, isActive(state),
                                  CS.standstill)

    # send FCW alert if triggered by planner
    if plan.fcw:
        AM.add("fcw", enabled)

    # State specific actions

    if state in [State.preEnabled, State.disabled]:
        LaC.reset()
        LoC.reset(v_pid=CS.vEgo)

    elif state in [State.enabled, State.softDisabling]:
        # parse warnings from car specific interface
        for e in get_events(events, [ET.WARNING]):
            extra_text = ""
            if e == "belowSteerSpeed":
                if is_metric:
                    extra_text = str(
                        int(round(CP.minSteerSpeed * CV.MS_TO_KPH))) + " kph"
                else:
                    extra_text = str(
                        int(round(CP.minSteerSpeed * CV.MS_TO_MPH))) + " mph"
            AM.add(e, enabled, extra_text_2=extra_text)

    # Run angle offset learner at 20 Hz
    if rk.frame % 5 == 2 and plan.lateralValid:
        angle_offset = learn_angle_offset(active, CS.vEgo, angle_offset,
                                          PL.PP.c_poly, PL.PP.c_prob,
                                          CS.steeringAngle, CS.steeringPressed)

    # Gas/Brake PID loop
    actuators.gas, actuators.brake = LoC.update(active, CS.vEgo,
                                                CS.brakePressed, CS.standstill,
                                                CS.cruiseState.standstill,
                                                v_cruise_kph, plan.vTarget,
                                                plan.vTargetFuture,
                                                plan.aTarget, CP, PL.lead_1)
    # Steering PID loop and lateral MPC
    actuators.steer, actuators.steerAngle = LaC.update(
        active, CS.vEgo, CS.steeringAngle, CS.steeringPressed, plan.dPoly,
        angle_offset, CP, VM, PL)

    # Send a "steering required alert" if saturation count has reached the limit
    if LaC.sat_flag and CP.steerLimitAlert:
        AM.add("steerSaturated", enabled)

    # Parse permanent warnings to display constantly
    for e in get_events(events, [ET.PERMANENT]):
        extra_text_1, extra_text_2 = "", ""
        if e == "calibrationIncomplete":
            extra_text_1 = str(cal_perc) + "%"
            if is_metric:
                extra_text_2 = str(int(round(
                    Filter.MIN_SPEED * CV.MS_TO_KPH))) + " kph"
            else:
                extra_text_2 = str(int(round(
                    Filter.MIN_SPEED * CV.MS_TO_MPH))) + " mph"
        AM.add(str(e) + "Permanent",
               enabled,
               extra_text_1=extra_text_1,
               extra_text_2=extra_text_2)

    AM.process_alerts(sec_since_boot())

    return actuators, v_cruise_kph, driver_status, angle_offset
Example #29
0
def controlsd_thread(sm=None, pm=None, can_sock=None):
    gc.disable()

    # start the loop
    set_realtime_priority(3)

    params = Params()

    is_metric = params.get("IsMetric", encoding='utf8') == "1"
    is_ldw_enabled = params.get("IsLdwEnabled", encoding='utf8') == "1"
    passive = params.get("Passive", encoding='utf8') == "1"
    openpilot_enabled_toggle = params.get("OpenpilotEnabledToggle",
                                          encoding='utf8') == "1"
    community_feature_toggle = params.get("CommunityFeaturesToggle",
                                          encoding='utf8') == "1"

    passive = passive or not openpilot_enabled_toggle

    # Passive if internet needed
    internet_needed = params.get("Offroad_ConnectivityNeeded",
                                 encoding='utf8') is not None
    passive = passive or internet_needed

    # Pub/Sub Sockets
    if pm is None:
        pm = messaging.PubMaster([
            'sendcan', 'controlsState', 'carState', 'carControl', 'carEvents',
            'carParams'
        ])

    if sm is None:
        sm = messaging.SubMaster(['thermal', 'health', 'liveCalibration', 'dMonitoringState', 'plan', 'pathPlan', \
                                  'model'])

    if can_sock is None:
        can_timeout = None if os.environ.get('NO_CAN_TIMEOUT', False) else 100
        can_sock = messaging.sub_sock('can', timeout=can_timeout)

    # wait for health and CAN packets
    hw_type = messaging.recv_one(sm.sock['health']).health.hwType
    has_relay = hw_type in [HwType.blackPanda, HwType.uno]
    print("Waiting for CAN messages...")
    messaging.get_one_can(can_sock)

    CI, CP = get_car(can_sock, pm.sock['sendcan'], has_relay)

    car_recognized = CP.carName != 'mock'
    # If stock camera is disconnected, we loaded car controls and it's not chffrplus
    controller_available = CP.enableCamera and CI.CC is not None and not passive
    community_feature_disallowed = CP.communityFeature and not community_feature_toggle
    read_only = not car_recognized or not controller_available or CP.dashcamOnly or community_feature_disallowed
    if read_only:
        CP.safetyModel = car.CarParams.SafetyModel.noOutput

    # Write CarParams for radard and boardd safety mode
    cp_bytes = CP.to_bytes()
    params.put("CarParams", cp_bytes)
    params.put("CarParamsCache", cp_bytes)
    params.put("LongitudinalControl",
               "1" if CP.openpilotLongitudinalControl else "0")

    CC = car.CarControl.new_message()
    AM = AlertManager()

    startup_alert = get_startup_alert(car_recognized, controller_available)
    AM.add(sm.frame, startup_alert, False)

    LoC = LongControl(CP, CI.compute_gb)
    VM = VehicleModel(CP)

    if CP.lateralTuning.which() == 'pid':
        LaC = LatControlPID(CP)
    elif CP.lateralTuning.which() == 'indi':
        LaC = LatControlINDI(CP)
    elif CP.lateralTuning.which() == 'lqr':
        LaC = LatControlLQR(CP)

    state = State.disabled
    soft_disable_timer = 0
    v_cruise_kph = 255
    v_cruise_kph_last = 0
    mismatch_counter = 0
    can_error_counter = 0
    last_blinker_frame = 0
    active_count = 0
    events_prev = []

    sm['liveCalibration'].calStatus = Calibration.INVALID
    sm['pathPlan'].sensorValid = True
    sm['pathPlan'].posenetValid = True
    sm['thermal'].freeSpace = 1.
    sm['dMonitoringState'].events = []
    sm['dMonitoringState'].awarenessStatus = 1.
    sm['dMonitoringState'].faceDetected = False

    # detect sound card presence
    sounds_available = not os.path.isfile('/EON') or (
        os.path.isdir('/proc/asound/card0')
        and open('/proc/asound/card0/state').read().strip() == 'ONLINE')

    # controlsd is driven by can recv, expected at 100Hz
    rk = Ratekeeper(100, print_delay_threshold=None)

    prof = Profiler(False)  # off by default

    while True:
        start_time = sec_since_boot()
        prof.checkpoint("Ratekeeper", ignore=True)

        # Sample data and compute car events
        CS, events, cal_perc, mismatch_counter, can_error_counter = data_sample(
            CI, CC, sm, can_sock, state, mismatch_counter, can_error_counter,
            params)
        prof.checkpoint("Sample")

        # Create alerts
        if not sm.alive['plan'] and sm.alive[
                'pathPlan']:  # only plan not being received: radar not communicating
            events.append(
                create_event('radarCommIssue', [ET.NO_ENTRY, ET.SOFT_DISABLE]))
        elif not sm.all_alive_and_valid():
            events.append(
                create_event('commIssue', [ET.NO_ENTRY, ET.SOFT_DISABLE]))
        if not sm['pathPlan'].mpcSolutionValid:
            events.append(
                create_event('plannerError',
                             [ET.NO_ENTRY, ET.IMMEDIATE_DISABLE]))
        if not sm['pathPlan'].sensorValid and os.getenv("NOSENSOR") is None:
            events.append(
                create_event('sensorDataInvalid', [ET.NO_ENTRY, ET.PERMANENT]))
        if not sm['pathPlan'].paramsValid:
            events.append(create_event('vehicleModelInvalid', [ET.WARNING]))
        if not sm['pathPlan'].posenetValid:
            events.append(
                create_event('posenetInvalid', [ET.NO_ENTRY, ET.WARNING]))
        if not sm['plan'].radarValid:
            events.append(
                create_event('radarFault', [ET.NO_ENTRY, ET.SOFT_DISABLE]))
        if sm['plan'].radarCanError:
            events.append(
                create_event('radarCanError', [ET.NO_ENTRY, ET.SOFT_DISABLE]))
        if not CS.canValid:
            events.append(
                create_event('canError', [ET.NO_ENTRY, ET.IMMEDIATE_DISABLE]))
        if not sounds_available:
            events.append(
                create_event('soundsUnavailable', [ET.NO_ENTRY, ET.PERMANENT]))
        if internet_needed:
            events.append(
                create_event('internetConnectivityNeeded',
                             [ET.NO_ENTRY, ET.PERMANENT]))
        if community_feature_disallowed:
            events.append(
                create_event('communityFeatureDisallowed', [ET.PERMANENT]))
        if read_only and not passive:
            events.append(create_event('carUnrecognized', [ET.PERMANENT]))

        # Only allow engagement with brake pressed when stopped behind another stopped car
        if CS.brakePressed and sm[
                'plan'].vTargetFuture >= STARTING_TARGET_SPEED and not CP.radarOffCan and CS.vEgo < 0.3:
            events.append(
                create_event('noTarget', [ET.NO_ENTRY, ET.IMMEDIATE_DISABLE]))

        if not read_only:
            # update control state
            state, soft_disable_timer, v_cruise_kph, v_cruise_kph_last = \
              state_transition(sm.frame, CS, CP, state, events, soft_disable_timer, v_cruise_kph, AM)
            prof.checkpoint("State transition")

        # Compute actuators (runs PID loops and lateral MPC)
        actuators, v_cruise_kph, v_acc, a_acc, lac_log, last_blinker_frame, active_count = \
          state_control(sm.frame, sm.rcv_frame, sm['plan'], sm['pathPlan'], CS, CP, state, events, v_cruise_kph, v_cruise_kph_last, AM, rk,
                        LaC, LoC, read_only, is_metric, cal_perc, last_blinker_frame, active_count)

        prof.checkpoint("State Control")

        # Publish data
        CC, events_prev = data_send(sm, pm, CS, CI, CP, VM, state, events,
                                    actuators, v_cruise_kph, rk, AM, LaC, LoC,
                                    read_only, start_time, v_acc, a_acc,
                                    lac_log, events_prev, last_blinker_frame,
                                    is_ldw_enabled, can_error_counter)
        prof.checkpoint("Sent")

        rk.monitor_time()
        prof.display()
Example #30
0
    def update_can(self, can_recv):
        self.msgs_upd = []
        cn_vl_max = 5  # no more than 5 wrong counter checks

        # we are subscribing to PID_XXX, else data from USB
        for msg, ts, cdat in can_recv:
            idxs = self._message_indices[msg]
            if idxs:
                self.msgs_upd += [msg]
                # read the entire message
                out = self.can_dbc.decode([msg, 0, cdat])[1]
                # checksum check
                self.ck[msg] = True
                if "CHECKSUM" in out.keys() and msg in self.msgs_ck:
                    # remove checksum (half byte)
                    ck_portion = (''.join((cdat[:-1], '0'))).decode('hex')
                    # recalculate checksum
                    msg_vl = fix(ck_portion, msg)
                    # compare recalculated vs received checksum
                    if msg_vl != cdat.decode('hex'):
                        print hex(msg), "CHECKSUM FAIL"
                        self.ck[msg] = False
                        self.ok[msg] = False
                # counter check
                cn = 0
                if "COUNTER" in out.keys():
                    cn = out["COUNTER"]
                # check counter validity if it's a relevant message
                if cn != (
                    (self.cn[msg] + 1) %
                        4) and msg in self.msgs_ck and "COUNTER" in out.keys():
                    #print hex(msg), "FAILED COUNTER!"
                    self.cn_vl[msg] += 1  # counter check failed
                else:
                    self.cn_vl[msg] -= 1  # counter check passed
                # message status is invalid if we received too many wrong counter values
                if self.cn_vl[msg] >= cn_vl_max:
                    self.ok[msg] = False

                # update msg time stamps and counter value
                self.ts[msg] = ts
                self.ct[msg] = sec_since_boot()
                self.cn[msg] = cn
                self.cn_vl[msg] = min(max(self.cn_vl[msg], 0), cn_vl_max)

                # set msg valid status if checksum is good and wrong counter counter is zero
                if self.ck[msg] and self.cn_vl[msg] == 0:
                    self.ok[msg] = True

                # update value of signals in the
                for ii in idxs:
                    sg = self._sgs[ii]
                    self.vl[msg][sg] = out[sg]

    # for each message, check if it's too long since last time we received it
        self._check_dead_msgs()

        # assess overall can validity: if there is one relevant message invalid, then set can validity flag to False
        self.can_valid = True
        if False in self.ok.values():
            #print "CAN INVALID!"
            self.can_valid = False