Beispiel #1
0
  def update(self, enabled, active, CS, frame, actuators, pcm_cancel_cmd, hud_alert,
             left_line, right_line, lead, left_lane_depart, right_lane_depart):

    # *** compute control surfaces ***

    # gas and brake
    if CS.CP.enableGasInterceptor and enabled:
      MAX_INTERCEPTOR_GAS = 0.5
      # RAV4 has very sensitive gas pedal
      if CS.CP.carFingerprint in [CAR.RAV4, CAR.RAV4H, CAR.HIGHLANDER, CAR.HIGHLANDERH]:
        PEDAL_SCALE = interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION], [0.15, 0.3, 0.0])
      elif CS.CP.carFingerprint in [CAR.COROLLA]:
        PEDAL_SCALE = interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION], [0.3, 0.4, 0.0])
      else:
        PEDAL_SCALE = interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION], [0.4, 0.5, 0.0])
      # offset for creep and windbrake
      pedal_offset = interp(CS.out.vEgo, [0.0, 2.3, MIN_ACC_SPEED + PEDAL_TRANSITION], [-.4, 0.0, 0.2])
      pedal_command = PEDAL_SCALE * (actuators.accel + pedal_offset)
      interceptor_gas_cmd = clip(pedal_command, 0., MAX_INTERCEPTOR_GAS)
    else:
      interceptor_gas_cmd = 0.
    pcm_accel_cmd = clip(actuators.accel, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX)

    # steer torque
    new_steer = int(round(actuators.steer * CarControllerParams.STEER_MAX))
    apply_steer = apply_toyota_steer_torque_limits(new_steer, self.last_steer, CS.out.steeringTorqueEps, CarControllerParams)
    self.steer_rate_limited = new_steer != apply_steer

    # Cut steering while we're in a known fault state (2s)
    if not enabled or CS.steer_state in [9, 25]:
      apply_steer = 0
      apply_steer_req = 0
    else:
      apply_steer_req = 1

    # TODO: probably can delete this. CS.pcm_acc_status uses a different signal
    # than CS.cruiseState.enabled. confirm they're not meaningfully different
    if not enabled and CS.pcm_acc_status:
      pcm_cancel_cmd = 1

    # on entering standstill, send standstill request
    if CS.out.standstill and not self.last_standstill and CS.CP.carFingerprint not in NO_STOP_TIMER_CAR:
      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_standstill = CS.out.standstill

    can_sends = []

    #*** control msgs ***
    #print("steer {0} {1} {2} {3}".format(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
    can_sends.append(create_steer_command(self.packer, apply_steer, apply_steer_req, frame))
    if frame % 2 == 0 and CS.CP.carFingerprint in TSS2_CAR:
      can_sends.append(create_lta_steer_command(self.packer, 0, 0, frame // 2))

    # LTA mode. Set ret.steerControlType = car.CarParams.SteerControlType.angle and whitelist 0x191 in the panda
    # if frame % 2 == 0:
    #   can_sends.append(create_steer_command(self.packer, 0, 0, frame // 2))
    #   can_sends.append(create_lta_steer_command(self.packer, actuators.steeringAngleDeg, apply_steer_req, frame // 2))

    # we can spam can to cancel the system even if we are using lat only control
    if (frame % 3 == 0 and CS.CP.openpilotLongitudinalControl) or pcm_cancel_cmd:
      lead = lead or CS.out.vEgo < 12.    # at low speed we always assume the lead is present do ACC can be engaged

      # Lexus IS uses a different cancellation message
      if pcm_cancel_cmd and CS.CP.carFingerprint in [CAR.LEXUS_IS, CAR.LEXUS_RC]:
        can_sends.append(create_acc_cancel_command(self.packer))
      elif CS.CP.openpilotLongitudinalControl:
        can_sends.append(create_accel_command(self.packer, pcm_accel_cmd, pcm_cancel_cmd, self.standstill_req, lead, CS.acc_type))
      else:
        can_sends.append(create_accel_command(self.packer, 0, pcm_cancel_cmd, False, lead, CS.acc_type))

    if frame % 2 == 0 and CS.CP.enableGasInterceptor:
      # send exactly zero if gas cmd is zero. Interceptor will send the max between read value and gas cmd.
      # This prevents unexpected pedal range rescaling
      can_sends.append(create_gas_interceptor_command(self.packer, interceptor_gas_cmd, frame // 2))

    # ui mesg is at 100Hz but we send asap if:
    # - there is something to display
    # - there is something to stop displaying
    fcw_alert = hud_alert == VisualAlert.fcw
    steer_alert = hud_alert in [VisualAlert.steerRequired, VisualAlert.ldw]

    send_ui = False
    if ((fcw_alert or steer_alert) and not self.alert_active) or \
       (not (fcw_alert or steer_alert) and self.alert_active):
      send_ui = True
      self.alert_active = not self.alert_active
    elif pcm_cancel_cmd:
      # forcing the pcm to disengage causes a bad fault sound so play a good sound instead
      send_ui = True

    if (frame % 100 == 0 or send_ui):
      can_sends.append(create_ui_command(self.packer, steer_alert, pcm_cancel_cmd, left_line, right_line, left_lane_depart, right_lane_depart, enabled))

    if frame % 100 == 0 and CS.CP.enableDsu:
      can_sends.append(create_fcw_command(self.packer, fcw_alert))

    #*** static msgs ***

    for (addr, cars, bus, fr_step, vl) in STATIC_DSU_MSGS:
      if frame % fr_step == 0 and CS.CP.enableDsu and CS.CP.carFingerprint in cars:
        can_sends.append(make_can_msg(addr, vl, bus))

    return can_sends
Beispiel #2
0
    def update(self, c, enabled, CS, frame, actuators, hud_v_cruise,
               hud_show_lanes, hud_show_car, hud_alert):

        P = self.params

        # Send CAN commands.
        can_sends = []

        # Steering (50Hz)
        # Avoid GM EPS faults when transmitting messages too close together: skip this transmit if we just received the
        # next Panda loopback confirmation in the current CS frame.
        if CS.lka_steering_cmd_counter != self.lka_steering_cmd_counter_last:
            self.lka_steering_cmd_counter_last = CS.lka_steering_cmd_counter
        if (frame % P.STEER_STEP) == 0:
            lkas_enabled = c.active and not (
                CS.out.steerWarning
                or CS.out.steerError) and CS.out.vEgo > P.MIN_STEER_SPEED
            if lkas_enabled:
                new_steer = int(round(actuators.steer * P.STEER_MAX))
                apply_steer = apply_std_steer_torque_limits(
                    new_steer, self.apply_steer_last, CS.out.steeringTorque, P)
                self.steer_rate_limited = new_steer != apply_steer
            else:
                apply_steer = 0

            self.apply_steer_last = apply_steer
            # GM EPS faults on any gap in received message counters. To handle transient OP/Panda safety sync issues at the
            # moment of disengaging, increment the counter based on the last message known to pass Panda safety checks.
            idx = (CS.lka_steering_cmd_counter + 1) % 4

            can_sends.append(
                gmcan.create_steering_control(self.packer_pt,
                                              CanBus.POWERTRAIN, apply_steer,
                                              idx, lkas_enabled))

        # TODO: All three conditions should not be required - really only last two?
        if CS.CP.carFingerprint not in NO_ASCM and CS.CP.openpilotLongitudinalControl and not CS.CP.pcmCruise:
            # Gas/regen and brakes - all at 25Hz
            if (frame % 4) == 0:
                if not c.active:
                    # Stock ECU sends max regen when not enabled.
                    self.apply_gas = P.MAX_ACC_REGEN
                    self.apply_brake = 0
                else:
                    self.apply_gas = int(
                        round(
                            interp(actuators.accel, P.GAS_LOOKUP_BP,
                                   P.GAS_LOOKUP_V)))
                    self.apply_brake = int(
                        round(
                            interp(actuators.accel, P.BRAKE_LOOKUP_BP,
                                   P.BRAKE_LOOKUP_V)))

                idx = (frame // 4) % 4
                # TODO: Should all instances of "enabled" be replaced with c.active?
                at_full_stop = enabled and CS.out.standstill
                near_stop = enabled and (CS.out.vEgo < P.NEAR_STOP_BRAKE_PHASE)
                can_sends.append(
                    gmcan.create_friction_brake_command(
                        self.packer_ch, CanBus.CHASSIS, self.apply_brake, idx,
                        near_stop, at_full_stop))
                can_sends.append(
                    gmcan.create_gas_regen_command(self.packer_pt,
                                                   CanBus.POWERTRAIN,
                                                   self.apply_gas, idx,
                                                   enabled, at_full_stop))

            # Send dashboard UI commands (ACC status), 25hz
            if (frame % 4) == 0:
                send_fcw = hud_alert == VisualAlert.fcw
                can_sends.append(
                    gmcan.create_acc_dashboard_command(
                        self.packer_pt, CanBus.POWERTRAIN, enabled,
                        hud_v_cruise * CV.MS_TO_KPH, hud_show_car, send_fcw))

            # Radar needs to know current speed and yaw rate (50hz),
            # and that ADAS is alive (10hz)
            time_and_headlights_step = 10
            tt = frame * DT_CTRL

            if frame % time_and_headlights_step == 0:
                idx = (frame // time_and_headlights_step) % 4
                can_sends.append(
                    gmcan.create_adas_time_status(
                        CanBus.OBSTACLE, int((tt - self.start_time) * 60),
                        idx))
                can_sends.append(
                    gmcan.create_adas_headlights_status(
                        self.packer_obj, CanBus.OBSTACLE))

            speed_and_accelerometer_step = 2
            if frame % speed_and_accelerometer_step == 0:
                idx = (frame // speed_and_accelerometer_step) % 4
                can_sends.append(
                    gmcan.create_adas_steering_status(CanBus.OBSTACLE, idx))
                can_sends.append(
                    gmcan.create_adas_accelerometer_speed_status(
                        CanBus.OBSTACLE, CS.out.vEgo, idx))

            if frame % P.ADAS_KEEPALIVE_STEP == 0:
                can_sends += gmcan.create_adas_keepalive(CanBus.POWERTRAIN)
        elif CS.CP.openpilotLongitudinalControl:
            # Gas/regen and brakes - all at 25Hz
            if (frame % 4) == 0:
                if not c.active:
                    # Stock ECU sends max regen when not enabled.
                    self.apply_gas = P.MAX_ACC_REGEN
                    self.apply_brake = 0
                else:
                    self.apply_gas = int(
                        round(
                            interp(actuators.accel, P.GAS_LOOKUP_BP,
                                   P.GAS_LOOKUP_V)))
                    self.apply_brake = int(
                        round(
                            interp(actuators.accel, P.BRAKE_LOOKUP_BP,
                                   P.BRAKE_LOOKUP_V)))

                idx = (frame // 4) % 4

                at_full_stop = enabled and CS.out.standstill
                # near_stop = enabled and (CS.out.vEgo < P.NEAR_STOP_BRAKE_PHASE)
                # VOACC based cars have brakes on PT bus - OP won't be doing VOACC for a while
                # can_sends.append(gmcan.create_friction_brake_command(self.packer_pt, CanBus.POWERTRAIN, self.apply_brake, idx, near_stop, at_full_stop))

                if CS.CP.enableGasInterceptor:
                    # TODO: JJS Unsure if low is single pedal mode in any non-electric cars
                    singlePedalMode = CS.out.gearShifter == GearShifter.low and CS.CP.carFingerprint in EV_CAR
                    # TODO: JJS Detect saturated battery and fallback to D mode (until regen is available
                    if singlePedalMode:
                        # In L Mode, Pedal applies regen at a fixed coast-point (TODO: max regen in L mode may be different per car)
                        # This will apply to EVs in L mode.
                        # accel values below zero down to a cutoff point
                        #  that approximates the percentage of braking regen can handle should be scaled between 0 and the coast-point
                        # accell values below this point will need to be add-on future hijacked AEB
                        # TODO: Determine (or guess) at regen precentage

                        # From Felger's Bolt Bort
                        #It seems in L mode, accel / decel point is around 1/5
                        #-1-------AEB------0----regen---0.15-------accel----------+1
                        # Shrink gas request to 0.85, have it start at 0.2
                        # Shrink brake request to 0.85, first 0.15 gives regen, rest gives AEB

                        zero = 40 / 256

                        if (actuators.accel > 0.):
                            pedal_gas = clip(
                                ((1 - zero) * actuators.accel + zero), 0., 1.)
                        else:
                            pedal_gas = clip(
                                actuators.accel, 0., zero
                            )  # Make brake the same size as gas, but clip to regen
                            # aeb = actuators.brake*(1-zero)-regen # For use later, braking more than regen
                    else:
                        # In D Mode, Pedal is close to coast at 0, 100% at 1.
                        # This will apply to non-EVs and EVs in D mode.
                        # accel values below zero will need to be handled by future hijacked AEB
                        # TODO: Determine if this clipping is correct
                        pedal_gas = clip(actuators.accel, 0., 1.)

                    can_sends.append(
                        create_gas_interceptor_command(self.packer_pt,
                                                       pedal_gas, idx))
                else:
                    can_sends.append(
                        gmcan.create_gas_regen_command(self.packer_pt,
                                                       CanBus.POWERTRAIN,
                                                       self.apply_gas, idx,
                                                       enabled, at_full_stop))

        # Show green icon when LKA torque is applied, and
        # alarming orange icon when approaching torque limit.
        # If not sent again, LKA icon disappears in about 5 seconds.
        # Conveniently, sending camera message periodically also works as a keepalive.
        lka_active = CS.lkas_status == 1
        lka_critical = lka_active and abs(actuators.steer) > 0.9
        lka_icon_status = (lka_active, lka_critical)
        if frame % P.CAMERA_KEEPALIVE_STEP == 0 or lka_icon_status != self.lka_icon_status_last:
            steer_alert = hud_alert in (VisualAlert.steerRequired,
                                        VisualAlert.ldw)
            can_sends.append(
                gmcan.create_lka_icon_command(CanBus.SW_GMLAN, lka_active,
                                              lka_critical, steer_alert))
            self.lka_icon_status_last = lka_icon_status

        new_actuators = actuators.copy()
        new_actuators.steer = self.apply_steer_last / P.STEER_MAX
        new_actuators.gas = self.apply_gas
        new_actuators.brake = self.apply_brake

        return new_actuators, can_sends
Beispiel #3
0
    def update(self, c, CS, frame, actuators, pcm_cancel_cmd, hud_v_cruise,
               hud_show_lanes, hud_show_car, hud_alert):

        P = self.params

        if c.longActive:
            accel = actuators.accel
            gas, brake = compute_gas_brake(actuators.accel, CS.out.vEgo,
                                           CS.CP.carFingerprint)
        else:
            accel = 0.0
            gas, brake = 0.0, 0.0

        # *** apply brake hysteresis ***
        pre_limit_brake, self.braking, self.brake_steady = actuator_hystereses(
            brake, self.braking, self.brake_steady, CS.out.vEgo,
            CS.CP.carFingerprint)

        # *** rate limit after the enable check ***
        self.brake_last = rate_limit(pre_limit_brake, self.brake_last, -2.,
                                     DT_CTRL)

        # vehicle hud display, wait for one update from 10Hz 0x304 msg
        if hud_show_lanes:
            hud_lanes = 1
        else:
            hud_lanes = 0

        if c.enabled:
            if hud_show_car:
                hud_car = 2
            else:
                hud_car = 1
        else:
            hud_car = 0

        fcw_display, steer_required, acc_alert = process_hud_alert(hud_alert)

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

        # steer torque is converted back to CAN reference (positive when steering right)
        apply_steer = int(
            interp(-actuators.steer * P.STEER_MAX, P.STEER_LOOKUP_BP,
                   P.STEER_LOOKUP_V))

        # Send CAN commands.
        can_sends = []

        # tester present - w/ no response (keeps radar disabled)
        if CS.CP.carFingerprint in HONDA_BOSCH and CS.CP.openpilotLongitudinalControl:
            if (frame % 10) == 0:
                can_sends.append(
                    (0x18DAB0F1, 0, b"\x02\x3E\x80\x00\x00\x00\x00\x00", 1))

        # Send steering command.
        idx = frame % 4
        can_sends.append(
            hondacan.create_steering_control(
                self.packer, apply_steer, c.latActive, CS.CP.carFingerprint,
                idx, CS.CP.openpilotLongitudinalControl))

        stopping = actuators.longControlState == LongCtrlState.stopping

        # wind brake from air resistance decel at high speed
        wind_brake = interp(CS.out.vEgo, [0.0, 2.3, 35.0],
                            [0.001, 0.002, 0.15])
        # all of this is only relevant for HONDA NIDEC
        max_accel = interp(CS.out.vEgo, P.NIDEC_MAX_ACCEL_BP,
                           P.NIDEC_MAX_ACCEL_V)
        # TODO this 1.44 is just to maintain previous behavior
        pcm_speed_BP = [-wind_brake, -wind_brake * (3 / 4), 0.0, 0.5]
        # The Honda ODYSSEY seems to have different PCM_ACCEL
        # msgs, is it other cars too?
        if CS.CP.enableGasInterceptor:
            pcm_speed = 0.0
            pcm_accel = int(0.0)
        elif CS.CP.carFingerprint in HONDA_NIDEC_ALT_PCM_ACCEL:
            pcm_speed_V = [
                0.0,
                clip(CS.out.vEgo - 3.0, 0.0, 100.0),
                clip(CS.out.vEgo + 0.0, 0.0, 100.0),
                clip(CS.out.vEgo + 5.0, 0.0, 100.0)
            ]
            pcm_speed = interp(gas - brake, pcm_speed_BP, pcm_speed_V)
            pcm_accel = int((1.0) * 0xc6)
        else:
            pcm_speed_V = [
                0.0,
                clip(CS.out.vEgo - 2.0, 0.0, 100.0),
                clip(CS.out.vEgo + 2.0, 0.0, 100.0),
                clip(CS.out.vEgo + 5.0, 0.0, 100.0)
            ]
            pcm_speed = interp(gas - brake, pcm_speed_BP, pcm_speed_V)
            pcm_accel = int(clip((accel / 1.44) / max_accel, 0.0, 1.0) * 0xc6)

        if not CS.CP.openpilotLongitudinalControl:
            if (frame % 2) == 0:
                idx = frame // 2
                can_sends.append(
                    hondacan.create_bosch_supplemental_1(
                        self.packer, CS.CP.carFingerprint, idx))
            # If using stock ACC, spam cancel command to kill gas when OP disengages.
            if pcm_cancel_cmd:
                can_sends.append(
                    hondacan.spam_buttons_command(self.packer,
                                                  CruiseButtons.CANCEL, idx,
                                                  CS.CP.carFingerprint))
            elif CS.out.cruiseState.standstill:
                can_sends.append(
                    hondacan.spam_buttons_command(self.packer,
                                                  CruiseButtons.RES_ACCEL, idx,
                                                  CS.CP.carFingerprint))

        else:
            # Send gas and brake commands.
            if (frame % 2) == 0:
                idx = frame // 2
                ts = frame * DT_CTRL

                if CS.CP.carFingerprint in HONDA_BOSCH:
                    self.accel = clip(accel, P.BOSCH_ACCEL_MIN,
                                      P.BOSCH_ACCEL_MAX)
                    self.gas = interp(accel, P.BOSCH_GAS_LOOKUP_BP,
                                      P.BOSCH_GAS_LOOKUP_V)
                    can_sends.extend(
                        hondacan.create_acc_commands(self.packer, c.enabled,
                                                     c.longActive, accel,
                                                     self.gas, idx, stopping,
                                                     CS.CP.carFingerprint))
                else:
                    apply_brake = clip(self.brake_last - wind_brake, 0.0, 1.0)
                    apply_brake = int(
                        clip(apply_brake * P.NIDEC_BRAKE_MAX, 0,
                             P.NIDEC_BRAKE_MAX - 1))
                    pump_on, self.last_pump_ts = brake_pump_hysteresis(
                        apply_brake, self.apply_brake_last, self.last_pump_ts,
                        ts)

                    pcm_override = True
                    can_sends.append(
                        hondacan.create_brake_command(self.packer, apply_brake,
                                                      pump_on, pcm_override,
                                                      pcm_cancel_cmd,
                                                      fcw_display, idx,
                                                      CS.CP.carFingerprint,
                                                      CS.stock_brake))
                    self.apply_brake_last = apply_brake
                    self.brake = apply_brake / P.NIDEC_BRAKE_MAX

                    if CS.CP.enableGasInterceptor:
                        # way too aggressive at low speed without this
                        gas_mult = interp(CS.out.vEgo, [0., 10.], [0.4, 1.0])
                        # 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
                        # Sending non-zero gas when OP is not enabled will cause the PCM not to respond to throttle as expected
                        # when you do enable.
                        if c.longActive:
                            self.gas = clip(
                                gas_mult * (gas - brake + wind_brake * 3 / 4),
                                0., 1.)
                        else:
                            self.gas = 0.0
                        can_sends.append(
                            create_gas_interceptor_command(
                                self.packer, self.gas, idx))

        # Send dashboard UI commands.
        if (frame % 10) == 0:
            idx = (frame // 10) % 4
            hud = HUDData(int(pcm_accel), int(round(hud_v_cruise)), hud_car,
                          hud_lanes, fcw_display, acc_alert, steer_required)
            can_sends.extend(
                hondacan.create_ui_commands(self.packer, CS.CP, pcm_speed, hud,
                                            CS.is_metric, idx, CS.stock_hud))

            if (CS.CP.openpilotLongitudinalControl) and (CS.CP.carFingerprint
                                                         not in HONDA_BOSCH):
                self.speed = pcm_speed

                if not CS.CP.enableGasInterceptor:
                    self.gas = pcm_accel / 0xc6

        new_actuators = actuators.copy()
        new_actuators.speed = self.speed
        new_actuators.accel = self.accel
        new_actuators.gas = self.gas
        new_actuators.brake = self.brake

        return new_actuators, can_sends
Beispiel #4
0
  def update(self, enabled, active, CS, frame, actuators, pcm_cancel_cmd, hud_alert,
             left_line, right_line, lead, left_lane_depart, right_lane_depart):

    # gas and brake
    if CS.CP.enableGasInterceptor and active:
      MAX_INTERCEPTOR_GAS = 0.5
      # RAV4 has very sensitive gas pedal
      if CS.CP.carFingerprint in (CAR.RAV4, CAR.RAV4H, CAR.HIGHLANDER, CAR.HIGHLANDERH):
        PEDAL_SCALE = interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION], [0.15, 0.3, 0.0])
      elif CS.CP.carFingerprint in (CAR.COROLLA,):
        PEDAL_SCALE = interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION], [0.3, 0.4, 0.0])
      else:
        PEDAL_SCALE = interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION], [0.4, 0.5, 0.0])
      # offset for creep and windbrake
      pedal_offset = interp(CS.out.vEgo, [0.0, 2.3, MIN_ACC_SPEED + PEDAL_TRANSITION], [-.4, 0.0, 0.2])
      pedal_command = PEDAL_SCALE * (actuators.accel + pedal_offset)
      interceptor_gas_cmd = clip(pedal_command, 0., MAX_INTERCEPTOR_GAS)
    else:
      interceptor_gas_cmd = 0.
    pcm_accel_cmd = clip(actuators.accel, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX)

    # steer torque
    new_steer = int(round(actuators.steer * CarControllerParams.STEER_MAX))
    apply_steer = apply_toyota_steer_torque_limits(new_steer, self.last_steer, CS.out.steeringTorqueEps, CarControllerParams)
    self.steer_rate_limited = new_steer != apply_steer

    # TODO: probably can delete this. CS.pcm_acc_status uses a different signal
    # than CS.cruiseState.enabled. confirm they're not meaningfully different
    if not enabled and CS.pcm_acc_status:
      pcm_cancel_cmd = 1

    # on entering standstill, send standstill request
    if CS.out.standstill and not self.last_standstill and CS.CP.carFingerprint not in NO_STOP_TIMER_CAR:
      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_standstill = CS.out.standstill

    can_sends = []

    if CS.CP.steerControlType == car.CarParams.SteerControlType.angle:
      can_sends.append(create_steer_command(self.packer, 0, 0, frame))
      # On TSS2 cars, the LTA and STEER_TORQUE_SENSOR messages use relative steering angle signals that start
      # at 0 degrees, so we need to offset the commanded angle as well.
      # Also need to pulse steer_req to prevent 5 second steering lockout in some cases
      steer_req = active  # TODO(thinh): Check and cut out steering while we are in a known fault state
      percentage = interp(abs(CS.out.steeringTorque), [50, 100], [100, 0])
      commanded_angle = interp(percentage, [-10, 100], [CS.out.steeringAngleDeg + CS.out.steeringAngleOffsetDeg,
                    actuators.steeringAngleDeg + CS.out.steeringAngleOffsetDeg])
      can_sends.append(create_lta_steer_command(self.packer, commanded_angle, steer_req, frame))
    else:
      # Cut steering while we're in a known fault state (2s)
      if not active or CS.steer_state in (9, 25):
        apply_steer = 0
        apply_steer_req = 0
      else:
        apply_steer_req = 1
      # 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
      can_sends.append(create_steer_command(self.packer, apply_steer, apply_steer_req, frame))
      if frame % 2 == 0 and CS.CP.carFingerprint in TSS2_CAR:
        can_sends.append(create_lta_steer_command(self.packer, 0, 0, frame // 2))

    # we can spam can to cancel the system even if we are using lat only control
    if (frame % 3 == 0 and CS.CP.openpilotLongitudinalControl) or pcm_cancel_cmd:
      lead = lead or CS.out.vEgo < 12.    # at low speed we always assume the lead is present so ACC can be engaged

      # Lexus IS uses a different cancellation message
      if pcm_cancel_cmd and CS.CP.carFingerprint in (CAR.LEXUS_IS, CAR.LEXUS_RC):
        can_sends.append(create_acc_cancel_command(self.packer))
      elif CS.CP.openpilotLongitudinalControl:
        can_sends.append(create_accel_command(self.packer, pcm_accel_cmd, pcm_cancel_cmd, self.standstill_req, lead, CS.acc_type, CS.distance_btn))
        self.accel = pcm_accel_cmd
      else:
        can_sends.append(create_accel_command(self.packer, 0, pcm_cancel_cmd, False, lead, CS.acc_type, CS.distance_btn))

    if frame % 2 == 0 and CS.CP.enableGasInterceptor and CS.CP.openpilotLongitudinalControl:
      # send exactly zero if gas cmd is zero. Interceptor will send the max between read value and gas cmd.
      # This prevents unexpected pedal range rescaling
      can_sends.append(create_gas_interceptor_command(self.packer, interceptor_gas_cmd, frame // 2))
      self.gas = interceptor_gas_cmd

    # ui mesg is at 1Hz but we send asap if:
    # - there is something to display
    # - there is something to stop displaying
    fcw_alert = hud_alert == VisualAlert.fcw
    steer_alert = hud_alert in (VisualAlert.steerRequired, VisualAlert.ldw)

    send_ui = False
    if ((fcw_alert or steer_alert) and not self.alert_active) or \
       (not (fcw_alert or steer_alert) and self.alert_active):
      send_ui = True
      self.alert_active = not self.alert_active
    elif pcm_cancel_cmd:
      # forcing the pcm to disengage causes a bad fault sound so play a good sound instead
      send_ui = True

    if (frame % 100 == 0 or send_ui):
      can_sends.append(create_ui_command(self.packer, steer_alert, pcm_cancel_cmd, left_line, right_line, left_lane_depart, right_lane_depart, enabled))

    if frame % 100 == 0 and CS.CP.enableDsu:
      can_sends.append(create_fcw_command(self.packer, fcw_alert))

    # *** static msgs ***
    for (addr, cars, bus, fr_step, vl) in STATIC_DSU_MSGS:
      if frame % fr_step == 0 and CS.CP.enableDsu and CS.CP.carFingerprint in cars:
        can_sends.append(make_can_msg(addr, vl, bus))

    new_actuators = actuators.copy()
    new_actuators.steer = apply_steer / CarControllerParams.STEER_MAX
    new_actuators.accel = self.accel
    new_actuators.gas = self.gas

    return new_actuators, can_sends