#!/usr/bin/env python
import os
import zmq
import numpy as np
import selfdrive . messaging as messaging
from cereal import car , log
from selfdrive . swaglog import cloudlog
from common . numpy_fast import clip
from common . fingerprints import fingerprint
from selfdrive . config import Conversions as CV
from selfdrive . services import service_list
from common . realtime import sec_since_boot , set_realtime_priority , Ratekeeper
from common . profiler import Profiler
from common . params import Params
from selfdrive . controls . lib . drive_helpers import learn_angle_offset
from selfdrive . controls . lib . longcontrol import LongControl
from selfdrive . controls . lib . latcontrol import LatControl
from selfdrive . controls . lib . alertmanager import AlertManager
V_CRUISE_MAX = 144
V_CRUISE_MIN = 8
V_CRUISE_DELTA = 8
V_CRUISE_ENABLE_MIN = 40
def controlsd_thread ( gctx , rate = 100 ) : #rate in Hz
# *** log ***
context = zmq . Context ( )
live100 = messaging . pub_sock ( context , service_list [ ' live100 ' ] . port )
carstate = messaging . pub_sock ( context , service_list [ ' carState ' ] . port )
carcontrol = messaging . pub_sock ( context , service_list [ ' carControl ' ] . port )
thermal = messaging . sub_sock ( context , service_list [ ' thermal ' ] . port )
health = messaging . sub_sock ( context , service_list [ ' health ' ] . port )
plan_sock = messaging . sub_sock ( context , service_list [ ' plan ' ] . port )
logcan = messaging . sub_sock ( context , service_list [ ' can ' ] . port )
# connects to can
CP = fingerprint ( logcan )
# import the car from the fingerprint
cloudlog . info ( " controlsd is importing %s " , CP . carName )
exec ( ' from selfdrive.car. ' + CP . carName + ' .interface import CarInterface ' )
sendcan = messaging . pub_sock ( context , service_list [ ' sendcan ' ] . port )
CI = CarInterface ( CP , logcan , sendcan )
# write CarParams
params = Params ( )
params . put ( " CarParams " , CP . to_bytes ( ) )
AM = AlertManager ( )
LoC = LongControl ( )
LaC = LatControl ( )
# fake plan
plan = log . Plan . new_message ( )
plan . lateralValid = False
plan . longitudinalValid = False
last_plan_time = 0
# controls enabled state
enabled = False
last_enable_request = 0
# learned angle offset
angle_offset = 0
# rear view camera state
rear_view_toggle = False
rear_view_allowed = bool ( params . get ( " IsRearViewMirror " ) )
v_cruise_kph = 255
# 0.0 - 1.0
awareness_status = 0.0
soft_disable_timer = None
# Is cpu temp too high to enable?
overtemp = False
free_space = 1.0
# start the loop
set_realtime_priority ( 2 )
rk = Ratekeeper ( rate , print_delay_threshold = 2. / 1000 )
while 1 :
prof = Profiler ( )
cur_time = sec_since_boot ( )
# read CAN
CS = CI . update ( )
# broadcast carState
cs_send = messaging . new_message ( )
cs_send . init ( ' carState ' )
cs_send . carState = CS # copy?
carstate . send ( cs_send . to_bytes ( ) )
prof . checkpoint ( " CarInterface " )
# did it request to enable?
enable_request , enable_condition = False , False
if enabled :
# gives the user 6 minutes
awareness_status - = 1.0 / ( 100 * 60 * 6 )
if awareness_status < = 0. :
AM . add ( " driverDistracted " , enabled )
# reset awareness status on steering
if CS . steeringPressed :
awareness_status = 1.0
# handle button presses
for b in CS . buttonEvents :
print b
# reset awareness on any user action
awareness_status = 1.0
# button presses for rear view
if b . type == " leftBlinker " or b . type == " rightBlinker " :
if b . pressed and rear_view_allowed :
rear_view_toggle = True
else :
rear_view_toggle = False
if b . type == " altButton1 " and b . pressed :
rear_view_toggle = not rear_view_toggle
if not CP . enableCruise and enabled and not b . pressed :
if b . type == " accelCruise " :
v_cruise_kph = v_cruise_kph - ( v_cruise_kph % V_CRUISE_DELTA ) + V_CRUISE_DELTA
elif b . type == " decelCruise " :
v_cruise_kph = v_cruise_kph - ( v_cruise_kph % V_CRUISE_DELTA ) - V_CRUISE_DELTA
v_cruise_kph = clip ( v_cruise_kph , V_CRUISE_MIN , V_CRUISE_MAX )
if not enabled and b . type in [ " accelCruise " , " decelCruise " ] and not b . pressed :
enable_request = True
# do disable on button down
if b . type == " cancel " and b . pressed :
AM . add ( " disable " , enabled )
prof . checkpoint ( " Buttons " )
# *** health checking logic ***
hh = messaging . recv_sock ( health )
if hh is not None :
# if the board isn't allowing controls but somehow we are enabled!
if not hh . health . controlsAllowed and enabled :
AM . add ( " controlsMismatch " , enabled )
# *** thermal checking logic ***
# thermal data, checked every second
td = messaging . recv_sock ( thermal )
if td is not None :
# Check temperature.
overtemp = any (
t > 950
for t in ( td . thermal . cpu0 , td . thermal . cpu1 , td . thermal . cpu2 ,
td . thermal . cpu3 , td . thermal . mem , td . thermal . gpu ) )
# under 15% of space free
free_space = td . thermal . freeSpace
prof . checkpoint ( " Health " )
# disable if the pedals are pressed while engaged, this is a user disable
if enabled :
if CS . gasPressed or CS . brakePressed :
AM . add ( " disable " , enabled )
if enable_request :
# check for pressed pedals
if CS . gasPressed or CS . brakePressed :
AM . add ( " pedalPressed " , enabled )
enable_request = False
else :
print " enabled pressed at " , cur_time
last_enable_request = cur_time
# don't engage with less than 15% free
if free_space < 0.15 :
AM . add ( " outOfSpace " , enabled )
enable_request = False
if CP . enableCruise :
enable_condition = ( ( cur_time - last_enable_request ) < 0.2 ) and CS . cruiseState . enabled
else :
enable_condition = enable_request
if CP . enableCruise and CS . cruiseState . enabled :
v_cruise_kph = CS . cruiseState . speed * CV . MS_TO_KPH
prof . checkpoint ( " AdaptiveCruise " )
# *** what's the plan ***
new_plan = messaging . recv_sock ( plan_sock )
if new_plan is not None :
plan = new_plan . plan
plan = plan . as_builder ( ) # plan can change in controls
last_plan_time = cur_time
# check plan for timeout
if cur_time - last_plan_time > 0.5 :
plan . lateralValid = False
plan . longitudinalValid = False
# gives 18 seconds before decel begins (w 6 minute timeout)
if awareness_status < - 0.05 :
plan . aTargetMax = min ( plan . aTargetMax , - 0.2 )
plan . aTargetMin = min ( plan . aTargetMin , plan . aTargetMax )
if enable_request or enable_condition or enabled :
# add all alerts from car
for alert in CS . errors :
AM . add ( alert , enabled )
if not plan . longitudinalValid :
AM . add ( " radarCommIssue " , enabled )
if not plan . lateralValid :
# If the model is not broadcasting, assume that it is because
# the user has uploaded insufficient data for calibration.
# Other cases that would trigger this are rare and unactionable by the user.
AM . add ( " dataNeeded " , enabled )
if overtemp :
AM . add ( " overheat " , enabled )
# *** angle offset learning ***
if rk . frame % 5 == 2 and plan . lateralValid :
# *** run this at 20hz again ***
angle_offset = learn_angle_offset ( enabled , CS . vEgo , angle_offset , np . asarray ( plan . dPoly ) , LaC . y_des , CS . steeringPressed )
# *** gas/brake PID loop ***
final_gas , final_brake = LoC . update ( enabled , CS . vEgo , v_cruise_kph ,
plan . vTarget ,
[ plan . aTargetMin , plan . aTargetMax ] ,
plan . jerkFactor , CP )
# *** steering PID loop ***
final_steer , sat_flag = LaC . update ( enabled , CS . vEgo , CS . steeringAngle , CS . steeringPressed , plan . dPoly , angle_offset , CP )
prof . checkpoint ( " PID " )
# ***** handle alerts ****
# send a "steering required alert" if saturation count has reached the limit
if sat_flag :
AM . add ( " steerSaturated " , enabled )
if enabled and AM . alertShouldDisable ( ) :
print " DISABLING IMMEDIATELY ON ALERT "
enabled = False
if enabled and AM . alertShouldSoftDisable ( ) :
if soft_disable_timer is None :
soft_disable_timer = 3 * rate
elif soft_disable_timer == 0 :
print " SOFT DISABLING ON ALERT "
enabled = False
else :
soft_disable_timer - = 1
else :
soft_disable_timer = None
if enable_condition and not enabled and not AM . alertPresent ( ) :
print " *** enabling controls "
# beep for enabling
AM . add ( " enable " , enabled )
# enable both lateral and longitudinal controls
enabled = True
# on activation, let's always set v_cruise from where we are, even if PCM ACC is active
v_cruise_kph = int ( round ( max ( CS . vEgo * CV . MS_TO_KPH , V_CRUISE_ENABLE_MIN ) ) )
# 6 minutes driver you're on
awareness_status = 1.0
# reset the PID loops
LaC . reset ( )
# start long control at actual speed
LoC . reset ( v_pid = CS . vEgo )
# *** push the alerts to current ***
alert_text_1 , alert_text_2 , visual_alert , audible_alert = AM . process_alerts ( cur_time )
# ***** control the car *****
CC = car . CarControl . new_message ( )
CC . enabled = enabled
CC . gas = float ( final_gas )
CC . brake = float ( final_brake )
CC . steeringTorque = float ( final_steer )
CC . cruiseControl . override = True
CC . cruiseControl . cancel = bool ( ( not CP . enableCruise ) or ( not enabled and CS . cruiseState . enabled ) ) # always cancel if we have an interceptor
# brake discount removes a sharp nonlinearity
brake_discount = ( 1.0 - clip ( final_brake * 3. , 0.0 , 1.0 ) )
CC . cruiseControl . speedOverride = float ( max ( 0.0 , ( ( LoC . v_pid - .5 ) * brake_discount ) ) if CP . enableCruise else 0.0 )
#CC.cruiseControl.accelOverride = float(AC.a_pcm)
# TODO: fix this
CC . cruiseControl . accelOverride = float ( 1.0 )
CC . hudControl . setSpeed = float ( v_cruise_kph * CV . KPH_TO_MS )
CC . hudControl . speedVisible = enabled
CC . hudControl . lanesVisible = enabled
CC . hudControl . leadVisible = plan . hasLead
CC . hudControl . visualAlert = visual_alert
CC . hudControl . audibleAlert = audible_alert
# this alert will apply next controls cycle
if not CI . apply ( CC ) :
AM . add ( " controlsFailed " , enabled )
# broadcast carControl
cc_send = messaging . new_message ( )
cc_send . init ( ' carControl ' )
cc_send . carControl = CC # copy?
carcontrol . send ( cc_send . to_bytes ( ) )
prof . checkpoint ( " CarControl " )
# ***** publish state to logger *****
# publish controls state at 100Hz
dat = messaging . new_message ( )
dat . init ( ' live100 ' )
# show rear view camera on phone if in reverse gear or when button is pressed
dat . live100 . rearViewCam = ( ' reverseGear ' in CS . errors and rear_view_allowed ) or rear_view_toggle
dat . live100 . alertText1 = alert_text_1
dat . live100 . alertText2 = alert_text_2
dat . live100 . awarenessStatus = max ( awareness_status , 0.0 ) if enabled else 0.0
# what packets were used to process
dat . live100 . canMonoTimes = list ( CS . canMonoTimes )
#dat.live100.mdMonoTime = PP.logMonoTime
#dat.live100.l20MonoTime = AC.logMonoTime
# if controls is enabled
dat . live100 . enabled = enabled
# car state
dat . live100 . vEgo = CS . vEgo
dat . live100 . angleSteers = CS . steeringAngle
dat . live100 . steerOverride = CS . steeringPressed
# longitudinal control state
dat . live100 . vPid = float ( LoC . v_pid )
dat . live100 . vCruise = float ( v_cruise_kph )
dat . live100 . upAccelCmd = float ( LoC . Up_accel_cmd )
dat . live100 . uiAccelCmd = float ( LoC . Ui_accel_cmd )
# lateral control state
dat . live100 . yActual = float ( LaC . y_actual )
dat . live100 . yDes = float ( LaC . y_des )
dat . live100 . upSteer = float ( LaC . Up_steer )
dat . live100 . uiSteer = float ( LaC . Ui_steer )
# processed radar state, should add a_pcm?
dat . live100 . vTargetLead = float ( plan . vTarget )
dat . live100 . aTargetMin = float ( plan . aTargetMin )
dat . live100 . aTargetMax = float ( plan . aTargetMax )
dat . live100 . jerkFactor = float ( plan . jerkFactor )
# log learned angle offset
dat . live100 . angleOffset = float ( angle_offset )
# lag
dat . live100 . cumLagMs = - rk . remaining * 1000.
live100 . send ( dat . to_bytes ( ) )
prof . checkpoint ( " Live100 " )
# *** run loop at fixed rate ***
if rk . keep_time ( ) :
prof . display ( )
def main ( gctx = None ) :
controlsd_thread ( gctx , 100 )
if __name__ == " __main__ " :
main ( )