Files
IQ.Pilot/artifacts/package_runtime/iqdbc/car/honda/carcontroller.py
2026-09-03 18:23:24 -05:00

491 lines
26 KiB
Python

import numpy as np
import math
from iqdbc.can import CANPacker
from iqdbc.car import ACCELERATION_DUE_TO_GRAVITY, Bus, DT_CTRL, rate_limit, make_tester_present_msg, structs
from iqdbc.car.common.pid import PIDController
from iqdbc.car.honda import dash_lane, dash_objects, hondacan
from iqdbc.car.honda.values import CAR, CruiseButtons, CruiseSettings, HONDA_BOSCH, HONDA_BOSCH_CANFD, HONDA_BOSCH_RADARLESS, \
HONDA_BOSCH_TJA_CONTROL, HONDA_NIDEC_ALT_PCM_ACCEL, CarControllerParams
from iqdbc.car.interfaces import CarControllerBase
from iqdbc.lvbs.car.honda.aol import AolCarController
from iqdbc.lvbs.car.honda.gas_interceptor import GasInterceptorCarController
VisualAlert = structs.CarControl.HUDControl.VisualAlert
LongCtrlState = structs.CarControl.Actuators.LongControlState
def compute_gb_honda_bosch(accel, speed):
# TODO returns 0s, is unused
return 0.0, 0.0
def compute_gb_honda_nidec(accel, speed):
creep_brake = 0.0
creep_speed = 2.3
creep_brake_value = 0.15
if speed < creep_speed:
creep_brake = (creep_speed - speed) / creep_speed * creep_brake_value
gb = float(accel) / 4.8 - creep_brake
return np.clip(gb, 0.0, 1.0), np.clip(-gb, 0.0, 1.0)
def compute_gas_brake(accel, speed, fingerprint):
if fingerprint in HONDA_BOSCH:
return compute_gb_honda_bosch(accel, speed)
else:
return compute_gb_honda_nidec(accel, speed)
# TODO not clear this does anything useful
def actuator_hysteresis(brake, braking, brake_steady, v_ego, car_fingerprint):
# hyst params
brake_hyst_on = 0.02 # to activate brakes exceed this value
brake_hyst_off = 0.005 # to deactivate brakes below this value
brake_hyst_gap = 0.01 # don't change brake command for small oscillations within this value
# *** hysteresis logic to avoid brake blinking. go above 0.1 to trigger
if (brake < brake_hyst_on and not braking) or brake < brake_hyst_off:
brake = 0.
braking = brake > 0.
# for small brake oscillations within brake_hyst_gap, don't change the brake command
if brake == 0.:
brake_steady = 0.
elif brake > brake_steady + brake_hyst_gap:
brake_steady = brake - brake_hyst_gap
elif brake < brake_steady - brake_hyst_gap:
brake_steady = brake + brake_hyst_gap
brake = brake_steady
return brake, braking, brake_steady
def brake_pump_hysteresis(apply_brake, apply_brake_last, last_pump_ts, ts):
pump_on = False
# reset pump timer if:
# - there is an increment in brake request
# - we are applying steady state brakes and we haven't been running the pump
# for more than 20s (to prevent pressure bleeding)
if apply_brake > apply_brake_last or (ts - last_pump_ts > 20. and apply_brake > 0):
last_pump_ts = ts
# once the pump is on, run it for at least 0.2s
if ts - last_pump_ts < 0.2 and apply_brake > 0:
pump_on = True
return pump_on, last_pump_ts
def process_hud_alert(hud_alert):
alert_fcw = False
alert_steer_required = False
# Make sure FCW is prioritized over steering required
# TODO: implement separate available LDW alert
if hud_alert == VisualAlert.fcw:
alert_fcw = True
elif hud_alert in (VisualAlert.steerRequired, VisualAlert.ldw):
alert_steer_required = True
return alert_fcw, alert_steer_required
class CarController(CarControllerBase, AolCarController, GasInterceptorCarController):
def __init__(self, dbc_names, CP, CP_IQ):
CarControllerBase.__init__(self, dbc_names, CP, CP_IQ)
AolCarController.__init__(self)
GasInterceptorCarController.__init__(self, CP, CP_IQ)
self.packer = CANPacker(dbc_names[Bus.pt])
self.params = CarControllerParams(CP)
self.CAN = hondacan.CanBus(CP)
self.tja_control = CP.carFingerprint in HONDA_BOSCH_TJA_CONTROL
self.lane_renderer = dash_lane.LanePathRenderer()
self.dash_object_author = dash_objects.DashObjectAuthor()
self.rendered_lane = dash_lane.RenderedLane()
self.lkas_hud_key = None
self.lkas_state_change_frames = 0
self.braking = False
self.brake_steady = 0.
self.brake_last = 0.
self.apply_brake_last = 0
self.last_pump_ts = 0.
self.stopping_counter = 0
self.accel = 0.0
self.speed = 0.0
self.gas = 0.0
self.brake = 0.0
self.last_torque = 0.0
self.bosch_last_gas = 0
self.lkas_button_send_remaining = 0
self.last_lkas_button_frame = 0
self.radar_disable_counter = 0
self.radar_mux = 0
# stock RADAR_HUD_CANFD raises its CMBS bit only for a short burst after ACC engages; 10Hz hud ticks
self.radar_hud_pulse = 0
self.last_acc_enabled = False
self.gasfactor = 1.0
self.gasfactor_before_maxgas = 1.0
self.windfactor = 1.0
self.windfactor_before_maxgas = 1.0
self.windfactor_before_brake = 0.0
self.pitch = 0.0
self.brake_pid = PIDController(k_p=0.0, k_i=1.0, pos_limit=0.0, neg_limit=-2.0, rate=50)
self.brake_pid.reset()
def update(self, CC, CC_IQ, CS, now_nanos):
AolCarController.update(self, self.CP, CC, CC_IQ)
gas_pedal_force = 0.0
min_gas = self.params.BOSCH_GAS_LOOKUP_BP[0]
actuators = CC.actuators
hud_control = CC.hudControl
hud_v_cruise = hud_control.setSpeed / CS.v_cruise_factor if hud_control.speedVisible else 255
pcm_cancel_cmd = CC.cruiseControl.cancel
if len(CC.orientationNED) == 3:
self.pitch = CC.orientationNED[1]
hill_brake = math.sin(self.pitch) * ACCELERATION_DUE_TO_GRAVITY
if CC.longActive:
accel = actuators.accel
gas, brake = compute_gas_brake(actuators.accel + hill_brake, CS.out.vEgo, self.CP.carFingerprint)
else:
accel = 0.0
gas, brake = 0.0, 0.0
# *** rate limit steer ***
limited_torque = rate_limit(actuators.torque, self.last_torque, -self.params.STEER_DELTA_DOWN * DT_CTRL,
self.params.STEER_DELTA_UP * DT_CTRL)
self.last_torque = limited_torque
# *** apply brake hysteresis ***
pre_limit_brake, self.braking, self.brake_steady = actuator_hysteresis(brake, self.braking, self.brake_steady,
CS.out.vEgo, self.CP.carFingerprint)
# *** rate limit after the enable check ***
self.brake_last = rate_limit(pre_limit_brake, self.brake_last, -2., 3 * DT_CTRL)
# vehicle hud display, wait for one update from 10Hz 0x304 msg
alert_fcw, alert_steer_required = process_hud_alert(hud_control.visualAlert)
# **** process the car messages ****
# steer torque is converted back to CAN reference (positive when steering right)
apply_torque = int(np.interp(-limited_torque * self.params.STEER_MAX,
self.params.STEER_LOOKUP_BP, self.params.STEER_LOOKUP_V))
# Send CAN commands
can_sends = []
if self.CP.carFingerprint in (HONDA_BOSCH - HONDA_BOSCH_RADARLESS) and self.CP.openpilotLongitudinalControl:
if self.CP.carFingerprint in HONDA_BOSCH_CANFD and CS.stock_acc_alive:
# CAN FD: the radar is silenced from here rather than from CarInterface.init(), and only once
# the comma relay is confirmed open: init() ran under the ELM327 safety mode, so the
# replacement ACC_CONTROL stream was blocked until the safety-mode switch landed, and whenever
# that took longer than ~110ms after radar silence the brake module latched CRUISE_FAULT for
# the whole drive. With the relay open the replacement stream starts within a few frames of
# radar silence (see CS.stock_acc_alive), well inside the fault threshold
if CS.canfd_relay_open:
if self.radar_disable_counter % 50 == 0:
# UDS extended diagnostic session, required before CommunicationControl
can_sends.append((0x18DAB0F1, b'\x02\x10\x03\x00\x00\x00\x00\x00', self.CAN.pt))
elif self.radar_disable_counter % 50 == 5:
# UDS CommunicationControl disableRxAndTx (0x80 suppresses the response), retried every
# 0.5s until the radar goes silent
can_sends.append((0x18DAB0F1, b'\x03\x28\x83\x03\x00\x00\x00\x00', self.CAN.pt))
self.radar_disable_counter += 1
elif self.frame % 10 == 0:
# tester present - w/ no response (keeps radar disabled)
can_sends.append(make_tester_present_msg(0x18DAB0F1, self.CAN.pt, suppress_response=True))
# simulate the disabled canfd radar to prevent faults. These look-alikes are consumed by both the
# camera (behind the relay, on the camera bus) and the powertrain: openpilot's own TX is not
# forwarded across the open relay, so each frame is packed exactly once (the packer's
# counter/checksum only advance once per cycle) and the identical bytes are mirrored onto both
# buses (re-packing would double-increment the counter and desync the buses). While the stock
# radar is still transmitting it authors all of these itself
if self.CP.carFingerprint in HONDA_BOSCH_CANFD and self.CP.openpilotLongitudinalControl and not CS.stock_acc_alive:
if CC.enabled and not self.last_acc_enabled:
self.radar_hud_pulse = 30 # ~3s at 10Hz, matching the stock 2-6s engage burst
self.last_acc_enabled = CC.enabled
radar_msgs = []
if CS.hud_tick:
radar_msgs.append(hondacan.create_radar_hud_canfd(self.packer, self.CAN.pt, CC.enabled, self.radar_hud_pulse > 0))
if self.radar_hud_pulse > 0:
self.radar_hud_pulse -= 1
if CS.supp_tick:
radar_msgs.append(hondacan.create_canfd_supplemental(self.packer, self.CAN.pt))
if CS.radar_50hz_tick:
# Cycle the radar MUX through the stock banks: 1-10, 17-26, 33-42, 49-58. This counter also
# drives the LANE_PATH/HUD_OBJECTS mux below: it advances exactly one step per transmitted
# frame, so the sweep stays contiguous even when a tick is missed (a frame-derived mux left
# holes in the sweep the stock radar never produces).
# These must be elif: a bare `if` at a bank start would fall through to the increment,
# skipping the bank-start values (17, 33, 49)
if self.radar_mux >= 58:
self.radar_mux = 1
elif self.radar_mux == 10:
self.radar_mux = 17
elif self.radar_mux == 26:
self.radar_mux = 33
elif self.radar_mux == 42:
self.radar_mux = 49
else:
self.radar_mux += 1
if CS.radar_5hz_tick:
# RADAR_LEAD's LANE_PATH_LENGTH must track the valid-point count of the LANE_PATH sweep being
# authored, and LEFT_LANE/RIGHT_LANE the per-side line-detected status, in lockstep with the
# stock radar's behavior or the dash won't draw the lane lines
radar_msgs.extend(hondacan.create_canfd_5hz_radar_messages(self.packer, self.CAN.pt, CS.radar_ref_counter,
dash_lane.canfd_lane_length(self.rendered_lane),
dash_lane.LANE_LINE_ON if self.rendered_lane.left_line else 0,
dash_lane.LANE_LINE_ON if self.rendered_lane.right_line else 0))
for addr, dat, _ in radar_msgs:
can_sends.append((addr, dat, self.CAN.pt))
can_sends.append((addr, dat, self.CAN.camera))
# Send steering command.
can_sends.append(hondacan.create_steering_control(self.packer, self.CAN, apply_torque, CC.latActive, self.tja_control))
# wind brake from air resistance decel at high speed
wind_brake = np.interp(CS.out.vEgo, [0.0, 2.3, 35.0], [0.001, 0.002, 0.15]) * self.windfactor # not in m/s2 units
wind_brake_ms2 = np.interp(CS.out.vEgo, [0.0, 13.4, 22.4, 31.3, 40.2], [0.000, 0.049, 0.136, 0.267, 0.441]) # in m/s2 units
# all of this is only relevant for HONDA NIDEC
max_accel = np.interp(CS.out.vEgo, self.params.NIDEC_MAX_ACCEL_BP, self.params.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 self.CP_IQ.enableGasInterceptor or not CC.longActive:
pcm_speed = 0.0
pcm_accel = int(0.0)
elif self.CP.carFingerprint in HONDA_NIDEC_ALT_PCM_ACCEL:
pcm_speed_V = [0.0,
np.clip(CS.out.vEgo - 3.0, 0.0, 100.0),
np.clip(CS.out.vEgo + 0.0, 0.0, 100.0),
np.clip(CS.out.vEgo + 5.0, 0.0, 100.0)]
pcm_speed = float(np.interp(gas - brake, pcm_speed_BP, pcm_speed_V))
pcm_accel = int(1.0 * self.params.NIDEC_GAS_MAX)
else:
pcm_speed_V = [0.0,
np.clip(CS.out.vEgo - 2.0, 0.0, 100.0),
np.clip(CS.out.vEgo + 2.0, 0.0, 100.0),
np.clip(CS.out.vEgo + 5.0, 0.0, 100.0)]
pcm_speed = float(np.interp(gas - brake, pcm_speed_BP, pcm_speed_V))
pcm_accel = int(np.clip((accel / 1.44) / max_accel, 0.0, 1.0) * self.params.NIDEC_GAS_MAX)
if not self.CP.openpilotLongitudinalControl:
if self.frame % 2 == 0 and self.CP.carFingerprint not in HONDA_BOSCH_RADARLESS | HONDA_BOSCH_CANFD:
can_sends.append(hondacan.create_bosch_supplemental_1(self.packer, self.CAN))
# 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, self.CAN, CruiseButtons.CANCEL, 0, CS.scm_ambient_light,
self.CP.carFingerprint))
elif CC.cruiseControl.resume:
can_sends.append(hondacan.spam_buttons_command(self.packer, self.CAN, CruiseButtons.RES_ACCEL, 0, CS.scm_ambient_light,
self.CP.carFingerprint))
else:
# Send gas and brake commands.
if self.frame % 2 == 0:
ts = self.frame * DT_CTRL
if self.CP.carFingerprint in HONDA_BOSCH:
# low-speed extra brake: the fixed accel command under-delivers approaching a stop, so an
# integral-only term closes the gap, releasing at 1 m/s^3 once out of the window
if (accel < min_gas) and (CS.out.vEgo < 3.0) and not (-1e-3 < CS.out.vEgo < 1e-3):
brake_addon = self.brake_pid.update(error=accel - CS.out.aEgo, speed=CS.out.vEgo)
target_accel = min(accel, accel + brake_addon)
else:
if (self.brake_pid.i < 0.0) and (accel < min_gas):
self.brake_pid.i = min(0.0, self.brake_pid.i + 0.02)
else:
self.brake_pid.reset()
target_accel = min(accel, accel + self.brake_pid.i)
self.accel = float(np.clip(target_accel, self.params.BOSCH_ACCEL_MIN, self.params.BOSCH_ACCEL_MAX))
# not using self.accel since the brake pid resets with the gas pedal
gas_pedal_force = accel + wind_brake_ms2 * self.windfactor + hill_brake
# Live-learn gas pedal adjustments when openpilot is controlling gas.
if (actuators.longControlState == LongCtrlState.pid) and (not CS.out.gasPressed):
gas_error = accel - CS.out.aEgo
if gas_error != 0.0 and gas_pedal_force > min_gas:
if self.CP.carFingerprint in (CAR.HONDA_INSIGHT, CAR.HONDA_CIVIC_BOSCH): # gas pedal reacts too slowly
learn_speed = 150
elif self.CP.carFingerprint == CAR.ACURA_RDX_3G: # prevent overreacting to turbo lag
learn_speed = 300
else:
learn_speed = 50
self.gasfactor = np.clip(self.gasfactor + gas_error / learn_speed * (gas_pedal_force - min_gas), 0.01, 3.0)
if gas_error != 0.0 and (not CS.out.brakePressed) and (CS.out.vEgo > 0.0):
wind_learn_speed = 100 if self.CP.carFingerprint == CAR.ACURA_RDX_3G else 1000
wind_adjust = 1 + wind_brake_ms2 / wind_learn_speed
self.windfactor = np.clip(self.windfactor * (wind_adjust if (gas_error > 0) else 1.0 / wind_adjust), 0.1, 3.0)
if gas_pedal_force <= min_gas:
self.windfactor = max(self.windfactor, self.windfactor_before_brake)
else:
self.windfactor_before_brake = self.windfactor
if gas_pedal_force >= self.params.BOSCH_ACCEL_MAX:
self.gasfactor = min(self.gasfactor, self.gasfactor_before_maxgas)
self.windfactor = min(self.windfactor, self.windfactor_before_maxgas)
else:
self.gasfactor_before_maxgas = self.gasfactor
self.windfactor_before_maxgas = self.windfactor
self.gas = float(np.interp((gas_pedal_force - min_gas) * self.gasfactor + min_gas,
self.params.BOSCH_GAS_LOOKUP_BP, self.params.BOSCH_GAS_LOOKUP_V))
# limit gas ramp to 60 units per frame, matches stock; higher sometimes makes the powertrain ignore the command
max_gas = max(60, self.bosch_last_gas + 60)
self.gas = min(self.gas, max_gas)
self.bosch_last_gas = self.gas
stopping = actuators.longControlState == LongCtrlState.stopping
self.stopping_counter = self.stopping_counter + 1 if stopping else 0
# CAN FD: never overlap the stock radar's own ACC_CONTROL stream; ours starts within a few
# frames of the radar going silent (see the deferred radar disable above)
if not (self.CP.carFingerprint in HONDA_BOSCH_CANFD and CS.stock_acc_alive):
can_sends.extend(hondacan.create_acc_commands(self.packer, self.CAN, CC.enabled, CC.longActive, self.accel, self.gas,
self.stopping_counter, self.CP, gas_pedal_force))
else:
apply_brake = np.clip(self.brake_last - wind_brake, 0.0, 1.0)
apply_brake = int(np.clip(apply_brake * self.params.NIDEC_BRAKE_MAX, 0, self.params.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, self.CAN, apply_brake, pump_on,
pcm_override, pcm_cancel_cmd, alert_fcw,
self.CP.carFingerprint, CS.stock_brake, self.CP_IQ))
self.apply_brake_last = apply_brake
self.brake = apply_brake / self.params.NIDEC_BRAKE_MAX
gas_error = actuators.accel - CS.out.aEgo
if (not CS.out.gasPressed) and (actuators.longControlState == LongCtrlState.pid) and self.CP_IQ.enableGasInterceptor:
if gas_error != 0.0 and gas > 0.0:
self.gasfactor = np.clip(self.gasfactor + gas_error / 50 * (gas * 4.8), 0.1, 3.0)
if gas_error != 0.0 and (not CS.out.brakePressed) and (CS.out.vEgo > 0.0):
wind_adjust = 1 + (wind_brake * 4.8) / 1000
self.windfactor = np.clip(self.windfactor * (wind_adjust if (gas_error > 0) else 1.0 / wind_adjust), 0.1, 5.0)
if gas <= 0.0:
self.windfactor = max(self.windfactor, self.windfactor_before_brake)
else:
self.windfactor_before_brake = self.windfactor
can_sends.extend(GasInterceptorCarController.update(self, CC, CS, gas * self.gasfactor, brake, wind_brake, self.packer, self.frame))
# Send dashboard UI commands. On CAN FD, ACC_HUD is a radar look-alike that openpilot only owns
# once it has disabled the radar; it rides the phase-locked 10Hz hud tick instead of frame % 10
if (self.CP.carFingerprint in HONDA_BOSCH_CANFD and CS.hud_tick and
self.CP.openpilotLongitudinalControl and not CS.stock_acc_alive):
can_sends.append(hondacan.create_acc_hud(self.packer, self.CAN.pt, self.CP, CC.enabled, pcm_speed, actuators.accel,
hud_control, hud_v_cruise, CS.is_metric, CS.acc_hud))
if self.frame % 10 == 0:
if self.CP.openpilotLongitudinalControl and self.CP.carFingerprint not in HONDA_BOSCH_CANFD:
# On Nidec, this also controls longitudinal positive acceleration
can_sends.append(hondacan.create_acc_hud(self.packer, self.CAN.pt, self.CP, CC.enabled, pcm_speed, pcm_accel,
hud_control, hud_v_cruise, CS.is_metric, CS.acc_hud))
steering_available = CS.out.cruiseState.available and CS.out.vEgo > self.CP.minSteerSpeed
reduced_steering = CS.out.steeringPressed
lkas_state_change = None
if self.CP.carFingerprint in HONDA_BOSCH_CANFD:
# The key must contain exactly the signals that change the LKAS_HUD payload, nothing more:
# a flickering input (like steer saturation) re-triggers the pulse continuously, which keeps
# LKAS_STATE_CHANGE high and suppresses the dash lane lines entirely
hud_key = (bool(CC.latActive), bool(self.dashed_lanes), bool(alert_steer_required), bool(CS.out.steerFaultPermanent))
if hud_key != self.lkas_hud_key:
self.lkas_hud_key = hud_key
self.lkas_state_change_frames = 30 # 3s at the 10Hz LKAS_HUD rate, matching the stock pulse length
lkas_state_change = self.lkas_state_change_frames > 0
self.lkas_state_change_frames = max(0, self.lkas_state_change_frames - 1)
can_sends.extend(hondacan.create_lkas_hud(self.packer, self.CAN.lkas, self.CP, hud_control, CC.latActive,
steering_available, reduced_steering, alert_steer_required, CS.lkas_hud, self.dashed_lanes,
steer_fault_permanent=CS.out.steerFaultPermanent, lkas_state_change=lkas_state_change))
if self.CP.openpilotLongitudinalControl:
# TODO: combining with create_acc_hud block above will change message order and will need replay logs regenerated
if self.CP.carFingerprint in (HONDA_BOSCH - HONDA_BOSCH_RADARLESS - HONDA_BOSCH_CANFD):
can_sends.append(hondacan.create_radar_hud(self.packer, self.CAN.pt))
if self.CP.carFingerprint == CAR.HONDA_CIVIC_BOSCH:
can_sends.append(hondacan.create_legacy_brake_command(self.packer, self.CAN.pt))
if self.CP.carFingerprint not in HONDA_BOSCH:
self.speed = pcm_speed
if not self.CP_IQ.enableGasInterceptor:
self.gas = pcm_accel / self.params.NIDEC_GAS_MAX
# Render OP's lane and lead cars on the dash. On CAN FD these are radar look-alikes that only
# exist (and are only allowed by panda safety) when the radar is disabled. Radarless keeps the
# camera as the dash authority (known-good), so OP does not author these there
if (CS.radar_50hz_tick and self.CP.carFingerprint in HONDA_BOSCH_CANFD and self.CP.openpilotLongitudinalControl
and not CS.stock_acc_alive):
leads = dash_objects.leads_from_model(self.model, CS.out.vEgo)
lead = leads[0]
lead_d = lead.dRel if lead.status else 0.0
self.rendered_lane = self.lane_renderer.update(self.model, CS.out.vEgo, lead_d)
mux = self.radar_mux
# no LKAS_HUD_2 on CAN FD: the dash reads the lane length from the in-band terminator, so the
# path is reshaped into the terminated-prefix form
lane_offsets = dash_lane.canfd_lane_offsets(self.rendered_lane)
lane_msg = dash_lane.create_lane_path(self.packer, self.CAN.lkas, lane_offsets, mux)
can_sends.append(lane_msg)
# CAN FD cars have no camera HUD_OBJECTS to poll (the disabled radar owned it): author OP's
# lead in slot 0 with the other slots blank (tracks=None)
tracks = CS.camera_object_tracker.snapshot() if CS.camera_object_tracker is not None else None
hud_msg = self.dash_object_author.create(self.packer, self.CAN.lkas, lead, tracks, mux, now_nanos * 1e-9,
extra_leads=leads[1:])
can_sends.append(hud_msg)
# the camera (behind the relay) also consumes these; mirror the identical packed bytes onto the
# camera bus (packed once, so the counter/checksum stay in lockstep)
for addr, dat, _ in (lane_msg, hud_msg):
can_sends.append((addr, dat, self.CAN.camera))
# CAN FD: when stock LKAS is active, the touch-steering-wheel nag eventually forces an ACC
# disengagement (a brake tap from the VSA). Disable LKAS automatically and block the driver's LKAS
# button by taking over SCM_BUTTONS on the camera bus while engaged (panda blocks the forwarded
# stock SCM_BUTTONS while this stream flows). Radarless keeps the stock camera LKAS untouched
if self.CP.carFingerprint in HONDA_BOSCH_CANFD and CC.enabled and self.frame % 4 == 0 and \
not pcm_cancel_cmd and not CC.cruiseControl.resume:
if self.lkas_button_send_remaining == 0 and CS.lkas_hud["LKAS_READY"] and self.frame >= self.last_lkas_button_frame + 500:
self.lkas_button_send_remaining = 3
if self.lkas_button_send_remaining > 0:
self.last_lkas_button_frame = self.frame
self.lkas_button_send_remaining -= 1
cruise_setting = CruiseSettings.LKAS
elif CS.cruise_setting == CruiseSettings.LKAS:
cruise_setting = 0 # block the driver's LKAS button press
else:
cruise_setting = CS.cruise_setting
can_sends.append(hondacan.spam_buttons_command(self.packer, self.CAN, CS.cruise_buttons, cruise_setting,
CS.scm_ambient_light, self.CP.carFingerprint, bus=self.CAN.camera))
# Finalize actuator state for downstream consumers
new_actuators = actuators.as_builder()
new_actuators.speed = self.speed
new_actuators.accel = self.accel
new_actuators.gas = self.gas
new_actuators.brake = self.brake
new_actuators.torque = self.last_torque
new_actuators.torqueOutputCan = apply_torque
self.frame += 1
return new_actuators, can_sends