diff --git a/panda/README.md b/panda/README.md index e323d400e1..42f432dfc3 100644 --- a/panda/README.md +++ b/panda/README.md @@ -3,7 +3,7 @@ Welcome to panda [panda](http://github.com/commaai/panda) is the nicest universal car interface ever. - + diff --git a/panda/board/bootstub.c b/panda/board/bootstub.c index 4e516110c5..e44986c29e 100644 --- a/panda/board/bootstub.c +++ b/panda/board/bootstub.c @@ -51,6 +51,9 @@ void fail() { // know where to sig check extern void *_app_start[]; +// FIXME: sometimes your panda will fail flashing and will quickly blink a single Green LED +// BOUNTY: $200 coupon on shop.comma.ai or $100 check. + int main() { __disable_irq(); clock_init(); diff --git a/panda/board/build.mk b/panda/board/build.mk index aee724c190..9f2c4250c2 100644 --- a/panda/board/build.mk +++ b/panda/board/build.mk @@ -1,4 +1,4 @@ -CFLAGS += -I inc -I ../ -nostdlib -fno-builtin -std=gnu11 -O2 +CFLAGS += -I inc -I ../ -nostdlib -fno-builtin -std=gnu11 -Os CFLAGS += -Tstm32_flash.ld @@ -51,6 +51,8 @@ obj/$(PROJ_NAME).bin: obj/$(STARTUP_FILE).o obj/main.$(PROJ_NAME).o $(CC) -Wl,--section-start,.isr_vector=0x8004000 $(CFLAGS) -o obj/$(PROJ_NAME).elf $^ $(OBJCOPY) -v -O binary obj/$(PROJ_NAME).elf obj/code.bin SETLEN=1 ../crypto/sign.py obj/code.bin $@ $(CERT) + @BINSIZE=$$(du -b "obj/$(PROJ_NAME).bin" | cut -f 1) ; if [ $$BINSIZE -ge 32768 ]; then echo "ERROR obj/$(PROJ_NAME).bin is too big!"; exit 1; fi; + obj/bootstub.$(PROJ_NAME).bin: obj/$(STARTUP_FILE).o obj/bootstub.$(PROJ_NAME).o obj/sha.$(PROJ_NAME).o obj/rsa.$(PROJ_NAME).o $(CC) $(CFLAGS) -o obj/bootstub.$(PROJ_NAME).elf $^ diff --git a/panda/board/safety/safety_chrysler.h b/panda/board/safety/safety_chrysler.h index eadc8eaa7c..b1c6a743f1 100644 --- a/panda/board/safety/safety_chrysler.h +++ b/panda/board/safety/safety_chrysler.h @@ -1,20 +1,19 @@ -// board enforces -// in-state -// ACC is active (green) -// out-state -// brake pressed -// stock LKAS ECU is online -// ACC is not active (white or disabled) - -// chrysler_: namespacing -int chrysler_speed = 0; - -// silence everything if stock ECUs are still online -int chrysler_lkas_detected = 0; +const int CHRYSLER_MAX_STEER = 261; +const int CHRYSLER_MAX_RT_DELTA = 112; // max delta torque allowed for real time checks +const int32_t CHRYSLER_RT_INTERVAL = 250000; // 250ms between real time checks +const int CHRYSLER_MAX_RATE_UP = 3; +const int CHRYSLER_MAX_RATE_DOWN = 3; +const int CHRYSLER_MAX_TORQUE_ERROR = 80; // max torque cmd in excess of torque motor + +int chrysler_camera_detected = 0; +int chrysler_rt_torque_last = 0; int chrysler_desired_torque_last = 0; +int chrysler_cruise_engaged_last = 0; +uint32_t chrysler_ts_last = 0; +struct sample_t chrysler_torque_meas; // last few torques measured static void chrysler_rx_hook(CAN_FIFOMailBox_TypeDef *to_push) { - int bus_number = (to_push->RDTR >> 4) & 0xFF; + int bus = (to_push->RDTR >> 4) & 0xFF; uint32_t addr; if (to_push->RIR & 4) { // Extended @@ -26,40 +25,37 @@ static void chrysler_rx_hook(CAN_FIFOMailBox_TypeDef *to_push) { addr = to_push->RIR >> 21; } - if (addr == 0x144 && bus_number == 0) { - chrysler_speed = ((to_push->RDLR & 0xFF000000) >> 16) | (to_push->RDHR & 0xFF); - } + // Measured eps torque + if (addr == 544) { + int rdhr = to_push->RDHR; + int torque_meas_new = ((rdhr & 0x7) << 8) + ((rdhr & 0xFF00) >> 8) - 1024; - // check if stock LKAS ECU is still online - if (addr == 0x292 && bus_number == 0) { - chrysler_lkas_detected = 1; - controls_allowed = 0; + // update array of samples + update_sample(&chrysler_torque_meas, torque_meas_new); } - // ["ACC_2"]['ACC_STATUS_2'] == 7 for active (green) Adaptive Cruise Control - if (addr == 0x1f4 && bus_number == 0) { - if (((to_push->RDLR & 0x380000) >> 19) == 7) { + // enter controls on rising edge of ACC, exit controls on ACC off + if (addr == 0x1f4) { + int cruise_engaged = ((to_push->RDLR & 0x380000) >> 19) == 7; + if (cruise_engaged && !chrysler_cruise_engaged_last) { controls_allowed = 1; - } else { + } else if (!cruise_engaged) { controls_allowed = 0; } + chrysler_cruise_engaged_last = cruise_engaged; } - // exit controls on brake press by human - if (addr == 0x140) { - if (to_push->RDLR & 0x4) { - controls_allowed = 0; - } + // check if stock camera ECU is still online + if (bus == 0 && addr == 0x292) { + chrysler_camera_detected = 1; + controls_allowed = 0; } } -// if controls_allowed -// allow steering up to limit -// else -// block all commands that produce actuation static int chrysler_tx_hook(CAN_FIFOMailBox_TypeDef *to_send) { - // There can be only one! (LKAS) - if (chrysler_lkas_detected) { + + // There can be only one! (camera) + if (chrysler_camera_detected) { return 0; } @@ -72,65 +68,69 @@ static int chrysler_tx_hook(CAN_FIFOMailBox_TypeDef *to_send) { addr = to_send->RIR >> 21; } - // LKA STEER: Too large of values cause the steering actuator ECU to silently - // fault and no longer actuate the wheel until the car is rebooted. + + // LKA STEER if (addr == 0x292) { int rdlr = to_send->RDLR; - int straight = 1024; - int steer = ((rdlr & 0x7) << 8) + ((rdlr & 0xFF00) >> 8) - straight; - int max_steer = 230; - int max_rate = 50; // ECU is fine with 100, but 3 is typical. - if (steer > max_steer) { - return false; - } - if (steer < -max_steer) { - return false; - } - if (!controls_allowed && steer != 0) { - // If controls not allowed, only allow steering to move closer to 0. - if (chrysler_desired_torque_last == 0) { - return false; - } - if ((chrysler_desired_torque_last > 0) && (steer >= chrysler_desired_torque_last)) { - return false; - } - if ((chrysler_desired_torque_last < 0) && (steer <= chrysler_desired_torque_last)) { - return false; + int desired_torque = ((rdlr & 0x7) << 8) + ((rdlr & 0xFF00) >> 8) - 1024; + uint32_t ts = TIM2->CNT; + int violation = 0; + + if (controls_allowed) { + + // *** global torque limit check *** + violation |= max_limit_check(desired_torque, CHRYSLER_MAX_STEER, -CHRYSLER_MAX_STEER); + + // *** torque rate limit check *** + violation |= dist_to_meas_check(desired_torque, chrysler_desired_torque_last, + &chrysler_torque_meas, CHRYSLER_MAX_RATE_UP, CHRYSLER_MAX_RATE_DOWN, CHRYSLER_MAX_TORQUE_ERROR); + + // used next time + chrysler_desired_torque_last = desired_torque; + + // *** torque real time rate limit check *** + violation |= rt_rate_limit_check(desired_torque, chrysler_rt_torque_last, CHRYSLER_MAX_RT_DELTA); + + // every RT_INTERVAL set the new limits + uint32_t ts_elapsed = get_ts_elapsed(ts, chrysler_ts_last); + if (ts_elapsed > CHRYSLER_RT_INTERVAL) { + chrysler_rt_torque_last = desired_torque; + chrysler_ts_last = ts; } } - if (steer < (chrysler_desired_torque_last - max_rate)) { - return false; + + // no torque if controls is not allowed + if (!controls_allowed && (desired_torque != 0)) { + violation = 1; + } + + // reset to 0 if either controls is not allowed or there's a violation + if (violation || !controls_allowed) { + chrysler_desired_torque_last = 0; + chrysler_rt_torque_last = 0; + chrysler_ts_last = ts; } - if (steer > (chrysler_desired_torque_last + max_rate)) { + + if (violation) { return false; } - - chrysler_desired_torque_last = steer; } + // FORCE CANCEL: safety check only relevant when spamming the cancel button. + // ensuring that only the cancel button press is sent when controls are off. + // This avoids unintended engagements while still allowing resume spam + // TODO: fix bug preventing the button msg to be fwd'd on bus 2 + // 1 allows the message through return true; } -static int chrysler_tx_lin_hook(int lin_num, uint8_t *data, int len) { - // LIN is not used. - return false; -} - -static void chrysler_init(int16_t param) { - controls_allowed = 0; -} - -static int chrysler_fwd_hook(int bus_num, CAN_FIFOMailBox_TypeDef *to_fwd) { - return -1; -} const safety_hooks chrysler_hooks = { - .init = chrysler_init, + .init = nooutput_init, .rx = chrysler_rx_hook, .tx = chrysler_tx_hook, - .tx_lin = chrysler_tx_lin_hook, + .tx_lin = nooutput_tx_lin_hook, .ignition = default_ign_hook, - .fwd = chrysler_fwd_hook, + .fwd = nooutput_fwd_hook, }; - diff --git a/panda/board/safety/safety_tesla.h b/panda/board/safety/safety_tesla.h index a13e78f5a5..78c450e85e 100644 --- a/panda/board/safety/safety_tesla.h +++ b/panda/board/safety/safety_tesla.h @@ -258,7 +258,7 @@ static int tesla_fwd_hook(int bus_num, CAN_FIFOMailBox_TypeDef *to_fwd) // remove EPB_epasControl if (addr == 0x214) { - return false; + return -1; } return 2; // Custom EPAS bus @@ -269,12 +269,12 @@ static int tesla_fwd_hook(int bus_num, CAN_FIFOMailBox_TypeDef *to_fwd) // remove GTW_epasControl in forwards if (addr == 0x101) { - return false; + return -1; } return 0; // Chassis CAN } - return false; + return -1; } const safety_hooks tesla_hooks = { diff --git a/panda/buy.png b/panda/buy.png index a4a9e0fd40..72c7057422 100644 Binary files a/panda/buy.png and b/panda/buy.png differ diff --git a/panda/tests/safety/libpandasafety_py.py b/panda/tests/safety/libpandasafety_py.py index c6df9d3340..53936761d5 100644 --- a/panda/tests/safety/libpandasafety_py.py +++ b/panda/tests/safety/libpandasafety_py.py @@ -42,10 +42,13 @@ void set_toyota_torque_meas(int min, int max); void set_cadillac_torque_driver(int min, int max); void set_gm_torque_driver(int min, int max); void set_hyundai_torque_driver(int min, int max); +void set_chrysler_torque_meas(int min, int max); void set_toyota_rt_torque_last(int t); void set_toyota_desired_torque_last(int t); int get_toyota_torque_meas_min(void); int get_toyota_torque_meas_max(void); +int get_chrysler_torque_meas_min(void); +int get_chrysler_torque_meas_max(void); void init_tests_honda(void); int get_ego_speed(void); @@ -84,8 +87,9 @@ int toyota_ipas_tx_hook(CAN_FIFOMailBox_TypeDef *to_send); void init_tests_chrysler(void); void chrysler_rx_hook(CAN_FIFOMailBox_TypeDef *to_push); int chrysler_tx_hook(CAN_FIFOMailBox_TypeDef *to_send); -void chrysler_init(int16_t param); void set_chrysler_desired_torque_last(int t); +void set_chrysler_rt_torque_last(int t); + """) diff --git a/panda/tests/safety/test.c b/panda/tests/safety/test.c index af0db3c92c..b1390c41c2 100644 --- a/panda/tests/safety/test.c +++ b/panda/tests/safety/test.c @@ -26,6 +26,7 @@ struct sample_t toyota_torque_meas; struct sample_t cadillac_torque_driver; struct sample_t gm_torque_driver; struct sample_t hyundai_torque_driver; +struct sample_t chrysler_torque_driver; TIM_TypeDef timer; TIM_TypeDef *TIM2 = &timer; @@ -81,6 +82,19 @@ void set_hyundai_torque_driver(int min, int max){ hyundai_torque_driver.max = max; } +void set_chrysler_torque_meas(int min, int max){ + chrysler_torque_meas.min = min; + chrysler_torque_meas.max = max; +} + +int get_chrysler_torque_meas_min(void){ + return chrysler_torque_meas.min; +} + +int get_chrysler_torque_meas_max(void){ + return chrysler_torque_meas.max; +} + int get_toyota_torque_meas_min(void){ return toyota_torque_meas.min; } @@ -105,6 +119,10 @@ void set_hyundai_rt_torque_last(int t){ hyundai_rt_torque_last = t; } +void set_chrysler_rt_torque_last(int t){ + chrysler_rt_torque_last = t; +} + void set_toyota_desired_torque_last(int t){ toyota_desired_torque_last = t; } @@ -181,6 +199,15 @@ void init_tests_hyundai(void){ set_timer(0); } +void init_tests_chrysler(void){ + chrysler_torque_driver.min = 0; + chrysler_torque_driver.max = 0; + chrysler_desired_torque_last = 0; + chrysler_rt_torque_last = 0; + chrysler_ts_last = 0; + set_timer(0); +} + void init_tests_honda(void){ ego_speed = 0; gas_interceptor_detected = 0; @@ -188,11 +215,6 @@ void init_tests_honda(void){ gas_prev = 0; } -void init_tests_chrysler(void){ - chrysler_desired_torque_last = 0; - set_timer(0); -} - void set_gmlan_digital_output(int to_set){ } diff --git a/panda/tests/safety/test_chrysler.py b/panda/tests/safety/test_chrysler.py index 9b8b17720f..568c92aaaa 100755 --- a/panda/tests/safety/test_chrysler.py +++ b/panda/tests/safety/test_chrysler.py @@ -3,86 +3,169 @@ import unittest import numpy as np import libpandasafety_py +MAX_RATE_UP = 3 +MAX_RATE_DOWN = 3 +MAX_STEER = 261 + +MAX_RT_DELTA = 112 +RT_INTERVAL = 250000 + +MAX_TORQUE_ERROR = 80 + +def twos_comp(val, bits): + if val >= 0: + return val + else: + return (2**bits) + val + +def sign(a): + if a > 0: + return 1 + else: + return -1 class TestChryslerSafety(unittest.TestCase): @classmethod def setUp(cls): cls.safety = libpandasafety_py.libpandasafety - cls.safety.chrysler_init(0) + cls.safety.nooutput_init(0) cls.safety.init_tests_chrysler() - def _acc_msg(self, active): + def _button_msg(self, buttons): to_send = libpandasafety_py.ffi.new('CAN_FIFOMailBox_TypeDef *') - to_send[0].RIR = 0x1f4 << 21 - to_send[0].RDLR = 0xfe3fff1f if active else 0xfe0fff1f + to_send[0].RIR = 1265 << 21 + to_send[0].RDLR = buttons return to_send - - def _brake_msg(self, brake): + def _set_prev_torque(self, t): + self.safety.set_chrysler_desired_torque_last(t) + self.safety.set_chrysler_rt_torque_last(t) + self.safety.set_chrysler_torque_meas(t, t) + + def _torque_meas_msg(self, torque): to_send = libpandasafety_py.ffi.new('CAN_FIFOMailBox_TypeDef *') - to_send[0].RIR = 0x140 << 21 - to_send[0].RDLR = 0x485 if brake else 0x480 + to_send[0].RIR = 544 << 21 + to_send[0].RDHR = ((torque + 1024) >> 8) + (((torque + 1024) & 0xff) << 8) return to_send - def _steer_msg(self, angle): + def _torque_msg(self, torque): to_send = libpandasafety_py.ffi.new('CAN_FIFOMailBox_TypeDef *') to_send[0].RIR = 0x292 << 21 - c_angle = (1024 + angle) - to_send[0].RDLR = 0x10 | ((c_angle & 0xf00) >> 8) | ((c_angle & 0xff) << 8) + to_send[0].RDLR = ((torque + 1024) >> 8) + (((torque + 1024) & 0xff) << 8) return to_send def test_default_controls_not_allowed(self): self.assertFalse(self.safety.get_controls_allowed()) - def test_acc_enables_controls(self): + def test_steer_safety_check(self): + for enabled in [0, 1]: + for t in range(-MAX_STEER*2, MAX_STEER*2): + self.safety.set_controls_allowed(enabled) + self._set_prev_torque(t) + if abs(t) > MAX_STEER or (not enabled and abs(t) > 0): + self.assertFalse(self.safety.chrysler_tx_hook(self._torque_msg(t))) + else: + self.assertTrue(self.safety.chrysler_tx_hook(self._torque_msg(t))) + + def test_manually_enable_controls_allowed(self): + self.safety.set_controls_allowed(1) + self.assertTrue(self.safety.get_controls_allowed()) self.safety.set_controls_allowed(0) - self.safety.chrysler_rx_hook(self._acc_msg(0)) self.assertFalse(self.safety.get_controls_allowed()) - self.safety.chrysler_rx_hook(self._acc_msg(1)) + + def test_enable_control_allowed_from_cruise(self): + to_push = libpandasafety_py.ffi.new('CAN_FIFOMailBox_TypeDef *') + to_push[0].RIR = 0x1f4 << 21 + to_push[0].RDLR = 0x380000 + + self.safety.chrysler_rx_hook(to_push) self.assertTrue(self.safety.get_controls_allowed()) - self.safety.chrysler_rx_hook(self._acc_msg(0)) - self.assertFalse(self.safety.get_controls_allowed()) - def test_disengage_on_brake(self): + def test_disable_control_allowed_from_cruise(self): + to_push = libpandasafety_py.ffi.new('CAN_FIFOMailBox_TypeDef *') + to_push[0].RIR = 0x1f4 << 21 + to_push[0].RDLR = 0 + self.safety.set_controls_allowed(1) - self.safety.chrysler_rx_hook(self._brake_msg(0)) - self.assertTrue(self.safety.get_controls_allowed()) - self.safety.chrysler_rx_hook(self._brake_msg(1)) + self.safety.chrysler_rx_hook(to_push) self.assertFalse(self.safety.get_controls_allowed()) - def test_steer_calculation(self): - self.assertEqual(0x14, self._steer_msg(0)[0].RDLR) # straight, no steering + def test_non_realtime_limit_up(self): + self.safety.set_controls_allowed(True) + + self._set_prev_torque(0) + self.assertTrue(self.safety.chrysler_tx_hook(self._torque_msg(MAX_RATE_UP))) + + self._set_prev_torque(0) + self.assertFalse(self.safety.chrysler_tx_hook(self._torque_msg(MAX_RATE_UP + 1))) + + def test_non_realtime_limit_down(self): + self.safety.set_controls_allowed(True) + + self.safety.set_chrysler_rt_torque_last(MAX_STEER) + torque_meas = MAX_STEER - MAX_TORQUE_ERROR - 20 + self.safety.set_chrysler_torque_meas(torque_meas, torque_meas) + self.safety.set_chrysler_desired_torque_last(MAX_STEER) + self.assertTrue(self.safety.chrysler_tx_hook(self._torque_msg(MAX_STEER - MAX_RATE_DOWN))) + + self.safety.set_chrysler_rt_torque_last(MAX_STEER) + self.safety.set_chrysler_torque_meas(torque_meas, torque_meas) + self.safety.set_chrysler_desired_torque_last(MAX_STEER) + self.assertFalse(self.safety.chrysler_tx_hook(self._torque_msg(MAX_STEER - MAX_RATE_DOWN + 1))) + + def test_exceed_torque_sensor(self): + self.safety.set_controls_allowed(True) + + for sign in [-1, 1]: + self._set_prev_torque(0) + for t in np.arange(0, MAX_TORQUE_ERROR + 2, 2): # step needs to be smaller than MAX_TORQUE_ERROR + t *= sign + self.assertTrue(self.safety.chrysler_tx_hook(self._torque_msg(t))) + + self.assertFalse(self.safety.chrysler_tx_hook(self._torque_msg(sign * (MAX_TORQUE_ERROR + 2)))) + + def test_realtime_limit_up(self): + self.safety.set_controls_allowed(True) + + for sign in [-1, 1]: + self.safety.init_tests_chrysler() + self._set_prev_torque(0) + for t in np.arange(0, MAX_RT_DELTA+1, 1): + t *= sign + self.safety.set_chrysler_torque_meas(t, t) + self.assertTrue(self.safety.chrysler_tx_hook(self._torque_msg(t))) + self.assertFalse(self.safety.chrysler_tx_hook(self._torque_msg(sign * (MAX_RT_DELTA + 1)))) + + self._set_prev_torque(0) + for t in np.arange(0, MAX_RT_DELTA+1, 1): + t *= sign + self.safety.set_chrysler_torque_meas(t, t) + self.assertTrue(self.safety.chrysler_tx_hook(self._torque_msg(t))) + + # Increase timer to update rt_torque_last + self.safety.set_timer(RT_INTERVAL + 1) + self.assertTrue(self.safety.chrysler_tx_hook(self._torque_msg(sign * MAX_RT_DELTA))) + self.assertTrue(self.safety.chrysler_tx_hook(self._torque_msg(sign * (MAX_RT_DELTA + 1)))) + + def test_torque_measurements(self): + self.safety.chrysler_rx_hook(self._torque_meas_msg(50)) + self.safety.chrysler_rx_hook(self._torque_meas_msg(-50)) + self.safety.chrysler_rx_hook(self._torque_meas_msg(0)) + self.safety.chrysler_rx_hook(self._torque_meas_msg(0)) + self.safety.chrysler_rx_hook(self._torque_meas_msg(0)) + self.safety.chrysler_rx_hook(self._torque_meas_msg(0)) + + self.assertEqual(-50, self.safety.get_chrysler_torque_meas_min()) + self.assertEqual(50, self.safety.get_chrysler_torque_meas_max()) + + self.safety.chrysler_rx_hook(self._torque_meas_msg(0)) + self.assertEqual(0, self.safety.get_chrysler_torque_meas_max()) + self.assertEqual(-50, self.safety.get_chrysler_torque_meas_min()) + + self.safety.chrysler_rx_hook(self._torque_meas_msg(0)) + self.assertEqual(0, self.safety.get_chrysler_torque_meas_max()) + self.assertEqual(0, self.safety.get_chrysler_torque_meas_min()) - def test_steer_tx(self): - self.safety.set_controls_allowed(1) - self.assertTrue(self.safety.chrysler_tx_hook(self._steer_msg(0))) - self.safety.set_chrysler_desired_torque_last(227) - self.assertTrue(self.safety.chrysler_tx_hook(self._steer_msg(230))) - self.assertFalse(self.safety.chrysler_tx_hook(self._steer_msg(231))) - self.safety.set_chrysler_desired_torque_last(-227) - self.assertFalse(self.safety.chrysler_tx_hook(self._steer_msg(-231))) - self.assertTrue(self.safety.chrysler_tx_hook(self._steer_msg(-230))) - # verify max change - self.safety.set_chrysler_desired_torque_last(0) - self.assertFalse(self.safety.chrysler_tx_hook(self._steer_msg(230))) - - self.safety.set_controls_allowed(0) - self.safety.set_chrysler_desired_torque_last(0) - self.assertFalse(self.safety.chrysler_tx_hook(self._steer_msg(3))) - self.assertTrue(self.safety.chrysler_tx_hook(self._steer_msg(0))) - # verify when controls not allowed we can still go back towards 0 - self.safety.set_chrysler_desired_torque_last(10) - self.assertFalse(self.safety.chrysler_tx_hook(self._steer_msg(10))) - self.assertFalse(self.safety.chrysler_tx_hook(self._steer_msg(11))) - self.assertTrue(self.safety.chrysler_tx_hook(self._steer_msg(7))) - self.assertTrue(self.safety.chrysler_tx_hook(self._steer_msg(4))) - self.assertTrue(self.safety.chrysler_tx_hook(self._steer_msg(0))) - self.safety.set_chrysler_desired_torque_last(-10) - self.assertFalse(self.safety.chrysler_tx_hook(self._steer_msg(-10))) - self.assertFalse(self.safety.chrysler_tx_hook(self._steer_msg(-11))) - self.assertTrue(self.safety.chrysler_tx_hook(self._steer_msg(-7))) - self.assertTrue(self.safety.chrysler_tx_hook(self._steer_msg(-4))) - self.assertTrue(self.safety.chrysler_tx_hook(self._steer_msg(0))) if __name__ == "__main__": unittest.main()