parent
fcf8efb826
commit
b0260dadba
69 changed files with 20008 additions and 0 deletions
@ -0,0 +1,2 @@ |
||||
calibration_param |
||||
traces |
@ -0,0 +1,626 @@ |
||||
#!/usr/bin/env python3 |
||||
import os |
||||
import gc |
||||
import capnp |
||||
from cereal import car, log |
||||
from common.numpy_fast import clip |
||||
from common.realtime import sec_since_boot, set_realtime_priority, Ratekeeper, DT_CTRL |
||||
from common.profiler import Profiler |
||||
from common.params import Params, put_nonblocking |
||||
import cereal.messaging as messaging |
||||
from selfdrive.config import Conversions as CV |
||||
from selfdrive.boardd.boardd import can_list_to_can_capnp |
||||
from selfdrive.car.car_helpers import get_car, get_startup_alert |
||||
from selfdrive.controls.lib.lane_planner import CAMERA_OFFSET |
||||
from selfdrive.controls.lib.drive_helpers import get_events, \ |
||||
create_event, \ |
||||
EventTypes as ET, \ |
||||
update_v_cruise, \ |
||||
initialize_v_cruise |
||||
from selfdrive.controls.lib.longcontrol import LongControl, STARTING_TARGET_SPEED |
||||
from selfdrive.controls.lib.latcontrol_pid import LatControlPID |
||||
from selfdrive.controls.lib.latcontrol_indi import LatControlINDI |
||||
from selfdrive.controls.lib.latcontrol_lqr import LatControlLQR |
||||
from selfdrive.controls.lib.alertmanager import AlertManager |
||||
from selfdrive.controls.lib.vehicle_model import VehicleModel |
||||
from selfdrive.controls.lib.driver_monitor import DriverStatus, MAX_TERMINAL_ALERTS, MAX_TERMINAL_DURATION |
||||
from selfdrive.controls.lib.planner import LON_MPC_STEP |
||||
from selfdrive.controls.lib.gps_helpers import is_rhd_region |
||||
from selfdrive.locationd.calibration_helpers import Calibration, Filter |
||||
|
||||
LANE_DEPARTURE_THRESHOLD = 0.1 |
||||
|
||||
ThermalStatus = log.ThermalData.ThermalStatus |
||||
State = log.ControlsState.OpenpilotState |
||||
HwType = log.HealthData.HwType |
||||
|
||||
LaneChangeState = log.PathPlan.LaneChangeState |
||||
LaneChangeDirection = log.PathPlan.LaneChangeDirection |
||||
|
||||
|
||||
def add_lane_change_event(events, path_plan): |
||||
if path_plan.laneChangeState == LaneChangeState.preLaneChange: |
||||
if path_plan.laneChangeDirection == LaneChangeDirection.left: |
||||
events.append(create_event('preLaneChangeLeft', [ET.WARNING])) |
||||
else: |
||||
events.append(create_event('preLaneChangeRight', [ET.WARNING])) |
||||
elif path_plan.laneChangeState in [LaneChangeState.laneChangeStarting, LaneChangeState.laneChangeFinishing]: |
||||
events.append(create_event('laneChange', [ET.WARNING])) |
||||
|
||||
|
||||
def isActive(state): |
||||
"""Check if the actuators are enabled""" |
||||
return state in [State.enabled, State.softDisabling] |
||||
|
||||
|
||||
def isEnabled(state): |
||||
"""Check if openpilot is engaged""" |
||||
return (isActive(state) or state == State.preEnabled) |
||||
|
||||
def events_to_bytes(events): |
||||
# optimization when comparing capnp structs: str() or tree traverse are much slower |
||||
ret = [] |
||||
for e in events: |
||||
if isinstance(e, capnp.lib.capnp._DynamicStructReader): |
||||
e = e.as_builder() |
||||
ret.append(e.to_bytes()) |
||||
return ret |
||||
|
||||
|
||||
def data_sample(CI, CC, sm, can_sock, driver_status, state, mismatch_counter, can_error_counter, params): |
||||
"""Receive data from sockets and create events for battery, temperature and disk space""" |
||||
|
||||
# Update carstate from CAN and create events |
||||
can_strs = messaging.drain_sock_raw(can_sock, wait_for_one=True) |
||||
CS = CI.update(CC, can_strs) |
||||
|
||||
sm.update(0) |
||||
|
||||
events = list(CS.events) |
||||
add_lane_change_event(events, sm['pathPlan']) |
||||
enabled = isEnabled(state) |
||||
|
||||
# Check for CAN timeout |
||||
if not can_strs: |
||||
can_error_counter += 1 |
||||
events.append(create_event('canError', [ET.NO_ENTRY, ET.IMMEDIATE_DISABLE])) |
||||
|
||||
overtemp = sm['thermal'].thermalStatus >= ThermalStatus.red |
||||
free_space = sm['thermal'].freeSpace < 0.07 # under 7% of space free no enable allowed |
||||
low_battery = sm['thermal'].batteryPercent < 1 and sm['thermal'].chargingError # at zero percent battery, while discharging, OP should not allowed |
||||
mem_low = sm['thermal'].memUsedPercent > 90 |
||||
|
||||
# Create events for battery, temperature and disk space |
||||
if low_battery: |
||||
events.append(create_event('lowBattery', [ET.NO_ENTRY, ET.SOFT_DISABLE])) |
||||
if overtemp: |
||||
events.append(create_event('overheat', [ET.NO_ENTRY, ET.SOFT_DISABLE])) |
||||
if free_space: |
||||
events.append(create_event('outOfSpace', [ET.NO_ENTRY])) |
||||
if mem_low: |
||||
events.append(create_event('lowMemory', [ET.NO_ENTRY, ET.SOFT_DISABLE, ET.PERMANENT])) |
||||
|
||||
if CS.stockAeb: |
||||
events.append(create_event('stockAeb', [])) |
||||
|
||||
# GPS coords RHD parsing, once every restart |
||||
if sm.updated['gpsLocation'] and not driver_status.is_rhd_region_checked: |
||||
is_rhd = is_rhd_region(sm['gpsLocation'].latitude, sm['gpsLocation'].longitude) |
||||
driver_status.is_rhd_region = is_rhd |
||||
driver_status.is_rhd_region_checked = True |
||||
put_nonblocking("IsRHD", "1" if is_rhd else "0") |
||||
|
||||
# Handle calibration |
||||
cal_status = sm['liveCalibration'].calStatus |
||||
cal_perc = sm['liveCalibration'].calPerc |
||||
|
||||
cal_rpy = [0,0,0] |
||||
if cal_status != Calibration.CALIBRATED: |
||||
if cal_status == Calibration.UNCALIBRATED: |
||||
events.append(create_event('calibrationIncomplete', [ET.NO_ENTRY, ET.SOFT_DISABLE, ET.PERMANENT])) |
||||
else: |
||||
events.append(create_event('calibrationInvalid', [ET.NO_ENTRY, ET.SOFT_DISABLE])) |
||||
else: |
||||
rpy = sm['liveCalibration'].rpyCalib |
||||
if len(rpy) == 3: |
||||
cal_rpy = rpy |
||||
|
||||
# When the panda and controlsd do not agree on controls_allowed |
||||
# we want to disengage openpilot. However the status from the panda goes through |
||||
# another socket other than the CAN messages and one can arrive earlier than the other. |
||||
# Therefore we allow a mismatch for two samples, then we trigger the disengagement. |
||||
if not enabled: |
||||
mismatch_counter = 0 |
||||
|
||||
controls_allowed = sm['health'].controlsAllowed |
||||
if not controls_allowed and enabled: |
||||
mismatch_counter += 1 |
||||
if mismatch_counter >= 200: |
||||
events.append(create_event('controlsMismatch', [ET.IMMEDIATE_DISABLE])) |
||||
|
||||
# Driver monitoring |
||||
if sm.updated['model']: |
||||
driver_status.set_policy(sm['model']) |
||||
|
||||
if sm.updated['driverMonitoring']: |
||||
driver_status.get_pose(sm['driverMonitoring'], cal_rpy, CS.vEgo, enabled) |
||||
|
||||
if driver_status.terminal_alert_cnt >= MAX_TERMINAL_ALERTS or driver_status.terminal_time >= MAX_TERMINAL_DURATION: |
||||
events.append(create_event("tooDistracted", [ET.NO_ENTRY])) |
||||
|
||||
return CS, events, cal_perc, mismatch_counter, can_error_counter |
||||
|
||||
|
||||
def state_transition(frame, CS, CP, state, events, soft_disable_timer, v_cruise_kph, AM): |
||||
"""Compute conditional state transitions and execute actions on state transitions""" |
||||
enabled = isEnabled(state) |
||||
|
||||
v_cruise_kph_last = v_cruise_kph |
||||
|
||||
# if stock cruise is completely disabled, then we can use our own set speed logic |
||||
if not CP.enableCruise: |
||||
v_cruise_kph = update_v_cruise(v_cruise_kph, CS.buttonEvents, enabled) |
||||
elif CP.enableCruise and CS.cruiseState.enabled: |
||||
v_cruise_kph = CS.cruiseState.speed * CV.MS_TO_KPH |
||||
|
||||
# decrease the soft disable timer at every step, as it's reset on |
||||
# entrance in SOFT_DISABLING state |
||||
soft_disable_timer = max(0, soft_disable_timer - 1) |
||||
|
||||
# DISABLED |
||||
if state == State.disabled: |
||||
if get_events(events, [ET.ENABLE]): |
||||
if get_events(events, [ET.NO_ENTRY]): |
||||
for e in get_events(events, [ET.NO_ENTRY]): |
||||
AM.add(frame, str(e) + "NoEntry", enabled) |
||||
|
||||
else: |
||||
if get_events(events, [ET.PRE_ENABLE]): |
||||
state = State.preEnabled |
||||
else: |
||||
state = State.enabled |
||||
AM.add(frame, "enable", enabled) |
||||
v_cruise_kph = initialize_v_cruise(CS.vEgo, CS.buttonEvents, v_cruise_kph_last) |
||||
|
||||
# ENABLED |
||||
elif state == State.enabled: |
||||
if get_events(events, [ET.USER_DISABLE]): |
||||
state = State.disabled |
||||
AM.add(frame, "disable", enabled) |
||||
|
||||
elif get_events(events, [ET.IMMEDIATE_DISABLE]): |
||||
state = State.disabled |
||||
for e in get_events(events, [ET.IMMEDIATE_DISABLE]): |
||||
AM.add(frame, e, enabled) |
||||
|
||||
elif get_events(events, [ET.SOFT_DISABLE]): |
||||
state = State.softDisabling |
||||
soft_disable_timer = 300 # 3s |
||||
for e in get_events(events, [ET.SOFT_DISABLE]): |
||||
AM.add(frame, e, enabled) |
||||
|
||||
# SOFT DISABLING |
||||
elif state == State.softDisabling: |
||||
if get_events(events, [ET.USER_DISABLE]): |
||||
state = State.disabled |
||||
AM.add(frame, "disable", enabled) |
||||
|
||||
elif get_events(events, [ET.IMMEDIATE_DISABLE]): |
||||
state = State.disabled |
||||
for e in get_events(events, [ET.IMMEDIATE_DISABLE]): |
||||
AM.add(frame, e, enabled) |
||||
|
||||
elif not get_events(events, [ET.SOFT_DISABLE]): |
||||
# no more soft disabling condition, so go back to ENABLED |
||||
state = State.enabled |
||||
|
||||
elif get_events(events, [ET.SOFT_DISABLE]) and soft_disable_timer > 0: |
||||
for e in get_events(events, [ET.SOFT_DISABLE]): |
||||
AM.add(frame, e, enabled) |
||||
|
||||
elif soft_disable_timer <= 0: |
||||
state = State.disabled |
||||
|
||||
# PRE ENABLING |
||||
elif state == State.preEnabled: |
||||
if get_events(events, [ET.USER_DISABLE]): |
||||
state = State.disabled |
||||
AM.add(frame, "disable", enabled) |
||||
|
||||
elif get_events(events, [ET.IMMEDIATE_DISABLE, ET.SOFT_DISABLE]): |
||||
state = State.disabled |
||||
for e in get_events(events, [ET.IMMEDIATE_DISABLE, ET.SOFT_DISABLE]): |
||||
AM.add(frame, e, enabled) |
||||
|
||||
elif not get_events(events, [ET.PRE_ENABLE]): |
||||
state = State.enabled |
||||
|
||||
return state, soft_disable_timer, v_cruise_kph, v_cruise_kph_last |
||||
|
||||
|
||||
def state_control(frame, rcv_frame, plan, path_plan, CS, CP, state, events, v_cruise_kph, v_cruise_kph_last, |
||||
AM, rk, driver_status, LaC, LoC, read_only, is_metric, cal_perc, last_blinker_frame): |
||||
"""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 |
||||
|
||||
if CS.leftBlinker or CS.rightBlinker: |
||||
last_blinker_frame = frame |
||||
|
||||
# add eventual driver distracted events |
||||
events = driver_status.update(events, driver_engaged, isActive(state), CS.standstill) |
||||
|
||||
if plan.fcw: |
||||
# send FCW alert if triggered by planner |
||||
AM.add(frame, "fcw", enabled) |
||||
|
||||
elif CS.stockFcw: |
||||
# send a silent alert when stock fcw triggers, since the car is already beeping |
||||
AM.add(frame, "fcwStock", 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(frame, e, enabled, extra_text_2=extra_text) |
||||
|
||||
plan_age = DT_CTRL * (frame - rcv_frame['plan']) |
||||
dt = min(plan_age, LON_MPC_STEP + DT_CTRL) + DT_CTRL # no greater than dt mpc + dt, to prevent too high extraps |
||||
|
||||
a_acc_sol = plan.aStart + (dt / LON_MPC_STEP) * (plan.aTarget - plan.aStart) |
||||
v_acc_sol = plan.vStart + dt * (a_acc_sol + plan.aStart) / 2.0 |
||||
|
||||
# Gas/Brake PID loop |
||||
actuators.gas, actuators.brake = LoC.update(active, CS.vEgo, CS.brakePressed, CS.standstill, CS.cruiseState.standstill, |
||||
v_cruise_kph, v_acc_sol, plan.vTargetFuture, a_acc_sol, CP) |
||||
# Steering PID loop and lateral MPC |
||||
actuators.steer, actuators.steerAngle, lac_log = LaC.update(active, CS.vEgo, CS.steeringAngle, CS.steeringRate, CS.steeringTorqueEps, CS.steeringPressed, CS.steeringRateLimited, CP, path_plan) |
||||
|
||||
# Send a "steering required alert" if saturation count has reached the limit |
||||
if lac_log.saturated and not CS.steeringPressed: |
||||
# Check if we deviated from the path |
||||
left_deviation = actuators.steer > 0 and path_plan.dPoly[3] > 0.1 |
||||
right_deviation = actuators.steer < 0 and path_plan.dPoly[3] < -0.1 |
||||
|
||||
if left_deviation or right_deviation: |
||||
AM.add(frame, "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(frame, str(e) + "Permanent", enabled, extra_text_1=extra_text_1, extra_text_2=extra_text_2) |
||||
|
||||
return actuators, v_cruise_kph, driver_status, v_acc_sol, a_acc_sol, lac_log, last_blinker_frame |
||||
|
||||
|
||||
def data_send(sm, pm, CS, CI, CP, VM, state, events, actuators, v_cruise_kph, rk, AM, |
||||
driver_status, LaC, LoC, read_only, start_time, v_acc, a_acc, lac_log, events_prev, |
||||
last_blinker_frame, is_ldw_enabled, can_error_counter): |
||||
"""Send actuators and hud commands to the car, send controlsstate and MPC logging""" |
||||
|
||||
CC = car.CarControl.new_message() |
||||
CC.enabled = isEnabled(state) |
||||
CC.actuators = actuators |
||||
|
||||
CC.cruiseControl.override = True |
||||
CC.cruiseControl.cancel = not CP.enableCruise or (not isEnabled(state) and CS.cruiseState.enabled) |
||||
|
||||
# Some override values for Honda |
||||
brake_discount = (1.0 - clip(actuators.brake * 3., 0.0, 1.0)) # brake discount removes a sharp nonlinearity |
||||
CC.cruiseControl.speedOverride = float(max(0.0, (LoC.v_pid + CS.cruiseState.speedOffset) * brake_discount) if CP.enableCruise else 0.0) |
||||
CC.cruiseControl.accelOverride = CI.calc_accel_override(CS.aEgo, sm['plan'].aTarget, CS.vEgo, sm['plan'].vTarget) |
||||
|
||||
CC.hudControl.setSpeed = float(v_cruise_kph * CV.KPH_TO_MS) |
||||
CC.hudControl.speedVisible = isEnabled(state) |
||||
CC.hudControl.lanesVisible = isEnabled(state) |
||||
CC.hudControl.leadVisible = sm['plan'].hasLead |
||||
|
||||
right_lane_visible = sm['pathPlan'].rProb > 0.5 |
||||
left_lane_visible = sm['pathPlan'].lProb > 0.5 |
||||
CC.hudControl.rightLaneVisible = bool(right_lane_visible) |
||||
CC.hudControl.leftLaneVisible = bool(left_lane_visible) |
||||
|
||||
recent_blinker = (sm.frame - last_blinker_frame) * DT_CTRL < 5.0 # 5s blinker cooldown |
||||
ldw_allowed = CS.vEgo > 31 * CV.MPH_TO_MS and not recent_blinker and is_ldw_enabled and not isActive(state) |
||||
|
||||
md = sm['model'] |
||||
if len(md.meta.desirePrediction): |
||||
l_lane_change_prob = md.meta.desirePrediction[log.PathPlan.Desire.laneChangeLeft - 1] |
||||
r_lane_change_prob = md.meta.desirePrediction[log.PathPlan.Desire.laneChangeRight - 1] |
||||
|
||||
l_lane_close = left_lane_visible and (sm['pathPlan'].lPoly[3] < (1.08 - CAMERA_OFFSET)) |
||||
r_lane_close = right_lane_visible and (sm['pathPlan'].rPoly[3] > -(1.08 + CAMERA_OFFSET)) |
||||
|
||||
if ldw_allowed: |
||||
CC.hudControl.leftLaneDepart = bool(l_lane_change_prob > LANE_DEPARTURE_THRESHOLD and l_lane_close) |
||||
CC.hudControl.rightLaneDepart = bool(r_lane_change_prob > LANE_DEPARTURE_THRESHOLD and r_lane_close) |
||||
|
||||
if CC.hudControl.rightLaneDepart or CC.hudControl.leftLaneDepart: |
||||
AM.add(sm.frame, 'ldwPermanent', False) |
||||
events.append(create_event('ldw', [ET.PERMANENT])) |
||||
|
||||
AM.process_alerts(sm.frame) |
||||
CC.hudControl.visualAlert = AM.visual_alert |
||||
|
||||
if not read_only: |
||||
# send car controls over can |
||||
can_sends = CI.apply(CC) |
||||
pm.send('sendcan', can_list_to_can_capnp(can_sends, msgtype='sendcan', valid=CS.canValid)) |
||||
|
||||
force_decel = driver_status.awareness < 0. |
||||
|
||||
# controlsState |
||||
dat = messaging.new_message() |
||||
dat.init('controlsState') |
||||
dat.valid = CS.canValid |
||||
dat.controlsState = { |
||||
"alertText1": AM.alert_text_1, |
||||
"alertText2": AM.alert_text_2, |
||||
"alertSize": AM.alert_size, |
||||
"alertStatus": AM.alert_status, |
||||
"alertBlinkingRate": AM.alert_rate, |
||||
"alertType": AM.alert_type, |
||||
"alertSound": AM.audible_alert, |
||||
"awarenessStatus": max(driver_status.awareness, -0.1) if isEnabled(state) else 1.0, |
||||
"driverMonitoringOn": bool(driver_status.face_detected), |
||||
"canMonoTimes": list(CS.canMonoTimes), |
||||
"planMonoTime": sm.logMonoTime['plan'], |
||||
"pathPlanMonoTime": sm.logMonoTime['pathPlan'], |
||||
"enabled": isEnabled(state), |
||||
"active": isActive(state), |
||||
"vEgo": CS.vEgo, |
||||
"vEgoRaw": CS.vEgoRaw, |
||||
"angleSteers": CS.steeringAngle, |
||||
"curvature": VM.calc_curvature((CS.steeringAngle - sm['pathPlan'].angleOffset) * CV.DEG_TO_RAD, CS.vEgo), |
||||
"steerOverride": CS.steeringPressed, |
||||
"state": state, |
||||
"engageable": not bool(get_events(events, [ET.NO_ENTRY])), |
||||
"longControlState": LoC.long_control_state, |
||||
"vPid": float(LoC.v_pid), |
||||
"vCruise": float(v_cruise_kph), |
||||
"upAccelCmd": float(LoC.pid.p), |
||||
"uiAccelCmd": float(LoC.pid.i), |
||||
"ufAccelCmd": float(LoC.pid.f), |
||||
"angleSteersDes": float(LaC.angle_steers_des), |
||||
"vTargetLead": float(v_acc), |
||||
"aTarget": float(a_acc), |
||||
"jerkFactor": float(sm['plan'].jerkFactor), |
||||
"gpsPlannerActive": sm['plan'].gpsPlannerActive, |
||||
"vCurvature": sm['plan'].vCurvature, |
||||
"decelForModel": sm['plan'].longitudinalPlanSource == log.Plan.LongitudinalPlanSource.model, |
||||
"cumLagMs": -rk.remaining * 1000., |
||||
"startMonoTime": int(start_time * 1e9), |
||||
"mapValid": sm['plan'].mapValid, |
||||
"forceDecel": bool(force_decel), |
||||
"canErrorCounter": can_error_counter, |
||||
} |
||||
|
||||
if CP.lateralTuning.which() == 'pid': |
||||
dat.controlsState.lateralControlState.pidState = lac_log |
||||
elif CP.lateralTuning.which() == 'lqr': |
||||
dat.controlsState.lateralControlState.lqrState = lac_log |
||||
elif CP.lateralTuning.which() == 'indi': |
||||
dat.controlsState.lateralControlState.indiState = lac_log |
||||
pm.send('controlsState', dat) |
||||
|
||||
# carState |
||||
cs_send = messaging.new_message() |
||||
cs_send.init('carState') |
||||
cs_send.valid = CS.canValid |
||||
cs_send.carState = CS |
||||
cs_send.carState.events = events |
||||
pm.send('carState', cs_send) |
||||
|
||||
# carEvents - logged every second or on change |
||||
events_bytes = events_to_bytes(events) |
||||
if (sm.frame % int(1. / DT_CTRL) == 0) or (events_bytes != events_prev): |
||||
ce_send = messaging.new_message() |
||||
ce_send.init('carEvents', len(events)) |
||||
ce_send.carEvents = events |
||||
pm.send('carEvents', ce_send) |
||||
|
||||
# carParams - logged every 50 seconds (> 1 per segment) |
||||
if (sm.frame % int(50. / DT_CTRL) == 0): |
||||
cp_send = messaging.new_message() |
||||
cp_send.init('carParams') |
||||
cp_send.carParams = CP |
||||
pm.send('carParams', cp_send) |
||||
|
||||
# carControl |
||||
cc_send = messaging.new_message() |
||||
cc_send.init('carControl') |
||||
cc_send.valid = CS.canValid |
||||
cc_send.carControl = CC |
||||
pm.send('carControl', cc_send) |
||||
|
||||
return CC, events_bytes |
||||
|
||||
|
||||
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 |
||||
|
||||
# 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', 'driverMonitoring', 'plan', 'pathPlan', \ |
||||
'model', 'gpsLocation'], ignore_alive=['gpsLocation']) |
||||
|
||||
|
||||
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 |
||||
params.put("CarParams", CP.to_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) |
||||
|
||||
driver_status = DriverStatus() |
||||
is_rhd = params.get("IsRHD") |
||||
if is_rhd is not None: |
||||
driver_status.is_rhd = bool(int(is_rhd)) |
||||
|
||||
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 |
||||
events_prev = [] |
||||
|
||||
sm['liveCalibration'].calStatus = Calibration.INVALID |
||||
sm['pathPlan'].sensorValid = True |
||||
sm['pathPlan'].posenetValid = True |
||||
sm['thermal'].freeSpace = 1. |
||||
|
||||
# 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) |
||||
|
||||
internet_needed = params.get("Offroad_ConnectivityNeeded", encoding='utf8') is not 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, driver_status, 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: |
||||
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, driver_status, v_acc, a_acc, lac_log, last_blinker_frame = \ |
||||
state_control(sm.frame, sm.rcv_frame, sm['plan'], sm['pathPlan'], CS, CP, state, events, v_cruise_kph, v_cruise_kph_last, AM, rk, |
||||
driver_status, LaC, LoC, read_only, is_metric, cal_perc, last_blinker_frame) |
||||
|
||||
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, driver_status, 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() |
||||
|
||||
|
||||
def main(sm=None, pm=None, logcan=None): |
||||
controlsd_thread(sm, pm, logcan) |
||||
|
||||
|
||||
if __name__ == "__main__": |
||||
main() |
@ -0,0 +1,381 @@ |
||||
#!/usr/bin/env python |
||||
import os |
||||
import zmq |
||||
import json |
||||
import time |
||||
import numpy as np |
||||
from numpy import linalg as LA |
||||
from threading import Thread |
||||
from scipy.spatial import cKDTree |
||||
|
||||
from selfdrive.swaglog import cloudlog |
||||
from cereal.services import service_list |
||||
from common.realtime import Ratekeeper |
||||
from common.kalman.ned import geodetic2ecef, NED |
||||
import cereal.messaging as messaging |
||||
from cereal import log |
||||
import warnings |
||||
from selfdrive.config import Conversions as CV |
||||
|
||||
|
||||
if os.getenv('EON_LIVE') == '1': |
||||
_REMOTE_ADDR = "192.168.5.11" |
||||
else: |
||||
_REMOTE_ADDR = "127.0.0.1" |
||||
|
||||
LOOP = 'small_loop' |
||||
|
||||
TRACK_SNAP_DIST = 17. # snap to a track below this distance |
||||
TRACK_LOST_DIST = 30. # lose a track above this distance |
||||
INSTRUCTION_APPROACHING_DIST = 200. |
||||
INSTRUCTION_ACTIVE_DIST = 20. |
||||
|
||||
ROT_CENTER_TO_LOC = 1.2 |
||||
|
||||
class INSTRUCTION_STATE: |
||||
NONE = log.UiNavigationEvent.Status.none |
||||
PASSIVE = log.UiNavigationEvent.Status.passive |
||||
APPROACHING = log.UiNavigationEvent.Status.approaching |
||||
ACTIVE = log.UiNavigationEvent.Status.active |
||||
|
||||
|
||||
def convert_ecef_to_capnp(points): |
||||
points_capnp = [] |
||||
for p in points: |
||||
point = log.ECEFPoint.new_message() |
||||
point.x, point.y, point.z = map(float, p[0:3]) |
||||
points_capnp.append(point) |
||||
return points_capnp |
||||
|
||||
|
||||
def get_spaced_points(track, start_index, cur_ecef, v_ego): |
||||
active_points = [] |
||||
look_ahead = 5.0 + 1.5 * v_ego # 5m + 1.5s |
||||
|
||||
# forward and backward passes for better poly fit |
||||
for idx_sign in [1, -1]: |
||||
for i in range(0, 1000): |
||||
index = start_index + i * idx_sign |
||||
# loop around |
||||
p = track[index % len(track)] |
||||
|
||||
distance = LA.norm(cur_ecef - p[0:3]) |
||||
if i > 5 and distance > look_ahead: |
||||
break |
||||
|
||||
active_points.append([p, index]) |
||||
|
||||
# sort points by index |
||||
active_points = sorted(active_points, key=lambda pt: pt[1]) |
||||
active_points = [p[0] for p in active_points] |
||||
|
||||
return active_points |
||||
|
||||
|
||||
def fit_poly(points, cur_ecef, cur_heading, ned_converter): |
||||
relative_points = [] |
||||
for point in points.points: |
||||
p = np.array([point.x, point.y, point.z]) |
||||
relative_points.append(ned_converter.ecef_to_ned_matrix.dot(p - cur_ecef)) |
||||
|
||||
relative_points = np.matrix(np.vstack(relative_points)) |
||||
|
||||
# Calculate relative postions and rotate wrt to heading of car |
||||
c, s = np.cos(-cur_heading), np.sin(-cur_heading) |
||||
R = np.array([[c, -s], [s, c]]) |
||||
|
||||
n, e = relative_points[:, 0], relative_points[:, 1] |
||||
relative_points = np.hstack([e, n]) |
||||
rotated_points = relative_points.dot(R) |
||||
|
||||
rotated_points = np.array(rotated_points) |
||||
x, y = rotated_points[:, 1], -rotated_points[:, 0] |
||||
|
||||
warnings.filterwarnings('error') |
||||
|
||||
# delete points that go backward |
||||
max_x = x[0] |
||||
x_new = [] |
||||
y_new = [] |
||||
|
||||
for xi, yi in zip(x, y): |
||||
if xi > max_x: |
||||
max_x = xi |
||||
x_new.append(xi) |
||||
y_new.append(yi) |
||||
|
||||
x = np.array(x_new) |
||||
y = np.array(y_new) |
||||
|
||||
if len(x) > 10: |
||||
poly = map(float, np.polyfit(x + ROT_CENTER_TO_LOC, y, 3)) # 1.2m in front |
||||
else: |
||||
poly = [0.0, 0.0, 0.0, 0.0] |
||||
return poly, float(max_x + ROT_CENTER_TO_LOC) |
||||
|
||||
|
||||
def get_closest_track(tracks, track_trees, cur_ecef): |
||||
|
||||
track_list = [(name, track_trees[name].query(cur_ecef, 1)) for name in track_trees] |
||||
closest_name, [closest_distance, closest_idx] = min(track_list, key=lambda x: x[1][0]) |
||||
|
||||
return {'name': closest_name, |
||||
'distance': closest_distance, |
||||
'idx': closest_idx, |
||||
'speed': tracks[closest_name][closest_idx][3], |
||||
'accel': tracks[closest_name][closest_idx][4]} |
||||
|
||||
|
||||
def get_track_from_name(tracks, track_trees, track_name, cur_ecef): |
||||
if track_name is None: |
||||
return None |
||||
else: |
||||
track_distance, track_idx = track_trees[track_name].query(cur_ecef, 1) |
||||
return {'name': track_name, |
||||
'distance': track_distance, |
||||
'idx': track_idx, |
||||
'speed': tracks[track_name][track_idx][3], |
||||
'accel': tracks[track_name][track_idx][4]} |
||||
|
||||
|
||||
def get_tracks_from_instruction(tracks,instruction, track_trees, cur_ecef): |
||||
if instruction is None: |
||||
return None, None |
||||
else: |
||||
source_track = get_track_from_name(tracks, track_trees, instruction['source'], cur_ecef) |
||||
target_track = get_track_from_name(tracks, track_trees, instruction['target'], cur_ecef) |
||||
return source_track, target_track |
||||
|
||||
|
||||
def get_next_instruction_distance(track, instruction, cur_ecef): |
||||
if instruction is None: |
||||
return None |
||||
else: |
||||
return np.linalg.norm(cur_ecef - track[instruction['start_idx']][0:3]) |
||||
|
||||
|
||||
def update_current_track(tracks, cur_track, cur_ecef, track_trees): |
||||
|
||||
closest_track = get_closest_track(tracks, track_trees, cur_ecef) |
||||
|
||||
# have we lost current track? |
||||
if cur_track is not None: |
||||
cur_track = get_track_from_name(tracks, track_trees, cur_track['name'], cur_ecef) |
||||
if cur_track['distance'] > TRACK_LOST_DIST: |
||||
cur_track = None |
||||
|
||||
# did we snap to a new track? |
||||
if cur_track is None and closest_track['distance'] < TRACK_SNAP_DIST: |
||||
cur_track = closest_track |
||||
|
||||
return cur_track, closest_track |
||||
|
||||
|
||||
def update_instruction(instruction, instructions, cur_track, source_track, state, cur_ecef, tracks): |
||||
|
||||
if state == INSTRUCTION_STATE.ACTIVE: # instruction frozen, just update distance |
||||
instruction['distance'] = get_next_instruction_distance(tracks[source_track['name']], instruction, cur_ecef) |
||||
return instruction |
||||
|
||||
elif cur_track is None: |
||||
return None |
||||
|
||||
else: |
||||
instruction_list = [i for i in instructions[cur_track['name']] if i['start_idx'] > cur_track['idx']] |
||||
if len(instruction_list) > 0: |
||||
next_instruction = min(instruction_list, key=lambda x: x['start_idx']) |
||||
next_instruction['distance'] = get_next_instruction_distance(tracks[cur_track['name']], next_instruction, cur_ecef) |
||||
return next_instruction |
||||
else: |
||||
return None |
||||
|
||||
|
||||
def calc_instruction_state(state, cur_track, closest_track, source_track, target_track, instruction): |
||||
|
||||
lost_track_or_instruction = cur_track is None or instruction is None |
||||
|
||||
if state == INSTRUCTION_STATE.NONE: |
||||
if lost_track_or_instruction: |
||||
pass |
||||
else: |
||||
state = INSTRUCTION_STATE.PASSIVE |
||||
|
||||
elif state == INSTRUCTION_STATE.PASSIVE: |
||||
if lost_track_or_instruction: |
||||
state = INSTRUCTION_STATE.NONE |
||||
elif instruction['distance'] < INSTRUCTION_APPROACHING_DIST: |
||||
state = INSTRUCTION_STATE.APPROACHING |
||||
|
||||
elif state == INSTRUCTION_STATE.APPROACHING: |
||||
if lost_track_or_instruction: |
||||
state = INSTRUCTION_STATE.NONE |
||||
elif instruction['distance'] < INSTRUCTION_ACTIVE_DIST: |
||||
state = INSTRUCTION_STATE.ACTIVE |
||||
|
||||
elif state == INSTRUCTION_STATE.ACTIVE: |
||||
if lost_track_or_instruction: |
||||
state = INSTRUCTION_STATE.NONE |
||||
elif target_track['distance'] < TRACK_SNAP_DIST and \ |
||||
source_track['idx'] > instruction['start_idx'] and \ |
||||
instruction['distance'] > 10.: |
||||
state = INSTRUCTION_STATE.NONE |
||||
cur_track = target_track |
||||
|
||||
return state, cur_track |
||||
|
||||
|
||||
def gps_planner_point_selection(): |
||||
|
||||
DECIMATION = 1 |
||||
|
||||
cloudlog.info("Starting gps_plannerd point selection") |
||||
|
||||
rk = Ratekeeper(10.0, print_delay_threshold=np.inf) |
||||
|
||||
context = zmq.Context() |
||||
live_location = messaging.sub_sock(context, 'liveLocation', conflate=True, addr=_REMOTE_ADDR) |
||||
car_state = messaging.sub_sock(context, 'carState', conflate=True) |
||||
gps_planner_points = messaging.pub_sock(context, 'gpsPlannerPoints') |
||||
ui_navigation_event = messaging.pub_sock(context, 'uiNavigationEvent') |
||||
|
||||
# Load tracks and instructions from disk |
||||
basedir = os.environ['BASEDIR'] |
||||
tracks = np.load(os.path.join(basedir, 'selfdrive/controls/tracks/%s.npy' % LOOP)).item() |
||||
instructions = json.loads(open(os.path.join(basedir, 'selfdrive/controls/tracks/instructions_%s.json' % LOOP)).read()) |
||||
|
||||
# Put tracks into KD-trees |
||||
track_trees = {} |
||||
for name in tracks: |
||||
tracks[name] = tracks[name][::DECIMATION] |
||||
track_trees[name] = cKDTree(tracks[name][:,0:3]) # xyz |
||||
cur_track = None |
||||
source_track = None |
||||
target_track = None |
||||
instruction = None |
||||
v_ego = 0. |
||||
state = INSTRUCTION_STATE.NONE |
||||
|
||||
counter = 0 |
||||
|
||||
while True: |
||||
counter += 1 |
||||
ll = messaging.recv_one(live_location) |
||||
ll = ll.liveLocation |
||||
cur_ecef = geodetic2ecef((ll.lat, ll.lon, ll.alt)) |
||||
cs = messaging.recv_one_or_none(car_state) |
||||
if cs is not None: |
||||
v_ego = cs.carState.vEgo |
||||
|
||||
cur_track, closest_track = update_current_track(tracks, cur_track, cur_ecef, track_trees) |
||||
#print cur_track |
||||
|
||||
instruction = update_instruction(instruction, instructions, cur_track, source_track, state, cur_ecef, tracks) |
||||
|
||||
source_track, target_track = get_tracks_from_instruction(tracks, instruction, track_trees, cur_ecef) |
||||
|
||||
state, cur_track = calc_instruction_state(state, cur_track, closest_track, source_track, target_track, instruction) |
||||
|
||||
active_points = [] |
||||
|
||||
# Make list of points used by gpsPlannerPlan |
||||
if cur_track is not None: |
||||
active_points = get_spaced_points(tracks[cur_track['name']], cur_track['idx'], cur_ecef, v_ego) |
||||
|
||||
cur_pos = log.ECEFPoint.new_message() |
||||
cur_pos.x, cur_pos.y, cur_pos.z = map(float, cur_ecef) |
||||
m = messaging.new_message() |
||||
m.init('gpsPlannerPoints') |
||||
m.gpsPlannerPoints.curPos = cur_pos |
||||
m.gpsPlannerPoints.points = convert_ecef_to_capnp(active_points) |
||||
m.gpsPlannerPoints.valid = len(active_points) > 10 |
||||
m.gpsPlannerPoints.trackName = "none" if cur_track is None else cur_track['name'] |
||||
m.gpsPlannerPoints.speedLimit = 100. if cur_track is None else float(cur_track['speed']) |
||||
m.gpsPlannerPoints.accelTarget = 0. if cur_track is None else float(cur_track['accel']) |
||||
gps_planner_points.send(m.to_bytes()) |
||||
|
||||
m = messaging.new_message() |
||||
m.init('uiNavigationEvent') |
||||
m.uiNavigationEvent.status = state |
||||
m.uiNavigationEvent.type = "none" if instruction is None else instruction['type'] |
||||
m.uiNavigationEvent.distanceTo = 0. if instruction is None else float(instruction['distance']) |
||||
endRoadPoint = log.ECEFPoint.new_message() |
||||
m.uiNavigationEvent.endRoadPoint = endRoadPoint |
||||
ui_navigation_event.send(m.to_bytes()) |
||||
|
||||
rk.keep_time() |
||||
|
||||
|
||||
def gps_planner_plan(): |
||||
|
||||
context = zmq.Context() |
||||
|
||||
live_location = messaging.sub_sock(context, 'liveLocation', conflate=True, addr=_REMOTE_ADDR) |
||||
gps_planner_points = messaging.sub_sock(context, 'gpsPlannerPoints', conflate=True) |
||||
gps_planner_plan = messaging.pub_sock(context, 'gpsPlannerPlan') |
||||
|
||||
points = messaging.recv_one(gps_planner_points).gpsPlannerPoints |
||||
|
||||
target_speed = 100. * CV.MPH_TO_MS |
||||
target_accel = 0. |
||||
|
||||
last_ecef = np.array([0., 0., 0.]) |
||||
|
||||
while True: |
||||
ll = messaging.recv_one(live_location) |
||||
ll = ll.liveLocation |
||||
p = messaging.recv_one_or_none(gps_planner_points) |
||||
if p is not None: |
||||
points = p.gpsPlannerPoints |
||||
target_speed = p.gpsPlannerPoints.speedLimit |
||||
target_accel = p.gpsPlannerPoints.accelTarget |
||||
|
||||
cur_ecef = geodetic2ecef((ll.lat, ll.lon, ll.alt)) |
||||
|
||||
# TODO: make NED initialization much faster so we can run this every time step |
||||
if np.linalg.norm(last_ecef - cur_ecef) > 200.: |
||||
ned_converter = NED(ll.lat, ll.lon, ll.alt) |
||||
last_ecef = cur_ecef |
||||
|
||||
cur_heading = np.radians(ll.heading) |
||||
|
||||
if points.valid: |
||||
poly, x_lookahead = fit_poly(points, cur_ecef, cur_heading, ned_converter) |
||||
else: |
||||
poly, x_lookahead = [0.0, 0.0, 0.0, 0.0], 0. |
||||
|
||||
valid = points.valid |
||||
|
||||
m = messaging.new_message() |
||||
m.init('gpsPlannerPlan') |
||||
m.gpsPlannerPlan.valid = valid |
||||
m.gpsPlannerPlan.poly = poly |
||||
m.gpsPlannerPlan.trackName = points.trackName |
||||
r = [] |
||||
for p in points.points: |
||||
point = log.ECEFPoint.new_message() |
||||
point.x, point.y, point.z = p.x, p.y, p.z |
||||
r.append(point) |
||||
m.gpsPlannerPlan.points = r |
||||
m.gpsPlannerPlan.speed = target_speed |
||||
m.gpsPlannerPlan.acceleration = target_accel |
||||
m.gpsPlannerPlan.xLookahead = x_lookahead |
||||
gps_planner_plan.send(m.to_bytes()) |
||||
|
||||
|
||||
def main(gctx=None): |
||||
cloudlog.info("Starting gps_plannerd main thread") |
||||
|
||||
point_thread = Thread(target=gps_planner_point_selection) |
||||
point_thread.daemon = True |
||||
control_thread = Thread(target=gps_planner_plan) |
||||
control_thread.daemon = True |
||||
|
||||
point_thread.start() |
||||
control_thread.start() |
||||
|
||||
while True: |
||||
time.sleep(1) |
||||
|
||||
|
||||
if __name__ == "__main__": |
||||
main() |
@ -0,0 +1,71 @@ |
||||
from cereal import car, log |
||||
from common.realtime import DT_CTRL |
||||
from selfdrive.swaglog import cloudlog |
||||
from selfdrive.controls.lib.alerts import ALERTS |
||||
import copy |
||||
|
||||
|
||||
AlertSize = log.ControlsState.AlertSize |
||||
AlertStatus = log.ControlsState.AlertStatus |
||||
VisualAlert = car.CarControl.HUDControl.VisualAlert |
||||
AudibleAlert = car.CarControl.HUDControl.AudibleAlert |
||||
|
||||
class AlertManager(): |
||||
|
||||
def __init__(self): |
||||
self.activealerts = [] |
||||
self.alerts = {alert.alert_type: alert for alert in ALERTS} |
||||
|
||||
def alertPresent(self): |
||||
return len(self.activealerts) > 0 |
||||
|
||||
def add(self, frame, alert_type, enabled=True, extra_text_1='', extra_text_2=''): |
||||
alert_type = str(alert_type) |
||||
added_alert = copy.copy(self.alerts[alert_type]) |
||||
added_alert.alert_text_1 += extra_text_1 |
||||
added_alert.alert_text_2 += extra_text_2 |
||||
added_alert.start_time = frame * DT_CTRL |
||||
|
||||
# if new alert is higher priority, log it |
||||
if not self.alertPresent() or added_alert.alert_priority > self.activealerts[0].alert_priority: |
||||
cloudlog.event('alert_add', alert_type=alert_type, enabled=enabled) |
||||
|
||||
self.activealerts.append(added_alert) |
||||
|
||||
# sort by priority first and then by start_time |
||||
self.activealerts.sort(key=lambda k: (k.alert_priority, k.start_time), reverse=True) |
||||
|
||||
def process_alerts(self, frame): |
||||
cur_time = frame * DT_CTRL |
||||
|
||||
# first get rid of all the expired alerts |
||||
self.activealerts = [a for a in self.activealerts if a.start_time + |
||||
max(a.duration_sound, a.duration_hud_alert, a.duration_text) > cur_time] |
||||
|
||||
current_alert = self.activealerts[0] if self.alertPresent() else None |
||||
|
||||
# start with assuming no alerts |
||||
self.alert_type = "" |
||||
self.alert_text_1 = "" |
||||
self.alert_text_2 = "" |
||||
self.alert_status = AlertStatus.normal |
||||
self.alert_size = AlertSize.none |
||||
self.visual_alert = VisualAlert.none |
||||
self.audible_alert = AudibleAlert.none |
||||
self.alert_rate = 0. |
||||
|
||||
if current_alert: |
||||
self.alert_type = current_alert.alert_type |
||||
|
||||
if current_alert.start_time + current_alert.duration_sound > cur_time: |
||||
self.audible_alert = current_alert.audible_alert |
||||
|
||||
if current_alert.start_time + current_alert.duration_hud_alert > cur_time: |
||||
self.visual_alert = current_alert.visual_alert |
||||
|
||||
if current_alert.start_time + current_alert.duration_text > cur_time: |
||||
self.alert_text_1 = current_alert.alert_text_1 |
||||
self.alert_text_2 = current_alert.alert_text_2 |
||||
self.alert_status = current_alert.alert_status |
||||
self.alert_size = current_alert.alert_size |
||||
self.alert_rate = current_alert.alert_rate |
@ -0,0 +1,775 @@ |
||||
from cereal import car, log |
||||
|
||||
# Priority |
||||
class Priority: |
||||
LOWEST = 0 |
||||
LOW_LOWEST = 1 |
||||
LOW = 2 |
||||
MID = 3 |
||||
HIGH = 4 |
||||
HIGHEST = 5 |
||||
|
||||
AlertSize = log.ControlsState.AlertSize |
||||
AlertStatus = log.ControlsState.AlertStatus |
||||
AudibleAlert = car.CarControl.HUDControl.AudibleAlert |
||||
VisualAlert = car.CarControl.HUDControl.VisualAlert |
||||
|
||||
class Alert(): |
||||
def __init__(self, |
||||
alert_type, |
||||
alert_text_1, |
||||
alert_text_2, |
||||
alert_status, |
||||
alert_size, |
||||
alert_priority, |
||||
visual_alert, |
||||
audible_alert, |
||||
duration_sound, |
||||
duration_hud_alert, |
||||
duration_text, |
||||
alert_rate=0.): |
||||
|
||||
self.alert_type = alert_type |
||||
self.alert_text_1 = alert_text_1 |
||||
self.alert_text_2 = alert_text_2 |
||||
self.alert_status = alert_status |
||||
self.alert_size = alert_size |
||||
self.alert_priority = alert_priority |
||||
self.visual_alert = visual_alert |
||||
self.audible_alert = audible_alert |
||||
|
||||
self.duration_sound = duration_sound |
||||
self.duration_hud_alert = duration_hud_alert |
||||
self.duration_text = duration_text |
||||
|
||||
self.start_time = 0. |
||||
self.alert_rate = alert_rate |
||||
|
||||
# typecheck that enums are valid on startup |
||||
tst = car.CarControl.new_message() |
||||
tst.hudControl.visualAlert = self.visual_alert |
||||
|
||||
def __str__(self): |
||||
return self.alert_text_1 + "/" + self.alert_text_2 + " " + str(self.alert_priority) + " " + str( |
||||
self.visual_alert) + " " + str(self.audible_alert) |
||||
|
||||
def __gt__(self, alert2): |
||||
return self.alert_priority > alert2.alert_priority |
||||
|
||||
|
||||
ALERTS = [ |
||||
# Miscellaneous alerts |
||||
Alert( |
||||
"enable", |
||||
"", |
||||
"", |
||||
AlertStatus.normal, AlertSize.none, |
||||
Priority.MID, VisualAlert.none, AudibleAlert.chimeEngage, .2, 0., 0.), |
||||
|
||||
Alert( |
||||
"disable", |
||||
"", |
||||
"", |
||||
AlertStatus.normal, AlertSize.none, |
||||
Priority.MID, VisualAlert.none, AudibleAlert.chimeDisengage, .2, 0., 0.), |
||||
|
||||
Alert( |
||||
"fcw", |
||||
"BRAKE!", |
||||
"Risk of Collision", |
||||
AlertStatus.critical, AlertSize.full, |
||||
Priority.HIGHEST, VisualAlert.fcw, AudibleAlert.chimeWarningRepeat, 1., 2., 2.), |
||||
|
||||
Alert( |
||||
"fcwStock", |
||||
"BRAKE!", |
||||
"Risk of Collision", |
||||
AlertStatus.critical, AlertSize.full, |
||||
Priority.HIGHEST, VisualAlert.fcw, AudibleAlert.none, 1., 2., 2.), # no EON chime for stock FCW |
||||
|
||||
Alert( |
||||
"steerSaturated", |
||||
"TAKE CONTROL", |
||||
"Turn Exceeds Steering Limit", |
||||
AlertStatus.userPrompt, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.steerRequired, AudibleAlert.chimePrompt, 1., 2., 3.), |
||||
|
||||
Alert( |
||||
"steerTempUnavailable", |
||||
"TAKE CONTROL", |
||||
"Steering Temporarily Unavailable", |
||||
AlertStatus.userPrompt, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.steerRequired, AudibleAlert.chimeWarning1, .4, 2., 3.), |
||||
|
||||
Alert( |
||||
"steerTempUnavailableMute", |
||||
"TAKE CONTROL", |
||||
"Steering Temporarily Unavailable", |
||||
AlertStatus.userPrompt, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.none, .2, .2, .2), |
||||
|
||||
Alert( |
||||
"preDriverDistracted", |
||||
"KEEP EYES ON ROAD: User Appears Distracted", |
||||
"", |
||||
AlertStatus.normal, AlertSize.small, |
||||
Priority.LOW, VisualAlert.steerRequired, AudibleAlert.none, .0, .1, .1, alert_rate=0.75), |
||||
|
||||
Alert( |
||||
"promptDriverDistracted", |
||||
"KEEP EYES ON ROAD", |
||||
"User Appears Distracted", |
||||
AlertStatus.userPrompt, AlertSize.mid, |
||||
Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarning2, .1, .1, .1), |
||||
|
||||
Alert( |
||||
"driverDistracted", |
||||
"DISENGAGE IMMEDIATELY", |
||||
"User Was Distracted", |
||||
AlertStatus.critical, AlertSize.full, |
||||
Priority.HIGH, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, .1, .1, .1), |
||||
|
||||
Alert( |
||||
"preDriverUnresponsive", |
||||
"TOUCH STEERING WHEEL: No Face Detected", |
||||
"", |
||||
AlertStatus.normal, AlertSize.small, |
||||
Priority.LOW, VisualAlert.steerRequired, AudibleAlert.none, .0, .1, .1, alert_rate=0.75), |
||||
|
||||
Alert( |
||||
"promptDriverUnresponsive", |
||||
"TOUCH STEERING WHEEL", |
||||
"User Is Unresponsive", |
||||
AlertStatus.userPrompt, AlertSize.mid, |
||||
Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarning2, .1, .1, .1), |
||||
|
||||
Alert( |
||||
"driverUnresponsive", |
||||
"DISENGAGE IMMEDIATELY", |
||||
"User Was Unresponsive", |
||||
AlertStatus.critical, AlertSize.full, |
||||
Priority.HIGH, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, .1, .1, .1), |
||||
|
||||
Alert( |
||||
"driverMonitorLowAcc", |
||||
"CHECK DRIVER FACE VISIBILITY", |
||||
"Driver Monitor Model Output Uncertain", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.none, .4, 0., 4.), |
||||
|
||||
Alert( |
||||
"geofence", |
||||
"DISENGAGEMENT REQUIRED", |
||||
"Not in Geofenced Area", |
||||
AlertStatus.userPrompt, AlertSize.mid, |
||||
Priority.HIGH, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, .1, .1, .1), |
||||
|
||||
Alert( |
||||
"startup", |
||||
"Be ready to take over at any time", |
||||
"Always keep hands on wheel and eyes on road", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW_LOWEST, VisualAlert.none, AudibleAlert.none, 0., 0., 15.), |
||||
|
||||
Alert( |
||||
"startupNoControl", |
||||
"Dashcam mode", |
||||
"Always keep hands on wheel and eyes on road", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW_LOWEST, VisualAlert.none, AudibleAlert.none, 0., 0., 15.), |
||||
|
||||
Alert( |
||||
"startupNoCar", |
||||
"Dashcam mode with unsupported car", |
||||
"Always keep hands on wheel and eyes on road", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW_LOWEST, VisualAlert.none, AudibleAlert.none, 0., 0., 15.), |
||||
|
||||
Alert( |
||||
"ethicalDilemma", |
||||
"TAKE CONTROL IMMEDIATELY", |
||||
"Ethical Dilemma Detected", |
||||
AlertStatus.critical, AlertSize.full, |
||||
Priority.HIGHEST, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, 1., 3., 3.), |
||||
|
||||
Alert( |
||||
"steerTempUnavailableNoEntry", |
||||
"openpilot Unavailable", |
||||
"Steering Temporarily Unavailable", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 0., 3.), |
||||
|
||||
Alert( |
||||
"manualRestart", |
||||
"TAKE CONTROL", |
||||
"Resume Driving Manually", |
||||
AlertStatus.userPrompt, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.none, 0., 0., .2), |
||||
|
||||
Alert( |
||||
"resumeRequired", |
||||
"STOPPED", |
||||
"Press Resume to Move", |
||||
AlertStatus.userPrompt, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.none, 0., 0., .2), |
||||
|
||||
Alert( |
||||
"belowSteerSpeed", |
||||
"TAKE CONTROL", |
||||
"Steer Unavailable Below ", |
||||
AlertStatus.userPrompt, AlertSize.mid, |
||||
Priority.MID, VisualAlert.steerRequired, AudibleAlert.none, 0., 0.4, .3), |
||||
|
||||
Alert( |
||||
"debugAlert", |
||||
"DEBUG ALERT", |
||||
"", |
||||
AlertStatus.userPrompt, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.none, .1, .1, .1), |
||||
Alert( |
||||
"preLaneChangeLeft", |
||||
"Steer Left to Start Lane Change", |
||||
"Monitor Other Vehicles", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.steerRequired, AudibleAlert.none, .0, .1, .1, alert_rate=0.75), |
||||
|
||||
Alert( |
||||
"preLaneChangeRight", |
||||
"Steer Right to Start Lane Change", |
||||
"Monitor Other Vehicles", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.steerRequired, AudibleAlert.none, .0, .1, .1, alert_rate=0.75), |
||||
|
||||
Alert( |
||||
"laneChange", |
||||
"Changing Lane", |
||||
"Monitor Other Vehicles", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.steerRequired, AudibleAlert.none, .0, .1, .1), |
||||
|
||||
Alert( |
||||
"posenetInvalid", |
||||
"TAKE CONTROL", |
||||
"Vision Model Output Uncertain", |
||||
AlertStatus.userPrompt, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.steerRequired, AudibleAlert.chimeWarning1, .4, 2., 3.), |
||||
|
||||
# Non-entry only alerts |
||||
Alert( |
||||
"wrongCarModeNoEntry", |
||||
"openpilot Unavailable", |
||||
"Main Switch Off", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 0., 3.), |
||||
|
||||
Alert( |
||||
"dataNeededNoEntry", |
||||
"openpilot Unavailable", |
||||
"Data Needed for Calibration. Upload Drive, Try Again", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 0., 3.), |
||||
|
||||
Alert( |
||||
"outOfSpaceNoEntry", |
||||
"openpilot Unavailable", |
||||
"Out of Storage Space", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 0., 3.), |
||||
|
||||
Alert( |
||||
"pedalPressedNoEntry", |
||||
"openpilot Unavailable", |
||||
"Pedal Pressed During Attempt", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, "brakePressed", AudibleAlert.chimeError, .4, 2., 3.), |
||||
|
||||
Alert( |
||||
"speedTooLowNoEntry", |
||||
"openpilot Unavailable", |
||||
"Speed Too Low", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), |
||||
|
||||
Alert( |
||||
"brakeHoldNoEntry", |
||||
"openpilot Unavailable", |
||||
"Brake Hold Active", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), |
||||
|
||||
Alert( |
||||
"parkBrakeNoEntry", |
||||
"openpilot Unavailable", |
||||
"Park Brake Engaged", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), |
||||
|
||||
Alert( |
||||
"lowSpeedLockoutNoEntry", |
||||
"openpilot Unavailable", |
||||
"Cruise Fault: Restart the Car", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), |
||||
|
||||
Alert( |
||||
"lowBatteryNoEntry", |
||||
"openpilot Unavailable", |
||||
"Low Battery", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), |
||||
|
||||
Alert( |
||||
"sensorDataInvalidNoEntry", |
||||
"openpilot Unavailable", |
||||
"No Data from EON Sensors", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), |
||||
|
||||
Alert( |
||||
"soundsUnavailableNoEntry", |
||||
"openpilot Unavailable", |
||||
"Speaker not found", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), |
||||
|
||||
Alert( |
||||
"tooDistractedNoEntry", |
||||
"openpilot Unavailable", |
||||
"Distraction Level Too High", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), |
||||
|
||||
# Cancellation alerts causing soft disabling |
||||
Alert( |
||||
"overheat", |
||||
"TAKE CONTROL IMMEDIATELY", |
||||
"System Overheated", |
||||
AlertStatus.critical, AlertSize.full, |
||||
Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, .1, 2., 2.), |
||||
|
||||
Alert( |
||||
"wrongGear", |
||||
"TAKE CONTROL IMMEDIATELY", |
||||
"Gear not D", |
||||
AlertStatus.critical, AlertSize.full, |
||||
Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, .1, 2., 2.), |
||||
|
||||
Alert( |
||||
"calibrationInvalid", |
||||
"TAKE CONTROL IMMEDIATELY", |
||||
"Calibration Invalid: Reposition EON and Recalibrate", |
||||
AlertStatus.critical, AlertSize.full, |
||||
Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, .1, 2., 2.), |
||||
|
||||
Alert( |
||||
"calibrationIncomplete", |
||||
"TAKE CONTROL IMMEDIATELY", |
||||
"Calibration in Progress", |
||||
AlertStatus.critical, AlertSize.full, |
||||
Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, .1, 2., 2.), |
||||
|
||||
Alert( |
||||
"doorOpen", |
||||
"TAKE CONTROL IMMEDIATELY", |
||||
"Door Open", |
||||
AlertStatus.critical, AlertSize.full, |
||||
Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, .1, 2., 2.), |
||||
|
||||
Alert( |
||||
"seatbeltNotLatched", |
||||
"TAKE CONTROL IMMEDIATELY", |
||||
"Seatbelt Unlatched", |
||||
AlertStatus.critical, AlertSize.full, |
||||
Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, .1, 2., 2.), |
||||
|
||||
Alert( |
||||
"espDisabled", |
||||
"TAKE CONTROL IMMEDIATELY", |
||||
"ESP Off", |
||||
AlertStatus.critical, AlertSize.full, |
||||
Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, .1, 2., 2.), |
||||
|
||||
Alert( |
||||
"lowBattery", |
||||
"TAKE CONTROL IMMEDIATELY", |
||||
"Low Battery", |
||||
AlertStatus.critical, AlertSize.full, |
||||
Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, .1, 2., 2.), |
||||
|
||||
Alert( |
||||
"commIssue", |
||||
"TAKE CONTROL IMMEDIATELY", |
||||
"Communication Issue between Processes", |
||||
AlertStatus.critical, AlertSize.full, |
||||
Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, .1, 2., 2.), |
||||
|
||||
Alert( |
||||
"radarCommIssue", |
||||
"TAKE CONTROL IMMEDIATELY", |
||||
"Radar Communication Issue", |
||||
AlertStatus.critical, AlertSize.full, |
||||
Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, .1, 2., 2.), |
||||
|
||||
Alert( |
||||
"radarCanError", |
||||
"TAKE CONTROL IMMEDIATELY", |
||||
"Radar Error: Restart the Car", |
||||
AlertStatus.critical, AlertSize.full, |
||||
Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, .1, 2., 2.), |
||||
|
||||
Alert( |
||||
"radarFault", |
||||
"TAKE CONTROL IMMEDIATELY", |
||||
"Radar Error: Restart the Car", |
||||
AlertStatus.critical, AlertSize.full, |
||||
Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, .1, 2., 2.), |
||||
|
||||
|
||||
Alert( |
||||
"lowMemory", |
||||
"TAKE CONTROL IMMEDIATELY", |
||||
"Low Memory: Reboot Your EON", |
||||
AlertStatus.critical, AlertSize.full, |
||||
Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, .1, 2., 2.), |
||||
|
||||
# Cancellation alerts causing immediate disabling |
||||
Alert( |
||||
"controlsFailed", |
||||
"TAKE CONTROL IMMEDIATELY", |
||||
"Controls Failed", |
||||
AlertStatus.critical, AlertSize.full, |
||||
Priority.HIGHEST, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, 2.2, 3., 4.), |
||||
|
||||
Alert( |
||||
"controlsMismatch", |
||||
"TAKE CONTROL IMMEDIATELY", |
||||
"Controls Mismatch", |
||||
AlertStatus.critical, AlertSize.full, |
||||
Priority.HIGHEST, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, 2.2, 3., 4.), |
||||
|
||||
Alert( |
||||
"canError", |
||||
"TAKE CONTROL IMMEDIATELY", |
||||
"CAN Error: Check Connections", |
||||
AlertStatus.critical, AlertSize.full, |
||||
Priority.HIGHEST, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, 2.2, 3., 4.), |
||||
|
||||
Alert( |
||||
"steerUnavailable", |
||||
"TAKE CONTROL IMMEDIATELY", |
||||
"LKAS Fault: Restart the Car", |
||||
AlertStatus.critical, AlertSize.full, |
||||
Priority.HIGHEST, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, 2.2, 3., 4.), |
||||
|
||||
Alert( |
||||
"brakeUnavailable", |
||||
"TAKE CONTROL IMMEDIATELY", |
||||
"Cruise Fault: Restart the Car", |
||||
AlertStatus.critical, AlertSize.full, |
||||
Priority.HIGHEST, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, 2.2, 3., 4.), |
||||
|
||||
Alert( |
||||
"gasUnavailable", |
||||
"TAKE CONTROL IMMEDIATELY", |
||||
"Gas Fault: Restart the Car", |
||||
AlertStatus.critical, AlertSize.full, |
||||
Priority.HIGHEST, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, 2.2, 3., 4.), |
||||
|
||||
Alert( |
||||
"reverseGear", |
||||
"TAKE CONTROL IMMEDIATELY", |
||||
"Reverse Gear", |
||||
AlertStatus.critical, AlertSize.full, |
||||
Priority.HIGHEST, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, 2.2, 3., 4.), |
||||
|
||||
Alert( |
||||
"cruiseDisabled", |
||||
"TAKE CONTROL IMMEDIATELY", |
||||
"Cruise Is Off", |
||||
AlertStatus.critical, AlertSize.full, |
||||
Priority.HIGHEST, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, 2.2, 3., 4.), |
||||
|
||||
Alert( |
||||
"plannerError", |
||||
"TAKE CONTROL IMMEDIATELY", |
||||
"Planner Solution Error", |
||||
AlertStatus.critical, AlertSize.full, |
||||
Priority.HIGHEST, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, 2.2, 3., 4.), |
||||
|
||||
# not loud cancellations (user is in control) |
||||
Alert( |
||||
"noTarget", |
||||
"openpilot Canceled", |
||||
"No close lead car", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.HIGH, VisualAlert.none, AudibleAlert.chimeDisengage, .4, 2., 3.), |
||||
|
||||
Alert( |
||||
"speedTooLow", |
||||
"openpilot Canceled", |
||||
"Speed too low", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.HIGH, VisualAlert.none, AudibleAlert.chimeDisengage, .4, 2., 3.), |
||||
|
||||
# Cancellation alerts causing non-entry |
||||
Alert( |
||||
"overheatNoEntry", |
||||
"openpilot Unavailable", |
||||
"System overheated", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), |
||||
|
||||
Alert( |
||||
"wrongGearNoEntry", |
||||
"openpilot Unavailable", |
||||
"Gear not D", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), |
||||
|
||||
Alert( |
||||
"calibrationInvalidNoEntry", |
||||
"openpilot Unavailable", |
||||
"Calibration Invalid: Reposition EON and Recalibrate", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), |
||||
|
||||
Alert( |
||||
"calibrationIncompleteNoEntry", |
||||
"openpilot Unavailable", |
||||
"Calibration in Progress", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), |
||||
|
||||
Alert( |
||||
"doorOpenNoEntry", |
||||
"openpilot Unavailable", |
||||
"Door open", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), |
||||
|
||||
Alert( |
||||
"seatbeltNotLatchedNoEntry", |
||||
"openpilot Unavailable", |
||||
"Seatbelt unlatched", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), |
||||
|
||||
Alert( |
||||
"espDisabledNoEntry", |
||||
"openpilot Unavailable", |
||||
"ESP Off", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), |
||||
|
||||
Alert( |
||||
"geofenceNoEntry", |
||||
"openpilot Unavailable", |
||||
"Not in Geofenced Area", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.MID, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), |
||||
|
||||
Alert( |
||||
"radarCanErrorNoEntry", |
||||
"openpilot Unavailable", |
||||
"Radar Error: Restart the Car", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), |
||||
|
||||
Alert( |
||||
"radarFaultNoEntry", |
||||
"openpilot Unavailable", |
||||
"Radar Error: Restart the Car", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), |
||||
|
||||
Alert( |
||||
"posenetInvalidNoEntry", |
||||
"openpilot Unavailable", |
||||
"Vision Model Output Uncertain", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), |
||||
|
||||
Alert( |
||||
"controlsFailedNoEntry", |
||||
"openpilot Unavailable", |
||||
"Controls Failed", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), |
||||
|
||||
Alert( |
||||
"canErrorNoEntry", |
||||
"openpilot Unavailable", |
||||
"CAN Error: Check Connections", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), |
||||
|
||||
Alert( |
||||
"steerUnavailableNoEntry", |
||||
"openpilot Unavailable", |
||||
"LKAS Fault: Restart the Car", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), |
||||
|
||||
Alert( |
||||
"brakeUnavailableNoEntry", |
||||
"openpilot Unavailable", |
||||
"Cruise Fault: Restart the Car", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), |
||||
|
||||
Alert( |
||||
"gasUnavailableNoEntry", |
||||
"openpilot Unavailable", |
||||
"Gas Error: Restart the Car", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), |
||||
|
||||
Alert( |
||||
"reverseGearNoEntry", |
||||
"openpilot Unavailable", |
||||
"Reverse Gear", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), |
||||
|
||||
Alert( |
||||
"cruiseDisabledNoEntry", |
||||
"openpilot Unavailable", |
||||
"Cruise is Off", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), |
||||
|
||||
Alert( |
||||
"noTargetNoEntry", |
||||
"openpilot Unavailable", |
||||
"No Close Lead Car", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), |
||||
|
||||
Alert( |
||||
"plannerErrorNoEntry", |
||||
"openpilot Unavailable", |
||||
"Planner Solution Error", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), |
||||
|
||||
Alert( |
||||
"commIssueNoEntry", |
||||
"openpilot Unavailable", |
||||
"Communication Issue between Processes", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.chimeDisengage, .4, 2., 3.), |
||||
|
||||
Alert( |
||||
"radarCommIssueNoEntry", |
||||
"openpilot Unavailable", |
||||
"Radar Communication Issue", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.chimeDisengage, .4, 2., 3.), |
||||
|
||||
Alert( |
||||
"internetConnectivityNeededNoEntry", |
||||
"openpilot Unavailable", |
||||
"Please Connect to Internet", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.chimeDisengage, .4, 2., 3.), |
||||
|
||||
Alert( |
||||
"lowMemoryNoEntry", |
||||
"openpilot Unavailable", |
||||
"Low Memory: Reboot Your EON", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.chimeDisengage, .4, 2., 3.), |
||||
|
||||
# permanent alerts |
||||
Alert( |
||||
"steerUnavailablePermanent", |
||||
"LKAS Fault: Restart the car to engage", |
||||
"", |
||||
AlertStatus.normal, AlertSize.small, |
||||
Priority.LOW_LOWEST, VisualAlert.none, AudibleAlert.none, 0., 0., .2), |
||||
|
||||
Alert( |
||||
"brakeUnavailablePermanent", |
||||
"Cruise Fault: Restart the car to engage", |
||||
"", |
||||
AlertStatus.normal, AlertSize.small, |
||||
Priority.LOW_LOWEST, VisualAlert.none, AudibleAlert.none, 0., 0., .2), |
||||
|
||||
Alert( |
||||
"lowSpeedLockoutPermanent", |
||||
"Cruise Fault: Restart the car to engage", |
||||
"", |
||||
AlertStatus.normal, AlertSize.small, |
||||
Priority.LOW_LOWEST, VisualAlert.none, AudibleAlert.none, 0., 0., .2), |
||||
|
||||
Alert( |
||||
"calibrationIncompletePermanent", |
||||
"Calibration in Progress: ", |
||||
"Drive Above ", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOWEST, VisualAlert.none, AudibleAlert.none, 0., 0., .2), |
||||
|
||||
Alert( |
||||
"invalidGiraffeToyotaPermanent", |
||||
"Unsupported Giraffe Configuration", |
||||
"Visit comma.ai/tg", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW_LOWEST, VisualAlert.none, AudibleAlert.none, 0., 0., .2), |
||||
|
||||
Alert( |
||||
"internetConnectivityNeededPermanent", |
||||
"Please connect to Internet", |
||||
"An Update Check Is Required to Engage", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW_LOWEST, VisualAlert.none, AudibleAlert.none, 0., 0., .2), |
||||
|
||||
Alert( |
||||
"communityFeatureDisallowedPermanent", |
||||
"Community Feature Detected", |
||||
"Enable Community Features in Developer Settings", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.none, AudibleAlert.none, 0., 0., .2), # LOW priority to overcome Cruise Error |
||||
|
||||
Alert( |
||||
"sensorDataInvalidPermanent", |
||||
"No Data from EON Sensors", |
||||
"Reboot your EON", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW_LOWEST, VisualAlert.none, AudibleAlert.none, 0., 0., .2), |
||||
|
||||
Alert( |
||||
"soundsUnavailablePermanent", |
||||
"Speaker not found", |
||||
"Reboot your EON", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW_LOWEST, VisualAlert.none, AudibleAlert.none, 0., 0., .2), |
||||
|
||||
Alert( |
||||
"lowMemoryPermanent", |
||||
"RAM Critically Low", |
||||
"Reboot your EON", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW_LOWEST, VisualAlert.none, AudibleAlert.none, 0., 0., .2), |
||||
|
||||
Alert( |
||||
"carUnrecognizedPermanent", |
||||
"Dashcam Mode", |
||||
"Car Unrecognized", |
||||
AlertStatus.normal, AlertSize.mid, |
||||
Priority.LOW_LOWEST, VisualAlert.none, AudibleAlert.none, 0., 0., .2), |
||||
|
||||
Alert( |
||||
"vehicleModelInvalid", |
||||
"Vehicle Parameter Identification Failed", |
||||
"", |
||||
AlertStatus.normal, AlertSize.small, |
||||
Priority.LOWEST, VisualAlert.steerRequired, AudibleAlert.none, .0, .0, .1), |
||||
|
||||
# offroad alerts |
||||
Alert( |
||||
"ldwPermanent", |
||||
"TAKE CONTROL", |
||||
"Lane Departure Detected", |
||||
AlertStatus.userPrompt, AlertSize.mid, |
||||
Priority.LOW, VisualAlert.steerRequired, AudibleAlert.chimePrompt, 1., 2., 3.), |
||||
] |
@ -0,0 +1,31 @@ |
||||
{ |
||||
"Offroad_ChargeDisabled": { |
||||
"text": "EON charging disabled after car being off for more than 30 hours. Turn ignition on to start charging again.", |
||||
"severity": 0 |
||||
}, |
||||
"Offroad_TemperatureTooHigh": { |
||||
"text": "EON temperature too high. System won't start.", |
||||
"severity": 1 |
||||
}, |
||||
"Offroad_ConnectivityNeededPrompt": { |
||||
"text": "Immediately connect to the internet to check for updates. If you do not connect to the internet, openpilot won't engage in ", |
||||
"severity": 0, |
||||
"_comment": "Append the number of days at the end of the text" |
||||
}, |
||||
"Offroad_ConnectivityNeeded": { |
||||
"text": "Connect to internet to check for updates. openpilot won't engage until it connects to internet to check for updates.", |
||||
"severity": 1 |
||||
}, |
||||
"Offroad_PandaFirmwareMismatch": { |
||||
"text": "Unexpected panda firmware version. System won't start. Reboot your EON to reflash panda.", |
||||
"severity": 1 |
||||
}, |
||||
"Offroad_InvalidTime": { |
||||
"text": "Invalid date and time settings, system won't start. Connect to internet to set time.", |
||||
"severity": 1 |
||||
}, |
||||
"Offroad_IsTakingSnapshot": { |
||||
"text": "Taking camera snapshots. System won't start until finished.", |
||||
"severity": 0 |
||||
} |
||||
} |
@ -0,0 +1 @@ |
||||
test |
@ -0,0 +1,79 @@ |
||||
C++ interface to fast hierarchical clustering algorithms |
||||
======================================================== |
||||
|
||||
This is a simplified C++ interface to fast implementations of hierarchical |
||||
clustering by Daniel Müllner. The original library with interfaces to R |
||||
and Python is described in: |
||||
|
||||
Daniel Müllner: "fastcluster: Fast Hierarchical, Agglomerative Clustering |
||||
Routines for R and Python." Journal of Statistical Software 53 (2013), |
||||
no. 9, pp. 1–18, http://www.jstatsoft.org/v53/i09/ |
||||
|
||||
|
||||
Usage of the library |
||||
-------------------- |
||||
|
||||
For using the library, the following source files are needed: |
||||
|
||||
fastcluster_dm.cpp, fastcluster_R_dm.cpp |
||||
original code by Daniel Müllner |
||||
these are included by fastcluster.cpp via #include, and therefore |
||||
need not be compiled to object code |
||||
|
||||
fastcluster.[h|cpp] |
||||
simplified C++ interface |
||||
fastcluster.cpp is the only file that must be compiled |
||||
|
||||
The library provides the clustering function *hclust_fast* for |
||||
creating the dendrogram information in an encoding as used by the |
||||
R function *hclust*. For a description of the parameters, see fastcluster.h. |
||||
Its parameter *method* can be one of |
||||
|
||||
HCLUST_METHOD_SINGLE |
||||
single link with the minimum spanning tree algorithm (Rohlf, 1973) |
||||
|
||||
HHCLUST_METHOD_COMPLETE |
||||
complete link with the nearest-neighbor-chain algorithm (Murtagh, 1984) |
||||
|
||||
HCLUST_METHOD_AVERAGE |
||||
complete link with the nearest-neighbor-chain algorithm (Murtagh, 1984) |
||||
|
||||
HCLUST_METHOD_MEDIAN |
||||
median link with the generic algorithm (Müllner, 2011) |
||||
|
||||
For splitting the dendrogram into clusters, the two functions *cutree_k* |
||||
and *cutree_cdist* are provided. |
||||
|
||||
Note that output parameters must be allocated beforehand, e.g. |
||||
int* merge = new int[2*(npoints-1)]; |
||||
For a complete usage example, see lines 135-142 of demo.cpp. |
||||
|
||||
|
||||
Demonstration program |
||||
--------------------- |
||||
|
||||
A simple demo is implemented in demo.cpp, which can be compiled and run with |
||||
|
||||
make |
||||
./hclust-demo -m complete lines.csv |
||||
|
||||
It creates two clusters of line segments such that the segment angle between |
||||
line segments of different clusters have a maximum (cosine) dissimilarity. |
||||
For visualizing the result, plotresult.r can be used as follows |
||||
(requires R <https://r-project.org> to be installed): |
||||
|
||||
./hclust-demo -m complete lines.csv | Rscript plotresult.r |
||||
|
||||
|
||||
Authors & Copyright |
||||
------------------- |
||||
|
||||
Daniel Müllner, 2011, <http://danifold.net> |
||||
Christoph Dalitz, 2018, <http://www.hsnr.de/ipattern/> |
||||
|
||||
|
||||
License |
||||
------- |
||||
|
||||
This code is provided under a BSD-style license. |
||||
See the file LICENSE for details. |
@ -0,0 +1,8 @@ |
||||
Import('env') |
||||
|
||||
fc = env.SharedLibrary("fastcluster", "fastcluster.cpp") |
||||
|
||||
# TODO: how do I gate on test |
||||
#env.Program("test", ["test.cpp"], LIBS=[fc]) |
||||
#valgrind --leak-check=full ./test |
||||
|
@ -0,0 +1,218 @@ |
||||
//
|
||||
// C++ standalone verion of fastcluster by Daniel Müllner
|
||||
//
|
||||
// Copyright: Christoph Dalitz, 2018
|
||||
// Daniel Müllner, 2011
|
||||
// License: BSD style license
|
||||
// (see the file LICENSE for details)
|
||||
//
|
||||
|
||||
|
||||
#include <vector> |
||||
#include <algorithm> |
||||
#include <cmath> |
||||
|
||||
|
||||
extern "C" { |
||||
#include "fastcluster.h" |
||||
} |
||||
|
||||
// Code by Daniel Müllner
|
||||
// workaround to make it usable as a standalone version (without R)
|
||||
bool fc_isnan(double x) { return false; } |
||||
#include "fastcluster_dm.cpp" |
||||
#include "fastcluster_R_dm.cpp" |
||||
|
||||
extern "C" { |
||||
//
|
||||
// Assigns cluster labels (0, ..., nclust-1) to the n points such
|
||||
// that the cluster result is split into nclust clusters.
|
||||
//
|
||||
// Input arguments:
|
||||
// n = number of observables
|
||||
// merge = clustering result in R format
|
||||
// nclust = number of clusters
|
||||
// Output arguments:
|
||||
// labels = allocated integer array of size n for result
|
||||
//
|
||||
void cutree_k(int n, const int* merge, int nclust, int* labels) { |
||||
|
||||
int k,m1,m2,j,l; |
||||
|
||||
if (nclust > n || nclust < 2) { |
||||
for (j=0; j<n; j++) labels[j] = 0; |
||||
return; |
||||
} |
||||
|
||||
// assign to each observable the number of its last merge step
|
||||
// beware: indices of observables in merge start at 1 (R convention)
|
||||
std::vector<int> last_merge(n, 0); |
||||
for (k=1; k<=(n-nclust); k++) { |
||||
// (m1,m2) = merge[k,]
|
||||
m1 = merge[k-1]; |
||||
m2 = merge[n-1+k-1]; |
||||
if (m1 < 0 && m2 < 0) { // both single observables
|
||||
last_merge[-m1-1] = last_merge[-m2-1] = k; |
||||
} |
||||
else if (m1 < 0 || m2 < 0) { // one is a cluster
|
||||
if(m1 < 0) { j = -m1; m1 = m2; } else j = -m2; |
||||
// merging single observable and cluster
|
||||
for(l = 0; l < n; l++) |
||||
if (last_merge[l] == m1) |
||||
last_merge[l] = k; |
||||
last_merge[j-1] = k; |
||||
} |
||||
else { // both cluster
|
||||
for(l=0; l < n; l++) { |
||||
if( last_merge[l] == m1 || last_merge[l] == m2 ) |
||||
last_merge[l] = k; |
||||
} |
||||
} |
||||
} |
||||
|
||||
// assign cluster labels
|
||||
int label = 0; |
||||
std::vector<int> z(n,-1); |
||||
for (j=0; j<n; j++) { |
||||
if (last_merge[j] == 0) { // still singleton
|
||||
labels[j] = label++; |
||||
} else { |
||||
if (z[last_merge[j]] < 0) { |
||||
z[last_merge[j]] = label++; |
||||
} |
||||
labels[j] = z[last_merge[j]]; |
||||
} |
||||
} |
||||
} |
||||
|
||||
//
|
||||
// Assigns cluster labels (0, ..., nclust-1) to the n points such
|
||||
// that the hierarchical clustering is stopped when cluster distance >= cdist
|
||||
//
|
||||
// Input arguments:
|
||||
// n = number of observables
|
||||
// merge = clustering result in R format
|
||||
// height = cluster distance at each merge step
|
||||
// cdist = cutoff cluster distance
|
||||
// Output arguments:
|
||||
// labels = allocated integer array of size n for result
|
||||
//
|
||||
void cutree_cdist(int n, const int* merge, double* height, double cdist, int* labels) { |
||||
|
||||
int k; |
||||
|
||||
for (k=0; k<(n-1); k++) { |
||||
if (height[k] >= cdist) { |
||||
break; |
||||
} |
||||
} |
||||
cutree_k(n, merge, n-k, labels); |
||||
} |
||||
|
||||
|
||||
//
|
||||
// Hierarchical clustering with one of Daniel Muellner's fast algorithms
|
||||
//
|
||||
// Input arguments:
|
||||
// n = number of observables
|
||||
// distmat = condensed distance matrix, i.e. an n*(n-1)/2 array representing
|
||||
// the upper triangle (without diagonal elements) of the distance
|
||||
// matrix, e.g. for n=4:
|
||||
// d00 d01 d02 d03
|
||||
// d10 d11 d12 d13 -> d01 d02 d03 d12 d13 d23
|
||||
// d20 d21 d22 d23
|
||||
// d30 d31 d32 d33
|
||||
// method = cluster metric (see enum method_code)
|
||||
// Output arguments:
|
||||
// merge = allocated (n-1)x2 matrix (2*(n-1) array) for storing result.
|
||||
// Result follows R hclust convention:
|
||||
// - observabe indices start with one
|
||||
// - merge[i][] contains the merged nodes in step i
|
||||
// - merge[i][j] is negative when the node is an atom
|
||||
// height = allocated (n-1) array with distances at each merge step
|
||||
// Return code:
|
||||
// 0 = ok
|
||||
// 1 = invalid method
|
||||
//
|
||||
int hclust_fast(int n, double* distmat, int method, int* merge, double* height) { |
||||
|
||||
// call appropriate culstering function
|
||||
cluster_result Z2(n-1); |
||||
if (method == HCLUST_METHOD_SINGLE) { |
||||
// single link
|
||||
MST_linkage_core(n, distmat, Z2); |
||||
} |
||||
else if (method == HCLUST_METHOD_COMPLETE) { |
||||
// complete link
|
||||
NN_chain_core<METHOD_METR_COMPLETE, t_float>(n, distmat, NULL, Z2); |
||||
} |
||||
else if (method == HCLUST_METHOD_AVERAGE) { |
||||
// best average distance
|
||||
double* members = new double[n]; |
||||
for (int i=0; i<n; i++) members[i] = 1; |
||||
NN_chain_core<METHOD_METR_AVERAGE, t_float>(n, distmat, members, Z2); |
||||
delete[] members; |
||||
} |
||||
else if (method == HCLUST_METHOD_MEDIAN) { |
||||
// best median distance (beware: O(n^3))
|
||||
generic_linkage<METHOD_METR_MEDIAN, t_float>(n, distmat, NULL, Z2); |
||||
} |
||||
else if (method == HCLUST_METHOD_CENTROID) { |
||||
// best centroid distance (beware: O(n^3))
|
||||
double* members = new double[n]; |
||||
for (int i=0; i<n; i++) members[i] = 1; |
||||
generic_linkage<METHOD_METR_CENTROID, t_float>(n, distmat, members, Z2); |
||||
delete[] members; |
||||
} |
||||
else { |
||||
return 1; |
||||
} |
||||
|
||||
int* order = new int[n]; |
||||
if (method == HCLUST_METHOD_MEDIAN || method == HCLUST_METHOD_CENTROID) { |
||||
generate_R_dendrogram<true>(merge, height, order, Z2, n); |
||||
} else { |
||||
generate_R_dendrogram<false>(merge, height, order, Z2, n); |
||||
} |
||||
delete[] order; // only needed for visualization
|
||||
|
||||
return 0; |
||||
} |
||||
|
||||
|
||||
// Build condensed distance matrix
|
||||
// Input arguments:
|
||||
// n = number of observables
|
||||
// m = dimension of observable
|
||||
// Output arguments:
|
||||
// out = allocated integer array of size n * (n - 1) / 2 for result
|
||||
void hclust_pdist(int n, int m, double* pts, double* out) { |
||||
int ii = 0; |
||||
for (int i = 0; i < n; i++){ |
||||
for (int j = i + 1; j < n; j++){ |
||||
// Compute euclidian distance
|
||||
double d = 0; |
||||
for (int k = 0; k < m; k ++){ |
||||
double error = pts[i * m + k] - pts[j * m + k]; |
||||
d += (error * error); |
||||
} |
||||
out[ii] = d;//sqrt(d);
|
||||
ii++; |
||||
} |
||||
} |
||||
} |
||||
|
||||
void cluster_points_centroid(int n, int m, double* pts, double dist, int* idx) { |
||||
double* pdist = new double[n * (n - 1) / 2]; |
||||
int* merge = new int[2 * (n - 1)]; |
||||
double* height = new double[n - 1]; |
||||
|
||||
hclust_pdist(n, m, pts, pdist); |
||||
hclust_fast(n, pdist, HCLUST_METHOD_CENTROID, merge, height); |
||||
cutree_cdist(n, merge, height, dist, idx); |
||||
|
||||
delete[] pdist; |
||||
delete[] merge; |
||||
delete[] height; |
||||
} |
||||
} |
@ -0,0 +1,77 @@ |
||||
//
|
||||
// C++ standalone verion of fastcluster by Daniel Muellner
|
||||
//
|
||||
// Copyright: Daniel Muellner, 2011
|
||||
// Christoph Dalitz, 2018
|
||||
// License: BSD style license
|
||||
// (see the file LICENSE for details)
|
||||
//
|
||||
|
||||
#ifndef fastclustercpp_H |
||||
#define fastclustercpp_H |
||||
|
||||
//
|
||||
// Assigns cluster labels (0, ..., nclust-1) to the n points such
|
||||
// that the cluster result is split into nclust clusters.
|
||||
//
|
||||
// Input arguments:
|
||||
// n = number of observables
|
||||
// merge = clustering result in R format
|
||||
// nclust = number of clusters
|
||||
// Output arguments:
|
||||
// labels = allocated integer array of size n for result
|
||||
//
|
||||
void cutree_k(int n, const int* merge, int nclust, int* labels); |
||||
|
||||
//
|
||||
// Assigns cluster labels (0, ..., nclust-1) to the n points such
|
||||
// that the hierarchical clsutering is stopped at cluster distance cdist
|
||||
//
|
||||
// Input arguments:
|
||||
// n = number of observables
|
||||
// merge = clustering result in R format
|
||||
// height = cluster distance at each merge step
|
||||
// cdist = cutoff cluster distance
|
||||
// Output arguments:
|
||||
// labels = allocated integer array of size n for result
|
||||
//
|
||||
void cutree_cdist(int n, const int* merge, double* height, double cdist, int* labels); |
||||
|
||||
//
|
||||
// Hierarchical clustering with one of Daniel Muellner's fast algorithms
|
||||
//
|
||||
// Input arguments:
|
||||
// n = number of observables
|
||||
// distmat = condensed distance matrix, i.e. an n*(n-1)/2 array representing
|
||||
// the upper triangle (without diagonal elements) of the distance
|
||||
// matrix, e.g. for n=4:
|
||||
// d00 d01 d02 d03
|
||||
// d10 d11 d12 d13 -> d01 d02 d03 d12 d13 d23
|
||||
// d20 d21 d22 d23
|
||||
// d30 d31 d32 d33
|
||||
// method = cluster metric (see enum method_code)
|
||||
// Output arguments:
|
||||
// merge = allocated (n-1)x2 matrix (2*(n-1) array) for storing result.
|
||||
// Result follows R hclust convention:
|
||||
// - observabe indices start with one
|
||||
// - merge[i][] contains the merged nodes in step i
|
||||
// - merge[i][j] is negative when the node is an atom
|
||||
// height = allocated (n-1) array with distances at each merge step
|
||||
// Return code:
|
||||
// 0 = ok
|
||||
// 1 = invalid method
|
||||
//
|
||||
int hclust_fast(int n, double* distmat, int method, int* merge, double* height); |
||||
enum hclust_fast_methods { |
||||
HCLUST_METHOD_SINGLE = 0, |
||||
HCLUST_METHOD_COMPLETE = 1, |
||||
HCLUST_METHOD_AVERAGE = 2, |
||||
HCLUST_METHOD_MEDIAN = 3, |
||||
HCLUST_METHOD_CENTROID = 5, |
||||
}; |
||||
|
||||
void hclust_pdist(int n, int m, double* pts, double* out); |
||||
void cluster_points_centroid(int n, int m, double* pts, double dist, int* idx); |
||||
|
||||
|
||||
#endif |
@ -0,0 +1,115 @@ |
||||
//
|
||||
// Excerpt from fastcluster_R.cpp
|
||||
//
|
||||
// Copyright: Daniel Müllner, 2011 <http://danifold.net>
|
||||
//
|
||||
|
||||
struct pos_node { |
||||
t_index pos; |
||||
int node; |
||||
}; |
||||
|
||||
void order_nodes(const int N, const int * const merge, const t_index * const node_size, int * const order) { |
||||
/* Parameters:
|
||||
N : number of data points |
||||
merge : (N-1)×2 array which specifies the node indices which are |
||||
merged in each step of the clustering procedure. |
||||
Negative entries -1...-N point to singleton nodes, while |
||||
positive entries 1...(N-1) point to nodes which are themselves |
||||
parents of other nodes. |
||||
node_size : array of node sizes - makes it easier |
||||
order : output array of size N |
||||
|
||||
Runtime: Θ(N) |
||||
*/ |
||||
auto_array_ptr<pos_node> queue(N/2); |
||||
|
||||
int parent; |
||||
int child; |
||||
t_index pos = 0; |
||||
|
||||
queue[0].pos = 0; |
||||
queue[0].node = N-2; |
||||
t_index idx = 1; |
||||
|
||||
do { |
||||
--idx; |
||||
pos = queue[idx].pos; |
||||
parent = queue[idx].node; |
||||
|
||||
// First child
|
||||
child = merge[parent]; |
||||
if (child<0) { // singleton node, write this into the 'order' array.
|
||||
order[pos] = -child; |
||||
++pos; |
||||
} |
||||
else { /* compound node: put it on top of the queue and decompose it
|
||||
in a later iteration. */ |
||||
queue[idx].pos = pos; |
||||
queue[idx].node = child-1; // convert index-1 based to index-0 based
|
||||
++idx; |
||||
pos += node_size[child-1]; |
||||
} |
||||
// Second child
|
||||
child = merge[parent+N-1]; |
||||
if (child<0) { |
||||
order[pos] = -child; |
||||
} |
||||
else { |
||||
queue[idx].pos = pos; |
||||
queue[idx].node = child-1; |
||||
++idx; |
||||
} |
||||
} while (idx>0); |
||||
} |
||||
|
||||
#define size_(r_) ( ((r_<N) ? 1 : node_size[r_-N]) ) |
||||
|
||||
template <const bool sorted> |
||||
void generate_R_dendrogram(int * const merge, double * const height, int * const order, cluster_result & Z2, const int N) { |
||||
// The array "nodes" is a union-find data structure for the cluster
|
||||
// identites (only needed for unsorted cluster_result input).
|
||||
union_find nodes(sorted ? 0 : N); |
||||
if (!sorted) { |
||||
std::stable_sort(Z2[0], Z2[N-1]); |
||||
} |
||||
|
||||
t_index node1, node2; |
||||
auto_array_ptr<t_index> node_size(N-1); |
||||
|
||||
for (t_index i=0; i<N-1; ++i) { |
||||
// Get two data points whose clusters are merged in step i.
|
||||
// Find the cluster identifiers for these points.
|
||||
if (sorted) { |
||||
node1 = Z2[i]->node1; |
||||
node2 = Z2[i]->node2; |
||||
} |
||||
else { |
||||
node1 = nodes.Find(Z2[i]->node1); |
||||
node2 = nodes.Find(Z2[i]->node2); |
||||
// Merge the nodes in the union-find data structure by making them
|
||||
// children of a new node.
|
||||
nodes.Union(node1, node2); |
||||
} |
||||
// Sort the nodes in the output array.
|
||||
if (node1>node2) { |
||||
t_index tmp = node1; |
||||
node1 = node2; |
||||
node2 = tmp; |
||||
} |
||||
/* Conversion between labeling conventions.
|
||||
Input: singleton nodes 0,...,N-1 |
||||
compound nodes N,...,2N-2 |
||||
Output: singleton nodes -1,...,-N |
||||
compound nodes 1,...,N |
||||
*/ |
||||
merge[i] = (node1<N) ? -static_cast<int>(node1)-1 |
||||
: static_cast<int>(node1)-N+1; |
||||
merge[i+N-1] = (node2<N) ? -static_cast<int>(node2)-1 |
||||
: static_cast<int>(node2)-N+1; |
||||
height[i] = Z2[i]->dist; |
||||
node_size[i] = size_(node1) + size_(node2); |
||||
} |
||||
|
||||
order_nodes(N, merge, node_size, order); |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,27 @@ |
||||
import os |
||||
import numpy as np |
||||
|
||||
from cffi import FFI |
||||
|
||||
cluster_dir = os.path.join(os.path.dirname(os.path.abspath(__file__))) |
||||
cluster_fn = os.path.join(cluster_dir, "libfastcluster.so") |
||||
|
||||
ffi = FFI() |
||||
ffi.cdef(""" |
||||
int hclust_fast(int n, double* distmat, int method, int* merge, double* height); |
||||
void cutree_cdist(int n, const int* merge, double* height, double cdist, int* labels); |
||||
void hclust_pdist(int n, int m, double* pts, double* out); |
||||
void cluster_points_centroid(int n, int m, double* pts, double dist, int* idx); |
||||
""") |
||||
|
||||
hclust = ffi.dlopen(cluster_fn) |
||||
|
||||
|
||||
def cluster_points_centroid(pts, dist): |
||||
pts = np.ascontiguousarray(pts, dtype=np.float64) |
||||
pts_ptr = ffi.cast("double *", pts.ctypes.data) |
||||
n, m = pts.shape |
||||
|
||||
labels_ptr = ffi.new("int[]", n) |
||||
hclust.cluster_points_centroid(n, m, pts_ptr, dist**2, labels_ptr) |
||||
return list(labels_ptr) |
@ -0,0 +1,35 @@ |
||||
#include <cassert> |
||||
|
||||
extern "C" { |
||||
#include "fastcluster.h" |
||||
} |
||||
|
||||
|
||||
int main(int argc, const char* argv[]){ |
||||
const int n = 11; |
||||
const int m = 3; |
||||
double* pts = new double[n*m]{59.26000137, -9.35999966, -5.42500019, |
||||
91.61999817, -0.31999999, -2.75, |
||||
31.38000031, 0.40000001, -0.2, |
||||
89.57999725, -8.07999992, -18.04999924, |
||||
53.42000122, 0.63999999, -0.175, |
||||
31.38000031, 0.47999999, -0.2, |
||||
36.33999939, 0.16, -0.2, |
||||
53.33999939, 0.95999998, -0.175, |
||||
59.26000137, -9.76000023, -5.44999981, |
||||
33.93999977, 0.40000001, -0.22499999, |
||||
106.74000092, -5.76000023, -18.04999924}; |
||||
|
||||
int * idx = new int[n]; |
||||
int * correct_idx = new int[n]{0, 1, 2, 3, 4, 2, 5, 4, 0, 5, 6}; |
||||
|
||||
cluster_points_centroid(n, m, pts, 2.5 * 2.5, idx); |
||||
|
||||
for (int i = 0; i < n; i++){ |
||||
assert(idx[i] == correct_idx[i]); |
||||
} |
||||
|
||||
delete[] idx; |
||||
delete[] correct_idx; |
||||
delete[] pts; |
||||
} |
@ -0,0 +1,82 @@ |
||||
from cereal import car |
||||
from common.numpy_fast import clip, interp |
||||
from selfdrive.config import Conversions as CV |
||||
|
||||
# kph |
||||
V_CRUISE_MAX = 144 |
||||
V_CRUISE_MIN = 8 |
||||
V_CRUISE_DELTA = 8 |
||||
V_CRUISE_ENABLE_MIN = 40 |
||||
|
||||
|
||||
class MPC_COST_LAT: |
||||
PATH = 1.0 |
||||
LANE = 3.0 |
||||
HEADING = 1.0 |
||||
STEER_RATE = 1.0 |
||||
|
||||
|
||||
class MPC_COST_LONG: |
||||
TTC = 5.0 |
||||
DISTANCE = 0.1 |
||||
ACCELERATION = 10.0 |
||||
JERK = 20.0 |
||||
|
||||
|
||||
class EventTypes: |
||||
ENABLE = 'enable' |
||||
PRE_ENABLE = 'preEnable' |
||||
NO_ENTRY = 'noEntry' |
||||
WARNING = 'warning' |
||||
USER_DISABLE = 'userDisable' |
||||
SOFT_DISABLE = 'softDisable' |
||||
IMMEDIATE_DISABLE = 'immediateDisable' |
||||
PERMANENT = 'permanent' |
||||
|
||||
|
||||
def create_event(name, types): |
||||
event = car.CarEvent.new_message() |
||||
event.name = name |
||||
for t in types: |
||||
setattr(event, t, True) |
||||
return event |
||||
|
||||
|
||||
def get_events(events, types): |
||||
out = [] |
||||
for e in events: |
||||
for t in types: |
||||
if getattr(e, t): |
||||
out.append(e.name) |
||||
return out |
||||
|
||||
|
||||
def rate_limit(new_value, last_value, dw_step, up_step): |
||||
return clip(new_value, last_value + dw_step, last_value + up_step) |
||||
|
||||
|
||||
def get_steer_max(CP, v_ego): |
||||
return interp(v_ego, CP.steerMaxBP, CP.steerMaxV) |
||||
|
||||
|
||||
def update_v_cruise(v_cruise_kph, buttonEvents, enabled): |
||||
# handle button presses. TODO: this should be in state_control, but a decelCruise press |
||||
# would have the effect of both enabling and changing speed is checked after the state transition |
||||
for b in buttonEvents: |
||||
if enabled and not b.pressed: |
||||
if b.type == "accelCruise": |
||||
v_cruise_kph += V_CRUISE_DELTA - (v_cruise_kph % V_CRUISE_DELTA) |
||||
elif b.type == "decelCruise": |
||||
v_cruise_kph -= V_CRUISE_DELTA - ((V_CRUISE_DELTA - v_cruise_kph) % V_CRUISE_DELTA) |
||||
v_cruise_kph = clip(v_cruise_kph, V_CRUISE_MIN, V_CRUISE_MAX) |
||||
|
||||
return v_cruise_kph |
||||
|
||||
|
||||
def initialize_v_cruise(v_ego, buttonEvents, v_cruise_last): |
||||
for b in buttonEvents: |
||||
# 250kph or above probably means we never had a set speed |
||||
if b.type == "accelCruise" and v_cruise_last < 250: |
||||
return v_cruise_last |
||||
|
||||
return int(round(clip(v_ego * CV.MS_TO_KPH, V_CRUISE_ENABLE_MIN, V_CRUISE_MAX))) |
@ -0,0 +1,252 @@ |
||||
import numpy as np |
||||
from common.realtime import DT_CTRL, DT_DMON |
||||
from selfdrive.controls.lib.drive_helpers import create_event, EventTypes as ET |
||||
from common.filter_simple import FirstOrderFilter |
||||
from common.stat_live import RunningStatFilter |
||||
|
||||
_AWARENESS_TIME = 100. # 1.6 minutes limit without user touching steering wheels make the car enter a terminal status |
||||
_AWARENESS_PRE_TIME_TILL_TERMINAL = 25. # a first alert is issued 25s before expiration |
||||
_AWARENESS_PROMPT_TIME_TILL_TERMINAL = 15. # a second alert is issued 15s before start decelerating the car |
||||
_DISTRACTED_TIME = 11. |
||||
_DISTRACTED_PRE_TIME_TILL_TERMINAL = 8. |
||||
_DISTRACTED_PROMPT_TIME_TILL_TERMINAL = 6. |
||||
|
||||
_FACE_THRESHOLD = 0.4 |
||||
_EYE_THRESHOLD = 0.6 |
||||
_BLINK_THRESHOLD = 0.5 # 0.225 |
||||
_BLINK_THRESHOLD_SLACK = 0.65 |
||||
_BLINK_THRESHOLD_STRICT = 0.5 |
||||
_PITCH_WEIGHT = 1.35 # 1.5 # pitch matters a lot more |
||||
_POSESTD_THRESHOLD = 0.14 |
||||
_METRIC_THRESHOLD = 0.4 |
||||
_METRIC_THRESHOLD_SLACK = 0.55 |
||||
_METRIC_THRESHOLD_STRICT = 0.4 |
||||
_PITCH_POS_ALLOWANCE = 0.12 # rad, to not be too sensitive on positive pitch |
||||
_PITCH_NATURAL_OFFSET = 0.02 # people don't seem to look straight when they drive relaxed, rather a bit up |
||||
_YAW_NATURAL_OFFSET = 0.08 # people don't seem to look straight when they drive relaxed, rather a bit to the right (center of car) |
||||
|
||||
_HI_STD_TIMEOUT = 2 |
||||
_DISTRACTED_FILTER_TS = 0.25 # 0.6Hz |
||||
|
||||
_POSE_CALIB_MIN_SPEED = 13 # 30 mph |
||||
_POSE_OFFSET_MIN_COUNT = 600 # valid data counts before calibration completes, 1 seg is 600 counts |
||||
_POSE_OFFSET_MAX_COUNT = 3600 # stop deweighting new data after 6 min, aka "short term memory" |
||||
|
||||
_RECOVERY_FACTOR_MAX = 5. # relative to minus step change |
||||
_RECOVERY_FACTOR_MIN = 1.25 # relative to minus step change |
||||
|
||||
MAX_TERMINAL_ALERTS = 3 # not allowed to engage after 3 terminal alerts |
||||
MAX_TERMINAL_DURATION = 3000 # 30s |
||||
|
||||
# model output refers to center of cropped image, so need to apply the x displacement offset |
||||
RESIZED_FOCAL = 320.0 |
||||
H, W, FULL_W = 320, 160, 426 |
||||
|
||||
class DistractedType(): |
||||
NOT_DISTRACTED = 0 |
||||
BAD_POSE = 1 |
||||
BAD_BLINK = 2 |
||||
|
||||
def face_orientation_from_net(angles_desc, pos_desc, rpy_calib): |
||||
# the output of these angles are in device frame |
||||
# so from driver's perspective, pitch is up and yaw is right |
||||
|
||||
pitch_net = angles_desc[0] |
||||
yaw_net = angles_desc[1] |
||||
roll_net = angles_desc[2] |
||||
|
||||
face_pixel_position = ((pos_desc[0] + .5)*W - W + FULL_W, (pos_desc[1]+.5)*H) |
||||
yaw_focal_angle = np.arctan2(face_pixel_position[0] - FULL_W//2, RESIZED_FOCAL) |
||||
pitch_focal_angle = np.arctan2(face_pixel_position[1] - H//2, RESIZED_FOCAL) |
||||
|
||||
roll = roll_net |
||||
pitch = pitch_net + pitch_focal_angle |
||||
yaw = -yaw_net + yaw_focal_angle |
||||
|
||||
# no calib for roll |
||||
pitch -= rpy_calib[1] |
||||
yaw -= rpy_calib[2] |
||||
return np.array([roll, pitch, yaw]) |
||||
|
||||
class DriverPose(): |
||||
def __init__(self): |
||||
self.yaw = 0. |
||||
self.pitch = 0. |
||||
self.roll = 0. |
||||
self.yaw_std = 0. |
||||
self.pitch_std = 0. |
||||
self.roll_std = 0. |
||||
self.pitch_offseter = RunningStatFilter(max_trackable=_POSE_OFFSET_MAX_COUNT) |
||||
self.yaw_offseter = RunningStatFilter(max_trackable=_POSE_OFFSET_MAX_COUNT) |
||||
self.low_std = True |
||||
self.cfactor = 1. |
||||
|
||||
class DriverBlink(): |
||||
def __init__(self): |
||||
self.left_blink = 0. |
||||
self.right_blink = 0. |
||||
self.cfactor = 1. |
||||
|
||||
class DriverStatus(): |
||||
def __init__(self): |
||||
self.pose = DriverPose() |
||||
self.pose_calibrated = self.pose.pitch_offseter.filtered_stat.n > _POSE_OFFSET_MIN_COUNT and \ |
||||
self.pose.yaw_offseter.filtered_stat.n > _POSE_OFFSET_MIN_COUNT |
||||
self.blink = DriverBlink() |
||||
self.awareness = 1. |
||||
self.awareness_active = 1. |
||||
self.awareness_passive = 1. |
||||
self.driver_distracted = False |
||||
self.driver_distraction_filter = FirstOrderFilter(0., _DISTRACTED_FILTER_TS, DT_DMON) |
||||
self.face_detected = False |
||||
self.terminal_alert_cnt = 0 |
||||
self.terminal_time = 0 |
||||
self.step_change = 0. |
||||
self.active_monitoring_mode = True |
||||
self.hi_stds = 0 |
||||
self.threshold_prompt = _DISTRACTED_PROMPT_TIME_TILL_TERMINAL / _DISTRACTED_TIME |
||||
|
||||
self.is_rhd_region = False |
||||
self.is_rhd_region_checked = False |
||||
|
||||
self._set_timers(active_monitoring=True) |
||||
|
||||
def _set_timers(self, active_monitoring): |
||||
if self.active_monitoring_mode and self.awareness <= self.threshold_prompt: |
||||
if active_monitoring: |
||||
self.step_change = DT_CTRL / _DISTRACTED_TIME |
||||
else: |
||||
self.step_change = 0. |
||||
return # no exploit after orange alert |
||||
elif self.awareness <= 0.: |
||||
return |
||||
|
||||
if active_monitoring: |
||||
# when falling back from passive mode to active mode, reset awareness to avoid false alert |
||||
if not self.active_monitoring_mode: |
||||
self.awareness_passive = self.awareness |
||||
self.awareness = self.awareness_active |
||||
|
||||
self.threshold_pre = _DISTRACTED_PRE_TIME_TILL_TERMINAL / _DISTRACTED_TIME |
||||
self.threshold_prompt = _DISTRACTED_PROMPT_TIME_TILL_TERMINAL / _DISTRACTED_TIME |
||||
self.step_change = DT_CTRL / _DISTRACTED_TIME |
||||
self.active_monitoring_mode = True |
||||
else: |
||||
if self.active_monitoring_mode: |
||||
self.awareness_active = self.awareness |
||||
self.awareness = self.awareness_passive |
||||
|
||||
self.threshold_pre = _AWARENESS_PRE_TIME_TILL_TERMINAL / _AWARENESS_TIME |
||||
self.threshold_prompt = _AWARENESS_PROMPT_TIME_TILL_TERMINAL / _AWARENESS_TIME |
||||
self.step_change = DT_CTRL / _AWARENESS_TIME |
||||
self.active_monitoring_mode = False |
||||
|
||||
def _is_driver_distracted(self, pose, blink): |
||||
if not self.pose_calibrated: |
||||
pitch_error = pose.pitch - _PITCH_NATURAL_OFFSET |
||||
yaw_error = pose.yaw - _YAW_NATURAL_OFFSET |
||||
else: |
||||
pitch_error = pose.pitch - self.pose.pitch_offseter.filtered_stat.mean() |
||||
yaw_error = pose.yaw - self.pose.yaw_offseter.filtered_stat.mean() |
||||
|
||||
# positive pitch allowance |
||||
if pitch_error > 0.: |
||||
pitch_error = max(pitch_error - _PITCH_POS_ALLOWANCE, 0.) |
||||
pitch_error *= _PITCH_WEIGHT |
||||
pose_metric = np.sqrt(yaw_error**2 + pitch_error**2) |
||||
|
||||
if pose_metric > _METRIC_THRESHOLD*pose.cfactor: |
||||
return DistractedType.BAD_POSE |
||||
elif (blink.left_blink + blink.right_blink)*0.5 > _BLINK_THRESHOLD*blink.cfactor: |
||||
return DistractedType.BAD_BLINK |
||||
else: |
||||
return DistractedType.NOT_DISTRACTED |
||||
|
||||
def set_policy(self, model_data): |
||||
ep = min(model_data.meta.engagedProb, 0.8) / 0.8 |
||||
self.pose.cfactor = np.interp(ep, [0, 0.5, 1], [_METRIC_THRESHOLD_STRICT, _METRIC_THRESHOLD, _METRIC_THRESHOLD_SLACK])/_METRIC_THRESHOLD |
||||
self.blink.cfactor = np.interp(ep, [0, 0.5, 1], [_BLINK_THRESHOLD_STRICT, _BLINK_THRESHOLD, _BLINK_THRESHOLD_SLACK])/_BLINK_THRESHOLD |
||||
|
||||
def get_pose(self, driver_monitoring, cal_rpy, car_speed, op_engaged): |
||||
# 10 Hz |
||||
if len(driver_monitoring.faceOrientation) == 0 or len(driver_monitoring.facePosition) == 0 or len(driver_monitoring.faceOrientationStd) == 0 or len(driver_monitoring.facePositionStd) == 0: |
||||
return |
||||
|
||||
self.pose.roll, self.pose.pitch, self.pose.yaw = face_orientation_from_net(driver_monitoring.faceOrientation, driver_monitoring.facePosition, cal_rpy) |
||||
self.pose.pitch_std = driver_monitoring.faceOrientationStd[0] |
||||
self.pose.yaw_std = driver_monitoring.faceOrientationStd[1] |
||||
# self.pose.roll_std = driver_monitoring.faceOrientationStd[2] |
||||
max_std = np.max([self.pose.pitch_std, self.pose.yaw_std]) |
||||
self.pose.low_std = max_std < _POSESTD_THRESHOLD |
||||
self.blink.left_blink = driver_monitoring.leftBlinkProb * (driver_monitoring.leftEyeProb>_EYE_THRESHOLD) |
||||
self.blink.right_blink = driver_monitoring.rightBlinkProb * (driver_monitoring.rightEyeProb>_EYE_THRESHOLD) |
||||
self.face_detected = driver_monitoring.faceProb > _FACE_THRESHOLD and \ |
||||
abs(driver_monitoring.facePosition[0]) <= 0.4 and abs(driver_monitoring.facePosition[1]) <= 0.45 and \ |
||||
not self.is_rhd_region |
||||
|
||||
self.driver_distracted = self._is_driver_distracted(self.pose, self.blink) > 0 |
||||
# first order filters |
||||
self.driver_distraction_filter.update(self.driver_distracted) |
||||
|
||||
# update offseter |
||||
# only update when driver is actively driving the car above a certain speed |
||||
if self.face_detected and car_speed>_POSE_CALIB_MIN_SPEED and self.pose.low_std and (not op_engaged or not self.driver_distracted): |
||||
self.pose.pitch_offseter.push_and_update(self.pose.pitch) |
||||
self.pose.yaw_offseter.push_and_update(self.pose.yaw) |
||||
|
||||
self.pose_calibrated = self.pose.pitch_offseter.filtered_stat.n > _POSE_OFFSET_MIN_COUNT and \ |
||||
self.pose.yaw_offseter.filtered_stat.n > _POSE_OFFSET_MIN_COUNT |
||||
|
||||
self._set_timers(self.face_detected) |
||||
if self.face_detected and not self.pose.low_std: |
||||
self.step_change *= max(0, (max_std-0.5)*(max_std-2)) |
||||
self.hi_stds += 1 |
||||
elif self.face_detected and self.pose.low_std: |
||||
self.hi_stds = 0 |
||||
|
||||
def update(self, events, driver_engaged, ctrl_active, standstill): |
||||
if (driver_engaged and self.awareness > 0) or not ctrl_active: |
||||
# reset only when on disengagement if red reached |
||||
self.awareness = 1. |
||||
self.awareness_active = 1. |
||||
self.awareness_passive = 1. |
||||
return events |
||||
|
||||
driver_attentive = self.driver_distraction_filter.x < 0.37 |
||||
awareness_prev = self.awareness |
||||
|
||||
if self.face_detected and self.hi_stds * DT_DMON > _HI_STD_TIMEOUT: |
||||
events.append(create_event('driverMonitorLowAcc', [ET.WARNING])) |
||||
|
||||
if (driver_attentive and self.face_detected and self.awareness > 0): |
||||
# only restore awareness when paying attention and alert is not red |
||||
self.awareness = min(self.awareness + ((_RECOVERY_FACTOR_MAX-_RECOVERY_FACTOR_MIN)*(1.-self.awareness)+_RECOVERY_FACTOR_MIN)*self.step_change, 1.) |
||||
if self.awareness == 1.: |
||||
self.awareness_passive = min(self.awareness_passive + self.step_change, 1.) |
||||
# don't display alert banner when awareness is recovering and has cleared orange |
||||
if self.awareness > self.threshold_prompt: |
||||
return events |
||||
|
||||
# should always be counting if distracted unless at standstill and reaching orange |
||||
if (not self.face_detected or (self.driver_distraction_filter.x > 0.63 and self.driver_distracted and self.face_detected)) and \ |
||||
not (standstill and self.awareness - self.step_change <= self.threshold_prompt): |
||||
self.awareness = max(self.awareness - self.step_change, -0.1) |
||||
|
||||
alert = None |
||||
if self.awareness <= 0.: |
||||
# terminal red alert: disengagement required |
||||
alert = 'driverDistracted' if self.active_monitoring_mode else 'driverUnresponsive' |
||||
self.terminal_time += 1 |
||||
if awareness_prev > 0.: |
||||
self.terminal_alert_cnt += 1 |
||||
elif self.awareness <= self.threshold_prompt: |
||||
# prompt orange alert |
||||
alert = 'promptDriverDistracted' if self.active_monitoring_mode else 'promptDriverUnresponsive' |
||||
elif self.awareness <= self.threshold_pre: |
||||
# pre green alert |
||||
alert = 'preDriverDistracted' if self.active_monitoring_mode else 'preDriverUnresponsive' |
||||
|
||||
if alert is not None: |
||||
events.append(create_event(alert, [ET.WARNING])) |
||||
|
||||
return events |
@ -0,0 +1,77 @@ |
||||
import numpy as np |
||||
from collections import defaultdict |
||||
|
||||
from common.numpy_fast import interp |
||||
|
||||
_FCW_A_ACT_V = [-3., -2.] |
||||
_FCW_A_ACT_BP = [0., 30.] |
||||
|
||||
|
||||
class FCWChecker(): |
||||
def __init__(self): |
||||
self.reset_lead(0.0) |
||||
self.common_counters = defaultdict(lambda: 0) |
||||
|
||||
def reset_lead(self, cur_time): |
||||
self.last_fcw_a = 0.0 |
||||
self.v_lead_max = 0.0 |
||||
self.lead_seen_t = cur_time |
||||
self.last_fcw_time = 0.0 |
||||
self.last_min_a = 0.0 |
||||
|
||||
self.counters = defaultdict(lambda: 0) |
||||
|
||||
@staticmethod |
||||
def calc_ttc(v_ego, a_ego, x_lead, v_lead, a_lead): |
||||
max_ttc = 5.0 |
||||
|
||||
v_rel = v_ego - v_lead |
||||
a_rel = a_ego - a_lead |
||||
|
||||
# assuming that closing gap ARel comes from lead vehicle decel, |
||||
# then limit ARel so that v_lead will get to zero in no sooner than t_decel. |
||||
# This helps underweighting ARel when v_lead is close to zero. |
||||
t_decel = 2. |
||||
a_rel = np.minimum(a_rel, v_lead / t_decel) |
||||
|
||||
# delta of the quadratic equation to solve for ttc |
||||
delta = v_rel**2 + 2 * x_lead * a_rel |
||||
|
||||
# assign an arbitrary high ttc value if there is no solution to ttc |
||||
if delta < 0.1 or (np.sqrt(delta) + v_rel < 0.1): |
||||
ttc = max_ttc |
||||
else: |
||||
ttc = np.minimum(2 * x_lead / (np.sqrt(delta) + v_rel), max_ttc) |
||||
return ttc |
||||
|
||||
def update(self, mpc_solution, cur_time, active, v_ego, a_ego, x_lead, v_lead, a_lead, y_lead, vlat_lead, fcw_lead, blinkers): |
||||
mpc_solution_a = list(mpc_solution[0].a_ego) |
||||
|
||||
self.last_min_a = min(mpc_solution_a) |
||||
self.v_lead_max = max(self.v_lead_max, v_lead) |
||||
|
||||
self.common_counters['blinkers'] = self.common_counters['blinkers'] + 10.0 / (20 * 3.0) if not blinkers else 0 |
||||
self.common_counters['v_ego'] = self.common_counters['v_ego'] + 1 if v_ego > 5.0 else 0 |
||||
|
||||
if (fcw_lead > 0.99): |
||||
ttc = self.calc_ttc(v_ego, a_ego, x_lead, v_lead, a_lead) |
||||
self.counters['ttc'] = self.counters['ttc'] + 1 if ttc < 2.5 else 0 |
||||
self.counters['v_lead_max'] = self.counters['v_lead_max'] + 1 if self.v_lead_max > 2.5 else 0 |
||||
self.counters['v_ego_lead'] = self.counters['v_ego_lead'] + 1 if v_ego > v_lead else 0 |
||||
self.counters['lead_seen'] = self.counters['lead_seen'] + 0.33 |
||||
self.counters['y_lead'] = self.counters['y_lead'] + 1 if abs(y_lead) < 1.0 else 0 |
||||
self.counters['vlat_lead'] = self.counters['vlat_lead'] + 1 if abs(vlat_lead) < 0.4 else 0 |
||||
|
||||
a_thr = interp(v_lead, _FCW_A_ACT_BP, _FCW_A_ACT_V) |
||||
a_delta = min(mpc_solution_a[:15]) - min(0.0, a_ego) |
||||
|
||||
future_fcw_allowed = all(c >= 10 for c in self.counters.values()) |
||||
future_fcw_allowed = future_fcw_allowed and all(c >= 10 for c in self.common_counters.values()) |
||||
future_fcw = (self.last_min_a < -3.0 or a_delta < a_thr) and future_fcw_allowed |
||||
|
||||
if future_fcw and (self.last_fcw_time + 5.0 < cur_time): |
||||
self.last_fcw_time = cur_time |
||||
self.last_fcw_a = self.last_min_a |
||||
return True |
||||
|
||||
return False |
@ -0,0 +1,73 @@ |
||||
#!/usr/bin/env python3 |
||||
import os |
||||
import json |
||||
import numpy as np |
||||
from common.basedir import BASEDIR |
||||
from common.realtime import sec_since_boot |
||||
from shapely.geometry import Point, Polygon |
||||
|
||||
LATITUDE_DEG_TO_M = 111133 # ~111km per deg is the mean between equator and poles |
||||
GEOFENCE_THRESHOLD = 8. |
||||
LOC_FILTER_F = 0.5 # 0.5Hz |
||||
DT = 0.1 # external gps packets are at 10Hz |
||||
LOC_FILTER_K = 2 * np.pi * LOC_FILTER_F * DT / (1 + 2 * np.pi * LOC_FILTER_F * DT) |
||||
|
||||
class Geofence(): |
||||
def __init__(self, active): |
||||
self.lat_filt = None |
||||
self.lon_filt = None |
||||
self.ts_last_check = 0. |
||||
self.active = active |
||||
# hack: does not work at north/south poles, and when longitude is ~180 |
||||
self.in_geofence = not active # initialize false if geofence is active |
||||
# get full geofenced polygon in lat and lon coordinates |
||||
geofence_polygon = np.load(os.path.join(BASEDIR, 'selfdrive/controls/geofence_routes/press_demo.npy')) |
||||
# for small latitude variations, we can assume constant conversion between longitude and meters (use the first point) |
||||
self.longitude_deg_to_m = LATITUDE_DEG_TO_M * np.cos(np.radians(geofence_polygon[0,0])) |
||||
# convert to m |
||||
geofence_polygon_m = geofence_polygon * LATITUDE_DEG_TO_M |
||||
geofence_polygon_m[:, 1] = geofence_polygon[:,1] * self.longitude_deg_to_m |
||||
self.geofence_polygon = Polygon(geofence_polygon_m) |
||||
|
||||
|
||||
def update_geofence_status(self, gps_loc, params): |
||||
|
||||
if self.lat_filt is None: |
||||
# first time we get a location packet |
||||
self.latitude = gps_loc.latitude |
||||
self.longitude = gps_loc.longitude |
||||
else: |
||||
# apply a filter |
||||
self.latitude = LOC_FILTER_K * gps_loc.latitude + (1. - LOC_FILTER_K) * self.latitude |
||||
self.longitude = LOC_FILTER_K * gps_loc.longitude + (1. - LOC_FILTER_K) * self.longitude |
||||
|
||||
ts = sec_since_boot() |
||||
|
||||
if ts - self.ts_last_check > 1.: # tun check at 1Hz, since is computationally intense |
||||
self.active = params.get("IsGeofenceEnabled") == "1" |
||||
self.ts_last_check = ts |
||||
|
||||
p = Point([self.latitude * LATITUDE_DEG_TO_M, self.longitude * self.longitude_deg_to_m]) |
||||
|
||||
# histeresys |
||||
geofence_distance = self.geofence_polygon.distance(p) |
||||
if self.in_geofence and geofence_distance > GEOFENCE_THRESHOLD and self.active: |
||||
self.in_geofence = False |
||||
elif (not self.in_geofence and geofence_distance < 1.) or not self.active: |
||||
self.in_geofence = True |
||||
|
||||
|
||||
if __name__ == "__main__": |
||||
from common.params import Params |
||||
# for tests |
||||
params = Params() |
||||
gf = Geofence(True) |
||||
class GpsPos(): |
||||
def __init__(self, lat, lon): |
||||
self.latitude = lat |
||||
self.longitude = lon |
||||
|
||||
#pos = GpsPos(37.687347, -122.471117) # True |
||||
pos = GpsPos(37.687347, -122.571117) # False |
||||
gf.update_geofence_status(pos, params) |
||||
print(gf.in_geofence) |
@ -0,0 +1,18 @@ |
||||
_RHD_REGION_MAP = [ ['AUS', -54.76, -9.23, 112.91, 159.11], \ |
||||
['IN1', 6.75, 28.10, 68.17, 97.4], \ |
||||
['IN2', 28.09, 35.99, 72.18, 80.87], \ |
||||
['IRL', 51.42, 55.38, -10.58, -5.99], \ |
||||
['JP1', 32.66, 45.52, 137.27, 146.02], \ |
||||
['JP2', 32.79, 37.60, 131.41, 137.28], \ |
||||
['JP3', 24.04, 34.78, 122.93, 131.42], \ |
||||
['MY', 0.86, 7.36, 99.64, 119.27], \ |
||||
['NZ', -52.61, -29.24, 166, 178.84], \ |
||||
['SF', -35.14, -22.13, 16.07, 33.21], \ |
||||
['UK', 49.9, 60.84, -8.62, 1.77] ] |
||||
|
||||
def is_rhd_region(latitude, longitude): |
||||
for region in _RHD_REGION_MAP: |
||||
if region[1] <= latitude <= region[2] and \ |
||||
region[3] <= longitude <= region[4]: |
||||
return True |
||||
return False |
@ -0,0 +1,89 @@ |
||||
from common.numpy_fast import interp |
||||
import numpy as np |
||||
from cereal import log |
||||
|
||||
CAMERA_OFFSET = 0.06 # m from center car to camera |
||||
|
||||
def compute_path_pinv(l=50): |
||||
deg = 3 |
||||
x = np.arange(l*1.0) |
||||
X = np.vstack(tuple(x**n for n in range(deg, -1, -1))).T |
||||
pinv = np.linalg.pinv(X) |
||||
return pinv |
||||
|
||||
|
||||
def model_polyfit(points, path_pinv): |
||||
return np.dot(path_pinv, [float(x) for x in points]) |
||||
|
||||
|
||||
def calc_d_poly(l_poly, r_poly, p_poly, l_prob, r_prob, lane_width): |
||||
# This will improve behaviour when lanes suddenly widen |
||||
lane_width = min(4.0, lane_width) |
||||
l_prob = l_prob * interp(abs(l_poly[3]), [2, 2.5], [1.0, 0.0]) |
||||
r_prob = r_prob * interp(abs(r_poly[3]), [2, 2.5], [1.0, 0.0]) |
||||
|
||||
path_from_left_lane = l_poly.copy() |
||||
path_from_left_lane[3] -= lane_width / 2.0 |
||||
path_from_right_lane = r_poly.copy() |
||||
path_from_right_lane[3] += lane_width / 2.0 |
||||
|
||||
lr_prob = l_prob + r_prob - l_prob * r_prob |
||||
|
||||
d_poly_lane = (l_prob * path_from_left_lane + r_prob * path_from_right_lane) / (l_prob + r_prob + 0.0001) |
||||
return lr_prob * d_poly_lane + (1.0 - lr_prob) * p_poly |
||||
|
||||
|
||||
class LanePlanner(): |
||||
def __init__(self): |
||||
self.l_poly = [0., 0., 0., 0.] |
||||
self.r_poly = [0., 0., 0., 0.] |
||||
self.p_poly = [0., 0., 0., 0.] |
||||
self.d_poly = [0., 0., 0., 0.] |
||||
|
||||
self.lane_width_estimate = 3.7 |
||||
self.lane_width_certainty = 1.0 |
||||
self.lane_width = 3.7 |
||||
|
||||
self.l_prob = 0. |
||||
self.r_prob = 0. |
||||
|
||||
self.l_lane_change_prob = 0. |
||||
self.r_lane_change_prob = 0. |
||||
|
||||
self._path_pinv = compute_path_pinv() |
||||
self.x_points = np.arange(50) |
||||
|
||||
def parse_model(self, md): |
||||
if len(md.leftLane.poly): |
||||
self.l_poly = np.array(md.leftLane.poly) |
||||
self.r_poly = np.array(md.rightLane.poly) |
||||
self.p_poly = np.array(md.path.poly) |
||||
else: |
||||
self.l_poly = model_polyfit(md.leftLane.points, self._path_pinv) # left line |
||||
self.r_poly = model_polyfit(md.rightLane.points, self._path_pinv) # right line |
||||
self.p_poly = model_polyfit(md.path.points, self._path_pinv) # predicted path |
||||
self.l_prob = md.leftLane.prob # left line prob |
||||
self.r_prob = md.rightLane.prob # right line prob |
||||
|
||||
if len(md.meta.desirePrediction): |
||||
self.l_lane_change_prob = md.meta.desirePrediction[log.PathPlan.Desire.laneChangeLeft - 1] |
||||
self.r_lane_change_prob = md.meta.desirePrediction[log.PathPlan.Desire.laneChangeRight - 1] |
||||
|
||||
def update_d_poly(self, v_ego): |
||||
# only offset left and right lane lines; offsetting p_poly does not make sense |
||||
self.l_poly[3] += CAMERA_OFFSET |
||||
self.r_poly[3] += CAMERA_OFFSET |
||||
|
||||
# Find current lanewidth |
||||
self.lane_width_certainty += 0.05 * (self.l_prob * self.r_prob - self.lane_width_certainty) |
||||
current_lane_width = abs(self.l_poly[3] - self.r_poly[3]) |
||||
self.lane_width_estimate += 0.005 * (current_lane_width - self.lane_width_estimate) |
||||
speed_lane_width = interp(v_ego, [0., 31.], [2.8, 3.5]) |
||||
self.lane_width = self.lane_width_certainty * self.lane_width_estimate + \ |
||||
(1 - self.lane_width_certainty) * speed_lane_width |
||||
|
||||
self.d_poly = calc_d_poly(self.l_poly, self.r_poly, self.p_poly, self.l_prob, self.r_prob, self.lane_width) |
||||
|
||||
def update(self, v_ego, md): |
||||
self.parse_model(md) |
||||
self.update_d_poly(v_ego) |
@ -0,0 +1,122 @@ |
||||
import math |
||||
import numpy as np |
||||
|
||||
from cereal import log |
||||
from common.realtime import DT_CTRL |
||||
from common.numpy_fast import clip |
||||
from selfdrive.car.toyota.values import SteerLimitParams |
||||
from selfdrive.car import apply_toyota_steer_torque_limits |
||||
from selfdrive.controls.lib.drive_helpers import get_steer_max |
||||
|
||||
|
||||
class LatControlINDI(): |
||||
def __init__(self, CP): |
||||
self.angle_steers_des = 0. |
||||
|
||||
A = np.matrix([[1.0, DT_CTRL, 0.0], |
||||
[0.0, 1.0, DT_CTRL], |
||||
[0.0, 0.0, 1.0]]) |
||||
C = np.matrix([[1.0, 0.0, 0.0], |
||||
[0.0, 1.0, 0.0]]) |
||||
|
||||
# Q = np.matrix([[1e-2, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 10.0]]) |
||||
# R = np.matrix([[1e-2, 0.0], [0.0, 1e3]]) |
||||
|
||||
# (x, l, K) = control.dare(np.transpose(A), np.transpose(C), Q, R) |
||||
# K = np.transpose(K) |
||||
K = np.matrix([[7.30262179e-01, 2.07003658e-04], |
||||
[7.29394177e+00, 1.39159419e-02], |
||||
[1.71022442e+01, 3.38495381e-02]]) |
||||
|
||||
self.K = K |
||||
self.A_K = A - np.dot(K, C) |
||||
self.x = np.matrix([[0.], [0.], [0.]]) |
||||
|
||||
self.enforce_rate_limit = CP.carName == "toyota" |
||||
|
||||
self.RC = CP.lateralTuning.indi.timeConstant |
||||
self.G = CP.lateralTuning.indi.actuatorEffectiveness |
||||
self.outer_loop_gain = CP.lateralTuning.indi.outerLoopGain |
||||
self.inner_loop_gain = CP.lateralTuning.indi.innerLoopGain |
||||
self.alpha = 1. - DT_CTRL / (self.RC + DT_CTRL) |
||||
|
||||
self.sat_count_rate = 1.0 * DT_CTRL |
||||
self.sat_limit = CP.steerLimitTimer |
||||
|
||||
self.reset() |
||||
|
||||
def reset(self): |
||||
self.delayed_output = 0. |
||||
self.output_steer = 0. |
||||
self.sat_count = 0.0 |
||||
|
||||
def _check_saturation(self, control, check_saturation, limit): |
||||
saturated = abs(control) == limit |
||||
|
||||
if saturated and check_saturation: |
||||
self.sat_count += self.sat_count_rate |
||||
else: |
||||
self.sat_count -= self.sat_count_rate |
||||
|
||||
self.sat_count = clip(self.sat_count, 0.0, 1.0) |
||||
|
||||
return self.sat_count > self.sat_limit |
||||
|
||||
def update(self, active, v_ego, angle_steers, angle_steers_rate, eps_torque, steer_override, rate_limited, CP, path_plan): |
||||
# Update Kalman filter |
||||
y = np.matrix([[math.radians(angle_steers)], [math.radians(angle_steers_rate)]]) |
||||
self.x = np.dot(self.A_K, self.x) + np.dot(self.K, y) |
||||
|
||||
indi_log = log.ControlsState.LateralINDIState.new_message() |
||||
indi_log.steerAngle = math.degrees(self.x[0]) |
||||
indi_log.steerRate = math.degrees(self.x[1]) |
||||
indi_log.steerAccel = math.degrees(self.x[2]) |
||||
|
||||
if v_ego < 0.3 or not active: |
||||
indi_log.active = False |
||||
self.output_steer = 0.0 |
||||
self.delayed_output = 0.0 |
||||
else: |
||||
self.angle_steers_des = path_plan.angleSteers |
||||
self.rate_steers_des = path_plan.rateSteers |
||||
|
||||
steers_des = math.radians(self.angle_steers_des) |
||||
rate_des = math.radians(self.rate_steers_des) |
||||
|
||||
# Expected actuator value |
||||
self.delayed_output = self.delayed_output * self.alpha + self.output_steer * (1. - self.alpha) |
||||
|
||||
# Compute acceleration error |
||||
rate_sp = self.outer_loop_gain * (steers_des - self.x[0]) + rate_des |
||||
accel_sp = self.inner_loop_gain * (rate_sp - self.x[1]) |
||||
accel_error = accel_sp - self.x[2] |
||||
|
||||
# Compute change in actuator |
||||
g_inv = 1. / self.G |
||||
delta_u = g_inv * accel_error |
||||
|
||||
# Enforce rate limit |
||||
if self.enforce_rate_limit: |
||||
steer_max = float(SteerLimitParams.STEER_MAX) |
||||
new_output_steer_cmd = steer_max * (self.delayed_output + delta_u) |
||||
prev_output_steer_cmd = steer_max * self.output_steer |
||||
new_output_steer_cmd = apply_toyota_steer_torque_limits(new_output_steer_cmd, prev_output_steer_cmd, prev_output_steer_cmd, SteerLimitParams) |
||||
self.output_steer = new_output_steer_cmd / steer_max |
||||
else: |
||||
self.output_steer = self.delayed_output + delta_u |
||||
|
||||
steers_max = get_steer_max(CP, v_ego) |
||||
self.output_steer = clip(self.output_steer, -steers_max, steers_max) |
||||
|
||||
indi_log.active = True |
||||
indi_log.rateSetPoint = float(rate_sp) |
||||
indi_log.accelSetPoint = float(accel_sp) |
||||
indi_log.accelError = float(accel_error) |
||||
indi_log.delayedOutput = float(self.delayed_output) |
||||
indi_log.delta = float(delta_u) |
||||
indi_log.output = float(self.output_steer) |
||||
|
||||
check_saturation = (v_ego > 10.) and not rate_limited and not steer_override |
||||
indi_log.saturated = self._check_saturation(self.output_steer, check_saturation, steers_max) |
||||
|
||||
return float(self.output_steer), float(self.angle_steers_des), indi_log |
@ -0,0 +1,95 @@ |
||||
import numpy as np |
||||
from selfdrive.controls.lib.drive_helpers import get_steer_max |
||||
from common.numpy_fast import clip |
||||
from common.realtime import DT_CTRL |
||||
from cereal import log |
||||
|
||||
|
||||
class LatControlLQR(): |
||||
def __init__(self, CP): |
||||
self.scale = CP.lateralTuning.lqr.scale |
||||
self.ki = CP.lateralTuning.lqr.ki |
||||
|
||||
self.A = np.array(CP.lateralTuning.lqr.a).reshape((2,2)) |
||||
self.B = np.array(CP.lateralTuning.lqr.b).reshape((2,1)) |
||||
self.C = np.array(CP.lateralTuning.lqr.c).reshape((1,2)) |
||||
self.K = np.array(CP.lateralTuning.lqr.k).reshape((1,2)) |
||||
self.L = np.array(CP.lateralTuning.lqr.l).reshape((2,1)) |
||||
self.dc_gain = CP.lateralTuning.lqr.dcGain |
||||
|
||||
self.x_hat = np.array([[0], [0]]) |
||||
self.i_unwind_rate = 0.3 * DT_CTRL |
||||
self.i_rate = 1.0 * DT_CTRL |
||||
|
||||
self.sat_count_rate = 1.0 * DT_CTRL |
||||
self.sat_limit = CP.steerLimitTimer |
||||
|
||||
self.reset() |
||||
|
||||
def reset(self): |
||||
self.i_lqr = 0.0 |
||||
self.output_steer = 0.0 |
||||
self.sat_count = 0.0 |
||||
|
||||
def _check_saturation(self, control, check_saturation, limit): |
||||
saturated = abs(control) == limit |
||||
|
||||
if saturated and check_saturation: |
||||
self.sat_count += self.sat_count_rate |
||||
else: |
||||
self.sat_count -= self.sat_count_rate |
||||
|
||||
self.sat_count = clip(self.sat_count, 0.0, 1.0) |
||||
|
||||
return self.sat_count > self.sat_limit |
||||
|
||||
def update(self, active, v_ego, angle_steers, angle_steers_rate, eps_torque, steer_override, rate_limited, CP, path_plan): |
||||
lqr_log = log.ControlsState.LateralLQRState.new_message() |
||||
|
||||
steers_max = get_steer_max(CP, v_ego) |
||||
torque_scale = (0.45 + v_ego / 60.0)**2 # Scale actuator model with speed |
||||
|
||||
# Subtract offset. Zero angle should correspond to zero torque |
||||
self.angle_steers_des = path_plan.angleSteers - path_plan.angleOffset |
||||
angle_steers -= path_plan.angleOffset |
||||
|
||||
# Update Kalman filter |
||||
angle_steers_k = float(self.C.dot(self.x_hat)) |
||||
e = angle_steers - angle_steers_k |
||||
self.x_hat = self.A.dot(self.x_hat) + self.B.dot(eps_torque / torque_scale) + self.L.dot(e) |
||||
|
||||
if v_ego < 0.3 or not active: |
||||
lqr_log.active = False |
||||
lqr_output = 0. |
||||
self.reset() |
||||
else: |
||||
lqr_log.active = True |
||||
|
||||
# LQR |
||||
u_lqr = float(self.angle_steers_des / self.dc_gain - self.K.dot(self.x_hat)) |
||||
lqr_output = torque_scale * u_lqr / self.scale |
||||
|
||||
# Integrator |
||||
if steer_override: |
||||
self.i_lqr -= self.i_unwind_rate * float(np.sign(self.i_lqr)) |
||||
else: |
||||
error = self.angle_steers_des - angle_steers_k |
||||
i = self.i_lqr + self.ki * self.i_rate * error |
||||
control = lqr_output + i |
||||
|
||||
if ((error >= 0 and (control <= steers_max or i < 0.0)) or \ |
||||
(error <= 0 and (control >= -steers_max or i > 0.0))): |
||||
self.i_lqr = i |
||||
|
||||
self.output_steer = lqr_output + self.i_lqr |
||||
self.output_steer = clip(self.output_steer, -steers_max, steers_max) |
||||
|
||||
check_saturation = (v_ego > 10) and not rate_limited and not steer_override |
||||
saturated = self._check_saturation(self.output_steer, check_saturation, steers_max) |
||||
|
||||
lqr_log.steerAngle = angle_steers_k + path_plan.angleOffset |
||||
lqr_log.i = self.i_lqr |
||||
lqr_log.output = self.output_steer |
||||
lqr_log.lqrOutput = lqr_output |
||||
lqr_log.saturated = saturated |
||||
return self.output_steer, float(self.angle_steers_des), lqr_log |
@ -0,0 +1,49 @@ |
||||
from selfdrive.controls.lib.pid import PIController |
||||
from selfdrive.controls.lib.drive_helpers import get_steer_max |
||||
from cereal import car |
||||
from cereal import log |
||||
|
||||
|
||||
class LatControlPID(): |
||||
def __init__(self, CP): |
||||
self.pid = PIController((CP.lateralTuning.pid.kpBP, CP.lateralTuning.pid.kpV), |
||||
(CP.lateralTuning.pid.kiBP, CP.lateralTuning.pid.kiV), |
||||
k_f=CP.lateralTuning.pid.kf, pos_limit=1.0, sat_limit=CP.steerLimitTimer) |
||||
self.angle_steers_des = 0. |
||||
|
||||
def reset(self): |
||||
self.pid.reset() |
||||
|
||||
def update(self, active, v_ego, angle_steers, angle_steers_rate, eps_torque, steer_override, rate_limited, CP, path_plan): |
||||
pid_log = log.ControlsState.LateralPIDState.new_message() |
||||
pid_log.steerAngle = float(angle_steers) |
||||
pid_log.steerRate = float(angle_steers_rate) |
||||
|
||||
if v_ego < 0.3 or not active: |
||||
output_steer = 0.0 |
||||
pid_log.active = False |
||||
self.pid.reset() |
||||
else: |
||||
self.angle_steers_des = path_plan.angleSteers # get from MPC/PathPlanner |
||||
|
||||
steers_max = get_steer_max(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 CP.steerControlType == car.CarParams.SteerControlType.torque: |
||||
# TODO: feedforward something based on path_plan.rateSteers |
||||
steer_feedforward -= path_plan.angleOffset # subtract the offset, since it does not contribute to resistive torque |
||||
steer_feedforward *= v_ego**2 # proportional to realigning tire momentum (~ lateral accel) |
||||
deadzone = 0.0 |
||||
|
||||
check_saturation = (v_ego > 10) and not rate_limited and not steer_override |
||||
output_steer = self.pid.update(self.angle_steers_des, angle_steers, check_saturation=check_saturation, override=steer_override, |
||||
feedforward=steer_feedforward, speed=v_ego, deadzone=deadzone) |
||||
pid_log.active = True |
||||
pid_log.p = self.pid.p |
||||
pid_log.i = self.pid.i |
||||
pid_log.f = self.pid.f |
||||
pid_log.output = output_steer |
||||
pid_log.saturated = bool(self.pid.saturated) |
||||
|
||||
return output_steer, float(self.angle_steers_des), pid_log |
@ -0,0 +1,2 @@ |
||||
generator |
||||
lib_qp/ |
@ -0,0 +1,29 @@ |
||||
Import('env', 'arch') |
||||
|
||||
cpp_path = [ |
||||
"#phonelibs/acado/include", |
||||
"#phonelibs/acado/include/acado", |
||||
"#phonelibs/qpoases/INCLUDE", |
||||
"#phonelibs/qpoases/INCLUDE/EXTRAS", |
||||
"#phonelibs/qpoases/SRC/", |
||||
"#phonelibs/qpoases", |
||||
"lib_mpc_export" |
||||
|
||||
] |
||||
|
||||
mpc_files = [ |
||||
"lateral_mpc.c", |
||||
Glob("lib_mpc_export/*.c"), |
||||
Glob("lib_mpc_export/*.cpp"), |
||||
] |
||||
|
||||
interface_dir = Dir('lib_mpc_export') |
||||
|
||||
SConscript(['#phonelibs/qpoases/SConscript'], variant_dir='lib_qp', exports=['interface_dir']) |
||||
|
||||
env.SharedLibrary('mpc', mpc_files, LIBS=['m', 'qpoases'], LIBPATH=['lib_qp'], CPPPATH=cpp_path) |
||||
# if arch != "aarch64": |
||||
# acado_libs = [File("#phonelibs/acado/x64/lib/libacado_toolkit.a"), |
||||
# File("#phonelibs/acado/x64/lib/libacado_casadi.a"), |
||||
# File("#phonelibs/acado/x64/lib/libacado_csparse.a")] |
||||
# env.Program('generator', 'generator.cpp', LIBS=acado_libs, CPPPATH=cpp_path) |
@ -0,0 +1,139 @@ |
||||
#include <acado_code_generation.hpp> |
||||
|
||||
#define PI 3.1415926536 |
||||
#define deg2rad(d) (d/180.0*PI) |
||||
|
||||
const int controlHorizon = 50; |
||||
|
||||
using namespace std; |
||||
|
||||
int main( ) |
||||
{ |
||||
USING_NAMESPACE_ACADO |
||||
|
||||
|
||||
DifferentialEquation f; |
||||
|
||||
DifferentialState xx; // x position
|
||||
DifferentialState yy; // y position
|
||||
DifferentialState psi; // vehicle heading
|
||||
DifferentialState delta; |
||||
|
||||
OnlineData curvature_factor; |
||||
OnlineData v_ref; // m/s
|
||||
OnlineData l_poly_r0, l_poly_r1, l_poly_r2, l_poly_r3; |
||||
OnlineData r_poly_r0, r_poly_r1, r_poly_r2, r_poly_r3; |
||||
OnlineData d_poly_r0, d_poly_r1, d_poly_r2, d_poly_r3; |
||||
OnlineData l_prob, r_prob; |
||||
OnlineData lane_width; |
||||
|
||||
Control t; |
||||
|
||||
// Equations of motion
|
||||
f << dot(xx) == v_ref * cos(psi); |
||||
f << dot(yy) == v_ref * sin(psi); |
||||
f << dot(psi) == v_ref * delta * curvature_factor; |
||||
f << dot(delta) == t; |
||||
|
||||
auto lr_prob = l_prob + r_prob - l_prob * r_prob; |
||||
|
||||
auto poly_l = l_poly_r0*(xx*xx*xx) + l_poly_r1*(xx*xx) + l_poly_r2*xx + l_poly_r3; |
||||
auto poly_r = r_poly_r0*(xx*xx*xx) + r_poly_r1*(xx*xx) + r_poly_r2*xx + r_poly_r3; |
||||
auto poly_d = d_poly_r0*(xx*xx*xx) + d_poly_r1*(xx*xx) + d_poly_r2*xx + d_poly_r3; |
||||
|
||||
auto angle_d = atan(3*d_poly_r0*xx*xx + 2*d_poly_r1*xx + d_poly_r2); |
||||
|
||||
// When the lane is not visible, use an estimate of its position
|
||||
auto weighted_left_lane = l_prob * poly_l + (1 - l_prob) * (poly_d + lane_width/2.0); |
||||
auto weighted_right_lane = r_prob * poly_r + (1 - r_prob) * (poly_d - lane_width/2.0); |
||||
|
||||
auto c_left_lane = exp(-(weighted_left_lane - yy)); |
||||
auto c_right_lane = exp(weighted_right_lane - yy); |
||||
|
||||
// Running cost
|
||||
Function h; |
||||
|
||||
// Distance errors
|
||||
h << poly_d - yy; |
||||
h << lr_prob * c_left_lane; |
||||
h << lr_prob * c_right_lane; |
||||
|
||||
// Heading error
|
||||
h << (v_ref + 1.0 ) * (angle_d - psi); |
||||
|
||||
// Angular rate error
|
||||
h << (v_ref + 1.0 ) * t; |
||||
|
||||
BMatrix Q(5,5); Q.setAll(true); |
||||
// Q(0,0) = 1.0;
|
||||
// Q(1,1) = 1.0;
|
||||
// Q(2,2) = 1.0;
|
||||
// Q(3,3) = 1.0;
|
||||
// Q(4,4) = 2.0;
|
||||
|
||||
// Terminal cost
|
||||
Function hN; |
||||
|
||||
// Distance errors
|
||||
hN << poly_d - yy; |
||||
hN << l_prob * c_left_lane; |
||||
hN << r_prob * c_right_lane; |
||||
|
||||
// Heading errors
|
||||
hN << (2.0 * v_ref + 1.0 ) * (angle_d - psi); |
||||
|
||||
BMatrix QN(4,4); QN.setAll(true); |
||||
// QN(0,0) = 1.0;
|
||||
// QN(1,1) = 1.0;
|
||||
// QN(2,2) = 1.0;
|
||||
// QN(3,3) = 1.0;
|
||||
|
||||
// Non uniform time grid
|
||||
// First 5 timesteps are 0.05, after that it's 0.15
|
||||
DMatrix numSteps(20, 1); |
||||
for (int i = 0; i < 5; i++){ |
||||
numSteps(i) = 1; |
||||
} |
||||
for (int i = 5; i < 20; i++){ |
||||
numSteps(i) = 3; |
||||
} |
||||
|
||||
// Setup Optimal Control Problem
|
||||
const double tStart = 0.0; |
||||
const double tEnd = 2.5; |
||||
|
||||
OCP ocp( tStart, tEnd, numSteps); |
||||
ocp.subjectTo(f); |
||||
|
||||
ocp.minimizeLSQ(Q, h); |
||||
ocp.minimizeLSQEndTerm(QN, hN); |
||||
|
||||
// car can't go backward to avoid "circles"
|
||||
ocp.subjectTo( deg2rad(-90) <= psi <= deg2rad(90)); |
||||
// more than absolute max steer angle
|
||||
ocp.subjectTo( deg2rad(-50) <= delta <= deg2rad(50)); |
||||
ocp.setNOD(17); |
||||
|
||||
OCPexport mpc(ocp); |
||||
mpc.set( HESSIAN_APPROXIMATION, GAUSS_NEWTON ); |
||||
mpc.set( DISCRETIZATION_TYPE, MULTIPLE_SHOOTING ); |
||||
mpc.set( INTEGRATOR_TYPE, INT_RK4 ); |
||||
mpc.set( NUM_INTEGRATOR_STEPS, 1 * controlHorizon); |
||||
mpc.set( MAX_NUM_QP_ITERATIONS, 500); |
||||
mpc.set( CG_USE_VARIABLE_WEIGHTING_MATRIX, YES); |
||||
|
||||
mpc.set( SPARSE_QP_SOLUTION, CONDENSING ); |
||||
mpc.set( QP_SOLVER, QP_QPOASES ); |
||||
mpc.set( HOTSTART_QP, YES ); |
||||
mpc.set( GENERATE_TEST_FILE, NO); |
||||
mpc.set( GENERATE_MAKE_FILE, NO ); |
||||
mpc.set( GENERATE_MATLAB_INTERFACE, NO ); |
||||
mpc.set( GENERATE_SIMULINK_INTERFACE, NO ); |
||||
|
||||
if (mpc.exportCode( "lib_mpc_export" ) != SUCCESSFUL_RETURN) |
||||
exit( EXIT_FAILURE ); |
||||
|
||||
mpc.printDimensionsQP( ); |
||||
|
||||
return EXIT_SUCCESS; |
||||
} |
@ -0,0 +1,134 @@ |
||||
#include "acado_common.h" |
||||
#include "acado_auxiliary_functions.h" |
||||
|
||||
#include <stdio.h> |
||||
|
||||
#define NX ACADO_NX /* Number of differential state variables. */ |
||||
#define NXA ACADO_NXA /* Number of algebraic variables. */ |
||||
#define NU ACADO_NU /* Number of control inputs. */ |
||||
#define NOD ACADO_NOD /* Number of online data values. */ |
||||
|
||||
#define NY ACADO_NY /* Number of measurements/references on nodes 0..N - 1. */ |
||||
#define NYN ACADO_NYN /* Number of measurements/references on node N. */ |
||||
|
||||
#define N ACADO_N /* Number of intervals in the horizon. */ |
||||
|
||||
ACADOvariables acadoVariables; |
||||
ACADOworkspace acadoWorkspace; |
||||
|
||||
typedef struct { |
||||
double x, y, psi, delta, t; |
||||
} state_t; |
||||
|
||||
|
||||
typedef struct { |
||||
double x[N+1]; |
||||
double y[N+1]; |
||||
double psi[N+1]; |
||||
double delta[N+1]; |
||||
double rate[N]; |
||||
double cost; |
||||
} log_t; |
||||
|
||||
void init_weights(double pathCost, double laneCost, double headingCost, double steerRateCost){ |
||||
int i; |
||||
const int STEP_MULTIPLIER = 3; |
||||
|
||||
for (i = 0; i < N; i++) { |
||||
int f = 1; |
||||
if (i > 4){ |
||||
f = STEP_MULTIPLIER; |
||||
} |
||||
// Setup diagonal entries
|
||||
acadoVariables.W[NY*NY*i + (NY+1)*0] = pathCost * f; |
||||
acadoVariables.W[NY*NY*i + (NY+1)*1] = laneCost * f; |
||||
acadoVariables.W[NY*NY*i + (NY+1)*2] = laneCost * f; |
||||
acadoVariables.W[NY*NY*i + (NY+1)*3] = headingCost * f; |
||||
acadoVariables.W[NY*NY*i + (NY+1)*4] = steerRateCost * f; |
||||
} |
||||
acadoVariables.WN[(NYN+1)*0] = pathCost * STEP_MULTIPLIER; |
||||
acadoVariables.WN[(NYN+1)*1] = laneCost * STEP_MULTIPLIER; |
||||
acadoVariables.WN[(NYN+1)*2] = laneCost * STEP_MULTIPLIER; |
||||
acadoVariables.WN[(NYN+1)*3] = headingCost * STEP_MULTIPLIER; |
||||
} |
||||
|
||||
void init(double pathCost, double laneCost, double headingCost, double steerRateCost){ |
||||
acado_initializeSolver(); |
||||
int i; |
||||
|
||||
/* Initialize the states and controls. */ |
||||
for (i = 0; i < NX * (N + 1); ++i) acadoVariables.x[ i ] = 0.0; |
||||
for (i = 0; i < NU * N; ++i) acadoVariables.u[ i ] = 0.1; |
||||
|
||||
/* Initialize the measurements/reference. */ |
||||
for (i = 0; i < NY * N; ++i) acadoVariables.y[ i ] = 0.0; |
||||
for (i = 0; i < NYN; ++i) acadoVariables.yN[ i ] = 0.0; |
||||
|
||||
/* MPC: initialize the current state feedback. */ |
||||
for (i = 0; i < NX; ++i) acadoVariables.x0[ i ] = 0.0; |
||||
|
||||
init_weights(pathCost, laneCost, headingCost, steerRateCost); |
||||
} |
||||
|
||||
int run_mpc(state_t * x0, log_t * solution, |
||||
double l_poly[4], double r_poly[4], double d_poly[4], |
||||
double l_prob, double r_prob, double curvature_factor, double v_ref, double lane_width){ |
||||
|
||||
int i; |
||||
|
||||
for (i = 0; i <= NOD * N; i+= NOD){ |
||||
acadoVariables.od[i] = curvature_factor; |
||||
acadoVariables.od[i+1] = v_ref; |
||||
|
||||
acadoVariables.od[i+2] = l_poly[0]; |
||||
acadoVariables.od[i+3] = l_poly[1]; |
||||
acadoVariables.od[i+4] = l_poly[2]; |
||||
acadoVariables.od[i+5] = l_poly[3]; |
||||
|
||||
acadoVariables.od[i+6] = r_poly[0]; |
||||
acadoVariables.od[i+7] = r_poly[1]; |
||||
acadoVariables.od[i+8] = r_poly[2]; |
||||
acadoVariables.od[i+9] = r_poly[3]; |
||||
|
||||
acadoVariables.od[i+10] = d_poly[0]; |
||||
acadoVariables.od[i+11] = d_poly[1]; |
||||
acadoVariables.od[i+12] = d_poly[2]; |
||||
acadoVariables.od[i+13] = d_poly[3]; |
||||
|
||||
|
||||
acadoVariables.od[i+14] = l_prob; |
||||
acadoVariables.od[i+15] = r_prob; |
||||
acadoVariables.od[i+16] = lane_width; |
||||
|
||||
} |
||||
|
||||
acadoVariables.x0[0] = x0->x; |
||||
acadoVariables.x0[1] = x0->y; |
||||
acadoVariables.x0[2] = x0->psi; |
||||
acadoVariables.x0[3] = x0->delta; |
||||
|
||||
|
||||
acado_preparationStep(); |
||||
acado_feedbackStep(); |
||||
|
||||
/* printf("lat its: %d\n", acado_getNWSR()); // n iterations
|
||||
printf("Objective: %.6f\n", acado_getObjective()); // solution cost */
|
||||
|
||||
for (i = 0; i <= N; i++){ |
||||
solution->x[i] = acadoVariables.x[i*NX]; |
||||
solution->y[i] = acadoVariables.x[i*NX+1]; |
||||
solution->psi[i] = acadoVariables.x[i*NX+2]; |
||||
solution->delta[i] = acadoVariables.x[i*NX+3]; |
||||
if (i < N){ |
||||
solution->rate[i] = acadoVariables.u[i]; |
||||
} |
||||
} |
||||
solution->cost = acado_getObjective(); |
||||
|
||||
// Dont shift states here. Current solution is closer to next timestep than if
|
||||
// we use the old solution as a starting point
|
||||
//acado_shiftStates(2, 0, 0);
|
||||
//acado_shiftControls( 0 );
|
||||
|
||||
return acado_getNWSR(); |
||||
} |
@ -0,0 +1,212 @@ |
||||
/*
|
||||
* This file was auto-generated using the ACADO Toolkit. |
||||
*
|
||||
* While ACADO Toolkit is free software released under the terms of |
||||
* the GNU Lesser General Public License (LGPL), the generated code |
||||
* as such remains the property of the user who used ACADO Toolkit |
||||
* to generate this code. In particular, user dependent data of the code |
||||
* do not inherit the GNU LGPL license. On the other hand, parts of the |
||||
* generated code that are a direct copy of source code from the |
||||
* ACADO Toolkit or the software tools it is based on, remain, as derived |
||||
* work, automatically covered by the LGPL license. |
||||
*
|
||||
* ACADO Toolkit is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
||||
*
|
||||
*/ |
||||
|
||||
|
||||
#include "acado_auxiliary_functions.h" |
||||
|
||||
#include <stdio.h> |
||||
|
||||
real_t* acado_getVariablesX( ) |
||||
{ |
||||
return acadoVariables.x; |
||||
} |
||||
|
||||
real_t* acado_getVariablesU( ) |
||||
{ |
||||
return acadoVariables.u; |
||||
} |
||||
|
||||
#if ACADO_NY > 0 |
||||
real_t* acado_getVariablesY( ) |
||||
{ |
||||
return acadoVariables.y; |
||||
} |
||||
#endif |
||||
|
||||
#if ACADO_NYN > 0 |
||||
real_t* acado_getVariablesYN( ) |
||||
{ |
||||
return acadoVariables.yN; |
||||
} |
||||
#endif |
||||
|
||||
real_t* acado_getVariablesX0( ) |
||||
{ |
||||
#if ACADO_INITIAL_VALUE_FIXED |
||||
return acadoVariables.x0; |
||||
#else |
||||
return 0; |
||||
#endif |
||||
} |
||||
|
||||
/** Print differential variables. */ |
||||
void acado_printDifferentialVariables( ) |
||||
{ |
||||
int i, j; |
||||
printf("\nDifferential variables:\n[\n"); |
||||
for (i = 0; i < ACADO_N + 1; ++i) |
||||
{ |
||||
for (j = 0; j < ACADO_NX; ++j) |
||||
printf("\t%e", acadoVariables.x[i * ACADO_NX + j]); |
||||
printf("\n"); |
||||
} |
||||
printf("]\n\n"); |
||||
} |
||||
|
||||
/** Print control variables. */ |
||||
void acado_printControlVariables( ) |
||||
{ |
||||
int i, j; |
||||
printf("\nControl variables:\n[\n"); |
||||
for (i = 0; i < ACADO_N; ++i) |
||||
{ |
||||
for (j = 0; j < ACADO_NU; ++j) |
||||
printf("\t%e", acadoVariables.u[i * ACADO_NU + j]); |
||||
printf("\n"); |
||||
} |
||||
printf("]\n\n"); |
||||
} |
||||
|
||||
/** Print ACADO code generation notice. */ |
||||
void acado_printHeader( ) |
||||
{ |
||||
printf( |
||||
"\nACADO Toolkit -- A Toolkit for Automatic Control and Dynamic Optimization.\n" |
||||
"Copyright (C) 2008-2015 by Boris Houska, Hans Joachim Ferreau,\n"
|
||||
"Milan Vukov and Rien Quirynen, KU Leuven.\n" |
||||
); |
||||
|
||||
printf( |
||||
"Developed within the Optimization in Engineering Center (OPTEC) under\n" |
||||
"supervision of Moritz Diehl. All rights reserved.\n\n" |
||||
"ACADO Toolkit is distributed under the terms of the GNU Lesser\n" |
||||
"General Public License 3 in the hope that it will be useful,\n" |
||||
"but WITHOUT ANY WARRANTY; without even the implied warranty of\n" |
||||
"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" |
||||
"GNU Lesser General Public License for more details.\n\n" |
||||
); |
||||
} |
||||
|
||||
#if !(defined _DSPACE) |
||||
#if (defined _WIN32 || defined _WIN64) && !(defined __MINGW32__ || defined __MINGW64__) |
||||
|
||||
void acado_tic( acado_timer* t ) |
||||
{ |
||||
QueryPerformanceFrequency(&t->freq); |
||||
QueryPerformanceCounter(&t->tic); |
||||
} |
||||
|
||||
real_t acado_toc( acado_timer* t ) |
||||
{ |
||||
QueryPerformanceCounter(&t->toc); |
||||
return ((t->toc.QuadPart - t->tic.QuadPart) / (real_t)t->freq.QuadPart); |
||||
} |
||||
|
||||
|
||||
#elif (defined __APPLE__) |
||||
|
||||
void acado_tic( acado_timer* t ) |
||||
{ |
||||
/* read current clock cycles */ |
||||
t->tic = mach_absolute_time(); |
||||
} |
||||
|
||||
real_t acado_toc( acado_timer* t ) |
||||
{ |
||||
|
||||
uint64_t duration; /* elapsed time in clock cycles*/ |
||||
|
||||
t->toc = mach_absolute_time(); |
||||
duration = t->toc - t->tic; |
||||
|
||||
/*conversion from clock cycles to nanoseconds*/ |
||||
mach_timebase_info(&(t->tinfo)); |
||||
duration *= t->tinfo.numer; |
||||
duration /= t->tinfo.denom; |
||||
|
||||
return (real_t)duration / 1e9; |
||||
} |
||||
|
||||
#else |
||||
|
||||
#if __STDC_VERSION__ >= 199901L |
||||
/* C99 mode */ |
||||
|
||||
/* read current time */ |
||||
void acado_tic( acado_timer* t ) |
||||
{ |
||||
gettimeofday(&t->tic, 0); |
||||
} |
||||
|
||||
/* return time passed since last call to tic on this timer */ |
||||
real_t acado_toc( acado_timer* t ) |
||||
{ |
||||
struct timeval temp; |
||||
|
||||
gettimeofday(&t->toc, 0); |
||||
|
||||
if ((t->toc.tv_usec - t->tic.tv_usec) < 0) |
||||
{ |
||||
temp.tv_sec = t->toc.tv_sec - t->tic.tv_sec - 1; |
||||
temp.tv_usec = 1000000 + t->toc.tv_usec - t->tic.tv_usec; |
||||
} |
||||
else |
||||
{ |
||||
temp.tv_sec = t->toc.tv_sec - t->tic.tv_sec; |
||||
temp.tv_usec = t->toc.tv_usec - t->tic.tv_usec; |
||||
} |
||||
|
||||
return (real_t)temp.tv_sec + (real_t)temp.tv_usec / 1e6; |
||||
} |
||||
|
||||
#else |
||||
/* ANSI */ |
||||
|
||||
/* read current time */ |
||||
void acado_tic( acado_timer* t ) |
||||
{ |
||||
clock_gettime(CLOCK_MONOTONIC, &t->tic); |
||||
} |
||||
|
||||
|
||||
/* return time passed since last call to tic on this timer */ |
||||
real_t acado_toc( acado_timer* t ) |
||||
{ |
||||
struct timespec temp; |
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &t->toc);
|
||||
|
||||
if ((t->toc.tv_nsec - t->tic.tv_nsec) < 0) |
||||
{ |
||||
temp.tv_sec = t->toc.tv_sec - t->tic.tv_sec - 1; |
||||
temp.tv_nsec = 1000000000+t->toc.tv_nsec - t->tic.tv_nsec; |
||||
} |
||||
else |
||||
{ |
||||
temp.tv_sec = t->toc.tv_sec - t->tic.tv_sec; |
||||
temp.tv_nsec = t->toc.tv_nsec - t->tic.tv_nsec; |
||||
} |
||||
|
||||
return (real_t)temp.tv_sec + (real_t)temp.tv_nsec / 1e9; |
||||
} |
||||
|
||||
#endif /* __STDC_VERSION__ >= 199901L */ |
||||
|
||||
#endif /* (defined _WIN32 || _WIN64) */ |
||||
|
||||
#endif |
@ -0,0 +1,138 @@ |
||||
/*
|
||||
* This file was auto-generated using the ACADO Toolkit. |
||||
*
|
||||
* While ACADO Toolkit is free software released under the terms of |
||||
* the GNU Lesser General Public License (LGPL), the generated code |
||||
* as such remains the property of the user who used ACADO Toolkit |
||||
* to generate this code. In particular, user dependent data of the code |
||||
* do not inherit the GNU LGPL license. On the other hand, parts of the |
||||
* generated code that are a direct copy of source code from the |
||||
* ACADO Toolkit or the software tools it is based on, remain, as derived |
||||
* work, automatically covered by the LGPL license. |
||||
*
|
||||
* ACADO Toolkit is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
||||
*
|
||||
*/ |
||||
|
||||
|
||||
#ifndef ACADO_AUXILIARY_FUNCTIONS_H |
||||
#define ACADO_AUXILIARY_FUNCTIONS_H |
||||
|
||||
#include "acado_common.h" |
||||
|
||||
#ifndef __MATLAB__ |
||||
#ifdef __cplusplus |
||||
extern "C" |
||||
{ |
||||
#endif /* __cplusplus */ |
||||
#endif /* __MATLAB__ */ |
||||
|
||||
/** Get pointer to the matrix with differential variables. */ |
||||
real_t* acado_getVariablesX( ); |
||||
|
||||
/** Get pointer to the matrix with control variables. */ |
||||
real_t* acado_getVariablesU( ); |
||||
|
||||
#if ACADO_NY > 0 |
||||
/** Get pointer to the matrix with references/measurements. */ |
||||
real_t* acado_getVariablesY( ); |
||||
#endif |
||||
|
||||
#if ACADO_NYN > 0 |
||||
/** Get pointer to the vector with references/measurement on the last node. */ |
||||
real_t* acado_getVariablesYN( ); |
||||
#endif |
||||
|
||||
/** Get pointer to the current state feedback vector. Only applicable for NMPC. */ |
||||
real_t* acado_getVariablesX0( ); |
||||
|
||||
/** Print differential variables. */ |
||||
void acado_printDifferentialVariables( ); |
||||
|
||||
/** Print control variables. */ |
||||
void acado_printControlVariables( ); |
||||
|
||||
/** Print ACADO code generation notice. */ |
||||
void acado_printHeader( ); |
||||
|
||||
/*
|
||||
* A huge thanks goes to Alexander Domahidi from ETHZ, Switzerland, for
|
||||
* providing us with the following timing routines. |
||||
*/ |
||||
|
||||
#if !(defined _DSPACE) |
||||
#if (defined _WIN32 || defined _WIN64) && !(defined __MINGW32__ || defined __MINGW64__) |
||||
|
||||
/* Use Windows QueryPerformanceCounter for timing. */ |
||||
#include <Windows.h> |
||||
|
||||
/** A structure for keeping internal timer data. */ |
||||
typedef struct acado_timer_ |
||||
{ |
||||
LARGE_INTEGER tic; |
||||
LARGE_INTEGER toc; |
||||
LARGE_INTEGER freq; |
||||
} acado_timer; |
||||
|
||||
|
||||
#elif (defined __APPLE__) |
||||
|
||||
#include "unistd.h" |
||||
#include <mach/mach_time.h> |
||||
|
||||
/** A structure for keeping internal timer data. */ |
||||
typedef struct acado_timer_ |
||||
{ |
||||
uint64_t tic; |
||||
uint64_t toc; |
||||
mach_timebase_info_data_t tinfo; |
||||
} acado_timer; |
||||
|
||||
#else |
||||
|
||||
/* Use POSIX clock_gettime() for timing on non-Windows machines. */ |
||||
#include <time.h> |
||||
|
||||
#if __STDC_VERSION__ >= 199901L |
||||
/* C99 mode of operation. */ |
||||
|
||||
#include <sys/stat.h> |
||||
#include <sys/time.h> |
||||
|
||||
typedef struct acado_timer_ |
||||
{ |
||||
struct timeval tic; |
||||
struct timeval toc; |
||||
} acado_timer; |
||||
|
||||
#else |
||||
/* ANSI C */ |
||||
|
||||
/** A structure for keeping internal timer data. */ |
||||
typedef struct acado_timer_ |
||||
{ |
||||
struct timespec tic; |
||||
struct timespec toc; |
||||
} acado_timer; |
||||
|
||||
#endif /* __STDC_VERSION__ >= 199901L */ |
||||
|
||||
#endif /* (defined _WIN32 || defined _WIN64) */ |
||||
|
||||
/** A function for measurement of the current time. */ |
||||
void acado_tic( acado_timer* t ); |
||||
|
||||
/** A function which returns the elapsed time. */ |
||||
real_t acado_toc( acado_timer* t ); |
||||
|
||||
#endif |
||||
|
||||
#ifndef __MATLAB__ |
||||
#ifdef __cplusplus |
||||
} /* extern "C" */ |
||||
#endif /* __cplusplus */ |
||||
#endif /* __MATLAB__ */ |
||||
|
||||
#endif /* ACADO_AUXILIARY_FUNCTIONS_H */ |
@ -0,0 +1,357 @@ |
||||
/*
|
||||
* This file was auto-generated using the ACADO Toolkit. |
||||
*
|
||||
* While ACADO Toolkit is free software released under the terms of |
||||
* the GNU Lesser General Public License (LGPL), the generated code |
||||
* as such remains the property of the user who used ACADO Toolkit |
||||
* to generate this code. In particular, user dependent data of the code |
||||
* do not inherit the GNU LGPL license. On the other hand, parts of the |
||||
* generated code that are a direct copy of source code from the |
||||
* ACADO Toolkit or the software tools it is based on, remain, as derived |
||||
* work, automatically covered by the LGPL license. |
||||
*
|
||||
* ACADO Toolkit is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
||||
*
|
||||
*/ |
||||
|
||||
|
||||
#ifndef ACADO_COMMON_H |
||||
#define ACADO_COMMON_H |
||||
|
||||
#include <math.h> |
||||
#include <string.h> |
||||
|
||||
#ifndef __MATLAB__ |
||||
#ifdef __cplusplus |
||||
extern "C" |
||||
{ |
||||
#endif /* __cplusplus */ |
||||
#endif /* __MATLAB__ */ |
||||
|
||||
/** \defgroup ACADO ACADO CGT generated module. */ |
||||
/** @{ */ |
||||
|
||||
/** qpOASES QP solver indicator. */ |
||||
#define ACADO_QPOASES 0 |
||||
#define ACADO_QPOASES3 1 |
||||
/** FORCES QP solver indicator.*/ |
||||
#define ACADO_FORCES 2 |
||||
/** qpDUNES QP solver indicator.*/ |
||||
#define ACADO_QPDUNES 3 |
||||
/** HPMPC QP solver indicator. */ |
||||
#define ACADO_HPMPC 4 |
||||
#define ACADO_GENERIC 5 |
||||
|
||||
/** Indicator for determining the QP solver used by the ACADO solver code. */ |
||||
#define ACADO_QP_SOLVER ACADO_QPOASES |
||||
|
||||
#include "acado_qpoases_interface.hpp" |
||||
|
||||
|
||||
/*
|
||||
* Common definitions |
||||
*/ |
||||
/** User defined block based condensing. */ |
||||
#define ACADO_BLOCK_CONDENSING 0 |
||||
/** Compute covariance matrix of the last state estimate. */ |
||||
#define ACADO_COMPUTE_COVARIANCE_MATRIX 0 |
||||
/** Flag indicating whether constraint values are hard-coded or not. */ |
||||
#define ACADO_HARDCODED_CONSTRAINT_VALUES 1 |
||||
/** Indicator for fixed initial state. */ |
||||
#define ACADO_INITIAL_STATE_FIXED 1 |
||||
/** Number of control/estimation intervals. */ |
||||
#define ACADO_N 20 |
||||
/** Number of online data values. */ |
||||
#define ACADO_NOD 17 |
||||
/** Number of path constraints. */ |
||||
#define ACADO_NPAC 0 |
||||
/** Number of control variables. */ |
||||
#define ACADO_NU 1 |
||||
/** Number of differential variables. */ |
||||
#define ACADO_NX 4 |
||||
/** Number of algebraic variables. */ |
||||
#define ACADO_NXA 0 |
||||
/** Number of differential derivative variables. */ |
||||
#define ACADO_NXD 0 |
||||
/** Number of references/measurements per node on the first N nodes. */ |
||||
#define ACADO_NY 5 |
||||
/** Number of references/measurements on the last (N + 1)st node. */ |
||||
#define ACADO_NYN 4 |
||||
/** Total number of QP optimization variables. */ |
||||
#define ACADO_QP_NV 24 |
||||
/** Number of Runge-Kutta stages per integration step. */ |
||||
#define ACADO_RK_NSTAGES 4 |
||||
/** Providing interface for arrival cost. */ |
||||
#define ACADO_USE_ARRIVAL_COST 0 |
||||
/** Indicator for usage of non-hard-coded linear terms in the objective. */ |
||||
#define ACADO_USE_LINEAR_TERMS 0 |
||||
/** Indicator for type of fixed weighting matrices. */ |
||||
#define ACADO_WEIGHTING_MATRICES_TYPE 2 |
||||
|
||||
|
||||
/*
|
||||
* Globally used structure definitions |
||||
*/ |
||||
|
||||
/** The structure containing the user data.
|
||||
*
|
||||
* Via this structure the user "communicates" with the solver code. |
||||
*/ |
||||
typedef struct ACADOvariables_ |
||||
{ |
||||
int dummy; |
||||
/** Matrix of size: 21 x 4 (row major format)
|
||||
*
|
||||
* Matrix containing 21 differential variable vectors. |
||||
*/ |
||||
real_t x[ 84 ]; |
||||
|
||||
/** Column vector of size: 20
|
||||
*
|
||||
* Matrix containing 20 control variable vectors. |
||||
*/ |
||||
real_t u[ 20 ]; |
||||
|
||||
/** Matrix of size: 21 x 17 (row major format)
|
||||
*
|
||||
* Matrix containing 21 online data vectors. |
||||
*/ |
||||
real_t od[ 357 ]; |
||||
|
||||
/** Column vector of size: 100
|
||||
*
|
||||
* Matrix containing 20 reference/measurement vectors of size 5 for first 20 nodes. |
||||
*/ |
||||
real_t y[ 100 ]; |
||||
|
||||
/** Column vector of size: 4
|
||||
*
|
||||
* Reference/measurement vector for the 21. node. |
||||
*/ |
||||
real_t yN[ 4 ]; |
||||
|
||||
/** Matrix of size: 100 x 5 (row major format) */ |
||||
real_t W[ 500 ]; |
||||
|
||||
/** Matrix of size: 4 x 4 (row major format) */ |
||||
real_t WN[ 16 ]; |
||||
|
||||
/** Column vector of size: 4
|
||||
*
|
||||
* Current state feedback vector. |
||||
*/ |
||||
real_t x0[ 4 ]; |
||||
|
||||
|
||||
} ACADOvariables; |
||||
|
||||
/** Private workspace used by the auto-generated code.
|
||||
*
|
||||
* Data members of this structure are private to the solver. |
||||
* In other words, the user code should not modify values of this
|
||||
* structure.
|
||||
*/ |
||||
typedef struct ACADOworkspace_ |
||||
{ |
||||
/** Column vector of size: 14 */ |
||||
real_t rhs_aux[ 14 ]; |
||||
|
||||
real_t rk_ttt; |
||||
|
||||
/** Row vector of size: 42 */ |
||||
real_t rk_xxx[ 42 ]; |
||||
|
||||
/** Matrix of size: 4 x 24 (row major format) */ |
||||
real_t rk_kkk[ 96 ]; |
||||
|
||||
/** Row vector of size: 42 */ |
||||
real_t state[ 42 ]; |
||||
|
||||
/** Column vector of size: 80 */ |
||||
real_t d[ 80 ]; |
||||
|
||||
/** Column vector of size: 100 */ |
||||
real_t Dy[ 100 ]; |
||||
|
||||
/** Column vector of size: 4 */ |
||||
real_t DyN[ 4 ]; |
||||
|
||||
/** Matrix of size: 80 x 4 (row major format) */ |
||||
real_t evGx[ 320 ]; |
||||
|
||||
/** Column vector of size: 80 */ |
||||
real_t evGu[ 80 ]; |
||||
|
||||
/** Column vector of size: 11 */ |
||||
real_t objAuxVar[ 11 ]; |
||||
|
||||
/** Row vector of size: 22 */ |
||||
real_t objValueIn[ 22 ]; |
||||
|
||||
/** Row vector of size: 30 */ |
||||
real_t objValueOut[ 30 ]; |
||||
|
||||
/** Matrix of size: 80 x 4 (row major format) */ |
||||
real_t Q1[ 320 ]; |
||||
|
||||
/** Matrix of size: 80 x 5 (row major format) */ |
||||
real_t Q2[ 400 ]; |
||||
|
||||
/** Column vector of size: 20 */ |
||||
real_t R1[ 20 ]; |
||||
|
||||
/** Matrix of size: 20 x 5 (row major format) */ |
||||
real_t R2[ 100 ]; |
||||
|
||||
/** Column vector of size: 80 */ |
||||
real_t S1[ 80 ]; |
||||
|
||||
/** Matrix of size: 4 x 4 (row major format) */ |
||||
real_t QN1[ 16 ]; |
||||
|
||||
/** Matrix of size: 4 x 4 (row major format) */ |
||||
real_t QN2[ 16 ]; |
||||
|
||||
/** Column vector of size: 4 */ |
||||
real_t Dx0[ 4 ]; |
||||
|
||||
/** Matrix of size: 4 x 4 (row major format) */ |
||||
real_t T[ 16 ]; |
||||
|
||||
/** Column vector of size: 840 */ |
||||
real_t E[ 840 ]; |
||||
|
||||
/** Column vector of size: 840 */ |
||||
real_t QE[ 840 ]; |
||||
|
||||
/** Matrix of size: 80 x 4 (row major format) */ |
||||
real_t QGx[ 320 ]; |
||||
|
||||
/** Column vector of size: 80 */ |
||||
real_t Qd[ 80 ]; |
||||
|
||||
/** Column vector of size: 84 */ |
||||
real_t QDy[ 84 ]; |
||||
|
||||
/** Matrix of size: 20 x 4 (row major format) */ |
||||
real_t H10[ 80 ]; |
||||
|
||||
/** Matrix of size: 24 x 24 (row major format) */ |
||||
real_t H[ 576 ]; |
||||
|
||||
/** Matrix of size: 40 x 24 (row major format) */ |
||||
real_t A[ 960 ]; |
||||
|
||||
/** Column vector of size: 24 */ |
||||
real_t g[ 24 ]; |
||||
|
||||
/** Column vector of size: 24 */ |
||||
real_t lb[ 24 ]; |
||||
|
||||
/** Column vector of size: 24 */ |
||||
real_t ub[ 24 ]; |
||||
|
||||
/** Column vector of size: 40 */ |
||||
real_t lbA[ 40 ]; |
||||
|
||||
/** Column vector of size: 40 */ |
||||
real_t ubA[ 40 ]; |
||||
|
||||
/** Column vector of size: 24 */ |
||||
real_t x[ 24 ]; |
||||
|
||||
/** Column vector of size: 64 */ |
||||
real_t y[ 64 ]; |
||||
|
||||
|
||||
} ACADOworkspace; |
||||
|
||||
/*
|
||||
* Forward function declarations.
|
||||
*/ |
||||
|
||||
|
||||
/** Performs the integration and sensitivity propagation for one shooting interval.
|
||||
* |
||||
* \param rk_eta Working array to pass the input values and return the results. |
||||
* \param resetIntegrator The internal memory of the integrator can be reset. |
||||
* \param rk_index Number of the shooting interval. |
||||
* |
||||
* \return Status code of the integrator. |
||||
*/ |
||||
int acado_integrate( real_t* const rk_eta, int resetIntegrator, int rk_index ); |
||||
|
||||
/** Export of an ACADO symbolic function.
|
||||
* |
||||
* \param in Input to the exported function. |
||||
* \param out Output of the exported function. |
||||
*/ |
||||
void acado_rhs_forw(const real_t* in, real_t* out); |
||||
|
||||
/** Preparation step of the RTI scheme.
|
||||
* |
||||
* \return Status of the integration module. =0: OK, otherwise the error code. |
||||
*/ |
||||
int acado_preparationStep( ); |
||||
|
||||
/** Feedback/estimation step of the RTI scheme.
|
||||
* |
||||
* \return Status code of the qpOASES QP solver. |
||||
*/ |
||||
int acado_feedbackStep( ); |
||||
|
||||
/** Solver initialization. Must be called once before any other function call.
|
||||
* |
||||
* \return =0: OK, otherwise an error code of a QP solver. |
||||
*/ |
||||
int acado_initializeSolver( ); |
||||
|
||||
/** Initialize shooting nodes by a forward simulation starting from the first node.
|
||||
*/ |
||||
void acado_initializeNodesByForwardSimulation( ); |
||||
|
||||
/** Shift differential variables vector by one interval.
|
||||
* |
||||
* \param strategy Shifting strategy: 1. Initialize node 21 with xEnd. 2. Initialize node 21 by forward simulation. |
||||
* \param xEnd Value for the x vector on the last node. If =0 the old value is used. |
||||
* \param uEnd Value for the u vector on the second to last node. If =0 the old value is used. |
||||
*/ |
||||
void acado_shiftStates( int strategy, real_t* const xEnd, real_t* const uEnd ); |
||||
|
||||
/** Shift controls vector by one interval.
|
||||
* |
||||
* \param uEnd Value for the u vector on the second to last node. If =0 the old value is used. |
||||
*/ |
||||
void acado_shiftControls( real_t* const uEnd ); |
||||
|
||||
/** Get the KKT tolerance of the current iterate.
|
||||
* |
||||
* \return The KKT tolerance value. |
||||
*/ |
||||
real_t acado_getKKT( ); |
||||
|
||||
/** Calculate the objective value.
|
||||
* |
||||
* \return Value of the objective function. |
||||
*/ |
||||
real_t acado_getObjective( ); |
||||
|
||||
|
||||
/*
|
||||
* Extern declarations.
|
||||
*/ |
||||
|
||||
extern ACADOworkspace acadoWorkspace; |
||||
extern ACADOvariables acadoVariables; |
||||
|
||||
/** @} */ |
||||
|
||||
#ifndef __MATLAB__ |
||||
#ifdef __cplusplus |
||||
} /* extern "C" */ |
||||
#endif /* __cplusplus */ |
||||
#endif /* __MATLAB__ */ |
||||
|
||||
#endif /* ACADO_COMMON_H */ |
@ -0,0 +1,255 @@ |
||||
/*
|
||||
* This file was auto-generated using the ACADO Toolkit. |
||||
*
|
||||
* While ACADO Toolkit is free software released under the terms of |
||||
* the GNU Lesser General Public License (LGPL), the generated code |
||||
* as such remains the property of the user who used ACADO Toolkit |
||||
* to generate this code. In particular, user dependent data of the code |
||||
* do not inherit the GNU LGPL license. On the other hand, parts of the |
||||
* generated code that are a direct copy of source code from the |
||||
* ACADO Toolkit or the software tools it is based on, remain, as derived |
||||
* work, automatically covered by the LGPL license. |
||||
*
|
||||
* ACADO Toolkit is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
||||
*
|
||||
*/ |
||||
|
||||
|
||||
#include "acado_common.h" |
||||
|
||||
|
||||
void acado_rhs_forw(const real_t* in, real_t* out) |
||||
{ |
||||
const real_t* xd = in; |
||||
const real_t* u = in + 24; |
||||
const real_t* od = in + 25; |
||||
/* Vector of auxiliary variables; number of elements: 14. */ |
||||
real_t* a = acadoWorkspace.rhs_aux; |
||||
|
||||
/* Compute intermediate quantities: */ |
||||
a[0] = (cos(xd[2])); |
||||
a[1] = (sin(xd[2])); |
||||
a[2] = ((real_t)(-1.0000000000000000e+00)*(sin(xd[2]))); |
||||
a[3] = (xd[12]*a[2]); |
||||
a[4] = (xd[13]*a[2]); |
||||
a[5] = (xd[14]*a[2]); |
||||
a[6] = (xd[15]*a[2]); |
||||
a[7] = (cos(xd[2])); |
||||
a[8] = (xd[12]*a[7]); |
||||
a[9] = (xd[13]*a[7]); |
||||
a[10] = (xd[14]*a[7]); |
||||
a[11] = (xd[15]*a[7]); |
||||
a[12] = (xd[22]*a[2]); |
||||
a[13] = (xd[22]*a[7]); |
||||
|
||||
/* Compute outputs: */ |
||||
out[0] = (od[1]*a[0]); |
||||
out[1] = (od[1]*a[1]); |
||||
out[2] = ((od[1]*xd[3])*od[0]); |
||||
out[3] = u[0]; |
||||
out[4] = (od[1]*a[3]); |
||||
out[5] = (od[1]*a[4]); |
||||
out[6] = (od[1]*a[5]); |
||||
out[7] = (od[1]*a[6]); |
||||
out[8] = (od[1]*a[8]); |
||||
out[9] = (od[1]*a[9]); |
||||
out[10] = (od[1]*a[10]); |
||||
out[11] = (od[1]*a[11]); |
||||
out[12] = ((od[1]*xd[16])*od[0]); |
||||
out[13] = ((od[1]*xd[17])*od[0]); |
||||
out[14] = ((od[1]*xd[18])*od[0]); |
||||
out[15] = ((od[1]*xd[19])*od[0]); |
||||
out[16] = (real_t)(0.0000000000000000e+00); |
||||
out[17] = (real_t)(0.0000000000000000e+00); |
||||
out[18] = (real_t)(0.0000000000000000e+00); |
||||
out[19] = (real_t)(0.0000000000000000e+00); |
||||
out[20] = (od[1]*a[12]); |
||||
out[21] = (od[1]*a[13]); |
||||
out[22] = ((od[1]*xd[23])*od[0]); |
||||
out[23] = (real_t)(1.0000000000000000e+00); |
||||
} |
||||
|
||||
/* Fixed step size:0.05 */ |
||||
int acado_integrate( real_t* const rk_eta, int resetIntegrator, int rk_index ) |
||||
{ |
||||
int error; |
||||
|
||||
int run1; |
||||
int numSteps[20] = {1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}; |
||||
int numInts = numSteps[rk_index]; |
||||
acadoWorkspace.rk_ttt = 0.0000000000000000e+00; |
||||
rk_eta[4] = 1.0000000000000000e+00; |
||||
rk_eta[5] = 0.0000000000000000e+00; |
||||
rk_eta[6] = 0.0000000000000000e+00; |
||||
rk_eta[7] = 0.0000000000000000e+00; |
||||
rk_eta[8] = 0.0000000000000000e+00; |
||||
rk_eta[9] = 1.0000000000000000e+00; |
||||
rk_eta[10] = 0.0000000000000000e+00; |
||||
rk_eta[11] = 0.0000000000000000e+00; |
||||
rk_eta[12] = 0.0000000000000000e+00; |
||||
rk_eta[13] = 0.0000000000000000e+00; |
||||
rk_eta[14] = 1.0000000000000000e+00; |
||||
rk_eta[15] = 0.0000000000000000e+00; |
||||
rk_eta[16] = 0.0000000000000000e+00; |
||||
rk_eta[17] = 0.0000000000000000e+00; |
||||
rk_eta[18] = 0.0000000000000000e+00; |
||||
rk_eta[19] = 1.0000000000000000e+00; |
||||
rk_eta[20] = 0.0000000000000000e+00; |
||||
rk_eta[21] = 0.0000000000000000e+00; |
||||
rk_eta[22] = 0.0000000000000000e+00; |
||||
rk_eta[23] = 0.0000000000000000e+00; |
||||
acadoWorkspace.rk_xxx[24] = rk_eta[24]; |
||||
acadoWorkspace.rk_xxx[25] = rk_eta[25]; |
||||
acadoWorkspace.rk_xxx[26] = rk_eta[26]; |
||||
acadoWorkspace.rk_xxx[27] = rk_eta[27]; |
||||
acadoWorkspace.rk_xxx[28] = rk_eta[28]; |
||||
acadoWorkspace.rk_xxx[29] = rk_eta[29]; |
||||
acadoWorkspace.rk_xxx[30] = rk_eta[30]; |
||||
acadoWorkspace.rk_xxx[31] = rk_eta[31]; |
||||
acadoWorkspace.rk_xxx[32] = rk_eta[32]; |
||||
acadoWorkspace.rk_xxx[33] = rk_eta[33]; |
||||
acadoWorkspace.rk_xxx[34] = rk_eta[34]; |
||||
acadoWorkspace.rk_xxx[35] = rk_eta[35]; |
||||
acadoWorkspace.rk_xxx[36] = rk_eta[36]; |
||||
acadoWorkspace.rk_xxx[37] = rk_eta[37]; |
||||
acadoWorkspace.rk_xxx[38] = rk_eta[38]; |
||||
acadoWorkspace.rk_xxx[39] = rk_eta[39]; |
||||
acadoWorkspace.rk_xxx[40] = rk_eta[40]; |
||||
acadoWorkspace.rk_xxx[41] = rk_eta[41]; |
||||
|
||||
for (run1 = 0; run1 < 1; ++run1) |
||||
{ |
||||
for(run1 = 0; run1 < numInts; run1++ ) { |
||||
acadoWorkspace.rk_xxx[0] = + rk_eta[0]; |
||||
acadoWorkspace.rk_xxx[1] = + rk_eta[1]; |
||||
acadoWorkspace.rk_xxx[2] = + rk_eta[2]; |
||||
acadoWorkspace.rk_xxx[3] = + rk_eta[3]; |
||||
acadoWorkspace.rk_xxx[4] = + rk_eta[4]; |
||||
acadoWorkspace.rk_xxx[5] = + rk_eta[5]; |
||||
acadoWorkspace.rk_xxx[6] = + rk_eta[6]; |
||||
acadoWorkspace.rk_xxx[7] = + rk_eta[7]; |
||||
acadoWorkspace.rk_xxx[8] = + rk_eta[8]; |
||||
acadoWorkspace.rk_xxx[9] = + rk_eta[9]; |
||||
acadoWorkspace.rk_xxx[10] = + rk_eta[10]; |
||||
acadoWorkspace.rk_xxx[11] = + rk_eta[11]; |
||||
acadoWorkspace.rk_xxx[12] = + rk_eta[12]; |
||||
acadoWorkspace.rk_xxx[13] = + rk_eta[13]; |
||||
acadoWorkspace.rk_xxx[14] = + rk_eta[14]; |
||||
acadoWorkspace.rk_xxx[15] = + rk_eta[15]; |
||||
acadoWorkspace.rk_xxx[16] = + rk_eta[16]; |
||||
acadoWorkspace.rk_xxx[17] = + rk_eta[17]; |
||||
acadoWorkspace.rk_xxx[18] = + rk_eta[18]; |
||||
acadoWorkspace.rk_xxx[19] = + rk_eta[19]; |
||||
acadoWorkspace.rk_xxx[20] = + rk_eta[20]; |
||||
acadoWorkspace.rk_xxx[21] = + rk_eta[21]; |
||||
acadoWorkspace.rk_xxx[22] = + rk_eta[22]; |
||||
acadoWorkspace.rk_xxx[23] = + rk_eta[23]; |
||||
acado_rhs_forw( acadoWorkspace.rk_xxx, acadoWorkspace.rk_kkk ); |
||||
acadoWorkspace.rk_xxx[0] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[0] + rk_eta[0]; |
||||
acadoWorkspace.rk_xxx[1] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[1] + rk_eta[1]; |
||||
acadoWorkspace.rk_xxx[2] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[2] + rk_eta[2]; |
||||
acadoWorkspace.rk_xxx[3] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[3] + rk_eta[3]; |
||||
acadoWorkspace.rk_xxx[4] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[4] + rk_eta[4]; |
||||
acadoWorkspace.rk_xxx[5] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[5] + rk_eta[5]; |
||||
acadoWorkspace.rk_xxx[6] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[6] + rk_eta[6]; |
||||
acadoWorkspace.rk_xxx[7] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[7] + rk_eta[7]; |
||||
acadoWorkspace.rk_xxx[8] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[8] + rk_eta[8]; |
||||
acadoWorkspace.rk_xxx[9] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[9] + rk_eta[9]; |
||||
acadoWorkspace.rk_xxx[10] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[10] + rk_eta[10]; |
||||
acadoWorkspace.rk_xxx[11] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[11] + rk_eta[11]; |
||||
acadoWorkspace.rk_xxx[12] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[12] + rk_eta[12]; |
||||
acadoWorkspace.rk_xxx[13] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[13] + rk_eta[13]; |
||||
acadoWorkspace.rk_xxx[14] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[14] + rk_eta[14]; |
||||
acadoWorkspace.rk_xxx[15] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[15] + rk_eta[15]; |
||||
acadoWorkspace.rk_xxx[16] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[16] + rk_eta[16]; |
||||
acadoWorkspace.rk_xxx[17] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[17] + rk_eta[17]; |
||||
acadoWorkspace.rk_xxx[18] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[18] + rk_eta[18]; |
||||
acadoWorkspace.rk_xxx[19] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[19] + rk_eta[19]; |
||||
acadoWorkspace.rk_xxx[20] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[20] + rk_eta[20]; |
||||
acadoWorkspace.rk_xxx[21] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[21] + rk_eta[21]; |
||||
acadoWorkspace.rk_xxx[22] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[22] + rk_eta[22]; |
||||
acadoWorkspace.rk_xxx[23] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[23] + rk_eta[23]; |
||||
acado_rhs_forw( acadoWorkspace.rk_xxx, &(acadoWorkspace.rk_kkk[ 24 ]) ); |
||||
acadoWorkspace.rk_xxx[0] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[24] + rk_eta[0]; |
||||
acadoWorkspace.rk_xxx[1] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[25] + rk_eta[1]; |
||||
acadoWorkspace.rk_xxx[2] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[26] + rk_eta[2]; |
||||
acadoWorkspace.rk_xxx[3] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[27] + rk_eta[3]; |
||||
acadoWorkspace.rk_xxx[4] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[28] + rk_eta[4]; |
||||
acadoWorkspace.rk_xxx[5] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[29] + rk_eta[5]; |
||||
acadoWorkspace.rk_xxx[6] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[30] + rk_eta[6]; |
||||
acadoWorkspace.rk_xxx[7] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[31] + rk_eta[7]; |
||||
acadoWorkspace.rk_xxx[8] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[32] + rk_eta[8]; |
||||
acadoWorkspace.rk_xxx[9] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[33] + rk_eta[9]; |
||||
acadoWorkspace.rk_xxx[10] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[34] + rk_eta[10]; |
||||
acadoWorkspace.rk_xxx[11] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[35] + rk_eta[11]; |
||||
acadoWorkspace.rk_xxx[12] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[36] + rk_eta[12]; |
||||
acadoWorkspace.rk_xxx[13] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[37] + rk_eta[13]; |
||||
acadoWorkspace.rk_xxx[14] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[38] + rk_eta[14]; |
||||
acadoWorkspace.rk_xxx[15] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[39] + rk_eta[15]; |
||||
acadoWorkspace.rk_xxx[16] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[40] + rk_eta[16]; |
||||
acadoWorkspace.rk_xxx[17] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[41] + rk_eta[17]; |
||||
acadoWorkspace.rk_xxx[18] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[42] + rk_eta[18]; |
||||
acadoWorkspace.rk_xxx[19] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[43] + rk_eta[19]; |
||||
acadoWorkspace.rk_xxx[20] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[44] + rk_eta[20]; |
||||
acadoWorkspace.rk_xxx[21] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[45] + rk_eta[21]; |
||||
acadoWorkspace.rk_xxx[22] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[46] + rk_eta[22]; |
||||
acadoWorkspace.rk_xxx[23] = + (real_t)2.4999999999999991e-02*acadoWorkspace.rk_kkk[47] + rk_eta[23]; |
||||
acado_rhs_forw( acadoWorkspace.rk_xxx, &(acadoWorkspace.rk_kkk[ 48 ]) ); |
||||
acadoWorkspace.rk_xxx[0] = + (real_t)4.9999999999999982e-02*acadoWorkspace.rk_kkk[48] + rk_eta[0]; |
||||
acadoWorkspace.rk_xxx[1] = + (real_t)4.9999999999999982e-02*acadoWorkspace.rk_kkk[49] + rk_eta[1]; |
||||
acadoWorkspace.rk_xxx[2] = + (real_t)4.9999999999999982e-02*acadoWorkspace.rk_kkk[50] + rk_eta[2]; |
||||
acadoWorkspace.rk_xxx[3] = + (real_t)4.9999999999999982e-02*acadoWorkspace.rk_kkk[51] + rk_eta[3]; |
||||
acadoWorkspace.rk_xxx[4] = + (real_t)4.9999999999999982e-02*acadoWorkspace.rk_kkk[52] + rk_eta[4]; |
||||
acadoWorkspace.rk_xxx[5] = + (real_t)4.9999999999999982e-02*acadoWorkspace.rk_kkk[53] + rk_eta[5]; |
||||
acadoWorkspace.rk_xxx[6] = + (real_t)4.9999999999999982e-02*acadoWorkspace.rk_kkk[54] + rk_eta[6]; |
||||
acadoWorkspace.rk_xxx[7] = + (real_t)4.9999999999999982e-02*acadoWorkspace.rk_kkk[55] + rk_eta[7]; |
||||
acadoWorkspace.rk_xxx[8] = + (real_t)4.9999999999999982e-02*acadoWorkspace.rk_kkk[56] + rk_eta[8]; |
||||
acadoWorkspace.rk_xxx[9] = + (real_t)4.9999999999999982e-02*acadoWorkspace.rk_kkk[57] + rk_eta[9]; |
||||
acadoWorkspace.rk_xxx[10] = + (real_t)4.9999999999999982e-02*acadoWorkspace.rk_kkk[58] + rk_eta[10]; |
||||
acadoWorkspace.rk_xxx[11] = + (real_t)4.9999999999999982e-02*acadoWorkspace.rk_kkk[59] + rk_eta[11]; |
||||
acadoWorkspace.rk_xxx[12] = + (real_t)4.9999999999999982e-02*acadoWorkspace.rk_kkk[60] + rk_eta[12]; |
||||
acadoWorkspace.rk_xxx[13] = + (real_t)4.9999999999999982e-02*acadoWorkspace.rk_kkk[61] + rk_eta[13]; |
||||
acadoWorkspace.rk_xxx[14] = + (real_t)4.9999999999999982e-02*acadoWorkspace.rk_kkk[62] + rk_eta[14]; |
||||
acadoWorkspace.rk_xxx[15] = + (real_t)4.9999999999999982e-02*acadoWorkspace.rk_kkk[63] + rk_eta[15]; |
||||
acadoWorkspace.rk_xxx[16] = + (real_t)4.9999999999999982e-02*acadoWorkspace.rk_kkk[64] + rk_eta[16]; |
||||
acadoWorkspace.rk_xxx[17] = + (real_t)4.9999999999999982e-02*acadoWorkspace.rk_kkk[65] + rk_eta[17]; |
||||
acadoWorkspace.rk_xxx[18] = + (real_t)4.9999999999999982e-02*acadoWorkspace.rk_kkk[66] + rk_eta[18]; |
||||
acadoWorkspace.rk_xxx[19] = + (real_t)4.9999999999999982e-02*acadoWorkspace.rk_kkk[67] + rk_eta[19]; |
||||
acadoWorkspace.rk_xxx[20] = + (real_t)4.9999999999999982e-02*acadoWorkspace.rk_kkk[68] + rk_eta[20]; |
||||
acadoWorkspace.rk_xxx[21] = + (real_t)4.9999999999999982e-02*acadoWorkspace.rk_kkk[69] + rk_eta[21]; |
||||
acadoWorkspace.rk_xxx[22] = + (real_t)4.9999999999999982e-02*acadoWorkspace.rk_kkk[70] + rk_eta[22]; |
||||
acadoWorkspace.rk_xxx[23] = + (real_t)4.9999999999999982e-02*acadoWorkspace.rk_kkk[71] + rk_eta[23]; |
||||
acado_rhs_forw( acadoWorkspace.rk_xxx, &(acadoWorkspace.rk_kkk[ 72 ]) ); |
||||
rk_eta[0] += + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[0] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[24] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[48] + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[72]; |
||||
rk_eta[1] += + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[1] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[25] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[49] + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[73]; |
||||
rk_eta[2] += + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[2] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[26] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[50] + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[74]; |
||||
rk_eta[3] += + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[3] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[27] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[51] + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[75]; |
||||
rk_eta[4] += + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[4] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[28] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[52] + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[76]; |
||||
rk_eta[5] += + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[5] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[29] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[53] + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[77]; |
||||
rk_eta[6] += + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[6] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[30] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[54] + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[78]; |
||||
rk_eta[7] += + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[7] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[31] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[55] + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[79]; |
||||
rk_eta[8] += + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[8] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[32] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[56] + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[80]; |
||||
rk_eta[9] += + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[9] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[33] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[57] + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[81]; |
||||
rk_eta[10] += + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[10] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[34] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[58] + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[82]; |
||||
rk_eta[11] += + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[11] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[35] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[59] + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[83]; |
||||
rk_eta[12] += + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[12] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[36] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[60] + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[84]; |
||||
rk_eta[13] += + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[13] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[37] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[61] + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[85]; |
||||
rk_eta[14] += + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[14] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[38] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[62] + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[86]; |
||||
rk_eta[15] += + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[15] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[39] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[63] + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[87]; |
||||
rk_eta[16] += + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[16] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[40] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[64] + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[88]; |
||||
rk_eta[17] += + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[17] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[41] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[65] + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[89]; |
||||
rk_eta[18] += + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[18] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[42] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[66] + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[90]; |
||||
rk_eta[19] += + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[19] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[43] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[67] + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[91]; |
||||
rk_eta[20] += + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[20] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[44] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[68] + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[92]; |
||||
rk_eta[21] += + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[21] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[45] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[69] + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[93]; |
||||
rk_eta[22] += + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[22] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[46] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[70] + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[94]; |
||||
rk_eta[23] += + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[23] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[47] + (real_t)1.6666666666666659e-02*acadoWorkspace.rk_kkk[71] + (real_t)8.3333333333333297e-03*acadoWorkspace.rk_kkk[95]; |
||||
acadoWorkspace.rk_ttt += 1.0000000000000000e+00; |
||||
} |
||||
} |
||||
error = 0; |
||||
return error; |
||||
} |
||||
|
@ -0,0 +1,70 @@ |
||||
/*
|
||||
* This file was auto-generated using the ACADO Toolkit. |
||||
*
|
||||
* While ACADO Toolkit is free software released under the terms of |
||||
* the GNU Lesser General Public License (LGPL), the generated code |
||||
* as such remains the property of the user who used ACADO Toolkit |
||||
* to generate this code. In particular, user dependent data of the code |
||||
* do not inherit the GNU LGPL license. On the other hand, parts of the |
||||
* generated code that are a direct copy of source code from the |
||||
* ACADO Toolkit or the software tools it is based on, remain, as derived |
||||
* work, automatically covered by the LGPL license. |
||||
*
|
||||
* ACADO Toolkit is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
||||
*
|
||||
*/ |
||||
|
||||
|
||||
extern "C" |
||||
{ |
||||
#include "acado_common.h" |
||||
} |
||||
|
||||
#include "INCLUDE/QProblem.hpp" |
||||
|
||||
#if ACADO_COMPUTE_COVARIANCE_MATRIX == 1 |
||||
#include "INCLUDE/EXTRAS/SolutionAnalysis.hpp" |
||||
#endif /* ACADO_COMPUTE_COVARIANCE_MATRIX */ |
||||
|
||||
static int acado_nWSR; |
||||
|
||||
|
||||
|
||||
#if ACADO_COMPUTE_COVARIANCE_MATRIX == 1 |
||||
static SolutionAnalysis acado_sa; |
||||
#endif /* ACADO_COMPUTE_COVARIANCE_MATRIX */ |
||||
|
||||
int acado_solve( void ) |
||||
{ |
||||
acado_nWSR = QPOASES_NWSRMAX; |
||||
|
||||
QProblem qp(24, 40); |
||||
|
||||
returnValue retVal = qp.init(acadoWorkspace.H, acadoWorkspace.g, acadoWorkspace.A, acadoWorkspace.lb, acadoWorkspace.ub, acadoWorkspace.lbA, acadoWorkspace.ubA, acado_nWSR, acadoWorkspace.y); |
||||
|
||||
qp.getPrimalSolution( acadoWorkspace.x ); |
||||
qp.getDualSolution( acadoWorkspace.y ); |
||||
|
||||
#if ACADO_COMPUTE_COVARIANCE_MATRIX == 1 |
||||
|
||||
if (retVal != SUCCESSFUL_RETURN) |
||||
return (int)retVal; |
||||
|
||||
retVal = acado_sa.getHessianInverse( &qp,var ); |
||||
|
||||
#endif /* ACADO_COMPUTE_COVARIANCE_MATRIX */ |
||||
|
||||
return (int)retVal; |
||||
} |
||||
|
||||
int acado_getNWSR( void ) |
||||
{ |
||||
return acado_nWSR; |
||||
} |
||||
|
||||
const char* acado_getErrorString( int error ) |
||||
{ |
||||
return MessageHandling::getErrorString( error ); |
||||
} |
@ -0,0 +1,65 @@ |
||||
/*
|
||||
* This file was auto-generated using the ACADO Toolkit. |
||||
*
|
||||
* While ACADO Toolkit is free software released under the terms of |
||||
* the GNU Lesser General Public License (LGPL), the generated code |
||||
* as such remains the property of the user who used ACADO Toolkit |
||||
* to generate this code. In particular, user dependent data of the code |
||||
* do not inherit the GNU LGPL license. On the other hand, parts of the |
||||
* generated code that are a direct copy of source code from the |
||||
* ACADO Toolkit or the software tools it is based on, remain, as derived |
||||
* work, automatically covered by the LGPL license. |
||||
*
|
||||
* ACADO Toolkit is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
||||
*
|
||||
*/ |
||||
|
||||
|
||||
#ifndef QPOASES_HEADER |
||||
#define QPOASES_HEADER |
||||
|
||||
#ifdef PC_DEBUG |
||||
#include <stdio.h> |
||||
#endif /* PC_DEBUG */ |
||||
|
||||
#include <math.h> |
||||
|
||||
#ifdef __cplusplus |
||||
#define EXTERNC extern "C" |
||||
#else |
||||
#define EXTERNC |
||||
#endif |
||||
|
||||
/*
|
||||
* A set of options for qpOASES |
||||
*/ |
||||
|
||||
/** Maximum number of optimization variables. */ |
||||
#define QPOASES_NVMAX 24 |
||||
/** Maximum number of constraints. */ |
||||
#define QPOASES_NCMAX 40 |
||||
/** Maximum number of working set recalculations. */ |
||||
#define QPOASES_NWSRMAX 500 |
||||
/** Print level for qpOASES. */ |
||||
#define QPOASES_PRINTLEVEL PL_NONE |
||||
/** The value of EPS */ |
||||
#define QPOASES_EPS 2.221e-16 |
||||
/** Internally used floating point type */ |
||||
typedef double real_t; |
||||
|
||||
/*
|
||||
* Forward function declarations |
||||
*/ |
||||
|
||||
/** A function that calls the QP solver */ |
||||
EXTERNC int acado_solve( void ); |
||||
|
||||
/** Get the number of active set changes */ |
||||
EXTERNC int acado_getNWSR( void ); |
||||
|
||||
/** Get the error string. */ |
||||
const char* acado_getErrorString( int error ); |
||||
|
||||
#endif /* QPOASES_HEADER */ |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,30 @@ |
||||
import os |
||||
|
||||
from cffi import FFI |
||||
|
||||
mpc_dir = os.path.dirname(os.path.abspath(__file__)) |
||||
libmpc_fn = os.path.join(mpc_dir, "libmpc.so") |
||||
|
||||
ffi = FFI() |
||||
ffi.cdef(""" |
||||
typedef struct { |
||||
double x, y, psi, delta, t; |
||||
} state_t; |
||||
|
||||
typedef struct { |
||||
double x[21]; |
||||
double y[21]; |
||||
double psi[21]; |
||||
double delta[21]; |
||||
double rate[20]; |
||||
double cost; |
||||
} log_t; |
||||
|
||||
void init(double pathCost, double laneCost, double headingCost, double steerRateCost); |
||||
void init_weights(double pathCost, double laneCost, double headingCost, double steerRateCost); |
||||
int run_mpc(state_t * x0, log_t * solution, |
||||
double l_poly[4], double r_poly[4], double d_poly[4], |
||||
double l_prob, double r_prob, double curvature_factor, double v_ref, double lane_width); |
||||
""") |
||||
|
||||
libmpc = ffi.dlopen(libmpc_fn) |
@ -0,0 +1,123 @@ |
||||
import os |
||||
import math |
||||
|
||||
import cereal.messaging as messaging |
||||
from selfdrive.swaglog import cloudlog |
||||
from common.realtime import sec_since_boot |
||||
from selfdrive.controls.lib.radar_helpers import _LEAD_ACCEL_TAU |
||||
from selfdrive.controls.lib.longitudinal_mpc import libmpc_py |
||||
from selfdrive.controls.lib.drive_helpers import MPC_COST_LONG |
||||
|
||||
LOG_MPC = os.environ.get('LOG_MPC', False) |
||||
|
||||
|
||||
class LongitudinalMpc(): |
||||
def __init__(self, mpc_id): |
||||
self.mpc_id = mpc_id |
||||
|
||||
self.setup_mpc() |
||||
self.v_mpc = 0.0 |
||||
self.v_mpc_future = 0.0 |
||||
self.a_mpc = 0.0 |
||||
self.v_cruise = 0.0 |
||||
self.prev_lead_status = False |
||||
self.prev_lead_x = 0.0 |
||||
self.new_lead = False |
||||
|
||||
self.last_cloudlog_t = 0.0 |
||||
|
||||
def send_mpc_solution(self, pm, qp_iterations, calculation_time): |
||||
qp_iterations = max(0, qp_iterations) |
||||
dat = messaging.new_message() |
||||
dat.init('liveLongitudinalMpc') |
||||
dat.liveLongitudinalMpc.xEgo = list(self.mpc_solution[0].x_ego) |
||||
dat.liveLongitudinalMpc.vEgo = list(self.mpc_solution[0].v_ego) |
||||
dat.liveLongitudinalMpc.aEgo = list(self.mpc_solution[0].a_ego) |
||||
dat.liveLongitudinalMpc.xLead = list(self.mpc_solution[0].x_l) |
||||
dat.liveLongitudinalMpc.vLead = list(self.mpc_solution[0].v_l) |
||||
dat.liveLongitudinalMpc.cost = self.mpc_solution[0].cost |
||||
dat.liveLongitudinalMpc.aLeadTau = self.a_lead_tau |
||||
dat.liveLongitudinalMpc.qpIterations = qp_iterations |
||||
dat.liveLongitudinalMpc.mpcId = self.mpc_id |
||||
dat.liveLongitudinalMpc.calculationTime = calculation_time |
||||
pm.send('liveLongitudinalMpc', dat) |
||||
|
||||
def setup_mpc(self): |
||||
ffi, self.libmpc = libmpc_py.get_libmpc(self.mpc_id) |
||||
self.libmpc.init(MPC_COST_LONG.TTC, MPC_COST_LONG.DISTANCE, |
||||
MPC_COST_LONG.ACCELERATION, MPC_COST_LONG.JERK) |
||||
|
||||
self.mpc_solution = ffi.new("log_t *") |
||||
self.cur_state = ffi.new("state_t *") |
||||
self.cur_state[0].v_ego = 0 |
||||
self.cur_state[0].a_ego = 0 |
||||
self.a_lead_tau = _LEAD_ACCEL_TAU |
||||
|
||||
def set_cur_state(self, v, a): |
||||
self.cur_state[0].v_ego = v |
||||
self.cur_state[0].a_ego = a |
||||
|
||||
def update(self, pm, CS, lead, v_cruise_setpoint): |
||||
v_ego = CS.vEgo |
||||
|
||||
# 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 = lead.aLeadTau |
||||
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 = v_ego + 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) |
||||
|
||||
if LOG_MPC: |
||||
self.send_mpc_solution(pm, 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 |
||||
crashing = any(lead - ego < -50 for (lead, ego) in zip(self.mpc_solution[0].x_l, self.mpc_solution[0].x_ego)) |
||||
nans = any(math.isnan(x) for x in self.mpc_solution[0].v_ego) |
||||
backwards = min(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 = v_ego |
||||
self.cur_state[0].a_ego = 0.0 |
||||
self.v_mpc = v_ego |
||||
self.a_mpc = CS.aEgo |
||||
self.prev_lead_status = False |
@ -0,0 +1,130 @@ |
||||
from cereal import log |
||||
from common.numpy_fast import clip, interp |
||||
from selfdrive.controls.lib.pid import PIController |
||||
|
||||
LongCtrlState = log.ControlsState.LongControlState |
||||
|
||||
STOPPING_EGO_SPEED = 0.5 |
||||
MIN_CAN_SPEED = 0.3 # TODO: parametrize this in car interface |
||||
STOPPING_TARGET_SPEED = MIN_CAN_SPEED + 0.01 |
||||
STARTING_TARGET_SPEED = 0.5 |
||||
BRAKE_THRESHOLD_TO_PID = 0.2 |
||||
|
||||
STOPPING_BRAKE_RATE = 0.2 # brake_travel/s while trying to stop |
||||
STARTING_BRAKE_RATE = 0.8 # brake_travel/s while releasing on restart |
||||
BRAKE_STOPPING_TARGET = 0.5 # apply at least this amount of brake to maintain the vehicle stationary |
||||
|
||||
_MAX_SPEED_ERROR_BP = [0., 30.] # speed breakpoints |
||||
_MAX_SPEED_ERROR_V = [1.5, .8] # max positive v_pid error VS actual speed; this avoids controls windup due to slow pedal resp |
||||
|
||||
RATE = 100.0 |
||||
|
||||
|
||||
def long_control_state_trans(active, long_control_state, v_ego, v_target, v_pid, |
||||
output_gb, brake_pressed, cruise_standstill): |
||||
"""Update longitudinal control state machine""" |
||||
stopping_condition = (v_ego < 2.0 and cruise_standstill) or \ |
||||
(v_ego < STOPPING_EGO_SPEED and \ |
||||
((v_pid < STOPPING_TARGET_SPEED and v_target < STOPPING_TARGET_SPEED) or |
||||
brake_pressed)) |
||||
|
||||
starting_condition = v_target > STARTING_TARGET_SPEED and not cruise_standstill |
||||
|
||||
if not active: |
||||
long_control_state = LongCtrlState.off |
||||
|
||||
else: |
||||
if long_control_state == LongCtrlState.off: |
||||
if active: |
||||
long_control_state = LongCtrlState.pid |
||||
|
||||
elif long_control_state == LongCtrlState.pid: |
||||
if stopping_condition: |
||||
long_control_state = LongCtrlState.stopping |
||||
|
||||
elif long_control_state == LongCtrlState.stopping: |
||||
if starting_condition: |
||||
long_control_state = LongCtrlState.starting |
||||
|
||||
elif long_control_state == LongCtrlState.starting: |
||||
if stopping_condition: |
||||
long_control_state = LongCtrlState.stopping |
||||
elif output_gb >= -BRAKE_THRESHOLD_TO_PID: |
||||
long_control_state = LongCtrlState.pid |
||||
|
||||
return long_control_state |
||||
|
||||
|
||||
class LongControl(): |
||||
def __init__(self, CP, compute_gb): |
||||
self.long_control_state = LongCtrlState.off # initialized to off |
||||
self.pid = PIController((CP.longitudinalTuning.kpBP, CP.longitudinalTuning.kpV), |
||||
(CP.longitudinalTuning.kiBP, CP.longitudinalTuning.kiV), |
||||
rate=RATE, |
||||
sat_limit=0.8, |
||||
convert=compute_gb) |
||||
self.v_pid = 0.0 |
||||
self.last_output_gb = 0.0 |
||||
|
||||
def reset(self, v_pid): |
||||
"""Reset PID controller and change setpoint""" |
||||
self.pid.reset() |
||||
self.v_pid = v_pid |
||||
|
||||
def update(self, active, v_ego, brake_pressed, standstill, cruise_standstill, v_cruise, v_target, v_target_future, a_target, CP): |
||||
"""Update longitudinal control. This updates the state machine and runs a PID loop""" |
||||
# Actuation limits |
||||
gas_max = interp(v_ego, CP.gasMaxBP, CP.gasMaxV) |
||||
brake_max = interp(v_ego, CP.brakeMaxBP, CP.brakeMaxV) |
||||
|
||||
# Update state machine |
||||
output_gb = self.last_output_gb |
||||
self.long_control_state = long_control_state_trans(active, self.long_control_state, v_ego, |
||||
v_target_future, self.v_pid, output_gb, |
||||
brake_pressed, cruise_standstill) |
||||
|
||||
v_ego_pid = max(v_ego, MIN_CAN_SPEED) # Without this we get jumps, CAN bus reports 0 when speed < 0.3 |
||||
|
||||
if self.long_control_state == LongCtrlState.off: |
||||
self.v_pid = v_ego_pid |
||||
self.pid.reset() |
||||
output_gb = 0. |
||||
|
||||
# tracking objects and driving |
||||
elif self.long_control_state == LongCtrlState.pid: |
||||
self.v_pid = v_target |
||||
self.pid.pos_limit = gas_max |
||||
self.pid.neg_limit = - brake_max |
||||
|
||||
# Toyota starts braking more when it thinks you want to stop |
||||
# Freeze the integrator so we don't accelerate to compensate, and don't allow positive acceleration |
||||
prevent_overshoot = not CP.stoppingControl and v_ego < 1.5 and v_target_future < 0.7 |
||||
deadzone = interp(v_ego_pid, CP.longitudinalTuning.deadzoneBP, CP.longitudinalTuning.deadzoneV) |
||||
|
||||
output_gb = self.pid.update(self.v_pid, v_ego_pid, speed=v_ego_pid, deadzone=deadzone, feedforward=a_target, freeze_integrator=prevent_overshoot) |
||||
|
||||
if prevent_overshoot: |
||||
output_gb = min(output_gb, 0.0) |
||||
|
||||
# Intention is to stop, switch to a different brake control until we stop |
||||
elif self.long_control_state == LongCtrlState.stopping: |
||||
# Keep applying brakes until the car is stopped |
||||
if not standstill or output_gb > -BRAKE_STOPPING_TARGET: |
||||
output_gb -= STOPPING_BRAKE_RATE / RATE |
||||
output_gb = clip(output_gb, -brake_max, gas_max) |
||||
|
||||
self.v_pid = v_ego |
||||
self.pid.reset() |
||||
|
||||
# Intention is to move again, release brake fast before handing control to PID |
||||
elif self.long_control_state == LongCtrlState.starting: |
||||
if output_gb < -0.2: |
||||
output_gb += STARTING_BRAKE_RATE / RATE |
||||
self.v_pid = v_ego |
||||
self.pid.reset() |
||||
|
||||
self.last_output_gb = output_gb |
||||
final_gas = clip(output_gb, 0., gas_max) |
||||
final_brake = -clip(output_gb, -brake_max, 0.) |
||||
|
||||
return final_gas, final_brake |
@ -0,0 +1,2 @@ |
||||
generator |
||||
lib_qp/ |
@ -0,0 +1,32 @@ |
||||
Import('env', 'arch') |
||||
|
||||
|
||||
cpp_path = [ |
||||
"#phonelibs/acado/include", |
||||
"#phonelibs/acado/include/acado", |
||||
"#phonelibs/qpoases/INCLUDE", |
||||
"#phonelibs/qpoases/INCLUDE/EXTRAS", |
||||
"#phonelibs/qpoases/SRC/", |
||||
"#phonelibs/qpoases", |
||||
"lib_mpc_export" |
||||
|
||||
] |
||||
|
||||
mpc_files = [ |
||||
"longitudinal_mpc.c", |
||||
Glob("lib_mpc_export/*.c"), |
||||
Glob("lib_mpc_export/*.cpp"), |
||||
] |
||||
|
||||
interface_dir = Dir('lib_mpc_export') |
||||
|
||||
SConscript(['#phonelibs/qpoases/SConscript'], variant_dir='lib_qp', exports=['interface_dir']) |
||||
|
||||
env.SharedLibrary('mpc1', mpc_files, LIBS=['m', 'qpoases'], LIBPATH=['lib_qp'], CPPPATH=cpp_path) |
||||
env.SharedLibrary('mpc2', mpc_files, LIBS=['m', 'qpoases'], LIBPATH=['lib_qp'], CPPPATH=cpp_path) |
||||
|
||||
# if arch != "aarch64": |
||||
# acado_libs = [File("#phonelibs/acado/x64/lib/libacado_toolkit.a"), |
||||
# File("#phonelibs/acado/x64/lib/libacado_casadi.a"), |
||||
# File("#phonelibs/acado/x64/lib/libacado_csparse.a")] |
||||
# env.Program('generator', 'generator.cpp', LIBS=acado_libs, CPPPATH=cpp_path) |
@ -0,0 +1,97 @@ |
||||
#include <acado_code_generation.hpp> |
||||
|
||||
const int controlHorizon = 50; |
||||
|
||||
using namespace std; |
||||
|
||||
#define G 9.81 |
||||
#define TR 1.8 |
||||
|
||||
#define RW(v_ego, v_l) (v_ego * TR - (v_l - v_ego) * TR + v_ego*v_ego/(2*G) - v_l*v_l / (2*G)) |
||||
#define NORM_RW_ERROR(v_ego, v_l, p) ((RW(v_ego, v_l) + 4.0 - p)/(sqrt(v_ego + 0.5) + 0.1)) |
||||
|
||||
int main( ) |
||||
{ |
||||
USING_NAMESPACE_ACADO |
||||
|
||||
|
||||
DifferentialEquation f; |
||||
|
||||
DifferentialState x_ego, v_ego, a_ego; |
||||
OnlineData x_l, v_l; |
||||
|
||||
Control j_ego; |
||||
|
||||
auto desired = 4.0 + RW(v_ego, v_l); |
||||
auto d_l = x_l - x_ego; |
||||
|
||||
// Equations of motion
|
||||
f << dot(x_ego) == v_ego; |
||||
f << dot(v_ego) == a_ego; |
||||
f << dot(a_ego) == j_ego; |
||||
|
||||
// Running cost
|
||||
Function h; |
||||
h << exp(0.3 * NORM_RW_ERROR(v_ego, v_l, d_l)) - 1; |
||||
h << (d_l - desired) / (0.05 * v_ego + 0.5); |
||||
h << a_ego * (0.1 * v_ego + 1.0); |
||||
h << j_ego * (0.1 * v_ego + 1.0); |
||||
|
||||
// Weights are defined in mpc.
|
||||
BMatrix Q(4,4); Q.setAll(true); |
||||
|
||||
// Terminal cost
|
||||
Function hN; |
||||
hN << exp(0.3 * NORM_RW_ERROR(v_ego, v_l, d_l)) - 1; |
||||
hN << (d_l - desired) / (0.05 * v_ego + 0.5); |
||||
hN << a_ego * (0.1 * v_ego + 1.0); |
||||
|
||||
// Weights are defined in mpc.
|
||||
BMatrix QN(3,3); QN.setAll(true); |
||||
|
||||
// Non uniform time grid
|
||||
// First 5 timesteps are 0.2, after that it's 0.6
|
||||
DMatrix numSteps(20, 1); |
||||
for (int i = 0; i < 5; i++){ |
||||
numSteps(i) = 1; |
||||
} |
||||
for (int i = 5; i < 20; i++){ |
||||
numSteps(i) = 3; |
||||
} |
||||
|
||||
// Setup Optimal Control Problem
|
||||
const double tStart = 0.0; |
||||
const double tEnd = 10.0; |
||||
|
||||
OCP ocp( tStart, tEnd, numSteps); |
||||
ocp.subjectTo(f); |
||||
|
||||
ocp.minimizeLSQ(Q, h); |
||||
ocp.minimizeLSQEndTerm(QN, hN); |
||||
|
||||
ocp.subjectTo( 0.0 <= v_ego); |
||||
ocp.setNOD(2); |
||||
|
||||
OCPexport mpc(ocp); |
||||
mpc.set( HESSIAN_APPROXIMATION, GAUSS_NEWTON ); |
||||
mpc.set( DISCRETIZATION_TYPE, MULTIPLE_SHOOTING ); |
||||
mpc.set( INTEGRATOR_TYPE, INT_RK4 ); |
||||
mpc.set( NUM_INTEGRATOR_STEPS, controlHorizon); |
||||
mpc.set( MAX_NUM_QP_ITERATIONS, 500); |
||||
mpc.set( CG_USE_VARIABLE_WEIGHTING_MATRIX, YES); |
||||
|
||||
mpc.set( SPARSE_QP_SOLUTION, CONDENSING ); |
||||
mpc.set( QP_SOLVER, QP_QPOASES ); |
||||
mpc.set( HOTSTART_QP, YES ); |
||||
mpc.set( GENERATE_TEST_FILE, NO); |
||||
mpc.set( GENERATE_MAKE_FILE, NO ); |
||||
mpc.set( GENERATE_MATLAB_INTERFACE, NO ); |
||||
mpc.set( GENERATE_SIMULINK_INTERFACE, NO ); |
||||
|
||||
if (mpc.exportCode( "lib_mpc_export" ) != SUCCESSFUL_RETURN) |
||||
exit( EXIT_FAILURE ); |
||||
|
||||
mpc.printDimensionsQP( ); |
||||
|
||||
return EXIT_SUCCESS; |
||||
} |
@ -0,0 +1,212 @@ |
||||
/*
|
||||
* This file was auto-generated using the ACADO Toolkit. |
||||
*
|
||||
* While ACADO Toolkit is free software released under the terms of |
||||
* the GNU Lesser General Public License (LGPL), the generated code |
||||
* as such remains the property of the user who used ACADO Toolkit |
||||
* to generate this code. In particular, user dependent data of the code |
||||
* do not inherit the GNU LGPL license. On the other hand, parts of the |
||||
* generated code that are a direct copy of source code from the |
||||
* ACADO Toolkit or the software tools it is based on, remain, as derived |
||||
* work, automatically covered by the LGPL license. |
||||
*
|
||||
* ACADO Toolkit is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
||||
*
|
||||
*/ |
||||
|
||||
|
||||
#include "acado_auxiliary_functions.h" |
||||
|
||||
#include <stdio.h> |
||||
|
||||
real_t* acado_getVariablesX( ) |
||||
{ |
||||
return acadoVariables.x; |
||||
} |
||||
|
||||
real_t* acado_getVariablesU( ) |
||||
{ |
||||
return acadoVariables.u; |
||||
} |
||||
|
||||
#if ACADO_NY > 0 |
||||
real_t* acado_getVariablesY( ) |
||||
{ |
||||
return acadoVariables.y; |
||||
} |
||||
#endif |
||||
|
||||
#if ACADO_NYN > 0 |
||||
real_t* acado_getVariablesYN( ) |
||||
{ |
||||
return acadoVariables.yN; |
||||
} |
||||
#endif |
||||
|
||||
real_t* acado_getVariablesX0( ) |
||||
{ |
||||
#if ACADO_INITIAL_VALUE_FIXED |
||||
return acadoVariables.x0; |
||||
#else |
||||
return 0; |
||||
#endif |
||||
} |
||||
|
||||
/** Print differential variables. */ |
||||
void acado_printDifferentialVariables( ) |
||||
{ |
||||
int i, j; |
||||
printf("\nDifferential variables:\n[\n"); |
||||
for (i = 0; i < ACADO_N + 1; ++i) |
||||
{ |
||||
for (j = 0; j < ACADO_NX; ++j) |
||||
printf("\t%e", acadoVariables.x[i * ACADO_NX + j]); |
||||
printf("\n"); |
||||
} |
||||
printf("]\n\n"); |
||||
} |
||||
|
||||
/** Print control variables. */ |
||||
void acado_printControlVariables( ) |
||||
{ |
||||
int i, j; |
||||
printf("\nControl variables:\n[\n"); |
||||
for (i = 0; i < ACADO_N; ++i) |
||||
{ |
||||
for (j = 0; j < ACADO_NU; ++j) |
||||
printf("\t%e", acadoVariables.u[i * ACADO_NU + j]); |
||||
printf("\n"); |
||||
} |
||||
printf("]\n\n"); |
||||
} |
||||
|
||||
/** Print ACADO code generation notice. */ |
||||
void acado_printHeader( ) |
||||
{ |
||||
printf( |
||||
"\nACADO Toolkit -- A Toolkit for Automatic Control and Dynamic Optimization.\n" |
||||
"Copyright (C) 2008-2015 by Boris Houska, Hans Joachim Ferreau,\n"
|
||||
"Milan Vukov and Rien Quirynen, KU Leuven.\n" |
||||
); |
||||
|
||||
printf( |
||||
"Developed within the Optimization in Engineering Center (OPTEC) under\n" |
||||
"supervision of Moritz Diehl. All rights reserved.\n\n" |
||||
"ACADO Toolkit is distributed under the terms of the GNU Lesser\n" |
||||
"General Public License 3 in the hope that it will be useful,\n" |
||||
"but WITHOUT ANY WARRANTY; without even the implied warranty of\n" |
||||
"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" |
||||
"GNU Lesser General Public License for more details.\n\n" |
||||
); |
||||
} |
||||
|
||||
#if !(defined _DSPACE) |
||||
#if (defined _WIN32 || defined _WIN64) && !(defined __MINGW32__ || defined __MINGW64__) |
||||
|
||||
void acado_tic( acado_timer* t ) |
||||
{ |
||||
QueryPerformanceFrequency(&t->freq); |
||||
QueryPerformanceCounter(&t->tic); |
||||
} |
||||
|
||||
real_t acado_toc( acado_timer* t ) |
||||
{ |
||||
QueryPerformanceCounter(&t->toc); |
||||
return ((t->toc.QuadPart - t->tic.QuadPart) / (real_t)t->freq.QuadPart); |
||||
} |
||||
|
||||
|
||||
#elif (defined __APPLE__) |
||||
|
||||
void acado_tic( acado_timer* t ) |
||||
{ |
||||
/* read current clock cycles */ |
||||
t->tic = mach_absolute_time(); |
||||
} |
||||
|
||||
real_t acado_toc( acado_timer* t ) |
||||
{ |
||||
|
||||
uint64_t duration; /* elapsed time in clock cycles*/ |
||||
|
||||
t->toc = mach_absolute_time(); |
||||
duration = t->toc - t->tic; |
||||
|
||||
/*conversion from clock cycles to nanoseconds*/ |
||||
mach_timebase_info(&(t->tinfo)); |
||||
duration *= t->tinfo.numer; |
||||
duration /= t->tinfo.denom; |
||||
|
||||
return (real_t)duration / 1e9; |
||||
} |
||||
|
||||
#else |
||||
|
||||
#if __STDC_VERSION__ >= 199901L |
||||
/* C99 mode */ |
||||
|
||||
/* read current time */ |
||||
void acado_tic( acado_timer* t ) |
||||
{ |
||||
gettimeofday(&t->tic, 0); |
||||
} |
||||
|
||||
/* return time passed since last call to tic on this timer */ |
||||
real_t acado_toc( acado_timer* t ) |
||||
{ |
||||
struct timeval temp; |
||||
|
||||
gettimeofday(&t->toc, 0); |
||||
|
||||
if ((t->toc.tv_usec - t->tic.tv_usec) < 0) |
||||
{ |
||||
temp.tv_sec = t->toc.tv_sec - t->tic.tv_sec - 1; |
||||
temp.tv_usec = 1000000 + t->toc.tv_usec - t->tic.tv_usec; |
||||
} |
||||
else |
||||
{ |
||||
temp.tv_sec = t->toc.tv_sec - t->tic.tv_sec; |
||||
temp.tv_usec = t->toc.tv_usec - t->tic.tv_usec; |
||||
} |
||||
|
||||
return (real_t)temp.tv_sec + (real_t)temp.tv_usec / 1e6; |
||||
} |
||||
|
||||
#else |
||||
/* ANSI */ |
||||
|
||||
/* read current time */ |
||||
void acado_tic( acado_timer* t ) |
||||
{ |
||||
clock_gettime(CLOCK_MONOTONIC, &t->tic); |
||||
} |
||||
|
||||
|
||||
/* return time passed since last call to tic on this timer */ |
||||
real_t acado_toc( acado_timer* t ) |
||||
{ |
||||
struct timespec temp; |
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &t->toc);
|
||||
|
||||
if ((t->toc.tv_nsec - t->tic.tv_nsec) < 0) |
||||
{ |
||||
temp.tv_sec = t->toc.tv_sec - t->tic.tv_sec - 1; |
||||
temp.tv_nsec = 1000000000+t->toc.tv_nsec - t->tic.tv_nsec; |
||||
} |
||||
else |
||||
{ |
||||
temp.tv_sec = t->toc.tv_sec - t->tic.tv_sec; |
||||
temp.tv_nsec = t->toc.tv_nsec - t->tic.tv_nsec; |
||||
} |
||||
|
||||
return (real_t)temp.tv_sec + (real_t)temp.tv_nsec / 1e9; |
||||
} |
||||
|
||||
#endif /* __STDC_VERSION__ >= 199901L */ |
||||
|
||||
#endif /* (defined _WIN32 || _WIN64) */ |
||||
|
||||
#endif |
@ -0,0 +1,138 @@ |
||||
/*
|
||||
* This file was auto-generated using the ACADO Toolkit. |
||||
*
|
||||
* While ACADO Toolkit is free software released under the terms of |
||||
* the GNU Lesser General Public License (LGPL), the generated code |
||||
* as such remains the property of the user who used ACADO Toolkit |
||||
* to generate this code. In particular, user dependent data of the code |
||||
* do not inherit the GNU LGPL license. On the other hand, parts of the |
||||
* generated code that are a direct copy of source code from the |
||||
* ACADO Toolkit or the software tools it is based on, remain, as derived |
||||
* work, automatically covered by the LGPL license. |
||||
*
|
||||
* ACADO Toolkit is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
||||
*
|
||||
*/ |
||||
|
||||
|
||||
#ifndef ACADO_AUXILIARY_FUNCTIONS_H |
||||
#define ACADO_AUXILIARY_FUNCTIONS_H |
||||
|
||||
#include "acado_common.h" |
||||
|
||||
#ifndef __MATLAB__ |
||||
#ifdef __cplusplus |
||||
extern "C" |
||||
{ |
||||
#endif /* __cplusplus */ |
||||
#endif /* __MATLAB__ */ |
||||
|
||||
/** Get pointer to the matrix with differential variables. */ |
||||
real_t* acado_getVariablesX( ); |
||||
|
||||
/** Get pointer to the matrix with control variables. */ |
||||
real_t* acado_getVariablesU( ); |
||||
|
||||
#if ACADO_NY > 0 |
||||
/** Get pointer to the matrix with references/measurements. */ |
||||
real_t* acado_getVariablesY( ); |
||||
#endif |
||||
|
||||
#if ACADO_NYN > 0 |
||||
/** Get pointer to the vector with references/measurement on the last node. */ |
||||
real_t* acado_getVariablesYN( ); |
||||
#endif |
||||
|
||||
/** Get pointer to the current state feedback vector. Only applicable for NMPC. */ |
||||
real_t* acado_getVariablesX0( ); |
||||
|
||||
/** Print differential variables. */ |
||||
void acado_printDifferentialVariables( ); |
||||
|
||||
/** Print control variables. */ |
||||
void acado_printControlVariables( ); |
||||
|
||||
/** Print ACADO code generation notice. */ |
||||
void acado_printHeader( ); |
||||
|
||||
/*
|
||||
* A huge thanks goes to Alexander Domahidi from ETHZ, Switzerland, for
|
||||
* providing us with the following timing routines. |
||||
*/ |
||||
|
||||
#if !(defined _DSPACE) |
||||
#if (defined _WIN32 || defined _WIN64) && !(defined __MINGW32__ || defined __MINGW64__) |
||||
|
||||
/* Use Windows QueryPerformanceCounter for timing. */ |
||||
#include <Windows.h> |
||||
|
||||
/** A structure for keeping internal timer data. */ |
||||
typedef struct acado_timer_ |
||||
{ |
||||
LARGE_INTEGER tic; |
||||
LARGE_INTEGER toc; |
||||
LARGE_INTEGER freq; |
||||
} acado_timer; |
||||
|
||||
|
||||
#elif (defined __APPLE__) |
||||
|
||||
#include "unistd.h" |
||||
#include <mach/mach_time.h> |
||||
|
||||
/** A structure for keeping internal timer data. */ |
||||
typedef struct acado_timer_ |
||||
{ |
||||
uint64_t tic; |
||||
uint64_t toc; |
||||
mach_timebase_info_data_t tinfo; |
||||
} acado_timer; |
||||
|
||||
#else |
||||
|
||||
/* Use POSIX clock_gettime() for timing on non-Windows machines. */ |
||||
#include <time.h> |
||||
|
||||
#if __STDC_VERSION__ >= 199901L |
||||
/* C99 mode of operation. */ |
||||
|
||||
#include <sys/stat.h> |
||||
#include <sys/time.h> |
||||
|
||||
typedef struct acado_timer_ |
||||
{ |
||||
struct timeval tic; |
||||
struct timeval toc; |
||||
} acado_timer; |
||||
|
||||
#else |
||||
/* ANSI C */ |
||||
|
||||
/** A structure for keeping internal timer data. */ |
||||
typedef struct acado_timer_ |
||||
{ |
||||
struct timespec tic; |
||||
struct timespec toc; |
||||
} acado_timer; |
||||
|
||||
#endif /* __STDC_VERSION__ >= 199901L */ |
||||
|
||||
#endif /* (defined _WIN32 || defined _WIN64) */ |
||||
|
||||
/** A function for measurement of the current time. */ |
||||
void acado_tic( acado_timer* t ); |
||||
|
||||
/** A function which returns the elapsed time. */ |
||||
real_t acado_toc( acado_timer* t ); |
||||
|
||||
#endif |
||||
|
||||
#ifndef __MATLAB__ |
||||
#ifdef __cplusplus |
||||
} /* extern "C" */ |
||||
#endif /* __cplusplus */ |
||||
#endif /* __MATLAB__ */ |
||||
|
||||
#endif /* ACADO_AUXILIARY_FUNCTIONS_H */ |
@ -0,0 +1,354 @@ |
||||
/*
|
||||
* This file was auto-generated using the ACADO Toolkit. |
||||
*
|
||||
* While ACADO Toolkit is free software released under the terms of |
||||
* the GNU Lesser General Public License (LGPL), the generated code |
||||
* as such remains the property of the user who used ACADO Toolkit |
||||
* to generate this code. In particular, user dependent data of the code |
||||
* do not inherit the GNU LGPL license. On the other hand, parts of the |
||||
* generated code that are a direct copy of source code from the |
||||
* ACADO Toolkit or the software tools it is based on, remain, as derived |
||||
* work, automatically covered by the LGPL license. |
||||
*
|
||||
* ACADO Toolkit is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
||||
*
|
||||
*/ |
||||
|
||||
|
||||
#ifndef ACADO_COMMON_H |
||||
#define ACADO_COMMON_H |
||||
|
||||
#include <math.h> |
||||
#include <string.h> |
||||
|
||||
#ifndef __MATLAB__ |
||||
#ifdef __cplusplus |
||||
extern "C" |
||||
{ |
||||
#endif /* __cplusplus */ |
||||
#endif /* __MATLAB__ */ |
||||
|
||||
/** \defgroup ACADO ACADO CGT generated module. */ |
||||
/** @{ */ |
||||
|
||||
/** qpOASES QP solver indicator. */ |
||||
#define ACADO_QPOASES 0 |
||||
#define ACADO_QPOASES3 1 |
||||
/** FORCES QP solver indicator.*/ |
||||
#define ACADO_FORCES 2 |
||||
/** qpDUNES QP solver indicator.*/ |
||||
#define ACADO_QPDUNES 3 |
||||
/** HPMPC QP solver indicator. */ |
||||
#define ACADO_HPMPC 4 |
||||
#define ACADO_GENERIC 5 |
||||
|
||||
/** Indicator for determining the QP solver used by the ACADO solver code. */ |
||||
#define ACADO_QP_SOLVER ACADO_QPOASES |
||||
|
||||
#include "acado_qpoases_interface.hpp" |
||||
|
||||
|
||||
/*
|
||||
* Common definitions |
||||
*/ |
||||
/** User defined block based condensing. */ |
||||
#define ACADO_BLOCK_CONDENSING 0 |
||||
/** Compute covariance matrix of the last state estimate. */ |
||||
#define ACADO_COMPUTE_COVARIANCE_MATRIX 0 |
||||
/** Flag indicating whether constraint values are hard-coded or not. */ |
||||
#define ACADO_HARDCODED_CONSTRAINT_VALUES 1 |
||||
/** Indicator for fixed initial state. */ |
||||
#define ACADO_INITIAL_STATE_FIXED 1 |
||||
/** Number of control/estimation intervals. */ |
||||
#define ACADO_N 20 |
||||
/** Number of online data values. */ |
||||
#define ACADO_NOD 2 |
||||
/** Number of path constraints. */ |
||||
#define ACADO_NPAC 0 |
||||
/** Number of control variables. */ |
||||
#define ACADO_NU 1 |
||||
/** Number of differential variables. */ |
||||
#define ACADO_NX 3 |
||||
/** Number of algebraic variables. */ |
||||
#define ACADO_NXA 0 |
||||
/** Number of differential derivative variables. */ |
||||
#define ACADO_NXD 0 |
||||
/** Number of references/measurements per node on the first N nodes. */ |
||||
#define ACADO_NY 4 |
||||
/** Number of references/measurements on the last (N + 1)st node. */ |
||||
#define ACADO_NYN 3 |
||||
/** Total number of QP optimization variables. */ |
||||
#define ACADO_QP_NV 23 |
||||
/** Number of Runge-Kutta stages per integration step. */ |
||||
#define ACADO_RK_NSTAGES 4 |
||||
/** Providing interface for arrival cost. */ |
||||
#define ACADO_USE_ARRIVAL_COST 0 |
||||
/** Indicator for usage of non-hard-coded linear terms in the objective. */ |
||||
#define ACADO_USE_LINEAR_TERMS 0 |
||||
/** Indicator for type of fixed weighting matrices. */ |
||||
#define ACADO_WEIGHTING_MATRICES_TYPE 2 |
||||
|
||||
|
||||
/*
|
||||
* Globally used structure definitions |
||||
*/ |
||||
|
||||
/** The structure containing the user data.
|
||||
*
|
||||
* Via this structure the user "communicates" with the solver code. |
||||
*/ |
||||
typedef struct ACADOvariables_ |
||||
{ |
||||
int dummy; |
||||
/** Matrix of size: 21 x 3 (row major format)
|
||||
*
|
||||
* Matrix containing 21 differential variable vectors. |
||||
*/ |
||||
real_t x[ 63 ]; |
||||
|
||||
/** Column vector of size: 20
|
||||
*
|
||||
* Matrix containing 20 control variable vectors. |
||||
*/ |
||||
real_t u[ 20 ]; |
||||
|
||||
/** Matrix of size: 21 x 2 (row major format)
|
||||
*
|
||||
* Matrix containing 21 online data vectors. |
||||
*/ |
||||
real_t od[ 42 ]; |
||||
|
||||
/** Column vector of size: 80
|
||||
*
|
||||
* Matrix containing 20 reference/measurement vectors of size 4 for first 20 nodes. |
||||
*/ |
||||
real_t y[ 80 ]; |
||||
|
||||
/** Column vector of size: 3
|
||||
*
|
||||
* Reference/measurement vector for the 21. node. |
||||
*/ |
||||
real_t yN[ 3 ]; |
||||
|
||||
/** Matrix of size: 80 x 4 (row major format) */ |
||||
real_t W[ 320 ]; |
||||
|
||||
/** Matrix of size: 3 x 3 (row major format) */ |
||||
real_t WN[ 9 ]; |
||||
|
||||
/** Column vector of size: 3
|
||||
*
|
||||
* Current state feedback vector. |
||||
*/ |
||||
real_t x0[ 3 ]; |
||||
|
||||
|
||||
} ACADOvariables; |
||||
|
||||
/** Private workspace used by the auto-generated code.
|
||||
*
|
||||
* Data members of this structure are private to the solver. |
||||
* In other words, the user code should not modify values of this
|
||||
* structure.
|
||||
*/ |
||||
typedef struct ACADOworkspace_ |
||||
{ |
||||
real_t rk_ttt; |
||||
|
||||
/** Row vector of size: 18 */ |
||||
real_t rk_xxx[ 18 ]; |
||||
|
||||
/** Matrix of size: 4 x 15 (row major format) */ |
||||
real_t rk_kkk[ 60 ]; |
||||
|
||||
/** Row vector of size: 18 */ |
||||
real_t state[ 18 ]; |
||||
|
||||
/** Column vector of size: 60 */ |
||||
real_t d[ 60 ]; |
||||
|
||||
/** Column vector of size: 80 */ |
||||
real_t Dy[ 80 ]; |
||||
|
||||
/** Column vector of size: 3 */ |
||||
real_t DyN[ 3 ]; |
||||
|
||||
/** Matrix of size: 60 x 3 (row major format) */ |
||||
real_t evGx[ 180 ]; |
||||
|
||||
/** Column vector of size: 60 */ |
||||
real_t evGu[ 60 ]; |
||||
|
||||
/** Column vector of size: 13 */ |
||||
real_t objAuxVar[ 13 ]; |
||||
|
||||
/** Row vector of size: 6 */ |
||||
real_t objValueIn[ 6 ]; |
||||
|
||||
/** Row vector of size: 20 */ |
||||
real_t objValueOut[ 20 ]; |
||||
|
||||
/** Matrix of size: 60 x 3 (row major format) */ |
||||
real_t Q1[ 180 ]; |
||||
|
||||
/** Matrix of size: 60 x 4 (row major format) */ |
||||
real_t Q2[ 240 ]; |
||||
|
||||
/** Column vector of size: 20 */ |
||||
real_t R1[ 20 ]; |
||||
|
||||
/** Matrix of size: 20 x 4 (row major format) */ |
||||
real_t R2[ 80 ]; |
||||
|
||||
/** Column vector of size: 60 */ |
||||
real_t S1[ 60 ]; |
||||
|
||||
/** Matrix of size: 3 x 3 (row major format) */ |
||||
real_t QN1[ 9 ]; |
||||
|
||||
/** Matrix of size: 3 x 3 (row major format) */ |
||||
real_t QN2[ 9 ]; |
||||
|
||||
/** Column vector of size: 3 */ |
||||
real_t Dx0[ 3 ]; |
||||
|
||||
/** Matrix of size: 3 x 3 (row major format) */ |
||||
real_t T[ 9 ]; |
||||
|
||||
/** Column vector of size: 630 */ |
||||
real_t E[ 630 ]; |
||||
|
||||
/** Column vector of size: 630 */ |
||||
real_t QE[ 630 ]; |
||||
|
||||
/** Matrix of size: 60 x 3 (row major format) */ |
||||
real_t QGx[ 180 ]; |
||||
|
||||
/** Column vector of size: 60 */ |
||||
real_t Qd[ 60 ]; |
||||
|
||||
/** Column vector of size: 63 */ |
||||
real_t QDy[ 63 ]; |
||||
|
||||
/** Matrix of size: 20 x 3 (row major format) */ |
||||
real_t H10[ 60 ]; |
||||
|
||||
/** Matrix of size: 23 x 23 (row major format) */ |
||||
real_t H[ 529 ]; |
||||
|
||||
/** Matrix of size: 20 x 23 (row major format) */ |
||||
real_t A[ 460 ]; |
||||
|
||||
/** Column vector of size: 23 */ |
||||
real_t g[ 23 ]; |
||||
|
||||
/** Column vector of size: 23 */ |
||||
real_t lb[ 23 ]; |
||||
|
||||
/** Column vector of size: 23 */ |
||||
real_t ub[ 23 ]; |
||||
|
||||
/** Column vector of size: 20 */ |
||||
real_t lbA[ 20 ]; |
||||
|
||||
/** Column vector of size: 20 */ |
||||
real_t ubA[ 20 ]; |
||||
|
||||
/** Column vector of size: 23 */ |
||||
real_t x[ 23 ]; |
||||
|
||||
/** Column vector of size: 43 */ |
||||
real_t y[ 43 ]; |
||||
|
||||
|
||||
} ACADOworkspace; |
||||
|
||||
/*
|
||||
* Forward function declarations.
|
||||
*/ |
||||
|
||||
|
||||
/** Performs the integration and sensitivity propagation for one shooting interval.
|
||||
* |
||||
* \param rk_eta Working array to pass the input values and return the results. |
||||
* \param resetIntegrator The internal memory of the integrator can be reset. |
||||
* \param rk_index Number of the shooting interval. |
||||
* |
||||
* \return Status code of the integrator. |
||||
*/ |
||||
int acado_integrate( real_t* const rk_eta, int resetIntegrator, int rk_index ); |
||||
|
||||
/** Export of an ACADO symbolic function.
|
||||
* |
||||
* \param in Input to the exported function. |
||||
* \param out Output of the exported function. |
||||
*/ |
||||
void acado_rhs_forw(const real_t* in, real_t* out); |
||||
|
||||
/** Preparation step of the RTI scheme.
|
||||
* |
||||
* \return Status of the integration module. =0: OK, otherwise the error code. |
||||
*/ |
||||
int acado_preparationStep( ); |
||||
|
||||
/** Feedback/estimation step of the RTI scheme.
|
||||
* |
||||
* \return Status code of the qpOASES QP solver. |
||||
*/ |
||||
int acado_feedbackStep( ); |
||||
|
||||
/** Solver initialization. Must be called once before any other function call.
|
||||
* |
||||
* \return =0: OK, otherwise an error code of a QP solver. |
||||
*/ |
||||
int acado_initializeSolver( ); |
||||
|
||||
/** Initialize shooting nodes by a forward simulation starting from the first node.
|
||||
*/ |
||||
void acado_initializeNodesByForwardSimulation( ); |
||||
|
||||
/** Shift differential variables vector by one interval.
|
||||
* |
||||
* \param strategy Shifting strategy: 1. Initialize node 21 with xEnd. 2. Initialize node 21 by forward simulation. |
||||
* \param xEnd Value for the x vector on the last node. If =0 the old value is used. |
||||
* \param uEnd Value for the u vector on the second to last node. If =0 the old value is used. |
||||
*/ |
||||
void acado_shiftStates( int strategy, real_t* const xEnd, real_t* const uEnd ); |
||||
|
||||
/** Shift controls vector by one interval.
|
||||
* |
||||
* \param uEnd Value for the u vector on the second to last node. If =0 the old value is used. |
||||
*/ |
||||
void acado_shiftControls( real_t* const uEnd ); |
||||
|
||||
/** Get the KKT tolerance of the current iterate.
|
||||
* |
||||
* \return The KKT tolerance value. |
||||
*/ |
||||
real_t acado_getKKT( ); |
||||
|
||||
/** Calculate the objective value.
|
||||
* |
||||
* \return Value of the objective function. |
||||
*/ |
||||
real_t acado_getObjective( ); |
||||
|
||||
|
||||
/*
|
||||
* Extern declarations.
|
||||
*/ |
||||
|
||||
extern ACADOworkspace acadoWorkspace; |
||||
extern ACADOvariables acadoVariables; |
||||
|
||||
/** @} */ |
||||
|
||||
#ifndef __MATLAB__ |
||||
#ifdef __cplusplus |
||||
} /* extern "C" */ |
||||
#endif /* __cplusplus */ |
||||
#endif /* __MATLAB__ */ |
||||
|
||||
#endif /* ACADO_COMMON_H */ |
@ -0,0 +1,159 @@ |
||||
/*
|
||||
* This file was auto-generated using the ACADO Toolkit. |
||||
*
|
||||
* While ACADO Toolkit is free software released under the terms of |
||||
* the GNU Lesser General Public License (LGPL), the generated code |
||||
* as such remains the property of the user who used ACADO Toolkit |
||||
* to generate this code. In particular, user dependent data of the code |
||||
* do not inherit the GNU LGPL license. On the other hand, parts of the |
||||
* generated code that are a direct copy of source code from the |
||||
* ACADO Toolkit or the software tools it is based on, remain, as derived |
||||
* work, automatically covered by the LGPL license. |
||||
*
|
||||
* ACADO Toolkit is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
||||
*
|
||||
*/ |
||||
|
||||
|
||||
#include "acado_common.h" |
||||
|
||||
|
||||
void acado_rhs_forw(const real_t* in, real_t* out) |
||||
{ |
||||
const real_t* xd = in; |
||||
const real_t* u = in + 15; |
||||
|
||||
/* Compute outputs: */ |
||||
out[0] = xd[1]; |
||||
out[1] = xd[2]; |
||||
out[2] = u[0]; |
||||
out[3] = xd[6]; |
||||
out[4] = xd[7]; |
||||
out[5] = xd[8]; |
||||
out[6] = xd[9]; |
||||
out[7] = xd[10]; |
||||
out[8] = xd[11]; |
||||
out[9] = (real_t)(0.0000000000000000e+00); |
||||
out[10] = (real_t)(0.0000000000000000e+00); |
||||
out[11] = (real_t)(0.0000000000000000e+00); |
||||
out[12] = xd[13]; |
||||
out[13] = xd[14]; |
||||
out[14] = (real_t)(1.0000000000000000e+00); |
||||
} |
||||
|
||||
/* Fixed step size:0.2 */ |
||||
int acado_integrate( real_t* const rk_eta, int resetIntegrator, int rk_index ) |
||||
{ |
||||
int error; |
||||
|
||||
int run1; |
||||
int numSteps[20] = {1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}; |
||||
int numInts = numSteps[rk_index]; |
||||
acadoWorkspace.rk_ttt = 0.0000000000000000e+00; |
||||
rk_eta[3] = 1.0000000000000000e+00; |
||||
rk_eta[4] = 0.0000000000000000e+00; |
||||
rk_eta[5] = 0.0000000000000000e+00; |
||||
rk_eta[6] = 0.0000000000000000e+00; |
||||
rk_eta[7] = 1.0000000000000000e+00; |
||||
rk_eta[8] = 0.0000000000000000e+00; |
||||
rk_eta[9] = 0.0000000000000000e+00; |
||||
rk_eta[10] = 0.0000000000000000e+00; |
||||
rk_eta[11] = 1.0000000000000000e+00; |
||||
rk_eta[12] = 0.0000000000000000e+00; |
||||
rk_eta[13] = 0.0000000000000000e+00; |
||||
rk_eta[14] = 0.0000000000000000e+00; |
||||
acadoWorkspace.rk_xxx[15] = rk_eta[15]; |
||||
acadoWorkspace.rk_xxx[16] = rk_eta[16]; |
||||
acadoWorkspace.rk_xxx[17] = rk_eta[17]; |
||||
|
||||
for (run1 = 0; run1 < 1; ++run1) |
||||
{ |
||||
for(run1 = 0; run1 < numInts; run1++ ) { |
||||
acadoWorkspace.rk_xxx[0] = + rk_eta[0]; |
||||
acadoWorkspace.rk_xxx[1] = + rk_eta[1]; |
||||
acadoWorkspace.rk_xxx[2] = + rk_eta[2]; |
||||
acadoWorkspace.rk_xxx[3] = + rk_eta[3]; |
||||
acadoWorkspace.rk_xxx[4] = + rk_eta[4]; |
||||
acadoWorkspace.rk_xxx[5] = + rk_eta[5]; |
||||
acadoWorkspace.rk_xxx[6] = + rk_eta[6]; |
||||
acadoWorkspace.rk_xxx[7] = + rk_eta[7]; |
||||
acadoWorkspace.rk_xxx[8] = + rk_eta[8]; |
||||
acadoWorkspace.rk_xxx[9] = + rk_eta[9]; |
||||
acadoWorkspace.rk_xxx[10] = + rk_eta[10]; |
||||
acadoWorkspace.rk_xxx[11] = + rk_eta[11]; |
||||
acadoWorkspace.rk_xxx[12] = + rk_eta[12]; |
||||
acadoWorkspace.rk_xxx[13] = + rk_eta[13]; |
||||
acadoWorkspace.rk_xxx[14] = + rk_eta[14]; |
||||
acado_rhs_forw( acadoWorkspace.rk_xxx, acadoWorkspace.rk_kkk ); |
||||
acadoWorkspace.rk_xxx[0] = + (real_t)9.9999999999999964e-02*acadoWorkspace.rk_kkk[0] + rk_eta[0]; |
||||
acadoWorkspace.rk_xxx[1] = + (real_t)9.9999999999999964e-02*acadoWorkspace.rk_kkk[1] + rk_eta[1]; |
||||
acadoWorkspace.rk_xxx[2] = + (real_t)9.9999999999999964e-02*acadoWorkspace.rk_kkk[2] + rk_eta[2]; |
||||
acadoWorkspace.rk_xxx[3] = + (real_t)9.9999999999999964e-02*acadoWorkspace.rk_kkk[3] + rk_eta[3]; |
||||
acadoWorkspace.rk_xxx[4] = + (real_t)9.9999999999999964e-02*acadoWorkspace.rk_kkk[4] + rk_eta[4]; |
||||
acadoWorkspace.rk_xxx[5] = + (real_t)9.9999999999999964e-02*acadoWorkspace.rk_kkk[5] + rk_eta[5]; |
||||
acadoWorkspace.rk_xxx[6] = + (real_t)9.9999999999999964e-02*acadoWorkspace.rk_kkk[6] + rk_eta[6]; |
||||
acadoWorkspace.rk_xxx[7] = + (real_t)9.9999999999999964e-02*acadoWorkspace.rk_kkk[7] + rk_eta[7]; |
||||
acadoWorkspace.rk_xxx[8] = + (real_t)9.9999999999999964e-02*acadoWorkspace.rk_kkk[8] + rk_eta[8]; |
||||
acadoWorkspace.rk_xxx[9] = + (real_t)9.9999999999999964e-02*acadoWorkspace.rk_kkk[9] + rk_eta[9]; |
||||
acadoWorkspace.rk_xxx[10] = + (real_t)9.9999999999999964e-02*acadoWorkspace.rk_kkk[10] + rk_eta[10]; |
||||
acadoWorkspace.rk_xxx[11] = + (real_t)9.9999999999999964e-02*acadoWorkspace.rk_kkk[11] + rk_eta[11]; |
||||
acadoWorkspace.rk_xxx[12] = + (real_t)9.9999999999999964e-02*acadoWorkspace.rk_kkk[12] + rk_eta[12]; |
||||
acadoWorkspace.rk_xxx[13] = + (real_t)9.9999999999999964e-02*acadoWorkspace.rk_kkk[13] + rk_eta[13]; |
||||
acadoWorkspace.rk_xxx[14] = + (real_t)9.9999999999999964e-02*acadoWorkspace.rk_kkk[14] + rk_eta[14]; |
||||
acado_rhs_forw( acadoWorkspace.rk_xxx, &(acadoWorkspace.rk_kkk[ 15 ]) ); |
||||
acadoWorkspace.rk_xxx[0] = + (real_t)9.9999999999999964e-02*acadoWorkspace.rk_kkk[15] + rk_eta[0]; |
||||
acadoWorkspace.rk_xxx[1] = + (real_t)9.9999999999999964e-02*acadoWorkspace.rk_kkk[16] + rk_eta[1]; |
||||
acadoWorkspace.rk_xxx[2] = + (real_t)9.9999999999999964e-02*acadoWorkspace.rk_kkk[17] + rk_eta[2]; |
||||
acadoWorkspace.rk_xxx[3] = + (real_t)9.9999999999999964e-02*acadoWorkspace.rk_kkk[18] + rk_eta[3]; |
||||
acadoWorkspace.rk_xxx[4] = + (real_t)9.9999999999999964e-02*acadoWorkspace.rk_kkk[19] + rk_eta[4]; |
||||
acadoWorkspace.rk_xxx[5] = + (real_t)9.9999999999999964e-02*acadoWorkspace.rk_kkk[20] + rk_eta[5]; |
||||
acadoWorkspace.rk_xxx[6] = + (real_t)9.9999999999999964e-02*acadoWorkspace.rk_kkk[21] + rk_eta[6]; |
||||
acadoWorkspace.rk_xxx[7] = + (real_t)9.9999999999999964e-02*acadoWorkspace.rk_kkk[22] + rk_eta[7]; |
||||
acadoWorkspace.rk_xxx[8] = + (real_t)9.9999999999999964e-02*acadoWorkspace.rk_kkk[23] + rk_eta[8]; |
||||
acadoWorkspace.rk_xxx[9] = + (real_t)9.9999999999999964e-02*acadoWorkspace.rk_kkk[24] + rk_eta[9]; |
||||
acadoWorkspace.rk_xxx[10] = + (real_t)9.9999999999999964e-02*acadoWorkspace.rk_kkk[25] + rk_eta[10]; |
||||
acadoWorkspace.rk_xxx[11] = + (real_t)9.9999999999999964e-02*acadoWorkspace.rk_kkk[26] + rk_eta[11]; |
||||
acadoWorkspace.rk_xxx[12] = + (real_t)9.9999999999999964e-02*acadoWorkspace.rk_kkk[27] + rk_eta[12]; |
||||
acadoWorkspace.rk_xxx[13] = + (real_t)9.9999999999999964e-02*acadoWorkspace.rk_kkk[28] + rk_eta[13]; |
||||
acadoWorkspace.rk_xxx[14] = + (real_t)9.9999999999999964e-02*acadoWorkspace.rk_kkk[29] + rk_eta[14]; |
||||
acado_rhs_forw( acadoWorkspace.rk_xxx, &(acadoWorkspace.rk_kkk[ 30 ]) ); |
||||
acadoWorkspace.rk_xxx[0] = + (real_t)1.9999999999999993e-01*acadoWorkspace.rk_kkk[30] + rk_eta[0]; |
||||
acadoWorkspace.rk_xxx[1] = + (real_t)1.9999999999999993e-01*acadoWorkspace.rk_kkk[31] + rk_eta[1]; |
||||
acadoWorkspace.rk_xxx[2] = + (real_t)1.9999999999999993e-01*acadoWorkspace.rk_kkk[32] + rk_eta[2]; |
||||
acadoWorkspace.rk_xxx[3] = + (real_t)1.9999999999999993e-01*acadoWorkspace.rk_kkk[33] + rk_eta[3]; |
||||
acadoWorkspace.rk_xxx[4] = + (real_t)1.9999999999999993e-01*acadoWorkspace.rk_kkk[34] + rk_eta[4]; |
||||
acadoWorkspace.rk_xxx[5] = + (real_t)1.9999999999999993e-01*acadoWorkspace.rk_kkk[35] + rk_eta[5]; |
||||
acadoWorkspace.rk_xxx[6] = + (real_t)1.9999999999999993e-01*acadoWorkspace.rk_kkk[36] + rk_eta[6]; |
||||
acadoWorkspace.rk_xxx[7] = + (real_t)1.9999999999999993e-01*acadoWorkspace.rk_kkk[37] + rk_eta[7]; |
||||
acadoWorkspace.rk_xxx[8] = + (real_t)1.9999999999999993e-01*acadoWorkspace.rk_kkk[38] + rk_eta[8]; |
||||
acadoWorkspace.rk_xxx[9] = + (real_t)1.9999999999999993e-01*acadoWorkspace.rk_kkk[39] + rk_eta[9]; |
||||
acadoWorkspace.rk_xxx[10] = + (real_t)1.9999999999999993e-01*acadoWorkspace.rk_kkk[40] + rk_eta[10]; |
||||
acadoWorkspace.rk_xxx[11] = + (real_t)1.9999999999999993e-01*acadoWorkspace.rk_kkk[41] + rk_eta[11]; |
||||
acadoWorkspace.rk_xxx[12] = + (real_t)1.9999999999999993e-01*acadoWorkspace.rk_kkk[42] + rk_eta[12]; |
||||
acadoWorkspace.rk_xxx[13] = + (real_t)1.9999999999999993e-01*acadoWorkspace.rk_kkk[43] + rk_eta[13]; |
||||
acadoWorkspace.rk_xxx[14] = + (real_t)1.9999999999999993e-01*acadoWorkspace.rk_kkk[44] + rk_eta[14]; |
||||
acado_rhs_forw( acadoWorkspace.rk_xxx, &(acadoWorkspace.rk_kkk[ 45 ]) ); |
||||
rk_eta[0] += + (real_t)3.3333333333333319e-02*acadoWorkspace.rk_kkk[0] + (real_t)6.6666666666666638e-02*acadoWorkspace.rk_kkk[15] + (real_t)6.6666666666666638e-02*acadoWorkspace.rk_kkk[30] + (real_t)3.3333333333333319e-02*acadoWorkspace.rk_kkk[45]; |
||||
rk_eta[1] += + (real_t)3.3333333333333319e-02*acadoWorkspace.rk_kkk[1] + (real_t)6.6666666666666638e-02*acadoWorkspace.rk_kkk[16] + (real_t)6.6666666666666638e-02*acadoWorkspace.rk_kkk[31] + (real_t)3.3333333333333319e-02*acadoWorkspace.rk_kkk[46]; |
||||
rk_eta[2] += + (real_t)3.3333333333333319e-02*acadoWorkspace.rk_kkk[2] + (real_t)6.6666666666666638e-02*acadoWorkspace.rk_kkk[17] + (real_t)6.6666666666666638e-02*acadoWorkspace.rk_kkk[32] + (real_t)3.3333333333333319e-02*acadoWorkspace.rk_kkk[47]; |
||||
rk_eta[3] += + (real_t)3.3333333333333319e-02*acadoWorkspace.rk_kkk[3] + (real_t)6.6666666666666638e-02*acadoWorkspace.rk_kkk[18] + (real_t)6.6666666666666638e-02*acadoWorkspace.rk_kkk[33] + (real_t)3.3333333333333319e-02*acadoWorkspace.rk_kkk[48]; |
||||
rk_eta[4] += + (real_t)3.3333333333333319e-02*acadoWorkspace.rk_kkk[4] + (real_t)6.6666666666666638e-02*acadoWorkspace.rk_kkk[19] + (real_t)6.6666666666666638e-02*acadoWorkspace.rk_kkk[34] + (real_t)3.3333333333333319e-02*acadoWorkspace.rk_kkk[49]; |
||||
rk_eta[5] += + (real_t)3.3333333333333319e-02*acadoWorkspace.rk_kkk[5] + (real_t)6.6666666666666638e-02*acadoWorkspace.rk_kkk[20] + (real_t)6.6666666666666638e-02*acadoWorkspace.rk_kkk[35] + (real_t)3.3333333333333319e-02*acadoWorkspace.rk_kkk[50]; |
||||
rk_eta[6] += + (real_t)3.3333333333333319e-02*acadoWorkspace.rk_kkk[6] + (real_t)6.6666666666666638e-02*acadoWorkspace.rk_kkk[21] + (real_t)6.6666666666666638e-02*acadoWorkspace.rk_kkk[36] + (real_t)3.3333333333333319e-02*acadoWorkspace.rk_kkk[51]; |
||||
rk_eta[7] += + (real_t)3.3333333333333319e-02*acadoWorkspace.rk_kkk[7] + (real_t)6.6666666666666638e-02*acadoWorkspace.rk_kkk[22] + (real_t)6.6666666666666638e-02*acadoWorkspace.rk_kkk[37] + (real_t)3.3333333333333319e-02*acadoWorkspace.rk_kkk[52]; |
||||
rk_eta[8] += + (real_t)3.3333333333333319e-02*acadoWorkspace.rk_kkk[8] + (real_t)6.6666666666666638e-02*acadoWorkspace.rk_kkk[23] + (real_t)6.6666666666666638e-02*acadoWorkspace.rk_kkk[38] + (real_t)3.3333333333333319e-02*acadoWorkspace.rk_kkk[53]; |
||||
rk_eta[9] += + (real_t)3.3333333333333319e-02*acadoWorkspace.rk_kkk[9] + (real_t)6.6666666666666638e-02*acadoWorkspace.rk_kkk[24] + (real_t)6.6666666666666638e-02*acadoWorkspace.rk_kkk[39] + (real_t)3.3333333333333319e-02*acadoWorkspace.rk_kkk[54]; |
||||
rk_eta[10] += + (real_t)3.3333333333333319e-02*acadoWorkspace.rk_kkk[10] + (real_t)6.6666666666666638e-02*acadoWorkspace.rk_kkk[25] + (real_t)6.6666666666666638e-02*acadoWorkspace.rk_kkk[40] + (real_t)3.3333333333333319e-02*acadoWorkspace.rk_kkk[55]; |
||||
rk_eta[11] += + (real_t)3.3333333333333319e-02*acadoWorkspace.rk_kkk[11] + (real_t)6.6666666666666638e-02*acadoWorkspace.rk_kkk[26] + (real_t)6.6666666666666638e-02*acadoWorkspace.rk_kkk[41] + (real_t)3.3333333333333319e-02*acadoWorkspace.rk_kkk[56]; |
||||
rk_eta[12] += + (real_t)3.3333333333333319e-02*acadoWorkspace.rk_kkk[12] + (real_t)6.6666666666666638e-02*acadoWorkspace.rk_kkk[27] + (real_t)6.6666666666666638e-02*acadoWorkspace.rk_kkk[42] + (real_t)3.3333333333333319e-02*acadoWorkspace.rk_kkk[57]; |
||||
rk_eta[13] += + (real_t)3.3333333333333319e-02*acadoWorkspace.rk_kkk[13] + (real_t)6.6666666666666638e-02*acadoWorkspace.rk_kkk[28] + (real_t)6.6666666666666638e-02*acadoWorkspace.rk_kkk[43] + (real_t)3.3333333333333319e-02*acadoWorkspace.rk_kkk[58]; |
||||
rk_eta[14] += + (real_t)3.3333333333333319e-02*acadoWorkspace.rk_kkk[14] + (real_t)6.6666666666666638e-02*acadoWorkspace.rk_kkk[29] + (real_t)6.6666666666666638e-02*acadoWorkspace.rk_kkk[44] + (real_t)3.3333333333333319e-02*acadoWorkspace.rk_kkk[59]; |
||||
acadoWorkspace.rk_ttt += 1.0000000000000000e+00; |
||||
} |
||||
} |
||||
error = 0; |
||||
return error; |
||||
} |
||||
|
@ -0,0 +1,70 @@ |
||||
/*
|
||||
* This file was auto-generated using the ACADO Toolkit. |
||||
*
|
||||
* While ACADO Toolkit is free software released under the terms of |
||||
* the GNU Lesser General Public License (LGPL), the generated code |
||||
* as such remains the property of the user who used ACADO Toolkit |
||||
* to generate this code. In particular, user dependent data of the code |
||||
* do not inherit the GNU LGPL license. On the other hand, parts of the |
||||
* generated code that are a direct copy of source code from the |
||||
* ACADO Toolkit or the software tools it is based on, remain, as derived |
||||
* work, automatically covered by the LGPL license. |
||||
*
|
||||
* ACADO Toolkit is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
||||
*
|
||||
*/ |
||||
|
||||
|
||||
extern "C" |
||||
{ |
||||
#include "acado_common.h" |
||||
} |
||||
|
||||
#include "INCLUDE/QProblem.hpp" |
||||
|
||||
#if ACADO_COMPUTE_COVARIANCE_MATRIX == 1 |
||||
#include "INCLUDE/EXTRAS/SolutionAnalysis.hpp" |
||||
#endif /* ACADO_COMPUTE_COVARIANCE_MATRIX */ |
||||
|
||||
static int acado_nWSR; |
||||
|
||||
|
||||
|
||||
#if ACADO_COMPUTE_COVARIANCE_MATRIX == 1 |
||||
static SolutionAnalysis acado_sa; |
||||
#endif /* ACADO_COMPUTE_COVARIANCE_MATRIX */ |
||||
|
||||
int acado_solve( void ) |
||||
{ |
||||
acado_nWSR = QPOASES_NWSRMAX; |
||||
|
||||
QProblem qp(23, 20); |
||||
|
||||
returnValue retVal = qp.init(acadoWorkspace.H, acadoWorkspace.g, acadoWorkspace.A, acadoWorkspace.lb, acadoWorkspace.ub, acadoWorkspace.lbA, acadoWorkspace.ubA, acado_nWSR, acadoWorkspace.y); |
||||
|
||||
qp.getPrimalSolution( acadoWorkspace.x ); |
||||
qp.getDualSolution( acadoWorkspace.y ); |
||||
|
||||
#if ACADO_COMPUTE_COVARIANCE_MATRIX == 1 |
||||
|
||||
if (retVal != SUCCESSFUL_RETURN) |
||||
return (int)retVal; |
||||
|
||||
retVal = acado_sa.getHessianInverse( &qp,var ); |
||||
|
||||
#endif /* ACADO_COMPUTE_COVARIANCE_MATRIX */ |
||||
|
||||
return (int)retVal; |
||||
} |
||||
|
||||
int acado_getNWSR( void ) |
||||
{ |
||||
return acado_nWSR; |
||||
} |
||||
|
||||
const char* acado_getErrorString( int error ) |
||||
{ |
||||
return MessageHandling::getErrorString( error ); |
||||
} |
@ -0,0 +1,65 @@ |
||||
/*
|
||||
* This file was auto-generated using the ACADO Toolkit. |
||||
*
|
||||
* While ACADO Toolkit is free software released under the terms of |
||||
* the GNU Lesser General Public License (LGPL), the generated code |
||||
* as such remains the property of the user who used ACADO Toolkit |
||||
* to generate this code. In particular, user dependent data of the code |
||||
* do not inherit the GNU LGPL license. On the other hand, parts of the |
||||
* generated code that are a direct copy of source code from the |
||||
* ACADO Toolkit or the software tools it is based on, remain, as derived |
||||
* work, automatically covered by the LGPL license. |
||||
*
|
||||
* ACADO Toolkit is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
||||
*
|
||||
*/ |
||||
|
||||
|
||||
#ifndef QPOASES_HEADER |
||||
#define QPOASES_HEADER |
||||
|
||||
#ifdef PC_DEBUG |
||||
#include <stdio.h> |
||||
#endif /* PC_DEBUG */ |
||||
|
||||
#include <math.h> |
||||
|
||||
#ifdef __cplusplus |
||||
#define EXTERNC extern "C" |
||||
#else |
||||
#define EXTERNC |
||||
#endif |
||||
|
||||
/*
|
||||
* A set of options for qpOASES |
||||
*/ |
||||
|
||||
/** Maximum number of optimization variables. */ |
||||
#define QPOASES_NVMAX 23 |
||||
/** Maximum number of constraints. */ |
||||
#define QPOASES_NCMAX 20 |
||||
/** Maximum number of working set recalculations. */ |
||||
#define QPOASES_NWSRMAX 500 |
||||
/** Print level for qpOASES. */ |
||||
#define QPOASES_PRINTLEVEL PL_NONE |
||||
/** The value of EPS */ |
||||
#define QPOASES_EPS 2.221e-16 |
||||
/** Internally used floating point type */ |
||||
typedef double real_t; |
||||
|
||||
/*
|
||||
* Forward function declarations |
||||
*/ |
||||
|
||||
/** A function that calls the QP solver */ |
||||
EXTERNC int acado_solve( void ); |
||||
|
||||
/** Get the number of active set changes */ |
||||
EXTERNC int acado_getNWSR( void ); |
||||
|
||||
/** Get the error string. */ |
||||
const char* acado_getErrorString( int error ); |
||||
|
||||
#endif /* QPOASES_HEADER */ |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,40 @@ |
||||
import os |
||||
|
||||
from cffi import FFI |
||||
|
||||
mpc_dir = os.path.join(os.path.dirname(os.path.abspath(__file__))) |
||||
|
||||
def _get_libmpc(mpc_id): |
||||
libmpc_fn = os.path.join(mpc_dir, "libmpc%d.so" % mpc_id) |
||||
|
||||
ffi = FFI() |
||||
ffi.cdef(""" |
||||
typedef struct { |
||||
double x_ego, v_ego, a_ego, x_l, v_l, a_l; |
||||
} state_t; |
||||
|
||||
|
||||
typedef struct { |
||||
double x_ego[21]; |
||||
double v_ego[21]; |
||||
double a_ego[21]; |
||||
double j_ego[20]; |
||||
double x_l[21]; |
||||
double v_l[21]; |
||||
double a_l[21]; |
||||
double t[21]; |
||||
double cost; |
||||
} log_t; |
||||
|
||||
void init(double ttcCost, double distanceCost, double accelerationCost, double jerkCost); |
||||
void init_with_simulation(double v_ego, double x_l, double v_l, double a_l, double l); |
||||
int run_mpc(state_t * x0, log_t * solution, |
||||
double l, double a_l_0); |
||||
""") |
||||
|
||||
return (ffi, ffi.dlopen(libmpc_fn)) |
||||
|
||||
mpcs = [_get_libmpc(1), _get_libmpc(2)] |
||||
|
||||
def get_libmpc(mpc_id): |
||||
return mpcs[mpc_id - 1] |
@ -0,0 +1,173 @@ |
||||
#include "acado_common.h" |
||||
#include "acado_auxiliary_functions.h" |
||||
|
||||
#include <stdio.h> |
||||
#include <math.h> |
||||
|
||||
#define NX ACADO_NX /* Number of differential state variables. */ |
||||
#define NXA ACADO_NXA /* Number of algebraic variables. */ |
||||
#define NU ACADO_NU /* Number of control inputs. */ |
||||
#define NOD ACADO_NOD /* Number of online data values. */ |
||||
|
||||
#define NY ACADO_NY /* Number of measurements/references on nodes 0..N - 1. */ |
||||
#define NYN ACADO_NYN /* Number of measurements/references on node N. */ |
||||
|
||||
#define N ACADO_N /* Number of intervals in the horizon. */ |
||||
|
||||
ACADOvariables acadoVariables; |
||||
ACADOworkspace acadoWorkspace; |
||||
|
||||
typedef struct { |
||||
double x_ego, v_ego, a_ego, x_l, v_l, a_l; |
||||
} state_t; |
||||
|
||||
|
||||
typedef struct { |
||||
double x_ego[N+1]; |
||||
double v_ego[N+1]; |
||||
double a_ego[N+1]; |
||||
double j_ego[N]; |
||||
double x_l[N+1]; |
||||
double v_l[N+1]; |
||||
double a_l[N+1]; |
||||
double t[N+1]; |
||||
double cost; |
||||
} log_t; |
||||
|
||||
void init(double ttcCost, double distanceCost, double accelerationCost, double jerkCost){ |
||||
acado_initializeSolver(); |
||||
int i; |
||||
const int STEP_MULTIPLIER = 3; |
||||
|
||||
/* Initialize the states and controls. */ |
||||
for (i = 0; i < NX * (N + 1); ++i) acadoVariables.x[ i ] = 0.0; |
||||
for (i = 0; i < NU * N; ++i) acadoVariables.u[ i ] = 0.0; |
||||
|
||||
/* Initialize the measurements/reference. */ |
||||
for (i = 0; i < NY * N; ++i) acadoVariables.y[ i ] = 0.0; |
||||
for (i = 0; i < NYN; ++i) acadoVariables.yN[ i ] = 0.0; |
||||
|
||||
/* MPC: initialize the current state feedback. */ |
||||
for (i = 0; i < NX; ++i) acadoVariables.x0[ i ] = 0.0; |
||||
// Set weights
|
||||
|
||||
for (i = 0; i < N; i++) { |
||||
int f = 1; |
||||
if (i > 4){ |
||||
f = STEP_MULTIPLIER; |
||||
} |
||||
// Setup diagonal entries
|
||||
acadoVariables.W[NY*NY*i + (NY+1)*0] = ttcCost * f; // exponential cost for time-to-collision (ttc)
|
||||
acadoVariables.W[NY*NY*i + (NY+1)*1] = distanceCost * f; // desired distance
|
||||
acadoVariables.W[NY*NY*i + (NY+1)*2] = accelerationCost * f; // acceleration
|
||||
acadoVariables.W[NY*NY*i + (NY+1)*3] = jerkCost * f; // jerk
|
||||
} |
||||
acadoVariables.WN[(NYN+1)*0] = ttcCost * STEP_MULTIPLIER; // exponential cost for danger zone
|
||||
acadoVariables.WN[(NYN+1)*1] = distanceCost * STEP_MULTIPLIER; // desired distance
|
||||
acadoVariables.WN[(NYN+1)*2] = accelerationCost * STEP_MULTIPLIER; // acceleration
|
||||
|
||||
} |
||||
|
||||
void init_with_simulation(double v_ego, double x_l_0, double v_l_0, double a_l_0, double l){ |
||||
int i; |
||||
|
||||
double x_l = x_l_0; |
||||
double v_l = v_l_0; |
||||
double a_l = a_l_0; |
||||
|
||||
double x_ego = 0.0; |
||||
double a_ego = -(v_ego - v_l) * (v_ego - v_l) / (2.0 * x_l + 0.01) + a_l; |
||||
|
||||
if (a_ego > 0){ |
||||
a_ego = 0.0; |
||||
} |
||||
|
||||
|
||||
double dt = 0.2; |
||||
double t = 0.; |
||||
|
||||
for (i = 0; i < N + 1; ++i){ |
||||
if (i > 4){ |
||||
dt = 0.6; |
||||
} |
||||
|
||||
/* printf("%.2f\t%.2f\t%.2f\t%.2f\n", t, x_ego, v_ego, a_l); */ |
||||
acadoVariables.x[i*NX] = x_ego; |
||||
acadoVariables.x[i*NX+1] = v_ego; |
||||
acadoVariables.x[i*NX+2] = a_ego; |
||||
|
||||
v_ego += a_ego * dt; |
||||
|
||||
if (v_ego <= 0.0) { |
||||
v_ego = 0.0; |
||||
a_ego = 0.0; |
||||
} |
||||
|
||||
x_ego += v_ego * dt; |
||||
t += dt; |
||||
} |
||||
|
||||
for (i = 0; i < NU * N; ++i) acadoVariables.u[ i ] = 0.0; |
||||
for (i = 0; i < NY * N; ++i) acadoVariables.y[ i ] = 0.0; |
||||
for (i = 0; i < NYN; ++i) acadoVariables.yN[ i ] = 0.0; |
||||
} |
||||
|
||||
int run_mpc(state_t * x0, log_t * solution, double l, double a_l_0){ |
||||
// Calculate lead vehicle predictions
|
||||
int i; |
||||
double t = 0.; |
||||
double dt = 0.2; |
||||
double x_l = x0->x_l; |
||||
double v_l = x0->v_l; |
||||
double a_l = a_l_0; |
||||
|
||||
/* printf("t\tx_l\t_v_l\t_al\n"); */ |
||||
for (i = 0; i < N + 1; ++i){ |
||||
if (i > 4){ |
||||
dt = 0.6; |
||||
} |
||||
|
||||
/* printf("%.2f\t%.2f\t%.2f\t%.2f\n", t, x_l, v_l, a_l); */ |
||||
|
||||
acadoVariables.od[i*NOD] = x_l; |
||||
acadoVariables.od[i*NOD+1] = v_l; |
||||
|
||||
solution->x_l[i] = x_l; |
||||
solution->v_l[i] = v_l; |
||||
solution->a_l[i] = a_l; |
||||
solution->t[i] = t; |
||||
|
||||
a_l = a_l_0 * exp(-l * t * t / 2); |
||||
x_l += v_l * dt; |
||||
v_l += a_l * dt; |
||||
if (v_l < 0.0){ |
||||
a_l = 0.0; |
||||
v_l = 0.0; |
||||
} |
||||
|
||||
t += dt; |
||||
} |
||||
|
||||
acadoVariables.x[0] = acadoVariables.x0[0] = x0->x_ego; |
||||
acadoVariables.x[1] = acadoVariables.x0[1] = x0->v_ego; |
||||
acadoVariables.x[2] = acadoVariables.x0[2] = x0->a_ego; |
||||
|
||||
acado_preparationStep(); |
||||
acado_feedbackStep(); |
||||
|
||||
for (i = 0; i <= N; i++){ |
||||
solution->x_ego[i] = acadoVariables.x[i*NX]; |
||||
solution->v_ego[i] = acadoVariables.x[i*NX+1]; |
||||
solution->a_ego[i] = acadoVariables.x[i*NX+2]; |
||||
|
||||
if (i < N){ |
||||
solution->j_ego[i] = acadoVariables.u[i]; |
||||
} |
||||
} |
||||
solution->cost = acado_getObjective(); |
||||
|
||||
// Dont shift states here. Current solution is closer to next timestep than if
|
||||
// we shift by 0.2 seconds.
|
||||
|
||||
return acado_getNWSR(); |
||||
} |
@ -0,0 +1,230 @@ |
||||
import os |
||||
import math |
||||
from common.realtime import sec_since_boot, DT_MDL |
||||
from selfdrive.swaglog import cloudlog |
||||
from selfdrive.controls.lib.lateral_mpc import libmpc_py |
||||
from selfdrive.controls.lib.drive_helpers import MPC_COST_LAT |
||||
from selfdrive.controls.lib.lane_planner import LanePlanner |
||||
from selfdrive.config import Conversions as CV |
||||
import cereal.messaging as messaging |
||||
from cereal import log |
||||
|
||||
LaneChangeState = log.PathPlan.LaneChangeState |
||||
LaneChangeDirection = log.PathPlan.LaneChangeDirection |
||||
|
||||
LOG_MPC = os.environ.get('LOG_MPC', False) |
||||
|
||||
LANE_CHANGE_SPEED_MIN = 45 * CV.MPH_TO_MS |
||||
LANE_CHANGE_TIME_MAX = 10. |
||||
|
||||
DESIRES = { |
||||
LaneChangeDirection.none: { |
||||
LaneChangeState.off: log.PathPlan.Desire.none, |
||||
LaneChangeState.preLaneChange: log.PathPlan.Desire.none, |
||||
LaneChangeState.laneChangeStarting: log.PathPlan.Desire.none, |
||||
LaneChangeState.laneChangeFinishing: log.PathPlan.Desire.none, |
||||
}, |
||||
LaneChangeDirection.left: { |
||||
LaneChangeState.off: log.PathPlan.Desire.none, |
||||
LaneChangeState.preLaneChange: log.PathPlan.Desire.none, |
||||
LaneChangeState.laneChangeStarting: log.PathPlan.Desire.laneChangeLeft, |
||||
LaneChangeState.laneChangeFinishing: log.PathPlan.Desire.laneChangeLeft, |
||||
}, |
||||
LaneChangeDirection.right: { |
||||
LaneChangeState.off: log.PathPlan.Desire.none, |
||||
LaneChangeState.preLaneChange: log.PathPlan.Desire.none, |
||||
LaneChangeState.laneChangeStarting: log.PathPlan.Desire.laneChangeRight, |
||||
LaneChangeState.laneChangeFinishing: log.PathPlan.Desire.laneChangeRight, |
||||
}, |
||||
} |
||||
|
||||
def calc_states_after_delay(states, v_ego, steer_angle, curvature_factor, steer_ratio, delay): |
||||
states[0].x = v_ego * delay |
||||
states[0].psi = v_ego * curvature_factor * math.radians(steer_angle) / steer_ratio * delay |
||||
return states |
||||
|
||||
|
||||
class PathPlanner(): |
||||
def __init__(self, CP): |
||||
self.LP = LanePlanner() |
||||
|
||||
self.last_cloudlog_t = 0 |
||||
self.steer_rate_cost = CP.steerRateCost |
||||
|
||||
self.setup_mpc() |
||||
self.solution_invalid_cnt = 0 |
||||
self.path_offset_i = 0.0 |
||||
self.lane_change_state = LaneChangeState.off |
||||
self.lane_change_timer = 0.0 |
||||
self.prev_one_blinker = False |
||||
|
||||
def setup_mpc(self): |
||||
self.libmpc = libmpc_py.libmpc |
||||
self.libmpc.init(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE, MPC_COST_LAT.HEADING, self.steer_rate_cost) |
||||
|
||||
self.mpc_solution = libmpc_py.ffi.new("log_t *") |
||||
self.cur_state = libmpc_py.ffi.new("state_t *") |
||||
self.cur_state[0].x = 0.0 |
||||
self.cur_state[0].y = 0.0 |
||||
self.cur_state[0].psi = 0.0 |
||||
self.cur_state[0].delta = 0.0 |
||||
|
||||
self.angle_steers_des = 0.0 |
||||
self.angle_steers_des_mpc = 0.0 |
||||
self.angle_steers_des_prev = 0.0 |
||||
self.angle_steers_des_time = 0.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 |
||||
|
||||
# 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) |
||||
|
||||
self.LP.parse_model(sm['model']) |
||||
|
||||
# Lane change logic |
||||
lane_change_direction = LaneChangeDirection.none |
||||
one_blinker = sm['carState'].leftBlinker != sm['carState'].rightBlinker |
||||
below_lane_change_speed = v_ego < LANE_CHANGE_SPEED_MIN |
||||
|
||||
if not active or self.lane_change_timer > LANE_CHANGE_TIME_MAX: |
||||
self.lane_change_state = LaneChangeState.off |
||||
else: |
||||
if sm['carState'].leftBlinker: |
||||
lane_change_direction = LaneChangeDirection.left |
||||
elif sm['carState'].rightBlinker: |
||||
lane_change_direction = LaneChangeDirection.right |
||||
|
||||
torque_applied = sm['carState'].steeringPressed and \ |
||||
((sm['carState'].steeringTorque > 0 and lane_change_direction == LaneChangeDirection.left) or \ |
||||
(sm['carState'].steeringTorque < 0 and 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 |
||||
|
||||
# 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: |
||||
self.lane_change_state = LaneChangeState.laneChangeStarting |
||||
|
||||
# starting |
||||
elif self.lane_change_state == LaneChangeState.laneChangeStarting and lane_change_prob > 0.5: |
||||
self.lane_change_state = LaneChangeState.laneChangeFinishing |
||||
|
||||
# finishing |
||||
elif self.lane_change_state == LaneChangeState.laneChangeFinishing and lane_change_prob < 0.2: |
||||
if one_blinker: |
||||
self.lane_change_state = LaneChangeState.preLaneChange |
||||
else: |
||||
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 |
||||
|
||||
self.prev_one_blinker = one_blinker |
||||
|
||||
desire = DESIRES[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 = 0. |
||||
self.LP.r_prob = 0. |
||||
self.libmpc.init_weights(MPC_COST_LAT.PATH / 10.0, MPC_COST_LAT.LANE, MPC_COST_LAT.HEADING, self.steer_rate_cost) |
||||
else: |
||||
self.libmpc.init_weights(MPC_COST_LAT.PATH, MPC_COST_LAT.LANE, MPC_COST_LAT.HEADING, self.steer_rate_cost) |
||||
|
||||
self.LP.update_d_poly(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 |
||||
|
||||
# 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) |
||||
|
||||
plan_send.pathPlan.desire = desire |
||||
plan_send.pathPlan.laneChangeState = self.lane_change_state |
||||
plan_send.pathPlan.laneChangeDirection = lane_change_direction |
||||
|
||||
pm.send('pathPlan', plan_send) |
||||
|
||||
if LOG_MPC: |
||||
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) |
@ -0,0 +1,88 @@ |
||||
import numpy as np |
||||
from common.numpy_fast import clip, interp |
||||
|
||||
def apply_deadzone(error, deadzone): |
||||
if error > deadzone: |
||||
error -= deadzone |
||||
elif error < - deadzone: |
||||
error += deadzone |
||||
else: |
||||
error = 0. |
||||
return error |
||||
|
||||
class PIController(): |
||||
def __init__(self, k_p, k_i, k_f=1., pos_limit=None, neg_limit=None, rate=100, sat_limit=0.8, convert=None): |
||||
self._k_p = k_p # proportional gain |
||||
self._k_i = k_i # integral gain |
||||
self.k_f = k_f # feedforward gain |
||||
|
||||
self.pos_limit = pos_limit |
||||
self.neg_limit = neg_limit |
||||
|
||||
self.sat_count_rate = 1.0 / rate |
||||
self.i_unwind_rate = 0.3 / rate |
||||
self.i_rate = 1.0 / rate |
||||
self.sat_limit = sat_limit |
||||
self.convert = convert |
||||
|
||||
self.reset() |
||||
|
||||
@property |
||||
def k_p(self): |
||||
return interp(self.speed, self._k_p[0], self._k_p[1]) |
||||
|
||||
@property |
||||
def k_i(self): |
||||
return interp(self.speed, self._k_i[0], self._k_i[1]) |
||||
|
||||
def _check_saturation(self, control, check_saturation, error): |
||||
saturated = (control < self.neg_limit) or (control > self.pos_limit) |
||||
|
||||
if saturated and check_saturation and abs(error) > 0.1: |
||||
self.sat_count += self.sat_count_rate |
||||
else: |
||||
self.sat_count -= self.sat_count_rate |
||||
|
||||
self.sat_count = clip(self.sat_count, 0.0, 1.0) |
||||
|
||||
return self.sat_count > self.sat_limit |
||||
|
||||
def reset(self): |
||||
self.p = 0.0 |
||||
self.i = 0.0 |
||||
self.f = 0.0 |
||||
self.sat_count = 0.0 |
||||
self.saturated = False |
||||
self.control = 0 |
||||
|
||||
def update(self, setpoint, measurement, speed=0.0, check_saturation=True, override=False, feedforward=0., deadzone=0., freeze_integrator=False): |
||||
self.speed = speed |
||||
|
||||
error = float(apply_deadzone(setpoint - measurement, deadzone)) |
||||
self.p = error * self.k_p |
||||
self.f = feedforward * self.k_f |
||||
|
||||
if override: |
||||
self.i -= self.i_unwind_rate * float(np.sign(self.i)) |
||||
else: |
||||
i = self.i + error * self.k_i * self.i_rate |
||||
control = self.p + self.f + i |
||||
|
||||
if self.convert is not None: |
||||
control = self.convert(control, speed=self.speed) |
||||
|
||||
# Update when changing i will move the control away from the limits |
||||
# or when i will move towards the sign of the error |
||||
if ((error >= 0 and (control <= self.pos_limit or i < 0.0)) or \ |
||||
(error <= 0 and (control >= self.neg_limit or i > 0.0))) and \ |
||||
not freeze_integrator: |
||||
self.i = i |
||||
|
||||
control = self.p + self.f + self.i |
||||
if self.convert is not None: |
||||
control = self.convert(control, speed=self.speed) |
||||
|
||||
self.saturated = self._check_saturation(control, check_saturation, error) |
||||
|
||||
self.control = clip(control, self.neg_limit, self.pos_limit) |
||||
return self.control |
@ -0,0 +1,253 @@ |
||||
#!/usr/bin/env python3 |
||||
import math |
||||
import numpy as np |
||||
from common.params import Params |
||||
from common.numpy_fast import interp |
||||
|
||||
import cereal.messaging as messaging |
||||
from cereal import car |
||||
from common.realtime import sec_since_boot |
||||
from selfdrive.swaglog import cloudlog |
||||
from selfdrive.config import Conversions as CV |
||||
from selfdrive.controls.lib.speed_smoother import speed_smoother |
||||
from selfdrive.controls.lib.longcontrol import LongCtrlState, MIN_CAN_SPEED |
||||
from selfdrive.controls.lib.fcw import FCWChecker |
||||
from selfdrive.controls.lib.long_mpc import LongitudinalMpc |
||||
|
||||
MAX_SPEED = 255.0 |
||||
|
||||
LON_MPC_STEP = 0.2 # first step is 0.2s |
||||
MAX_SPEED_ERROR = 2.0 |
||||
AWARENESS_DECEL = -0.2 # car smoothly decel at .2m/s^2 when user is distracted |
||||
|
||||
# lookup tables VS speed to determine min and max accels in cruise |
||||
# make sure these accelerations are smaller than mpc limits |
||||
_A_CRUISE_MIN_V = [-1.0, -.8, -.67, -.5, -.30] |
||||
_A_CRUISE_MIN_BP = [ 0., 5., 10., 20., 40.] |
||||
|
||||
# need fast accel at very low speed for stop and go |
||||
# make sure these accelerations are smaller than mpc limits |
||||
_A_CRUISE_MAX_V = [1.2, 1.2, 0.65, .4] |
||||
_A_CRUISE_MAX_V_FOLLOWING = [1.6, 1.6, 0.65, .4] |
||||
_A_CRUISE_MAX_BP = [0., 6.4, 22.5, 40.] |
||||
|
||||
# Lookup table for turns |
||||
_A_TOTAL_MAX_V = [1.7, 3.2] |
||||
_A_TOTAL_MAX_BP = [20., 40.] |
||||
|
||||
# 75th percentile |
||||
SPEED_PERCENTILE_IDX = 7 |
||||
|
||||
|
||||
def calc_cruise_accel_limits(v_ego, following): |
||||
a_cruise_min = interp(v_ego, _A_CRUISE_MIN_BP, _A_CRUISE_MIN_V) |
||||
|
||||
if following: |
||||
a_cruise_max = interp(v_ego, _A_CRUISE_MAX_BP, _A_CRUISE_MAX_V_FOLLOWING) |
||||
else: |
||||
a_cruise_max = interp(v_ego, _A_CRUISE_MAX_BP, _A_CRUISE_MAX_V) |
||||
return np.vstack([a_cruise_min, a_cruise_max]) |
||||
|
||||
|
||||
def limit_accel_in_turns(v_ego, angle_steers, a_target, CP): |
||||
""" |
||||
This function returns a limited long acceleration allowed, depending on the existing lateral acceleration |
||||
this should avoid accelerating when losing the target in turns |
||||
""" |
||||
|
||||
a_total_max = interp(v_ego, _A_TOTAL_MAX_BP, _A_TOTAL_MAX_V) |
||||
a_y = v_ego**2 * angle_steers * CV.DEG_TO_RAD / (CP.steerRatio * CP.wheelbase) |
||||
a_x_allowed = math.sqrt(max(a_total_max**2 - a_y**2, 0.)) |
||||
|
||||
return [a_target[0], min(a_target[1], a_x_allowed)] |
||||
|
||||
|
||||
class Planner(): |
||||
def __init__(self, CP): |
||||
self.CP = CP |
||||
|
||||
self.mpc1 = LongitudinalMpc(1) |
||||
self.mpc2 = LongitudinalMpc(2) |
||||
|
||||
self.v_acc_start = 0.0 |
||||
self.a_acc_start = 0.0 |
||||
|
||||
self.v_acc = 0.0 |
||||
self.v_acc_future = 0.0 |
||||
self.a_acc = 0.0 |
||||
self.v_cruise = 0.0 |
||||
self.a_cruise = 0.0 |
||||
self.v_model = 0.0 |
||||
self.a_model = 0.0 |
||||
|
||||
self.longitudinalPlanSource = 'cruise' |
||||
self.fcw_checker = FCWChecker() |
||||
self.path_x = np.arange(192) |
||||
|
||||
self.params = Params() |
||||
self.first_loop = True |
||||
|
||||
def choose_solution(self, v_cruise_setpoint, enabled): |
||||
if enabled: |
||||
solutions = {'model': self.v_model, 'cruise': self.v_cruise} |
||||
if self.mpc1.prev_lead_status: |
||||
solutions['mpc1'] = self.mpc1.v_mpc |
||||
if self.mpc2.prev_lead_status: |
||||
solutions['mpc2'] = self.mpc2.v_mpc |
||||
|
||||
slowest = min(solutions, key=solutions.get) |
||||
|
||||
self.longitudinalPlanSource = slowest |
||||
# Choose lowest of MPC and cruise |
||||
if slowest == 'mpc1': |
||||
self.v_acc = self.mpc1.v_mpc |
||||
self.a_acc = self.mpc1.a_mpc |
||||
elif slowest == 'mpc2': |
||||
self.v_acc = self.mpc2.v_mpc |
||||
self.a_acc = self.mpc2.a_mpc |
||||
elif slowest == 'cruise': |
||||
self.v_acc = self.v_cruise |
||||
self.a_acc = self.a_cruise |
||||
elif slowest == 'model': |
||||
self.v_acc = self.v_model |
||||
self.a_acc = self.a_model |
||||
|
||||
self.v_acc_future = min([self.mpc1.v_mpc_future, self.mpc2.v_mpc_future, v_cruise_setpoint]) |
||||
|
||||
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: |
||||
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 |
@ -0,0 +1,159 @@ |
||||
from common.kalman.simple_kalman import KF1D |
||||
from selfdrive.config import RADAR_TO_CAMERA |
||||
|
||||
|
||||
# the longer lead decels, the more likely it will keep decelerating |
||||
# TODO is this a good default? |
||||
_LEAD_ACCEL_TAU = 1.5 |
||||
|
||||
# radar tracks |
||||
SPEED, ACCEL = 0, 1 # Kalman filter states enum |
||||
|
||||
# stationary qualification parameters |
||||
v_ego_stationary = 4. # no stationary object flag below this speed |
||||
|
||||
|
||||
class Track(): |
||||
def __init__(self, v_lead, kalman_params): |
||||
self.cnt = 0 |
||||
self.aLeadTau = _LEAD_ACCEL_TAU |
||||
self.K_A = kalman_params.A |
||||
self.K_C = kalman_params.C |
||||
self.K_K = kalman_params.K |
||||
self.kf = KF1D([[v_lead], [0.0]], self.K_A, self.K_C, self.K_K) |
||||
|
||||
def update(self, d_rel, y_rel, v_rel, v_lead, measured): |
||||
# relative values, copy |
||||
self.dRel = d_rel # LONG_DIST |
||||
self.yRel = y_rel # -LAT_DIST |
||||
self.vRel = v_rel # REL_SPEED |
||||
self.vLead = v_lead |
||||
self.measured = measured # measured or estimate |
||||
|
||||
# computed velocity and accelerations |
||||
if self.cnt > 0: |
||||
self.kf.update(self.vLead) |
||||
|
||||
self.vLeadK = float(self.kf.x[SPEED][0]) |
||||
self.aLeadK = float(self.kf.x[ACCEL][0]) |
||||
|
||||
# Learn if constant acceleration |
||||
if abs(self.aLeadK) < 0.5: |
||||
self.aLeadTau = _LEAD_ACCEL_TAU |
||||
else: |
||||
self.aLeadTau *= 0.9 |
||||
|
||||
self.cnt += 1 |
||||
|
||||
def get_key_for_cluster(self): |
||||
# Weigh y higher since radar is inaccurate in this dimension |
||||
return [self.dRel, self.yRel*2, self.vRel] |
||||
|
||||
def reset_a_lead(self, aLeadK, aLeadTau): |
||||
self.kf = KF1D([[self.vLead], [aLeadK]], self.K_A, self.K_C, self.K_K) |
||||
self.aLeadK = aLeadK |
||||
self.aLeadTau = aLeadTau |
||||
|
||||
def mean(l): |
||||
return sum(l) / len(l) |
||||
|
||||
|
||||
class Cluster(): |
||||
def __init__(self): |
||||
self.tracks = set() |
||||
|
||||
def add(self, t): |
||||
# add the first track |
||||
self.tracks.add(t) |
||||
|
||||
# TODO: make generic |
||||
@property |
||||
def dRel(self): |
||||
return mean([t.dRel for t in self.tracks]) |
||||
|
||||
@property |
||||
def yRel(self): |
||||
return mean([t.yRel for t in self.tracks]) |
||||
|
||||
@property |
||||
def vRel(self): |
||||
return mean([t.vRel for t in self.tracks]) |
||||
|
||||
@property |
||||
def aRel(self): |
||||
return mean([t.aRel for t in self.tracks]) |
||||
|
||||
@property |
||||
def vLead(self): |
||||
return mean([t.vLead for t in self.tracks]) |
||||
|
||||
@property |
||||
def dPath(self): |
||||
return mean([t.dPath for t in self.tracks]) |
||||
|
||||
@property |
||||
def vLat(self): |
||||
return mean([t.vLat for t in self.tracks]) |
||||
|
||||
@property |
||||
def vLeadK(self): |
||||
return mean([t.vLeadK for t in self.tracks]) |
||||
|
||||
@property |
||||
def aLeadK(self): |
||||
if all(t.cnt <= 1 for t in self.tracks): |
||||
return 0. |
||||
else: |
||||
return mean([t.aLeadK for t in self.tracks if t.cnt > 1]) |
||||
|
||||
@property |
||||
def aLeadTau(self): |
||||
if all(t.cnt <= 1 for t in self.tracks): |
||||
return _LEAD_ACCEL_TAU |
||||
else: |
||||
return mean([t.aLeadTau for t in self.tracks if t.cnt > 1]) |
||||
|
||||
@property |
||||
def measured(self): |
||||
return any(t.measured for t in self.tracks) |
||||
|
||||
def get_RadarState(self, model_prob=0.0): |
||||
return { |
||||
"dRel": float(self.dRel), |
||||
"yRel": float(self.yRel), |
||||
"vRel": float(self.vRel), |
||||
"vLead": float(self.vLead), |
||||
"vLeadK": float(self.vLeadK), |
||||
"aLeadK": float(self.aLeadK), |
||||
"status": True, |
||||
"fcw": self.is_potential_fcw(model_prob), |
||||
"modelProb": model_prob, |
||||
"radar": True, |
||||
"aLeadTau": float(self.aLeadTau) |
||||
} |
||||
|
||||
def get_RadarState_from_vision(self, lead_msg, v_ego): |
||||
return { |
||||
"dRel": float(lead_msg.dist - RADAR_TO_CAMERA), |
||||
"yRel": float(lead_msg.relY), |
||||
"vRel": float(lead_msg.relVel), |
||||
"vLead": float(v_ego + lead_msg.relVel), |
||||
"vLeadK": float(v_ego + lead_msg.relVel), |
||||
"aLeadK": float(0), |
||||
"aLeadTau": _LEAD_ACCEL_TAU, |
||||
"fcw": False, |
||||
"modelProb": float(lead_msg.prob), |
||||
"radar": False, |
||||
"status": True |
||||
} |
||||
|
||||
def __str__(self): |
||||
ret = "x: %4.1f y: %4.1f v: %4.1f a: %4.1f" % (self.dRel, self.yRel, self.vRel, self.aLeadK) |
||||
return ret |
||||
|
||||
def potential_low_speed_lead(self, v_ego): |
||||
# stop for stuff in front of you and low speed, even without model confirmation |
||||
return abs(self.yRel) < 1.5 and (v_ego < v_ego_stationary) and self.dRel < 25 |
||||
|
||||
def is_potential_fcw(self, model_prob): |
||||
return model_prob > .9 |
@ -0,0 +1,99 @@ |
||||
import numpy as np |
||||
|
||||
|
||||
def get_delta_out_limits(aEgo, aMax, aMin, jMax, jMin): |
||||
|
||||
tDelta = 0. |
||||
if aEgo > aMax: |
||||
tDelta = (aMax - aEgo) / jMin |
||||
elif aEgo < aMin: |
||||
tDelta = (aMin - aEgo) / jMax |
||||
|
||||
return tDelta |
||||
|
||||
|
||||
def speed_smoother(vEgo, aEgo, vT, aMax, aMin, jMax, jMin, ts): |
||||
|
||||
dV = vT - vEgo |
||||
|
||||
# recover quickly if dV is positive and aEgo is negative or viceversa |
||||
if dV > 0. and aEgo < 0.: |
||||
jMax *= 3. |
||||
elif dV < 0. and aEgo > 0.: |
||||
jMin *= 3. |
||||
|
||||
tDelta = get_delta_out_limits(aEgo, aMax, aMin, jMax, jMin) |
||||
|
||||
if (ts <= tDelta): |
||||
if (aEgo < aMin): |
||||
vEgo += ts * aEgo + 0.5 * ts**2 * jMax |
||||
aEgo += ts * jMax |
||||
return vEgo, aEgo |
||||
elif (aEgo > aMax): |
||||
vEgo += ts * aEgo + 0.5 * ts**2 * jMin |
||||
aEgo += ts * jMin |
||||
return vEgo, aEgo |
||||
|
||||
if aEgo > aMax: |
||||
dV -= 0.5 * (aMax**2 - aEgo**2) / jMin |
||||
vEgo += 0.5 * (aMax**2 - aEgo**2) / jMin |
||||
aEgo += tDelta * jMin |
||||
elif aEgo < aMin: |
||||
dV -= 0.5 * (aMin**2 - aEgo**2) / jMax |
||||
vEgo += 0.5 * (aMin**2 - aEgo**2) / jMax |
||||
aEgo += tDelta * jMax |
||||
|
||||
ts -= tDelta |
||||
|
||||
jLim = jMin if aEgo >= 0 else jMax |
||||
# if we reduce the accel to zero immediately, how much delta speed we generate? |
||||
dv_min_shift = - 0.5 * aEgo**2 / jLim |
||||
|
||||
# flip signs so we can consider only one case |
||||
flipped = False |
||||
if dV < dv_min_shift: |
||||
flipped = True |
||||
dV *= -1 |
||||
vEgo *= -1 |
||||
aEgo *= -1 |
||||
aMax = -aMin |
||||
jMaxcopy = -jMin |
||||
jMin = -jMax |
||||
jMax = jMaxcopy |
||||
|
||||
# small addition needed to avoid numerical issues with sqrt of ~zero |
||||
aPeak = np.sqrt((0.5 * aEgo**2 / jMax + dV + 1e-9) / (0.5 / jMax - 0.5 / jMin)) |
||||
|
||||
if aPeak > aMax: |
||||
aPeak = aMax |
||||
t1 = (aPeak - aEgo) / jMax |
||||
if aPeak <= 0: # there is no solution, so stop after t1 |
||||
t2 = t1 + ts + 1e-9 |
||||
t3 = t2 |
||||
else: |
||||
vChange = dV - 0.5 * (aPeak**2 - aEgo**2) / jMax + 0.5 * aPeak**2 / jMin |
||||
if vChange < aPeak * ts: |
||||
t2 = t1 + vChange / aPeak |
||||
else: |
||||
t2 = t1 + ts |
||||
t3 = t2 - aPeak / jMin |
||||
else: |
||||
t1 = (aPeak - aEgo) / jMax |
||||
t2 = t1 |
||||
t3 = t2 - aPeak / jMin |
||||
|
||||
dt1 = min(ts, t1) |
||||
dt2 = max(min(ts, t2) - t1, 0.) |
||||
dt3 = max(min(ts, t3) - t2, 0.) |
||||
|
||||
if ts > t3: |
||||
vEgo += dV |
||||
aEgo = 0. |
||||
else: |
||||
vEgo += aEgo * dt1 + 0.5 * dt1**2 * jMax + aPeak * dt2 + aPeak * dt3 + 0.5 * dt3**2 * jMin |
||||
aEgo += jMax * dt1 + dt3 * jMin |
||||
|
||||
vEgo *= -1 if flipped else 1 |
||||
aEgo *= -1 if flipped else 1 |
||||
|
||||
return float(vEgo), float(aEgo) |
@ -0,0 +1,206 @@ |
||||
#!/usr/bin/env python3 |
||||
import numpy as np |
||||
from numpy.linalg import solve |
||||
|
||||
""" |
||||
Dynamic bycicle model from "The Science of Vehicle Dynamics (2014), M. Guiggiani" |
||||
|
||||
The state is x = [v, r]^T |
||||
with v lateral speed [m/s], and r rotational speed [rad/s] |
||||
|
||||
The input u is the steering angle [rad] |
||||
|
||||
The system is defined by |
||||
x_dot = A*x + B*u |
||||
|
||||
A depends on longitudinal speed, u [m/s], and vehicle parameters CP |
||||
""" |
||||
|
||||
|
||||
def create_dyn_state_matrices(u, VM): |
||||
"""Returns the A and B matrix for the dynamics system |
||||
|
||||
Args: |
||||
u: Vehicle speed [m/s] |
||||
VM: Vehicle model |
||||
|
||||
Returns: |
||||
A tuple with the 2x2 A matrix, and 2x1 B matrix |
||||
|
||||
Parameters in the vehicle model: |
||||
cF: Tire stiffnes Front [N/rad] |
||||
cR: Tire stiffnes Front [N/rad] |
||||
aF: Distance from CG to front wheels [m] |
||||
aR: Distance from CG to rear wheels [m] |
||||
m: Mass [kg] |
||||
j: Rotational inertia [kg m^2] |
||||
sR: Steering ratio [-] |
||||
chi: Steer ratio rear [-] |
||||
""" |
||||
A = np.zeros((2, 2)) |
||||
B = np.zeros((2, 1)) |
||||
A[0, 0] = - (VM.cF + VM.cR) / (VM.m * u) |
||||
A[0, 1] = - (VM.cF * VM.aF - VM.cR * VM.aR) / (VM.m * u) - u |
||||
A[1, 0] = - (VM.cF * VM.aF - VM.cR * VM.aR) / (VM.j * u) |
||||
A[1, 1] = - (VM.cF * VM.aF**2 + VM.cR * VM.aR**2) / (VM.j * u) |
||||
B[0, 0] = (VM.cF + VM.chi * VM.cR) / VM.m / VM.sR |
||||
B[1, 0] = (VM.cF * VM.aF - VM.chi * VM.cR * VM.aR) / VM.j / VM.sR |
||||
return A, B |
||||
|
||||
|
||||
def kin_ss_sol(sa, u, VM): |
||||
"""Calculate the steady state solution at low speeds |
||||
At low speeds the tire slip is undefined, so a kinematic |
||||
model is used. |
||||
|
||||
Args: |
||||
sa: Steering angle [rad] |
||||
u: Speed [m/s] |
||||
VM: Vehicle model |
||||
|
||||
Returns: |
||||
2x1 matrix with steady state solution |
||||
""" |
||||
K = np.zeros((2, 1)) |
||||
K[0, 0] = VM.aR / VM.sR / VM.l * u |
||||
K[1, 0] = 1. / VM.sR / VM.l * u |
||||
return K * sa |
||||
|
||||
|
||||
def dyn_ss_sol(sa, u, VM): |
||||
"""Calculate the steady state solution when x_dot = 0, |
||||
Ax + Bu = 0 => x = A^{-1} B u |
||||
|
||||
Args: |
||||
sa: Steering angle [rad] |
||||
u: Speed [m/s] |
||||
VM: Vehicle model |
||||
|
||||
Returns: |
||||
2x1 matrix with steady state solution |
||||
""" |
||||
A, B = create_dyn_state_matrices(u, VM) |
||||
return -solve(A, B) * sa |
||||
|
||||
|
||||
def calc_slip_factor(VM): |
||||
"""The slip factor is a measure of how the curvature changes with speed |
||||
it's positive for Oversteering vehicle, negative (usual case) otherwise. |
||||
""" |
||||
return VM.m * (VM.cF * VM.aF - VM.cR * VM.aR) / (VM.l**2 * VM.cF * VM.cR) |
||||
|
||||
|
||||
class VehicleModel(): |
||||
def __init__(self, CP): |
||||
""" |
||||
Args: |
||||
CP: Car Parameters |
||||
""" |
||||
# for math readability, convert long names car params into short names |
||||
self.m = CP.mass |
||||
self.j = CP.rotationalInertia |
||||
self.l = CP.wheelbase |
||||
self.aF = CP.centerToFront |
||||
self.aR = CP.wheelbase - CP.centerToFront |
||||
self.chi = CP.steerRatioRear |
||||
|
||||
self.cF_orig = CP.tireStiffnessFront |
||||
self.cR_orig = CP.tireStiffnessRear |
||||
self.update_params(1.0, CP.steerRatio) |
||||
|
||||
def update_params(self, stiffness_factor, steer_ratio): |
||||
"""Update the vehicle model with a new stiffness factor and steer ratio""" |
||||
self.cF = stiffness_factor * self.cF_orig |
||||
self.cR = stiffness_factor * self.cR_orig |
||||
self.sR = steer_ratio |
||||
|
||||
def steady_state_sol(self, sa, u): |
||||
"""Returns the steady state solution. |
||||
|
||||
If the speed is too small we can't use the dynamic model (tire slip is undefined), |
||||
we then have to use the kinematic model |
||||
|
||||
Args: |
||||
sa: Steering wheel angle [rad] |
||||
u: Speed [m/s] |
||||
|
||||
Returns: |
||||
2x1 matrix with steady state solution (lateral speed, rotational speed) |
||||
""" |
||||
if u > 0.1: |
||||
return dyn_ss_sol(sa, u, self) |
||||
else: |
||||
return kin_ss_sol(sa, u, self) |
||||
|
||||
def calc_curvature(self, sa, u): |
||||
"""Returns the curvature. Multiplied by the speed this will give the yaw rate. |
||||
|
||||
Args: |
||||
sa: Steering wheel angle [rad] |
||||
u: Speed [m/s] |
||||
|
||||
Returns: |
||||
Curvature factor [1/m] |
||||
""" |
||||
return self.curvature_factor(u) * sa / self.sR |
||||
|
||||
def curvature_factor(self, u): |
||||
"""Returns the curvature factor. |
||||
Multiplied by wheel angle (not steering wheel angle) this will give the curvature. |
||||
|
||||
Args: |
||||
u: Speed [m/s] |
||||
|
||||
Returns: |
||||
Curvature factor [1/m] |
||||
""" |
||||
sf = calc_slip_factor(self) |
||||
return (1. - self.chi) / (1. - sf * u**2) / self.l |
||||
|
||||
def get_steer_from_curvature(self, curv, u): |
||||
"""Calculates the required steering wheel angle for a given curvature |
||||
|
||||
Args: |
||||
curv: Desired curvature [1/m] |
||||
u: Speed [m/s] |
||||
|
||||
Returns: |
||||
Steering wheel angle [rad] |
||||
""" |
||||
|
||||
return curv * self.sR * 1.0 / self.curvature_factor(u) |
||||
|
||||
def get_steer_from_yaw_rate(self, yaw_rate, u): |
||||
"""Calculates the required steering wheel angle for a given yaw_rate |
||||
|
||||
Args: |
||||
yaw_rate: Desired yaw rate [rad/s] |
||||
u: Speed [m/s] |
||||
|
||||
Returns: |
||||
Steering wheel angle [rad] |
||||
""" |
||||
curv = yaw_rate / u |
||||
return self.get_steer_from_curvature(curv, u) |
||||
|
||||
def yaw_rate(self, sa, u): |
||||
"""Calculate yaw rate |
||||
|
||||
Args: |
||||
sa: Steering wheel angle [rad] |
||||
u: Speed [m/s] |
||||
|
||||
Returns: |
||||
Yaw rate [rad/s] |
||||
""" |
||||
return self.calc_curvature(sa, u) * u |
||||
|
||||
|
||||
if __name__ == '__main__': |
||||
import math |
||||
from selfdrive.car.honda.interface import CarInterface |
||||
from selfdrive.car.honda.values import CAR |
||||
|
||||
CP = CarInterface.get_params(CAR.CIVIC) |
||||
VM = VehicleModel(CP) |
||||
print(VM.yaw_rate(math.radians(20), 10.)) |
@ -0,0 +1,54 @@ |
||||
#!/usr/bin/env python3 |
||||
import gc |
||||
|
||||
from cereal import car |
||||
from common.params import Params |
||||
from common.realtime import set_realtime_priority |
||||
from selfdrive.swaglog import cloudlog |
||||
from selfdrive.controls.lib.planner import Planner |
||||
from selfdrive.controls.lib.vehicle_model import VehicleModel |
||||
from selfdrive.controls.lib.pathplanner import PathPlanner |
||||
import cereal.messaging as messaging |
||||
|
||||
|
||||
def plannerd_thread(sm=None, pm=None): |
||||
gc.disable() |
||||
|
||||
# start the loop |
||||
set_realtime_priority(2) |
||||
|
||||
cloudlog.info("plannerd is waiting for CarParams") |
||||
CP = car.CarParams.from_bytes(Params().get("CarParams", block=True)) |
||||
cloudlog.info("plannerd got CarParams: %s", CP.carName) |
||||
|
||||
PL = Planner(CP) |
||||
PP = PathPlanner(CP) |
||||
|
||||
VM = VehicleModel(CP) |
||||
|
||||
if sm is None: |
||||
sm = messaging.SubMaster(['carState', 'controlsState', 'radarState', 'model', 'liveParameters']) |
||||
|
||||
if pm is None: |
||||
pm = messaging.PubMaster(['plan', 'liveLongitudinalMpc', 'pathPlan', 'liveMpc']) |
||||
|
||||
sm['liveParameters'].valid = True |
||||
sm['liveParameters'].sensorValid = True |
||||
sm['liveParameters'].steerRatio = CP.steerRatio |
||||
sm['liveParameters'].stiffnessFactor = 1.0 |
||||
|
||||
while True: |
||||
sm.update() |
||||
|
||||
if sm.updated['model']: |
||||
PP.update(sm, pm, CP, VM) |
||||
if sm.updated['radarState']: |
||||
PL.update(sm, pm, CP, VM, PP) |
||||
|
||||
|
||||
def main(sm=None, pm=None): |
||||
plannerd_thread(sm, pm) |
||||
|
||||
|
||||
if __name__ == "__main__": |
||||
main() |
@ -0,0 +1,246 @@ |
||||
#!/usr/bin/env python3 |
||||
import importlib |
||||
import math |
||||
from collections import defaultdict, deque |
||||
|
||||
import cereal.messaging as messaging |
||||
from cereal import car |
||||
from common.numpy_fast import interp |
||||
from common.params import Params |
||||
from common.realtime import Ratekeeper, set_realtime_priority |
||||
from selfdrive.config import RADAR_TO_CAMERA |
||||
from selfdrive.controls.lib.cluster.fastcluster_py import cluster_points_centroid |
||||
from selfdrive.controls.lib.radar_helpers import Cluster, Track |
||||
from selfdrive.swaglog import cloudlog |
||||
|
||||
|
||||
class KalmanParams(): |
||||
def __init__(self, dt): |
||||
# Lead Kalman Filter params, calculating K from A, C, Q, R requires the control library. |
||||
# hardcoding a lookup table to compute K for values of radar_ts between 0.1s and 1.0s |
||||
assert dt > .01 and dt < .1, "Radar time step must be between .01s and 0.1s" |
||||
self.A = [[1.0, dt], [0.0, 1.0]] |
||||
self.C = [1.0, 0.0] |
||||
#Q = np.matrix([[10., 0.0], [0.0, 100.]]) |
||||
#R = 1e3 |
||||
#K = np.matrix([[ 0.05705578], [ 0.03073241]]) |
||||
dts = [dt * 0.01 for dt in range(1, 11)] |
||||
K0 = [0.12288, 0.14557, 0.16523, 0.18282, 0.19887, 0.21372, 0.22761, 0.24069, 0.2531, 0.26491] |
||||
K1 = [0.29666, 0.29331, 0.29043, 0.28787, 0.28555, 0.28342, 0.28144, 0.27958, 0.27783, 0.27617] |
||||
self.K = [[interp(dt, dts, K0)], [interp(dt, dts, K1)]] |
||||
|
||||
|
||||
def laplacian_cdf(x, mu, b): |
||||
b = max(b, 1e-4) |
||||
return math.exp(-abs(x-mu)/b) |
||||
|
||||
|
||||
def match_vision_to_cluster(v_ego, lead, clusters): |
||||
# match vision point to best statistical cluster match |
||||
offset_vision_dist = lead.dist - RADAR_TO_CAMERA |
||||
|
||||
def prob(c): |
||||
prob_d = laplacian_cdf(c.dRel, offset_vision_dist, lead.std) |
||||
prob_y = laplacian_cdf(c.yRel, lead.relY, lead.relYStd) |
||||
prob_v = laplacian_cdf(c.vRel, lead.relVel, lead.relVelStd) |
||||
|
||||
# This is isn't exactly right, but good heuristic |
||||
return prob_d * prob_y * prob_v |
||||
|
||||
cluster = max(clusters, key=prob) |
||||
|
||||
# if no 'sane' match is found return -1 |
||||
# stationary radar points can be false positives |
||||
dist_sane = abs(cluster.dRel - offset_vision_dist) < max([(offset_vision_dist)*.25, 5.0]) |
||||
vel_sane = (abs(cluster.vRel - lead.relVel) < 10) or (v_ego + cluster.vRel > 2) |
||||
if dist_sane and vel_sane: |
||||
return cluster |
||||
else: |
||||
return None |
||||
|
||||
|
||||
def get_lead(v_ego, ready, clusters, lead_msg, low_speed_override=True): |
||||
# Determine leads, this is where the essential logic happens |
||||
if len(clusters) > 0 and ready and lead_msg.prob > .5: |
||||
cluster = match_vision_to_cluster(v_ego, lead_msg, clusters) |
||||
else: |
||||
cluster = None |
||||
|
||||
lead_dict = {'status': False} |
||||
if cluster is not None: |
||||
lead_dict = cluster.get_RadarState(lead_msg.prob) |
||||
elif (cluster is None) and ready and (lead_msg.prob > .5): |
||||
lead_dict = Cluster().get_RadarState_from_vision(lead_msg, v_ego) |
||||
|
||||
if low_speed_override: |
||||
low_speed_clusters = [c for c in clusters if c.potential_low_speed_lead(v_ego)] |
||||
if len(low_speed_clusters) > 0: |
||||
closest_cluster = min(low_speed_clusters, key=lambda c: c.dRel) |
||||
|
||||
# Only choose new cluster if it is actually closer than the previous one |
||||
if (not lead_dict['status']) or (closest_cluster.dRel < lead_dict['dRel']): |
||||
lead_dict = closest_cluster.get_RadarState() |
||||
|
||||
return lead_dict |
||||
|
||||
|
||||
class RadarD(): |
||||
def __init__(self, radar_ts, delay=0): |
||||
self.current_time = 0 |
||||
|
||||
self.tracks = defaultdict(dict) |
||||
self.kalman_params = KalmanParams(radar_ts) |
||||
|
||||
self.last_md_ts = 0 |
||||
self.last_controls_state_ts = 0 |
||||
|
||||
self.active = 0 |
||||
|
||||
# v_ego |
||||
self.v_ego = 0. |
||||
self.v_ego_hist = deque([0], maxlen=delay+1) |
||||
|
||||
self.ready = False |
||||
|
||||
def update(self, frame, sm, rr, has_radar): |
||||
self.current_time = 1e-9*max([sm.logMonoTime[key] for key in sm.logMonoTime.keys()]) |
||||
|
||||
if sm.updated['controlsState']: |
||||
self.active = sm['controlsState'].active |
||||
self.v_ego = sm['controlsState'].vEgo |
||||
self.v_ego_hist.append(self.v_ego) |
||||
if sm.updated['model']: |
||||
self.ready = True |
||||
|
||||
ar_pts = {} |
||||
for pt in rr.points: |
||||
ar_pts[pt.trackId] = [pt.dRel, pt.yRel, pt.vRel, pt.measured] |
||||
|
||||
# *** remove missing points from meta data *** |
||||
for ids in list(self.tracks.keys()): |
||||
if ids not in ar_pts: |
||||
self.tracks.pop(ids, None) |
||||
|
||||
# *** compute the tracks *** |
||||
for ids in ar_pts: |
||||
rpt = ar_pts[ids] |
||||
|
||||
# align v_ego by a fixed time to align it with the radar measurement |
||||
v_lead = rpt[2] + self.v_ego_hist[0] |
||||
|
||||
# create the track if it doesn't exist or it's a new track |
||||
if ids not in self.tracks: |
||||
self.tracks[ids] = Track(v_lead, self.kalman_params) |
||||
self.tracks[ids].update(rpt[0], rpt[1], rpt[2], v_lead, rpt[3]) |
||||
|
||||
idens = list(sorted(self.tracks.keys())) |
||||
track_pts = list([self.tracks[iden].get_key_for_cluster() for iden in idens]) |
||||
|
||||
|
||||
# If we have multiple points, cluster them |
||||
if len(track_pts) > 1: |
||||
cluster_idxs = cluster_points_centroid(track_pts, 2.5) |
||||
clusters = [None] * (max(cluster_idxs) + 1) |
||||
|
||||
for idx in range(len(track_pts)): |
||||
cluster_i = cluster_idxs[idx] |
||||
if clusters[cluster_i] is None: |
||||
clusters[cluster_i] = Cluster() |
||||
clusters[cluster_i].add(self.tracks[idens[idx]]) |
||||
elif len(track_pts) == 1: |
||||
# FIXME: cluster_point_centroid hangs forever if len(track_pts) == 1 |
||||
cluster_idxs = [0] |
||||
clusters = [Cluster()] |
||||
clusters[0].add(self.tracks[idens[0]]) |
||||
else: |
||||
clusters = [] |
||||
|
||||
# if a new point, reset accel to the rest of the cluster |
||||
for idx in range(len(track_pts)): |
||||
if self.tracks[idens[idx]].cnt <= 1: |
||||
aLeadK = clusters[cluster_idxs[idx]].aLeadK |
||||
aLeadTau = clusters[cluster_idxs[idx]].aLeadTau |
||||
self.tracks[idens[idx]].reset_a_lead(aLeadK, aLeadTau) |
||||
|
||||
# *** publish radarState *** |
||||
dat = messaging.new_message() |
||||
dat.init('radarState') |
||||
dat.valid = sm.all_alive_and_valid(service_list=['controlsState', 'model']) |
||||
dat.radarState.mdMonoTime = self.last_md_ts |
||||
dat.radarState.canMonoTimes = list(rr.canMonoTimes) |
||||
dat.radarState.radarErrors = list(rr.errors) |
||||
dat.radarState.controlsStateMonoTime = self.last_controls_state_ts |
||||
|
||||
if has_radar: |
||||
dat.radarState.leadOne = get_lead(self.v_ego, self.ready, clusters, sm['model'].lead, low_speed_override=True) |
||||
dat.radarState.leadTwo = get_lead(self.v_ego, self.ready, clusters, sm['model'].leadFuture, low_speed_override=False) |
||||
return dat |
||||
|
||||
|
||||
# fuses camera and radar data for best lead detection |
||||
def radard_thread(sm=None, pm=None, can_sock=None): |
||||
set_realtime_priority(2) |
||||
|
||||
# wait for stats about the car to come in from controls |
||||
cloudlog.info("radard is waiting for CarParams") |
||||
CP = car.CarParams.from_bytes(Params().get("CarParams", block=True)) |
||||
cloudlog.info("radard got CarParams") |
||||
|
||||
# import the radar from the fingerprint |
||||
cloudlog.info("radard is importing %s", CP.carName) |
||||
RadarInterface = importlib.import_module('selfdrive.car.%s.radar_interface' % CP.carName).RadarInterface |
||||
|
||||
if can_sock is None: |
||||
can_sock = messaging.sub_sock('can') |
||||
|
||||
if sm is None: |
||||
sm = messaging.SubMaster(['model', 'controlsState', 'liveParameters']) |
||||
|
||||
# *** publish radarState and liveTracks |
||||
if pm is None: |
||||
pm = messaging.PubMaster(['radarState', 'liveTracks']) |
||||
|
||||
RI = RadarInterface(CP) |
||||
|
||||
rk = Ratekeeper(1.0 / CP.radarTimeStep, print_delay_threshold=None) |
||||
RD = RadarD(CP.radarTimeStep, RI.delay) |
||||
|
||||
has_radar = not CP.radarOffCan |
||||
|
||||
while 1: |
||||
can_strings = messaging.drain_sock_raw(can_sock, wait_for_one=True) |
||||
rr = RI.update(can_strings) |
||||
|
||||
if rr is None: |
||||
continue |
||||
|
||||
sm.update(0) |
||||
|
||||
dat = RD.update(rk.frame, sm, rr, has_radar) |
||||
dat.radarState.cumLagMs = -rk.remaining*1000. |
||||
|
||||
pm.send('radarState', dat) |
||||
|
||||
# *** publish tracks for UI debugging (keep last) *** |
||||
tracks = RD.tracks |
||||
dat = messaging.new_message() |
||||
dat.init('liveTracks', len(tracks)) |
||||
|
||||
for cnt, ids in enumerate(sorted(tracks.keys())): |
||||
dat.liveTracks[cnt] = { |
||||
"trackId": ids, |
||||
"dRel": float(tracks[ids].dRel), |
||||
"yRel": float(tracks[ids].yRel), |
||||
"vRel": float(tracks[ids].vRel), |
||||
} |
||||
pm.send('liveTracks', dat) |
||||
|
||||
rk.monitor_time() |
||||
|
||||
|
||||
def main(sm=None, pm=None, can_sock=None): |
||||
radard_thread(sm, pm, can_sock) |
||||
|
||||
|
||||
if __name__ == "__main__": |
||||
main() |
@ -0,0 +1,138 @@ |
||||
import time |
||||
import unittest |
||||
import numpy as np |
||||
from fastcluster import linkage_vector |
||||
from scipy.cluster import _hierarchy |
||||
from scipy.spatial.distance import pdist |
||||
|
||||
from selfdrive.controls.lib.cluster.fastcluster_py import hclust, ffi |
||||
from selfdrive.controls.lib.cluster.fastcluster_py import cluster_points_centroid |
||||
|
||||
|
||||
def fcluster(Z, t, criterion='inconsistent', depth=2, R=None, monocrit=None): |
||||
# supersimplified function to get fast clustering. Got it from scipy |
||||
Z = np.asarray(Z, order='c') |
||||
n = Z.shape[0] + 1 |
||||
T = np.zeros((n,), dtype='i') |
||||
_hierarchy.cluster_dist(Z, T, float(t), int(n)) |
||||
return T |
||||
|
||||
|
||||
TRACK_PTS = np.array([[59.26000137, -9.35999966, -5.42500019], |
||||
[91.61999817, -0.31999999, -2.75], |
||||
[31.38000031, 0.40000001, -0.2], |
||||
[89.57999725, -8.07999992, -18.04999924], |
||||
[53.42000122, 0.63999999, -0.175], |
||||
[31.38000031, 0.47999999, -0.2], |
||||
[36.33999939, 0.16, -0.2], |
||||
[53.33999939, 0.95999998, -0.175], |
||||
[59.26000137, -9.76000023, -5.44999981], |
||||
[33.93999977, 0.40000001, -0.22499999], |
||||
[106.74000092, -5.76000023, -18.04999924]]) |
||||
|
||||
CORRECT_LINK = np.array([[2., 5., 0.07999998, 2.], |
||||
[4., 7., 0.32984889, 2.], |
||||
[0., 8., 0.40078104, 2.], |
||||
[6., 9., 2.41209933, 2.], |
||||
[11., 14., 3.76342275, 4.], |
||||
[12., 13., 13.02297651, 4.], |
||||
[1., 3., 17.27626057, 2.], |
||||
[10., 17., 17.92918845, 3.], |
||||
[15., 16., 23.68525366, 8.], |
||||
[18., 19., 52.52351319, 11.]]) |
||||
|
||||
CORRECT_LABELS = np.array([7, 1, 4, 2, 6, 4, 5, 6, 7, 5, 3], dtype=np.int32) |
||||
|
||||
|
||||
def plot_cluster(pts, idx_old, idx_new): |
||||
import matplotlib.pyplot as plt |
||||
m = 'Set1' |
||||
|
||||
plt.figure() |
||||
plt.subplot(1, 2, 1) |
||||
plt.scatter(pts[:, 0], pts[:, 1], c=idx_old, cmap=m) |
||||
plt.title("Old") |
||||
plt.colorbar() |
||||
plt.subplot(1, 2, 2) |
||||
plt.scatter(pts[:, 0], pts[:, 1], c=idx_new, cmap=m) |
||||
plt.title("New") |
||||
plt.colorbar() |
||||
|
||||
plt.show() |
||||
|
||||
|
||||
def same_clusters(correct, other): |
||||
correct = np.asarray(correct) |
||||
other = np.asarray(other) |
||||
if len(correct) != len(other): |
||||
return False |
||||
|
||||
for i in range(len(correct)): |
||||
c = np.where(correct == correct[i]) |
||||
o = np.where(other == other[i]) |
||||
if not np.array_equal(c, o): |
||||
return False |
||||
return True |
||||
|
||||
|
||||
class TestClustering(unittest.TestCase): |
||||
def test_scipy_clustering(self): |
||||
old_link = linkage_vector(TRACK_PTS, method='centroid') |
||||
old_cluster_idxs = fcluster(old_link, 2.5, criterion='distance') |
||||
|
||||
np.testing.assert_allclose(old_link, CORRECT_LINK) |
||||
np.testing.assert_allclose(old_cluster_idxs, CORRECT_LABELS) |
||||
|
||||
def test_pdist(self): |
||||
pts = np.ascontiguousarray(TRACK_PTS, dtype=np.float64) |
||||
pts_ptr = ffi.cast("double *", pts.ctypes.data) |
||||
|
||||
n, m = pts.shape |
||||
out = np.zeros((n * (n - 1) // 2, ), dtype=np.float64) |
||||
out_ptr = ffi.cast("double *", out.ctypes.data) |
||||
hclust.hclust_pdist(n, m, pts_ptr, out_ptr) |
||||
|
||||
np.testing.assert_allclose(out, np.power(pdist(TRACK_PTS), 2)) |
||||
|
||||
def test_cpp_clustering(self): |
||||
pts = np.ascontiguousarray(TRACK_PTS, dtype=np.float64) |
||||
pts_ptr = ffi.cast("double *", pts.ctypes.data) |
||||
n, m = pts.shape |
||||
|
||||
labels = np.zeros((n, ), dtype=np.int32) |
||||
labels_ptr = ffi.cast("int *", labels.ctypes.data) |
||||
hclust.cluster_points_centroid(n, m, pts_ptr, 2.5**2, labels_ptr) |
||||
self.assertTrue(same_clusters(CORRECT_LABELS, labels)) |
||||
|
||||
def test_cpp_wrapper_clustering(self): |
||||
labels = cluster_points_centroid(TRACK_PTS, 2.5) |
||||
self.assertTrue(same_clusters(CORRECT_LABELS, labels)) |
||||
|
||||
def test_random_cluster(self): |
||||
np.random.seed(1337) |
||||
N = 1000 |
||||
|
||||
t_old = 0. |
||||
t_new = 0. |
||||
|
||||
for _ in range(N): |
||||
n = int(np.random.uniform(2, 32)) |
||||
x = np.random.uniform(-10, 50, (n, 1)) |
||||
y = np.random.uniform(-5, 5, (n, 1)) |
||||
vrel = np.random.uniform(-5, 5, (n, 1)) |
||||
pts = np.hstack([x, y, vrel]) |
||||
|
||||
t = time.time() |
||||
old_link = linkage_vector(pts, method='centroid') |
||||
old_cluster_idx = fcluster(old_link, 2.5, criterion='distance') |
||||
t_old += time.time() - t |
||||
|
||||
t = time.time() |
||||
cluster_idx = cluster_points_centroid(pts, 2.5) |
||||
t_new += time.time() - t |
||||
|
||||
self.assertTrue(same_clusters(old_cluster_idx, cluster_idx)) |
||||
|
||||
|
||||
if __name__ == "__main__": |
||||
unittest.main() |
@ -0,0 +1,91 @@ |
||||
import unittest |
||||
import numpy as np |
||||
|
||||
from cereal import log |
||||
import cereal.messaging as messaging |
||||
from selfdrive.config import Conversions as CV |
||||
from selfdrive.controls.lib.planner import calc_cruise_accel_limits |
||||
from selfdrive.controls.lib.speed_smoother import speed_smoother |
||||
from selfdrive.controls.lib.long_mpc import LongitudinalMpc |
||||
|
||||
|
||||
def RW(v_ego, v_l): |
||||
TR = 1.8 |
||||
G = 9.81 |
||||
return (v_ego * TR - (v_l - v_ego) * TR + v_ego * v_ego / (2 * G) - v_l * v_l / (2 * G)) |
||||
|
||||
|
||||
class FakePubMaster(): |
||||
def send(self, s, data): |
||||
assert data |
||||
|
||||
|
||||
def run_following_distance_simulation(v_lead, t_end=200.0): |
||||
dt = 0.2 |
||||
t = 0. |
||||
|
||||
x_lead = 200.0 |
||||
|
||||
x_ego = 0.0 |
||||
v_ego = v_lead |
||||
a_ego = 0.0 |
||||
|
||||
v_cruise_setpoint = v_lead + 10. |
||||
|
||||
pm = FakePubMaster() |
||||
mpc = LongitudinalMpc(1) |
||||
|
||||
first = True |
||||
while t < t_end: |
||||
# Run cruise control |
||||
accel_limits = [float(x) for x in calc_cruise_accel_limits(v_ego, False)] |
||||
jerk_limits = [min(-0.1, accel_limits[0]), max(0.1, accel_limits[1])] |
||||
v_cruise, a_cruise = speed_smoother(v_ego, a_ego, v_cruise_setpoint, |
||||
accel_limits[1], accel_limits[0], |
||||
jerk_limits[1], jerk_limits[0], |
||||
dt) |
||||
|
||||
# Setup CarState |
||||
CS = messaging.new_message() |
||||
CS.init('carState') |
||||
CS.carState.vEgo = v_ego |
||||
CS.carState.aEgo = a_ego |
||||
|
||||
# Setup lead packet |
||||
lead = log.RadarState.LeadData.new_message() |
||||
lead.status = True |
||||
lead.dRel = x_lead - x_ego |
||||
lead.vLead = v_lead |
||||
lead.aLeadK = 0.0 |
||||
|
||||
# Run MPC |
||||
mpc.set_cur_state(v_ego, a_ego) |
||||
if first: # Make sure MPC is converged on first timestep |
||||
for _ in range(20): |
||||
mpc.update(pm, CS.carState, lead, v_cruise_setpoint) |
||||
mpc.update(pm, CS.carState, lead, v_cruise_setpoint) |
||||
|
||||
# Choose slowest of two solutions |
||||
if v_cruise < mpc.v_mpc: |
||||
v_ego, a_ego = v_cruise, a_cruise |
||||
else: |
||||
v_ego, a_ego = mpc.v_mpc, mpc.a_mpc |
||||
|
||||
# Update state |
||||
x_lead += v_lead * dt |
||||
x_ego += v_ego * dt |
||||
t += dt |
||||
first = False |
||||
|
||||
return x_lead - x_ego |
||||
|
||||
|
||||
class TestFollowingDistance(unittest.TestCase): |
||||
def test_following_distanc(self): |
||||
for speed_mph in np.linspace(10, 100, num=10): |
||||
v_lead = float(speed_mph * CV.MPH_TO_MS) |
||||
|
||||
simulation_steady_state = run_following_distance_simulation(v_lead) |
||||
correct_steady_state = RW(v_lead, v_lead) + 4.0 |
||||
|
||||
self.assertAlmostEqual(simulation_steady_state, correct_steady_state, delta=0.1) |
@ -0,0 +1,129 @@ |
||||
import unittest |
||||
import numpy as np |
||||
from selfdrive.car.honda.interface import CarInterface |
||||
from selfdrive.controls.lib.lateral_mpc import libmpc_py |
||||
from selfdrive.controls.lib.vehicle_model import VehicleModel |
||||
from selfdrive.controls.lib.lane_planner import calc_d_poly |
||||
|
||||
|
||||
def run_mpc(v_ref=30., x_init=0., y_init=0., psi_init=0., delta_init=0., |
||||
l_prob=1., r_prob=1., p_prob=1., |
||||
poly_l=np.array([0., 0., 0., 1.8]), poly_r=np.array([0., 0., 0., -1.8]), poly_p=np.array([0., 0., 0., 0.]), |
||||
lane_width=3.6, poly_shift=0.): |
||||
|
||||
libmpc = libmpc_py.libmpc |
||||
libmpc.init(1.0, 3.0, 1.0, 1.0) |
||||
|
||||
mpc_solution = libmpc_py.ffi.new("log_t *") |
||||
|
||||
p_l = poly_l.copy() |
||||
p_l[3] += poly_shift |
||||
|
||||
p_r = poly_r.copy() |
||||
p_r[3] += poly_shift |
||||
|
||||
p_p = poly_p.copy() |
||||
p_p[3] += poly_shift |
||||
|
||||
d_poly = calc_d_poly(p_l, p_r, p_p, l_prob, r_prob, lane_width) |
||||
|
||||
CP = CarInterface.get_params("HONDA CIVIC 2016 TOURING") |
||||
VM = VehicleModel(CP) |
||||
|
||||
v_ref = v_ref |
||||
curvature_factor = VM.curvature_factor(v_ref) |
||||
|
||||
l_poly = libmpc_py.ffi.new("double[4]", list(map(float, p_l))) |
||||
r_poly = libmpc_py.ffi.new("double[4]", list(map(float, p_r))) |
||||
d_poly = libmpc_py.ffi.new("double[4]", list(map(float, d_poly))) |
||||
|
||||
cur_state = libmpc_py.ffi.new("state_t *") |
||||
cur_state[0].x = x_init |
||||
cur_state[0].y = y_init |
||||
cur_state[0].psi = psi_init |
||||
cur_state[0].delta = delta_init |
||||
|
||||
# converge in no more than 20 iterations |
||||
for _ in range(20): |
||||
libmpc.run_mpc(cur_state, mpc_solution, l_poly, r_poly, d_poly, l_prob, r_prob, |
||||
curvature_factor, v_ref, lane_width) |
||||
|
||||
return mpc_solution |
||||
|
||||
|
||||
class TestLateralMpc(unittest.TestCase): |
||||
|
||||
def _assert_null(self, sol, delta=1e-6): |
||||
for i in range(len(sol[0].y)): |
||||
self.assertAlmostEqual(sol[0].y[i], 0., delta=delta) |
||||
self.assertAlmostEqual(sol[0].psi[i], 0., delta=delta) |
||||
self.assertAlmostEqual(sol[0].delta[i], 0., delta=delta) |
||||
|
||||
def _assert_simmetry(self, sol, delta=1e-6): |
||||
for i in range(len(sol[0][0].y)): |
||||
self.assertAlmostEqual(sol[0][0].y[i], -sol[1][0].y[i], delta=delta) |
||||
self.assertAlmostEqual(sol[0][0].psi[i], -sol[1][0].psi[i], delta=delta) |
||||
self.assertAlmostEqual(sol[0][0].delta[i], -sol[1][0].delta[i], delta=delta) |
||||
self.assertAlmostEqual(sol[0][0].x[i], sol[1][0].x[i], delta=delta) |
||||
|
||||
def _assert_identity(self, sol, ignore_y=False, delta=1e-6): |
||||
for i in range(len(sol[0][0].y)): |
||||
self.assertAlmostEqual(sol[0][0].psi[i], sol[1][0].psi[i], delta=delta) |
||||
self.assertAlmostEqual(sol[0][0].delta[i], sol[1][0].delta[i], delta=delta) |
||||
self.assertAlmostEqual(sol[0][0].x[i], sol[1][0].x[i], delta=delta) |
||||
if not ignore_y: |
||||
self.assertAlmostEqual(sol[0][0].y[i], sol[1][0].y[i], delta=delta) |
||||
|
||||
def test_straight(self): |
||||
sol = run_mpc() |
||||
self._assert_null(sol) |
||||
|
||||
def test_y_symmetry(self): |
||||
sol = [] |
||||
for y_init in [-0.5, 0.5]: |
||||
sol.append(run_mpc(y_init=y_init)) |
||||
self._assert_simmetry(sol) |
||||
|
||||
def test_poly_symmetry(self): |
||||
sol = [] |
||||
for poly_shift in [-1., 1.]: |
||||
sol.append(run_mpc(poly_shift=poly_shift)) |
||||
self._assert_simmetry(sol) |
||||
|
||||
def test_delta_symmetry(self): |
||||
sol = [] |
||||
for delta_init in [-0.1, 0.1]: |
||||
sol.append(run_mpc(delta_init=delta_init)) |
||||
self._assert_simmetry(sol) |
||||
|
||||
def test_psi_symmetry(self): |
||||
sol = [] |
||||
for psi_init in [-0.1, 0.1]: |
||||
sol.append(run_mpc(psi_init=psi_init)) |
||||
self._assert_simmetry(sol) |
||||
|
||||
def test_prob_symmetry(self): |
||||
sol = [] |
||||
lane_width = 3. |
||||
for r_prob in [0., 1.]: |
||||
sol.append(run_mpc(r_prob=r_prob, l_prob=1.-r_prob, lane_width=lane_width)) |
||||
self._assert_simmetry(sol) |
||||
|
||||
def test_y_shift_vs_poly_shift(self): |
||||
shift = 1. |
||||
sol = [] |
||||
sol.append(run_mpc(y_init=shift)) |
||||
sol.append(run_mpc(poly_shift=-shift)) |
||||
# need larger delta than standard, otherwise it false triggers. |
||||
# this is acceptable because the 2 cases are very different from the optimizer standpoint |
||||
self._assert_identity(sol, ignore_y=True, delta=1e-5) |
||||
|
||||
def test_no_overshoot(self): |
||||
y_init = 1. |
||||
sol = run_mpc(y_init=y_init) |
||||
for y in list(sol[0].y): |
||||
self.assertGreaterEqual(y_init, abs(y)) |
||||
|
||||
|
||||
if __name__ == "__main__": |
||||
unittest.main() |
@ -0,0 +1,234 @@ |
||||
import unittest |
||||
import numpy as np |
||||
from common.realtime import DT_CTRL, DT_DMON |
||||
from selfdrive.controls.lib.driver_monitor import DriverStatus, MAX_TERMINAL_ALERTS, \ |
||||
_AWARENESS_TIME, _AWARENESS_PRE_TIME_TILL_TERMINAL, \ |
||||
_AWARENESS_PROMPT_TIME_TILL_TERMINAL, _DISTRACTED_TIME, \ |
||||
_DISTRACTED_PRE_TIME_TILL_TERMINAL, _DISTRACTED_PROMPT_TIME_TILL_TERMINAL, \ |
||||
_POSESTD_THRESHOLD, _HI_STD_TIMEOUT |
||||
from selfdrive.controls.lib.gps_helpers import is_rhd_region |
||||
|
||||
_TEST_TIMESPAN = 120 # seconds |
||||
_DISTRACTED_SECONDS_TO_ORANGE = _DISTRACTED_TIME - _DISTRACTED_PROMPT_TIME_TILL_TERMINAL + 1 |
||||
_DISTRACTED_SECONDS_TO_RED = _DISTRACTED_TIME + 1 |
||||
_INVISIBLE_SECONDS_TO_ORANGE = _AWARENESS_TIME - _AWARENESS_PROMPT_TIME_TILL_TERMINAL + 1 |
||||
_INVISIBLE_SECONDS_TO_RED = _AWARENESS_TIME + 1 |
||||
_UNCERTAIN_SECONDS_TO_GREEN = _HI_STD_TIMEOUT + 0.5 |
||||
|
||||
class fake_DM_msg(): |
||||
def __init__(self, is_face_detected, is_distracted=False, is_model_uncertain=False): |
||||
self.faceOrientation = [0.,0.,0.] |
||||
self.facePosition = [0.,0.] |
||||
self.faceProb = 1. * is_face_detected |
||||
self.leftEyeProb = 1. |
||||
self.rightEyeProb = 1. |
||||
self.leftBlinkProb = 1. * is_distracted |
||||
self.rightBlinkProb = 1. * is_distracted |
||||
self.faceOrientationStd = [1.*is_model_uncertain,1.*is_model_uncertain,1.*is_model_uncertain] |
||||
self.facePositionStd = [1.*is_model_uncertain,1.*is_model_uncertain] |
||||
|
||||
|
||||
# driver state from neural net, 10Hz |
||||
msg_NO_FACE_DETECTED = fake_DM_msg(is_face_detected=False) |
||||
msg_ATTENTIVE = fake_DM_msg(is_face_detected=True) |
||||
msg_DISTRACTED = fake_DM_msg(is_face_detected=True, is_distracted=True) |
||||
msg_ATTENTIVE_UNCERTAIN = fake_DM_msg(is_face_detected=True, is_model_uncertain=True) |
||||
msg_DISTRACTED_UNCERTAIN = fake_DM_msg(is_face_detected=True, is_distracted=True, is_model_uncertain=True) |
||||
msg_DISTRACTED_BUT_SOMEHOW_UNCERTAIN = fake_DM_msg(is_face_detected=True, is_distracted=True, is_model_uncertain=_POSESTD_THRESHOLD*1.5) |
||||
|
||||
# driver interaction with car |
||||
car_interaction_DETECTED = True |
||||
car_interaction_NOT_DETECTED = False |
||||
|
||||
# openpilot state |
||||
openpilot_ENGAGED = True |
||||
openpilot_NOT_ENGAGED = False |
||||
|
||||
# car standstill state |
||||
car_STANDSTILL = True |
||||
car_NOT_STANDSTILL = False |
||||
|
||||
# some common state vectors |
||||
always_no_face = [msg_NO_FACE_DETECTED] * int(_TEST_TIMESPAN/DT_DMON) |
||||
always_attentive = [msg_ATTENTIVE] * int(_TEST_TIMESPAN/DT_DMON) |
||||
always_distracted = [msg_DISTRACTED] * int(_TEST_TIMESPAN/DT_DMON) |
||||
always_true = [True] * int(_TEST_TIMESPAN/DT_DMON) |
||||
always_false = [False] * int(_TEST_TIMESPAN/DT_DMON) |
||||
|
||||
def run_DState_seq(driver_state_msgs, driver_car_interaction, openpilot_status, car_standstill_status): |
||||
# inputs are all 10Hz |
||||
DS = DriverStatus() |
||||
events_from_DM = [] |
||||
for idx in range(len(driver_state_msgs)): |
||||
DS.get_pose(driver_state_msgs[idx], [0,0,0], 0, openpilot_status[idx]) |
||||
# cal_rpy and car_speed don't matter here |
||||
|
||||
# to match frequency of controlsd (100Hz) |
||||
for _ in range(int(DT_DMON/DT_CTRL)): |
||||
event_per_state = DS.update([], driver_car_interaction[idx], openpilot_status[idx], car_standstill_status[idx]) |
||||
events_from_DM.append(event_per_state) # evaluate events at 10Hz for tests |
||||
|
||||
assert len(events_from_DM)==len(driver_state_msgs), 'somethings wrong' |
||||
return events_from_DM |
||||
|
||||
class TestMonitoring(unittest.TestCase): |
||||
# -1. rhd parser sanity check |
||||
def test_rhd_parser(self): |
||||
cities = [[32.7, -117.1, 0],\ |
||||
[51.5, 0.129, 1],\ |
||||
[35.7, 139.7, 1],\ |
||||
[-37.8, 144.9, 1],\ |
||||
[32.1, 41.74, 0],\ |
||||
[55.7, 12.69, 0]] |
||||
result = [] |
||||
for city in cities: |
||||
result.append(int(is_rhd_region(city[0],city[1]))) |
||||
self.assertEqual(result,[int(city[2]) for city in cities]) |
||||
|
||||
# 0. op engaged, driver is doing fine all the time |
||||
def test_fully_aware_driver(self): |
||||
events_output = run_DState_seq(always_attentive, always_false, always_true, always_false) |
||||
self.assertTrue(np.sum([len(event) for event in events_output])==0) |
||||
|
||||
# 1. op engaged, driver is distracted and does nothing |
||||
def test_fully_distracted_driver(self): |
||||
events_output = run_DState_seq(always_distracted, always_false, always_true, always_false) |
||||
self.assertTrue(len(events_output[int((_DISTRACTED_TIME-_DISTRACTED_PRE_TIME_TILL_TERMINAL)/2/DT_DMON)])==0) |
||||
self.assertEqual(events_output[int((_DISTRACTED_TIME-_DISTRACTED_PRE_TIME_TILL_TERMINAL+\ |
||||
((_DISTRACTED_PRE_TIME_TILL_TERMINAL-_DISTRACTED_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)][0].name, 'preDriverDistracted') |
||||
self.assertEqual(events_output[int((_DISTRACTED_TIME-_DISTRACTED_PROMPT_TIME_TILL_TERMINAL+\ |
||||
((_DISTRACTED_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)][0].name, 'promptDriverDistracted') |
||||
self.assertEqual(events_output[int((_DISTRACTED_TIME+\ |
||||
((_TEST_TIMESPAN-10-_DISTRACTED_TIME)/2))/DT_DMON)][0].name, 'driverDistracted') |
||||
|
||||
# 2. op engaged, no face detected the whole time, no action |
||||
def test_fully_invisible_driver(self): |
||||
events_output = run_DState_seq(always_no_face, always_false, always_true, always_false) |
||||
self.assertTrue(len(events_output[int((_AWARENESS_TIME-_AWARENESS_PRE_TIME_TILL_TERMINAL)/2/DT_DMON)])==0) |
||||
self.assertEqual(events_output[int((_AWARENESS_TIME-_AWARENESS_PRE_TIME_TILL_TERMINAL+\ |
||||
((_AWARENESS_PRE_TIME_TILL_TERMINAL-_AWARENESS_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)][0].name, 'preDriverUnresponsive') |
||||
self.assertEqual(events_output[int((_AWARENESS_TIME-_AWARENESS_PROMPT_TIME_TILL_TERMINAL+\ |
||||
((_AWARENESS_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)][0].name, 'promptDriverUnresponsive') |
||||
self.assertEqual(events_output[int((_AWARENESS_TIME+\ |
||||
((_TEST_TIMESPAN-10-_AWARENESS_TIME)/2))/DT_DMON)][0].name, 'driverUnresponsive') |
||||
|
||||
# 3. op engaged, down to orange, driver pays attention, back to normal; then down to orange, driver touches wheel |
||||
# - should have short orange recovery time and no green afterwards; should recover rightaway on wheel touch |
||||
def test_normal_driver(self): |
||||
ds_vector = [msg_DISTRACTED] * int(_DISTRACTED_SECONDS_TO_ORANGE/DT_DMON) + \ |
||||
[msg_ATTENTIVE] * int(_DISTRACTED_SECONDS_TO_ORANGE/DT_DMON) + \ |
||||
[msg_DISTRACTED] * (int(_TEST_TIMESPAN/DT_DMON)-int(_DISTRACTED_SECONDS_TO_ORANGE*2/DT_DMON)) |
||||
interaction_vector = [car_interaction_NOT_DETECTED] * int(_DISTRACTED_SECONDS_TO_ORANGE*3/DT_DMON) + \ |
||||
[car_interaction_DETECTED] * (int(_TEST_TIMESPAN/DT_DMON)-int(_DISTRACTED_SECONDS_TO_ORANGE*3/DT_DMON)) |
||||
events_output = run_DState_seq(ds_vector, interaction_vector, always_true, always_false) |
||||
self.assertTrue(len(events_output[int(_DISTRACTED_SECONDS_TO_ORANGE*0.5/DT_DMON)])==0) |
||||
self.assertEqual(events_output[int((_DISTRACTED_SECONDS_TO_ORANGE-0.1)/DT_DMON)][0].name, 'promptDriverDistracted') |
||||
self.assertTrue(len(events_output[int(_DISTRACTED_SECONDS_TO_ORANGE*1.5/DT_DMON)])==0) |
||||
self.assertEqual(events_output[int((_DISTRACTED_SECONDS_TO_ORANGE*3-0.1)/DT_DMON)][0].name, 'promptDriverDistracted') |
||||
self.assertTrue(len(events_output[int((_DISTRACTED_SECONDS_TO_ORANGE*3+0.1)/DT_DMON)])==0) |
||||
|
||||
# 4. op engaged, down to orange, driver dodges camera, then comes back still distracted, down to red, \ |
||||
# driver dodges, and then touches wheel to no avail, disengages and reengages |
||||
# - orange/red alert should remain after disappearance, and only disengaging clears red |
||||
def test_biggest_comma_fan(self): |
||||
_invisible_time = 2 # seconds |
||||
ds_vector = always_distracted[:] |
||||
interaction_vector = always_false[:] |
||||
op_vector = always_true[:] |
||||
ds_vector[int(_DISTRACTED_SECONDS_TO_ORANGE/DT_DMON):int((_DISTRACTED_SECONDS_TO_ORANGE+_invisible_time)/DT_DMON)] = [msg_NO_FACE_DETECTED] * int(_invisible_time/DT_DMON) |
||||
ds_vector[int((_DISTRACTED_SECONDS_TO_RED+_invisible_time)/DT_DMON):int((_DISTRACTED_SECONDS_TO_RED+2*_invisible_time)/DT_DMON)] = [msg_NO_FACE_DETECTED] * int(_invisible_time/DT_DMON) |
||||
interaction_vector[int((_DISTRACTED_SECONDS_TO_RED+2*_invisible_time+0.5)/DT_DMON):int((_DISTRACTED_SECONDS_TO_RED+2*_invisible_time+1.5)/DT_DMON)] = [True] * int(1/DT_DMON) |
||||
op_vector[int((_DISTRACTED_SECONDS_TO_RED+2*_invisible_time+2.5)/DT_DMON):int((_DISTRACTED_SECONDS_TO_RED+2*_invisible_time+3)/DT_DMON)] = [False] * int(0.5/DT_DMON) |
||||
events_output = run_DState_seq(ds_vector, interaction_vector, op_vector, always_false) |
||||
self.assertEqual(events_output[int((_DISTRACTED_SECONDS_TO_ORANGE+0.5*_invisible_time)/DT_DMON)][0].name, 'promptDriverDistracted') |
||||
self.assertEqual(events_output[int((_DISTRACTED_SECONDS_TO_RED+1.5*_invisible_time)/DT_DMON)][0].name, 'driverDistracted') |
||||
self.assertEqual(events_output[int((_DISTRACTED_SECONDS_TO_RED+2*_invisible_time+1.5)/DT_DMON)][0].name, 'driverDistracted') |
||||
self.assertTrue(len(events_output[int((_DISTRACTED_SECONDS_TO_RED+2*_invisible_time+3.5)/DT_DMON)])==0) |
||||
|
||||
# 5. op engaged, invisible driver, down to orange, driver touches wheel; then down to orange again, driver appears |
||||
# - both actions should clear the alert, but momentary appearence should not |
||||
def test_sometimes_transparent_commuter(self): |
||||
_visible_time = np.random.choice([1,10]) # seconds |
||||
# print _visible_time |
||||
ds_vector = always_no_face[:]*2 |
||||
interaction_vector = always_false[:]*2 |
||||
ds_vector[int((2*_INVISIBLE_SECONDS_TO_ORANGE+1)/DT_DMON):int((2*_INVISIBLE_SECONDS_TO_ORANGE+1+_visible_time)/DT_DMON)] = [msg_ATTENTIVE] * int(_visible_time/DT_DMON) |
||||
interaction_vector[int((_INVISIBLE_SECONDS_TO_ORANGE)/DT_DMON):int((_INVISIBLE_SECONDS_TO_ORANGE+1)/DT_DMON)] = [True] * int(1/DT_DMON) |
||||
events_output = run_DState_seq(ds_vector, interaction_vector, 2*always_true, 2*always_false) |
||||
self.assertTrue(len(events_output[int(_INVISIBLE_SECONDS_TO_ORANGE*0.5/DT_DMON)])==0) |
||||
self.assertEqual(events_output[int((_INVISIBLE_SECONDS_TO_ORANGE-0.1)/DT_DMON)][0].name, 'promptDriverUnresponsive') |
||||
self.assertTrue(len(events_output[int((_INVISIBLE_SECONDS_TO_ORANGE+0.1)/DT_DMON)])==0) |
||||
if _visible_time == 1: |
||||
self.assertEqual(events_output[int((_INVISIBLE_SECONDS_TO_ORANGE*2+1-0.1)/DT_DMON)][0].name, 'promptDriverUnresponsive') |
||||
self.assertEqual(events_output[int((_INVISIBLE_SECONDS_TO_ORANGE*2+1+0.1+_visible_time)/DT_DMON)][0].name, 'preDriverUnresponsive') |
||||
elif _visible_time == 10: |
||||
self.assertEqual(events_output[int((_INVISIBLE_SECONDS_TO_ORANGE*2+1-0.1)/DT_DMON)][0].name, 'promptDriverUnresponsive') |
||||
self.assertTrue(len(events_output[int((_INVISIBLE_SECONDS_TO_ORANGE*2+1+0.1+_visible_time)/DT_DMON)])==0) |
||||
else: |
||||
pass |
||||
|
||||
# 6. op engaged, invisible driver, down to red, driver appears and then touches wheel, then disengages/reengages |
||||
# - only disengage will clear the alert |
||||
def test_last_second_responder(self): |
||||
_visible_time = 2 # seconds |
||||
ds_vector = always_no_face[:] |
||||
interaction_vector = always_false[:] |
||||
op_vector = always_true[:] |
||||
ds_vector[int(_INVISIBLE_SECONDS_TO_RED/DT_DMON):int((_INVISIBLE_SECONDS_TO_RED+_visible_time)/DT_DMON)] = [msg_ATTENTIVE] * int(_visible_time/DT_DMON) |
||||
interaction_vector[int((_INVISIBLE_SECONDS_TO_RED+_visible_time)/DT_DMON):int((_INVISIBLE_SECONDS_TO_RED+_visible_time+1)/DT_DMON)] = [True] * int(1/DT_DMON) |
||||
op_vector[int((_INVISIBLE_SECONDS_TO_RED+_visible_time+1)/DT_DMON):int((_INVISIBLE_SECONDS_TO_RED+_visible_time+0.5)/DT_DMON)] = [False] * int(0.5/DT_DMON) |
||||
events_output = run_DState_seq(ds_vector, interaction_vector, op_vector, always_false) |
||||
self.assertTrue(len(events_output[int(_INVISIBLE_SECONDS_TO_ORANGE*0.5/DT_DMON)])==0) |
||||
self.assertEqual(events_output[int((_INVISIBLE_SECONDS_TO_ORANGE-0.1)/DT_DMON)][0].name, 'promptDriverUnresponsive') |
||||
self.assertEqual(events_output[int((_INVISIBLE_SECONDS_TO_RED-0.1)/DT_DMON)][0].name, 'driverUnresponsive') |
||||
self.assertEqual(events_output[int((_INVISIBLE_SECONDS_TO_RED+0.5*_visible_time)/DT_DMON)][0].name, 'driverUnresponsive') |
||||
self.assertEqual(events_output[int((_INVISIBLE_SECONDS_TO_RED+_visible_time+0.5)/DT_DMON)][0].name, 'driverUnresponsive') |
||||
self.assertTrue(len(events_output[int((_INVISIBLE_SECONDS_TO_RED+_visible_time+1+0.1)/DT_DMON)])==0) |
||||
|
||||
# 7. op not engaged, always distracted driver |
||||
# - dm should stay quiet when not engaged |
||||
def test_pure_dashcam_user(self): |
||||
events_output = run_DState_seq(always_distracted, always_false, always_false, always_false) |
||||
self.assertTrue(np.sum([len(event) for event in events_output])==0) |
||||
|
||||
# 8. op engaged, car stops at traffic light, down to orange, no action, then car starts moving |
||||
# - should only reach green when stopped, but continues counting down on launch |
||||
def test_long_traffic_light_victim(self): |
||||
_redlight_time = 60 # seconds |
||||
standstill_vector = always_true[:] |
||||
standstill_vector[int(_redlight_time/DT_DMON):] = [False] * int((_TEST_TIMESPAN-_redlight_time)/DT_DMON) |
||||
events_output = run_DState_seq(always_distracted, always_false, always_true, standstill_vector) |
||||
self.assertEqual(events_output[int((_DISTRACTED_TIME-_DISTRACTED_PRE_TIME_TILL_TERMINAL+1)/DT_DMON)][0].name, 'preDriverDistracted') |
||||
self.assertEqual(events_output[int((_redlight_time-0.1)/DT_DMON)][0].name, 'preDriverDistracted') |
||||
self.assertEqual(events_output[int((_redlight_time+0.5)/DT_DMON)][0].name, 'promptDriverDistracted') |
||||
|
||||
# 9. op engaged, model is extremely uncertain. driver first attentive, then distracted |
||||
# - should only pop the green alert about model uncertainty |
||||
# - (note: this's just for sanity check, std output should never be this high) |
||||
def test_one_indecisive_model(self): |
||||
ds_vector = [msg_ATTENTIVE_UNCERTAIN] * int(_UNCERTAIN_SECONDS_TO_GREEN/DT_DMON) + \ |
||||
[msg_ATTENTIVE] * int(_DISTRACTED_SECONDS_TO_ORANGE/DT_DMON) + \ |
||||
[msg_DISTRACTED_UNCERTAIN] * (int(_TEST_TIMESPAN/DT_DMON)-int((_DISTRACTED_SECONDS_TO_ORANGE+_UNCERTAIN_SECONDS_TO_GREEN)/DT_DMON)) |
||||
interaction_vector = always_false[:] |
||||
events_output = run_DState_seq(ds_vector, interaction_vector, always_true, always_false) |
||||
self.assertTrue(len(events_output[int(_UNCERTAIN_SECONDS_TO_GREEN*0.5/DT_DMON)])==0) |
||||
self.assertEqual(events_output[int((_UNCERTAIN_SECONDS_TO_GREEN-0.1)/DT_DMON)][0].name, 'driverMonitorLowAcc') |
||||
self.assertTrue(len(events_output[int((_UNCERTAIN_SECONDS_TO_GREEN+_DISTRACTED_SECONDS_TO_ORANGE-0.5)/DT_DMON)])==0) |
||||
self.assertEqual(events_output[int((_TEST_TIMESPAN-5.)/DT_DMON)][0].name, 'driverMonitorLowAcc') |
||||
|
||||
# 10. op engaged, model is somehow uncertain and driver is distracted |
||||
# - should slow down the alert countdown but it still gets there |
||||
def test_somehow_indecisive_model(self): |
||||
ds_vector = [msg_DISTRACTED_BUT_SOMEHOW_UNCERTAIN] * int(_TEST_TIMESPAN/DT_DMON) |
||||
interaction_vector = always_false[:] |
||||
events_output = run_DState_seq(ds_vector, interaction_vector, always_true, always_false) |
||||
self.assertTrue(len(events_output[int(_UNCERTAIN_SECONDS_TO_GREEN*0.5/DT_DMON)])==0) |
||||
self.assertEqual(events_output[int((_UNCERTAIN_SECONDS_TO_GREEN)/DT_DMON)][0].name, 'driverMonitorLowAcc') |
||||
self.assertEqual(events_output[int((2.5*(_DISTRACTED_TIME-_DISTRACTED_PRE_TIME_TILL_TERMINAL))/DT_DMON)][1].name, 'preDriverDistracted') |
||||
self.assertEqual(events_output[int((2.5*(_DISTRACTED_TIME-_DISTRACTED_PROMPT_TIME_TILL_TERMINAL))/DT_DMON)][1].name, 'promptDriverDistracted') |
||||
self.assertEqual(events_output[int((_DISTRACTED_TIME+1)/DT_DMON)][1].name, 'promptDriverDistracted') |
||||
self.assertEqual(events_output[int((_DISTRACTED_TIME*2.5)/DT_DMON)][1].name, 'driverDistracted') |
||||
|
||||
if __name__ == "__main__": |
||||
print('MAX_TERMINAL_ALERTS', MAX_TERMINAL_ALERTS) |
||||
unittest.main() |
Loading…
Reference in new issue