def test_panda_safety_carstate(self): """ Assert that panda safety matches openpilot's carState """ if self.CP.dashcamOnly: self.skipTest("no need to check panda safety for dashcamOnly") CC = car.CarControl.new_message() # warm up pass, as initial states may be different for can in self.can_msgs[:300]: for msg in can_capnp_to_can_list(can.can, src_filter=range(64)): to_send = package_can_msg(msg) self.safety.safety_rx_hook(to_send) self.CI.update(CC, (can_list_to_can_capnp([msg, ]), )) checks = defaultdict(lambda: 0) for can in self.can_msgs: CS = self.CI.update(CC, (can.as_builder().to_bytes(), )) for msg in can_capnp_to_can_list(can.can, src_filter=range(64)): to_send = package_can_msg(msg) ret = self.safety.safety_rx_hook(to_send) self.assertEqual(1, ret, f"safety rx failed ({ret=}): {to_send}") # TODO: check rest of panda's carstate (steering, ACC main on, etc.) # TODO: make the interceptor thresholds in openpilot and panda match, then remove this exception gas_pressed = CS.gasPressed if self.CP.enableGasInterceptor and gas_pressed and not self.safety.get_gas_pressed_prev(): # panda intentionally has a higher threshold if self.CP.carName == "toyota" and 15 < CS.gas < 15*1.5: gas_pressed = False if self.CP.carName == "honda": gas_pressed = False checks['gasPressed'] += gas_pressed != self.safety.get_gas_pressed_prev() # TODO: remove this exception once this mismatch is resolved brake_pressed = CS.brakePressed if CS.brakePressed and not self.safety.get_brake_pressed_prev(): if self.CP.carFingerprint in (HONDA.PILOT, HONDA.PASSPORT, HONDA.RIDGELINE) and CS.brake > 0.05: brake_pressed = False checks['brakePressed'] += brake_pressed != self.safety.get_brake_pressed_prev() if self.CP.pcmCruise: checks['controlsAllowed'] += not CS.cruiseState.enabled and self.safety.get_controls_allowed() if self.CP.carName == "honda": checks['mainOn'] += CS.cruiseState.available != self.safety.get_acc_main_on() # TODO: add flag to toyota safety if self.CP.carFingerprint == TOYOTA.SIENNA and checks['brakePressed'] < 25: checks['brakePressed'] = 0 # Honda Nidec uses button enable in panda, but pcm enable in openpilot if self.CP.carName == "honda" and self.CP.carFingerprint not in HONDA_BOSCH and checks['controlsAllowed'] < 25: checks['controlsAllowed'] = 0 failed_checks = {k: v for k, v in checks.items() if v > 0} self.assertFalse(len(failed_checks), f"panda safety doesn't agree with openpilot: {failed_checks}")
def test_diagnostics(self): tester_present = common.package_can_msg( (0x7d0, 0, b"\x02\x3E\x80\x00\x00\x00\x00\x00", 0)) self.assertTrue(self.safety.safety_tx_hook(tester_present)) not_tester_present = common.package_can_msg( (0x7d0, 0, b"\x03\xAA\xAA\x00\x00\x00\x00\x00", 0)) self.assertFalse(self.safety.safety_tx_hook(not_tester_present))
def test_panda_safety_rx_valid(self): if self.CP.dashcamOnly: self.skipTest("no need to check panda safety for dashcamOnly") start_ts = self.can_msgs[0].logMonoTime failed_addrs = Counter() for can in self.can_msgs: # update panda timer t = (can.logMonoTime - start_ts) / 1e3 self.safety.set_timer(int(t)) # run all msgs through the safety RX hook for msg in can.can: if msg.src >= 64: continue to_send = package_can_msg([msg.address, 0, msg.dat, msg.src]) if self.safety.safety_rx_hook(to_send) != 1: failed_addrs[hex(msg.address)] += 1 # ensure all msgs defined in the addr checks are valid if self.car_model not in ignore_addr_checks_valid: self.safety.safety_tick_current_rx_checks() if t > 1e6: self.assertTrue(self.safety.addr_checks_valid()) self.assertFalse(len(failed_addrs), f"panda safety RX check failed: {failed_addrs}")
def test_panda_safety_carstate(self): """ Assert that panda safety matches openpilot's carState """ if self.CP.dashcamOnly: self.skipTest("no need to check panda safety for dashcamOnly") if self.car_model in ignore_carstate_check: self.skipTest("see comments in test_models.py") checks = defaultdict(lambda: 0) CC = car.CarControl.new_message() for can in self.can_msgs: for msg in can.can: if msg.src >= 128: continue to_send = package_can_msg([msg.address, 0, msg.dat, msg.src]) ret = self.safety.safety_rx_hook(to_send) self.assertEqual(1, ret, f"safety rx failed ({ret=}): {to_send}") CS = self.CI.update(CC, (can.as_builder().to_bytes(), )) # TODO: check steering state # check that openpilot and panda safety agree on the car's state checks[ 'gasPressed'] += CS.gasPressed != self.safety.get_gas_pressed_prev( ) checks[ 'brakePressed'] += CS.brakePressed != self.safety.get_brake_pressed_prev( ) if self.CP.pcmCruise: checks[ 'controlsAllowed'] += not CS.cruiseState.enabled and self.safety.get_controls_allowed( ) # TODO: extend this to all cars if self.CP.carName == "honda": checks[ 'mainOn'] += CS.cruiseState.available != self.safety.get_acc_main_on( ) # TODO: reduce tolerance to 0 failed_checks = {k: v for k, v in checks.items() if v > 25} # TODO: the panda and openpilot interceptor thresholds should match skip_gas_check = self.CP.carName == 'chrysler' if "gasPressed" in failed_checks and (self.CP.enableGasInterceptor or skip_gas_check): if failed_checks['gasPressed'] < 150 or skip_gas_check: del failed_checks['gasPressed'] # TODO: honda nidec: do same checks in carState and panda if "brakePressed" in failed_checks and self.CP.carName == 'honda' and \ (self.car_model not in HONDA_BOSCH or self.car_model in [HONDA.CRV_HYBRID, HONDA.HONDA_E]): if failed_checks['brakePressed'] < 150: del failed_checks['brakePressed'] self.assertFalse( len(failed_checks), f"panda safety doesn't agree with CarState: {failed_checks}")
def test_block_aeb(self): for controls_allowed in (True, False): for bad in (True, False): for _ in range(10): self.safety.set_controls_allowed(controls_allowed) dat = [random.randint(1, 255) for _ in range(7)] if not bad: dat = [0] * 6 + dat[-1:] msg = common.package_can_msg([0x283, 0, bytes(dat), 0]) self.assertEqual(not bad, self._tx(msg))
def test_panda_safety_carstate(self): if self.CP.dashcamOnly: self.skipTest("no need to check panda safety for dashcamOnly") if self.car_model in ignore_carstate_check: self.skipTest("see comments in test_models.py") safety = libpandasafety_py.libpandasafety set_status = safety.set_safety_hooks(self.CP.safetyModel.raw, self.CP.safetyParam) self.assertEqual(0, set_status) checks = defaultdict(lambda: 0) CC = car.CarControl.new_message() for can in self.can_msgs: for msg in can.can: if msg.src >= 128: continue to_send = package_can_msg([msg.address, 0, msg.dat, msg.src]) safety.safety_rx_hook(to_send) CS = self.CI.update(CC, (can.as_builder().to_bytes(), )) # TODO: check steering state # check that openpilot and panda safety agree on the car's state checks[ 'gasPressed'] += CS.gasPressed != safety.get_gas_pressed_prev( ) checks[ 'brakePressed'] += CS.brakePressed != safety.get_brake_pressed_prev( ) checks[ 'controlsAllowed'] += not CS.cruiseState.enabled and safety.get_controls_allowed( ) # TODO: reduce tolerance to 0 failed_checks = {k: v for k, v in checks.items() if v > 25} # TODO: the panda and openpilot interceptor thresholds should match if "gasPressed" in failed_checks and self.CP.enableGasInterceptor: if failed_checks['gasPressed'] < 150: del failed_checks['gasPressed'] # TODO: honda nidec: do same checks in carState and panda if "brakePressed" in failed_checks and self.car_model.startswith( ("HONDA", "ACURA")) and self.car_model not in HONDA_BOSCH: if failed_checks['brakePressed'] < 150: del failed_checks['brakePressed'] self.assertFalse( len(failed_checks), f"panda safety doesn't agree with CarState: {failed_checks}")
def test_panda_safety_rx_valid(self): if self.CP.dashcamOnly: self.skipTest("no need to check panda safety for dashcamOnly") failed_addrs = Counter() for can in self.can_msgs: for msg in can.can: if msg.src >= 128: continue to_send = package_can_msg([msg.address, 0, msg.dat, msg.src]) if self.safety.safety_rx_hook(to_send) != 1: failed_addrs[hex(msg.address)] += 1 self.assertFalse(len(failed_addrs), f"panda safety RX check failed: {failed_addrs}")
def test_panda_safety_rx(self): if self.CP.dashcamOnly: self.skipTest("no need to check panda safety for dashcamOnly") safety = libpandasafety_py.libpandasafety set_status = safety.set_safety_hooks(self.CP.safetyModel.raw, self.CP.safetyParam) self.assertEqual(0, set_status) failed_addrs = Counter() for can in self.can_msgs: for msg in can.can: if msg.src >= 128: continue to_send = package_can_msg([msg.address, 0, msg.dat, msg.src]) if not safety.safety_rx_hook(to_send): failed_addrs[hex(msg.address)] += 1 self.assertFalse(len(failed_addrs), f"panda safety RX check failed: {failed_addrs}")
def test_panda_safety_carstate(self): """ Assert that panda safety matches openpilot's carState """ if self.CP.dashcamOnly: self.skipTest("no need to check panda safety for dashcamOnly") CC = car.CarControl.new_message() # warm up pass, as initial states may be different for can in self.can_msgs[:300]: for msg in can_capnp_to_can_list(can.can, src_filter=range(64)): to_send = package_can_msg(msg) self.safety.safety_rx_hook(to_send) self.CI.update(CC, (can_list_to_can_capnp([ msg, ]), )) if not self.CP.pcmCruise: self.safety.set_controls_allowed(0) controls_allowed_prev = False CS_prev = car.CarState.new_message() checks = defaultdict(lambda: 0) for can in self.can_msgs: CS = self.CI.update(CC, (can.as_builder().to_bytes(), )) for msg in can_capnp_to_can_list(can.can, src_filter=range(64)): to_send = package_can_msg(msg) ret = self.safety.safety_rx_hook(to_send) self.assertEqual(1, ret, f"safety rx failed ({ret=}): {to_send}") # TODO: check rest of panda's carstate (steering, ACC main on, etc.) checks[ 'gasPressed'] += CS.gasPressed != self.safety.get_gas_pressed_prev( ) # TODO: remove this exception once this mismatch is resolved brake_pressed = CS.brakePressed if CS.brakePressed and not self.safety.get_brake_pressed_prev(): if self.CP.carFingerprint in ( HONDA.PILOT, HONDA.PASSPORT, HONDA.RIDGELINE) and CS.brake > 0.05: brake_pressed = False checks[ 'brakePressed'] += brake_pressed != self.safety.get_brake_pressed_prev( ) if self.CP.pcmCruise: # On most pcmCruise cars, openpilot's state is always tied to the PCM's cruise state. # On Honda Nidec, we always engage on the rising edge of the PCM cruise state, but # openpilot brakes to zero even if the min ACC speed is non-zero (i.e. the PCM disengages). if self.CP.carName == "honda" and self.CP.carFingerprint not in HONDA_BOSCH: # only the rising edges are expected to match if CS.cruiseState.enabled and not CS_prev.cruiseState.enabled: checks[ 'controlsAllowed'] += not self.safety.get_controls_allowed( ) else: checks[ 'controlsAllowed'] += not CS.cruiseState.enabled and self.safety.get_controls_allowed( ) else: # Check for enable events on rising edge of controls allowed button_enable = any(evt.enable for evt in CS.events) mismatch = button_enable != ( self.safety.get_controls_allowed() and not controls_allowed_prev) checks['controlsAllowed'] += mismatch controls_allowed_prev = self.safety.get_controls_allowed() if button_enable and not mismatch: self.safety.set_controls_allowed(False) if self.CP.carName == "honda": checks[ 'mainOn'] += CS.cruiseState.available != self.safety.get_acc_main_on( ) # TODO: fix standstill mismatches for other makes checks[ 'standstill'] += CS.standstill == self.safety.get_vehicle_moving( ) CS_prev = CS # TODO: add flag to toyota safety if self.CP.carFingerprint == TOYOTA.SIENNA and checks[ 'brakePressed'] < 25: checks['brakePressed'] = 0 failed_checks = {k: v for k, v in checks.items() if v > 0} self.assertFalse( len(failed_checks), f"panda safety doesn't agree with openpilot: {failed_checks}")