@ -31,6 +31,25 @@
# include "selfdrive/boardd/panda.h"
# include "selfdrive/boardd/panda.h"
# include "selfdrive/boardd/pigeon.h"
# include "selfdrive/boardd/pigeon.h"
// -- Multi-panda conventions --
// Ordering:
// - The internal panda will always be the first panda
// - Consecutive pandas will be sorted based on panda type, and then serial number
// Connecting:
// - If a panda connection is dropped, boardd wil reconnect to all pandas
// - If a panda is added, we will only reconnect when we are offroad
// CAN buses:
// - Each panda will have it's block of 4 buses. E.g.: the second panda will use
// bus numbers 4, 5, 6 and 7
// - The internal panda will always be used for accessing the OBD2 port,
// and thus firmware queries
// Safety:
// - SafetyConfig is a list, which is mapped to the connected pandas
// - If there are more pandas connected than there are SafetyConfigs,
// the excess pandas will remain in "silent" ot "noOutput" mode
// Ignition:
// - If any of the ignition sources in any panda is high, ignition is high
# define MAX_IR_POWER 0.5f
# define MAX_IR_POWER 0.5f
# define MIN_IR_POWER 0.0f
# define MIN_IR_POWER 0.0f
# define CUTOFF_IL 200
# define CUTOFF_IL 200
@ -49,18 +68,30 @@ std::string get_time_str(const struct tm &time) {
return s ;
return s ;
}
}
bool safety_setter_thread ( Panda * panda ) {
bool check_all_connected ( std : : vector < Panda * > pandas ) {
for ( const auto & panda : pandas ) {
if ( ! panda - > connected ) return false ;
}
return true ;
}
bool safety_setter_thread ( std : : vector < Panda * > pandas ) {
LOGD ( " Starting safety setter thread " ) ;
LOGD ( " Starting safety setter thread " ) ;
// diagnostic only is the default, needed for VIN query
panda - > set_safety_model ( cereal : : CarParams : : SafetyModel : : ELM327 ) ;
// there should be at least one panda connected
if ( pandas . size ( ) = = 0 ) {
return false ;
}
pandas [ 0 ] - > set_safety_model ( cereal : : CarParams : : SafetyModel : : ELM327 ) ;
Params p = Params ( ) ;
Params p = Params ( ) ;
// switch to SILENT when CarVin param is read
// switch to SILENT when CarVin param is read
while ( true ) {
while ( true ) {
if ( do_exit | | ! panda - > connected | | ! ignition ) {
if ( do_exit | | ! check_all_connected ( pandas ) | | ! ignition ) {
return false ;
return false ;
} ;
}
std : : string value_vin = p . get ( " CarVin " ) ;
std : : string value_vin = p . get ( " CarVin " ) ;
if ( value_vin . size ( ) > 0 ) {
if ( value_vin . size ( ) > 0 ) {
@ -72,15 +103,16 @@ bool safety_setter_thread(Panda *panda) {
util : : sleep_for ( 20 ) ;
util : : sleep_for ( 20 ) ;
}
}
// VIN query done, stop listening to OBDII
pandas [ 0 ] - > set_safety_model ( cereal : : CarParams : : SafetyModel : : ELM327 , 1 ) ;
panda - > set_safety_model ( cereal : : CarParams : : SafetyModel : : ELM327 , 1 ) ;
std : : string params ;
std : : string params ;
LOGW ( " waiting for params to set safety model " ) ;
LOGW ( " waiting for params to set safety model " ) ;
while ( true ) {
while ( true ) {
if ( do_exit | | ! panda - > connected | | ! ignition ) {
for ( const auto & panda : pandas ) {
return false ;
if ( do_exit | | ! panda - > connected | | ! ignition ) {
} ;
return false ;
}
}
if ( p . getBool ( " ControlsReady " ) ) {
if ( p . getBool ( " ControlsReady " ) ) {
params = p . get ( " CarParams " ) ;
params = p . get ( " CarParams " ) ;
@ -97,27 +129,31 @@ bool safety_setter_thread(Panda *panda) {
int safety_param ;
int safety_param ;
auto safety_configs = car_params . getSafetyConfigs ( ) ;
auto safety_configs = car_params . getSafetyConfigs ( ) ;
if ( safety_configs . size ( ) > 0 ) {
for ( uint32_t i = 0 ; i < pandas . size ( ) ; i + + ) {
safety_model = safety_configs [ 0 ] . getSafetyModel ( ) ;
auto panda = pandas [ i ] ;
safety_param = safety_configs [ 0 ] . getSafetyParam ( ) ;
} else {
if ( safety_configs . size ( ) > i ) {
// If no safety mode is set, default to silent
safety_model = safety_configs [ i ] . getSafetyModel ( ) ;
safety_model = cereal : : CarParams : : SafetyModel : : SILENT ;
safety_param = safety_configs [ i ] . getSafetyParam ( ) ;
safety_param = 0 ;
} else {
}
// If no safety mode is specified, default to silent
safety_model = cereal : : CarParams : : SafetyModel : : SILENT ;
safety_param = 0 ;
}
LOGW ( " panda %d: setting safety model: %d with param %d " , i , ( int ) safety_model , safety_param ) ;
panda - > set_unsafe_mode ( 0 ) ; // see safety_declarations.h for allowed values
panda - > set_unsafe_mode ( 0 ) ; // see safety_declarations.h for allowed values
panda - > set_safety_model ( safety_model , safety_param ) ;
}
LOGW ( " setting safety model: %d with param %d " , ( int ) safety_model , safety_param ) ;
panda - > set_safety_model ( safety_model , safety_param ) ;
return true ;
return true ;
}
}
Panda * usb_connect ( std : : string serial = " " , uint32_t index = 0 ) {
Panda * usb_connect ( ) {
std : : unique_ptr < Panda > panda ;
std : : unique_ptr < Panda > panda ;
try {
try {
panda = std : : make_unique < Panda > ( ) ;
panda = std : : make_unique < Panda > ( serial , ( index * PANDA_BUS_CNT ) ) ;
} catch ( std : : exception & e ) {
} catch ( std : : exception & e ) {
return nullptr ;
return nullptr ;
}
}
@ -128,27 +164,6 @@ Panda *usb_connect() {
panda - > set_loopback ( true ) ;
panda - > set_loopback ( true ) ;
}
}
if ( auto fw_sig = panda - > get_firmware_version ( ) ; fw_sig ) {
params . put ( " PandaFirmware " , ( const char * ) fw_sig - > data ( ) , fw_sig - > size ( ) ) ;
// Convert to hex for offroad
char fw_sig_hex_buf [ 16 ] = { 0 } ;
const uint8_t * fw_sig_buf = fw_sig - > data ( ) ;
for ( size_t i = 0 ; i < 8 ; i + + ) {
fw_sig_hex_buf [ 2 * i ] = NIBBLE_TO_HEX ( ( uint8_t ) fw_sig_buf [ i ] > > 4 ) ;
fw_sig_hex_buf [ 2 * i + 1 ] = NIBBLE_TO_HEX ( ( uint8_t ) fw_sig_buf [ i ] & 0xF ) ;
}
params . put ( " PandaFirmwareHex " , fw_sig_hex_buf , 16 ) ;
LOGW ( " fw signature: %.*s " , 16 , fw_sig_hex_buf ) ;
} else { return nullptr ; }
// get panda serial
if ( auto serial = panda - > get_serial ( ) ; serial ) {
params . put ( " PandaDongleId " , serial - > c_str ( ) , serial - > length ( ) ) ;
LOGW ( " panda serial: %s " , serial - > c_str ( ) ) ;
} else { return nullptr ; }
// power on charging, only the first time. Panda can also change mode and it causes a brief disconneciton
// power on charging, only the first time. Panda can also change mode and it causes a brief disconneciton
# ifndef __x86_64__
# ifndef __x86_64__
static std : : once_flag connected_once ;
static std : : once_flag connected_once ;
@ -171,14 +186,7 @@ Panda *usb_connect() {
return panda . release ( ) ;
return panda . release ( ) ;
}
}
void can_recv ( Panda * panda , PubMaster & pm ) {
void can_send_thread ( std : : vector < Panda * > pandas , bool fake_send ) {
kj : : Array < capnp : : word > can_data ;
panda - > can_receive ( can_data ) ;
auto bytes = can_data . asBytes ( ) ;
pm . send ( " can " , bytes . begin ( ) , bytes . size ( ) ) ;
}
void can_send_thread ( Panda * panda , bool fake_send ) {
LOGD ( " start send thread " ) ;
LOGD ( " start send thread " ) ;
AlignedBuffer aligned_buf ;
AlignedBuffer aligned_buf ;
@ -188,7 +196,12 @@ void can_send_thread(Panda *panda, bool fake_send) {
subscriber - > setTimeout ( 100 ) ;
subscriber - > setTimeout ( 100 ) ;
// run as fast as messages come in
// run as fast as messages come in
while ( ! do_exit & & panda - > connected ) {
while ( ! do_exit ) {
if ( ! check_all_connected ( pandas ) ) {
do_exit = true ;
break ;
}
Message * msg = subscriber - > receive ( ) ;
Message * msg = subscriber - > receive ( ) ;
if ( ! msg ) {
if ( ! msg ) {
@ -204,7 +217,9 @@ void can_send_thread(Panda *panda, bool fake_send) {
//Dont send if older than 1 second
//Dont send if older than 1 second
if ( nanos_since_boot ( ) - event . getLogMonoTime ( ) < 1e9 ) {
if ( nanos_since_boot ( ) - event . getLogMonoTime ( ) < 1e9 ) {
if ( ! fake_send ) {
if ( ! fake_send ) {
panda - > can_send ( event . getSendcan ( ) ) ;
for ( const auto & panda : pandas ) {
panda - > can_send ( event . getSendcan ( ) ) ;
}
}
}
}
}
@ -215,7 +230,7 @@ void can_send_thread(Panda *panda, bool fake_send) {
delete context ;
delete context ;
}
}
void can_recv_thread ( Panda * panda ) {
void can_recv_thread ( std : : vector < Panda * > pandas ) {
LOGD ( " start recv thread " ) ;
LOGD ( " start recv thread " ) ;
// can = 8006
// can = 8006
@ -225,8 +240,29 @@ void can_recv_thread(Panda *panda) {
const uint64_t dt = 10000000ULL ;
const uint64_t dt = 10000000ULL ;
uint64_t next_frame_time = nanos_since_boot ( ) + dt ;
uint64_t next_frame_time = nanos_since_boot ( ) + dt ;
while ( ! do_exit & & panda - > connected ) {
while ( ! do_exit ) {
can_recv ( panda , pm ) ;
if ( ! check_all_connected ( pandas ) ) {
do_exit = true ;
break ;
}
std : : vector < can_frame > raw_can_data ;
bool comms_healthy = true ;
for ( const auto & panda : pandas ) {
comms_healthy & = panda - > can_receive ( raw_can_data ) ;
}
MessageBuilder msg ;
auto evt = msg . initEvent ( ) ;
evt . setValid ( comms_healthy ) ;
auto canData = evt . initCan ( raw_can_data . size ( ) ) ;
for ( uint i = 0 ; i < raw_can_data . size ( ) ; i + + ) {
canData [ i ] . setAddress ( raw_can_data [ i ] . address ) ;
canData [ i ] . setBusTime ( raw_can_data [ i ] . busTime ) ;
canData [ i ] . setDat ( kj : : arrayPtr ( ( uint8_t * ) raw_can_data [ i ] . dat . data ( ) , raw_can_data [ i ] . dat . size ( ) ) ) ;
canData [ i ] . setSrc ( raw_can_data [ i ] . src ) ;
}
pm . send ( " can " , msg ) ;
uint64_t cur_time = nanos_since_boot ( ) ;
uint64_t cur_time = nanos_since_boot ( ) ;
int64_t remaining = next_frame_time - cur_time ;
int64_t remaining = next_frame_time - cur_time ;
@ -257,70 +293,86 @@ void send_empty_panda_state(PubMaster *pm) {
pm - > send ( " pandaStates " , msg ) ;
pm - > send ( " pandaStates " , msg ) ;
}
}
bool send_panda_state ( PubMaster * pm , Panda * panda , bool spoofing_started ) {
bool send_panda_states ( PubMaster * pm , std : : vector < Panda * > pandas , bool spoofing_started ) {
health_t pandaState = panda - > get_state ( ) ;
bool ignition_local = false ;
if ( spoofing_started ) {
// build msg
pandaState . ignition_line = 1 ;
MessageBuilder msg ;
}
auto evt = msg . initEvent ( ) ;
auto pss = evt . initPandaStates ( pandas . size ( ) ) ;
// Make sure CAN buses are live: safety_setter_thread does not work if Panda CAN are silent and there is only one other CAN node
std : : vector < health_t > pandaStates ;
if ( pandaState . safety_model = = ( uint8_t ) ( cereal : : CarParams : : SafetyModel : : SILENT ) ) {
for ( const auto & panda : pandas ) {
panda - > set_safety_model ( cereal : : CarParams : : SafetyModel : : NO_OUTPUT ) ;
health_t pandaState = panda - > get_state ( ) ;
}
bool ignition_local = ( ( pandaState . ignition_line ! = 0 ) | | ( pandaState . ignition_can ! = 0 ) ) ;
if ( spoofing_started ) {
pandaState . ignition_line = 1 ;
}
# ifndef __x86_64__
ignition_local | = ( ( pandaState . ignition_line ! = 0 ) | | ( pandaState . ignition_can ! = 0 ) ) ;
bool power_save_desired = ! ignition_local & & ! pigeon_active ;
if ( pandaState . power_save_enabled ! = power_save_desired ) {
panda - > set_power_saving ( power_save_desired ) ;
}
// set safety mode to NO_OUTPUT when car is off. ELM327 is an alternative if we want to leverage athenad/connect
pandaStates . push_back ( pandaState ) ;
if ( ! ignition_local & & ( pandaState . safety_model ! = ( uint8_t ) ( cereal : : CarParams : : SafetyModel : : NO_OUTPUT ) ) ) {
panda - > set_safety_model ( cereal : : CarParams : : SafetyModel : : NO_OUTPUT ) ;
}
}
# endif
// build msg
for ( uint32_t i = 0 ; i < pandas . size ( ) ; i + + ) {
MessageBuilder msg ;
auto panda = pandas [ i ] ;
auto evt = msg . initEvent ( ) ;
auto pandaState = pandaStates [ i ] ;
evt . setValid ( panda - > comms_healthy ) ;
// Make sure CAN buses are live: safety_setter_thread does not work if Panda CAN are silent and there is only one other CAN node
if ( pandaState . safety_model = = ( uint8_t ) ( cereal : : CarParams : : SafetyModel : : SILENT ) ) {
panda - > set_safety_model ( cereal : : CarParams : : SafetyModel : : NO_OUTPUT ) ;
}
# ifndef __x86_64__
bool power_save_desired = ! ignition_local & & ! pigeon_active ;
if ( pandaState . power_save_enabled ! = power_save_desired ) {
panda - > set_power_saving ( power_save_desired ) ;
}
// set safety mode to NO_OUTPUT when car is off. ELM327 is an alternative if we want to leverage athenad/connect
if ( ! ignition_local & & ( pandaState . safety_model ! = ( uint8_t ) ( cereal : : CarParams : : SafetyModel : : NO_OUTPUT ) ) ) {
panda - > set_safety_model ( cereal : : CarParams : : SafetyModel : : NO_OUTPUT ) ;
}
# endif
// TODO: this has to be adapted to merge in multipanda support
// TODO: do we still need this?
auto ps = evt . initPandaStates ( 1 ) ;
if ( ! panda - > comms_healthy ) {
ps [ 0 ] . setUptime ( pandaState . uptime ) ;
evt . setValid ( false ) ;
ps [ 0 ] . setIgnitionLine ( pandaState . ignition_line ) ;
}
ps [ 0 ] . setIgnitionCan ( pandaState . ignition_can ) ;
ps [ 0 ] . setControlsAllowed ( pandaState . controls_allowed ) ;
auto ps = pss [ i ] ;
ps [ 0 ] . setGasInterceptorDetected ( pandaState . gas_interceptor_detected ) ;
ps . setUptime ( pandaState . uptime ) ;
ps [ 0 ] . setCanRxErrs ( pandaState . can_rx_errs ) ;
ps . setIgnitionLine ( pandaState . ignition_line ) ;
ps [ 0 ] . setCanSendErrs ( pandaState . can_send_errs ) ;
ps . setIgnitionCan ( pandaState . ignition_can ) ;
ps [ 0 ] . setCanFwdErrs ( pandaState . can_fwd_errs ) ;
ps . setControlsAllowed ( pandaState . controls_allowed ) ;
ps [ 0 ] . setGmlanSendErrs ( pandaState . gmlan_send_errs ) ;
ps . setGasInterceptorDetected ( pandaState . gas_interceptor_detected ) ;
ps [ 0 ] . setPandaType ( panda - > hw_type ) ;
ps . setCanRxErrs ( pandaState . can_rx_errs ) ;
ps [ 0 ] . setSafetyModel ( cereal : : CarParams : : SafetyModel ( pandaState . safety_model ) ) ;
ps . setCanSendErrs ( pandaState . can_send_errs ) ;
ps [ 0 ] . setSafetyParam ( pandaState . safety_param ) ;
ps . setCanFwdErrs ( pandaState . can_fwd_errs ) ;
ps [ 0 ] . setFaultStatus ( cereal : : PandaState : : FaultStatus ( pandaState . fault_status ) ) ;
ps . setGmlanSendErrs ( pandaState . gmlan_send_errs ) ;
ps [ 0 ] . setPowerSaveEnabled ( ( bool ) ( pandaState . power_save_enabled ) ) ;
ps . setPandaType ( panda - > hw_type ) ;
ps [ 0 ] . setHeartbeatLost ( ( bool ) ( pandaState . heartbeat_lost ) ) ;
ps . setSafetyModel ( cereal : : CarParams : : SafetyModel ( pandaState . safety_model ) ) ;
ps [ 0 ] . setHarnessStatus ( cereal : : PandaState : : HarnessStatus ( pandaState . car_harness_status ) ) ;
ps . setSafetyParam ( pandaState . safety_param ) ;
ps . setFaultStatus ( cereal : : PandaState : : FaultStatus ( pandaState . fault_status ) ) ;
// Convert faults bitset to capnp list
ps . setPowerSaveEnabled ( ( bool ) ( pandaState . power_save_enabled ) ) ;
std : : bitset < sizeof ( pandaState . faults ) * 8 > fault_bits ( pandaState . faults ) ;
ps . setHeartbeatLost ( ( bool ) ( pandaState . heartbeat_lost ) ) ;
auto faults = ps [ 0 ] . initFaults ( fault_bits . count ( ) ) ;
ps . setHarnessStatus ( cereal : : PandaState : : HarnessStatus ( pandaState . car_harness_status ) ) ;
size_t i = 0 ;
// Convert faults bitset to capnp list
for ( size_t f = size_t ( cereal : : PandaState : : FaultType : : RELAY_MALFUNCTION ) ;
std : : bitset < sizeof ( pandaState . faults ) * 8 > fault_bits ( pandaState . faults ) ;
f < = size_t ( cereal : : PandaState : : FaultType : : INTERRUPT_RATE_TICK ) ; f + + ) {
auto faults = ps . initFaults ( fault_bits . count ( ) ) ;
if ( fault_bits . test ( f ) ) {
faults . set ( i , cereal : : PandaState : : FaultType ( f ) ) ;
size_t j = 0 ;
i + + ;
for ( size_t f = size_t ( cereal : : PandaState : : FaultType : : RELAY_MALFUNCTION ) ;
f < = size_t ( cereal : : PandaState : : FaultType : : INTERRUPT_RATE_TICK ) ; f + + ) {
if ( fault_bits . test ( f ) ) {
faults . set ( j , cereal : : PandaState : : FaultType ( f ) ) ;
j + + ;
}
}
}
}
}
pm - > send ( " pandaStates " , msg ) ;
pm - > send ( " pandaStates " , msg ) ;
return ignition_local ;
return ignition_local ;
}
}
@ -355,23 +407,36 @@ void send_peripheral_state(PubMaster *pm, Panda *panda) {
pm - > send ( " peripheralState " , msg ) ;
pm - > send ( " peripheralState " , msg ) ;
}
}
void panda_state_thread ( PubMaster * pm , Panda * peripheral_panda , Panda * panda , bool spoofing_started ) {
void panda_state_thread ( PubMaster * pm , std : : vector < Panda * > pandas , bool spoofing_started ) {
Params params ;
Params params ;
Panda * peripheral_panda = pandas [ 0 ] ;
bool ignition_last = false ;
bool ignition_last = false ;
std : : future < bool > safety_future ;
std : : future < bool > safety_future ;
LOGD ( " start panda state thread " ) ;
LOGD ( " start panda state thread " ) ;
// run at 2hz
// run at 2hz
while ( ! do_exit & & panda - > connected ) {
while ( ! do_exit ) {
if ( ! check_all_connected ( pandas ) ) {
do_exit = true ;
break ;
}
send_peripheral_state ( pm , peripheral_panda ) ;
send_peripheral_state ( pm , peripheral_panda ) ;
ignition = send_panda_state ( pm , panda , spoofing_started ) ;
ignition = send_panda_states ( pm , pandas , spoofing_started ) ;
// check if we have new pandas and are offroad
if ( ! ignition & & ( pandas . size ( ) ! = Panda : : list ( ) . size ( ) ) ) {
LOGW ( " Reconnecting to changed amount of pandas! " ) ;
do_exit = true ;
break ;
}
// clear VIN, CarParams, and set new safety on car start
// clear VIN, CarParams, and set new safety on car start
if ( ignition & & ! ignition_last ) {
if ( ignition & & ! ignition_last ) {
params . clearAll ( CLEAR_ON_IGNITION_ON ) ;
params . clearAll ( CLEAR_ON_IGNITION_ON ) ;
if ( ! safety_future . valid ( ) | | safety_future . wait_for ( 0 ms ) = = std : : future_status : : ready ) {
if ( ! safety_future . valid ( ) | | safety_future . wait_for ( 0 ms ) = = std : : future_status : : ready ) {
safety_future = std : : async ( std : : launch : : async , safety_setter_thread , panda ) ;
safety_future = std : : async ( std : : launch : : async , safety_setter_thread , pandas ) ;
} else {
} else {
LOGW ( " Safety setter thread already running " ) ;
LOGW ( " Safety setter thread already running " ) ;
}
}
@ -381,7 +446,9 @@ void panda_state_thread(PubMaster *pm, Panda * peripheral_panda, Panda *panda, b
ignition_last = ignition ;
ignition_last = ignition ;
panda - > send_heartbeat ( ) ;
for ( const auto & panda : pandas ) {
panda - > send_heartbeat ( ) ;
}
util : : sleep_for ( 500 ) ;
util : : sleep_for ( 500 ) ;
}
}
}
}
@ -561,7 +628,11 @@ void pigeon_thread(Panda *panda) {
delete pigeon ;
delete pigeon ;
}
}
int main ( ) {
int main ( int argc , char * argv [ ] ) {
std : : vector < std : : thread > threads ;
std : : vector < Panda * > pandas ;
Panda * peripheral_panda ;
LOGW ( " starting boardd " ) ;
LOGW ( " starting boardd " ) ;
if ( ! Hardware : : PC ( ) ) {
if ( ! Hardware : : PC ( ) ) {
@ -575,31 +646,44 @@ int main() {
LOGW ( " attempting to connect " ) ;
LOGW ( " attempting to connect " ) ;
PubMaster pm ( { " pandaStates " , " peripheralState " } ) ;
PubMaster pm ( { " pandaStates " , " peripheralState " } ) ;
// connect loop
while ( ! do_exit ) {
while ( ! do_exit ) {
Panda * panda = usb_connect ( ) ;
std : : vector < std : : string > serials ( argv + 1 , argv + argc ) ;
Panda * peripheral_panda = panda ;
if ( serials . size ( ) = = 0 ) serials . push_back ( " " ) ;
// connect to all provided serials
for ( int i = 0 ; i < serials . size ( ) ; i + + ) {
Panda * p = usb_connect ( serials [ i ] , i ) ;
if ( p ! = NULL ) {
pandas . push_back ( p ) ;
}
}
// Send empty pandaState & peripheralState and try again
// s end empty pandaState & peripheralState and try again
if ( panda = = nullptr | | peripheral_panda = = nullptr ) {
if ( pandas . size ( ) ! = serials . size ( ) ) {
send_empty_panda_state ( & pm ) ;
send_empty_panda_state ( & pm ) ;
send_empty_peripheral_state ( & pm ) ;
send_empty_peripheral_state ( & pm ) ;
util : : sleep_for ( 500 ) ;
util : : sleep_for ( 500 ) ;
continue ;
} else {
break ;
}
}
}
peripheral_panda = pandas [ 0 ] ;
LOGW ( " connected to board " ) ;
LOGW ( " connected to board " ) ;
std : : vector < std : : thread > threads ;
threads . emplace_back ( panda_state_thread , & pm , pandas , getenv ( " STARTED " ) ! = nullptr ) ;
threads . emplace_back ( panda_state_thread , & pm , peripheral_panda , panda , getenv ( " STARTED " ) ! = nullptr ) ;
threads . emplace_back ( peripheral_control_thread , peripheral_panda ) ;
threads . emplace_back ( peripheral_control_thread , peripheral_panda ) ;
threads . emplace_back ( pigeon_thread , peripheral_panda ) ;
threads . emplace_back ( pigeon_thread , peripheral_panda ) ;
threads . emplace_back ( can_send_thread , panda , getenv ( " FAKESEND " ) ! = nullptr ) ;
threads . emplace_back ( can_send_thread , pandas , getenv ( " FAKESEND " ) ! = nullptr ) ;
threads . emplace_back ( can_recv_thread , panda ) ;
threads . emplace_back ( can_recv_thread , pandas ) ;
for ( auto & t : threads ) t . join ( ) ;
for ( auto & t : threads ) t . join ( ) ;
// we have exited, clean up pandas
for ( const auto & panda : pandas ) {
delete panda ;
delete panda ;
panda = nullptr ;
}
}
}
}