Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions opendbc/safety/ignition.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#pragma once

#include <stdbool.h>
#include <stdint.h>

#include "opendbc/safety/can.h"

static inline void ignition_can_hook(const CANPacket_t *msg, bool *ignition_can, uint32_t *ignition_can_cnt) {
if (msg->bus == 0U) {
int len = GET_LEN(msg);

// GM exception
if ((msg->addr == 0x1F1U) && (len == 8)) {
// SystemPowerMode (2=Run, 3=Crank Request)
*ignition_can = (msg->data[0] & 0x2U) != 0U;
*ignition_can_cnt = 0U;
}

// Rivian R1S/T GEN1 exception
if ((msg->addr == 0x152U) && (len == 8)) {
// 0x152 overlaps with Subaru pre-global which has this bit as the high beam
int counter = msg->data[1] & 0xFU; // max is only 14

static int prev_counter_rivian = -1;
if ((counter == ((prev_counter_rivian + 1) % 15)) && (prev_counter_rivian != -1)) {
// VDM_OutputSignals->VDM_EpasPowerMode
*ignition_can = ((msg->data[7] >> 4U) & 0x3U) == 1U; // VDM_EpasPowerMode_Drive_On=1
*ignition_can_cnt = 0U;
}
prev_counter_rivian = counter;
}

// Tesla Model 3/Y exception
if ((msg->addr == 0x221U) && (len == 8)) {
// 0x221 overlaps with Rivian which has random data on byte 0
int counter = msg->data[6] >> 4;

static int prev_counter_tesla = -1;
if ((counter == ((prev_counter_tesla + 1) % 16)) && (prev_counter_tesla != -1)) {
// VCFRONT_LVPowerState->VCFRONT_vehiclePowerState
int power_state = (msg->data[0] >> 5U) & 0x3U;
*ignition_can = power_state == 0x3; // VEHICLE_POWER_STATE_DRIVE=3
*ignition_can_cnt = 0U;
}
prev_counter_tesla = counter;
}

// Mazda exception
if ((msg->addr == 0x9EU) && (len == 8)) {
*ignition_can = (msg->data[0] >> 5) == 0x6U;
*ignition_can_cnt = 0U;
}
}

// TODO: this is too loose, Teslas have 0x222
// body v2 exception
// if (((msg->bus == 0U) || (msg->bus == 2U)) && (msg->addr == 0x222U)) {
// *ignition_can = true;
// *ignition_can_cnt = 0U;
// }
}
4 changes: 4 additions & 0 deletions opendbc/safety/tests/libsafety/libsafety_py.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ class CANPacket:

void mutation_set_active_mutant(int id);
int mutation_get_active_mutant(void);

void ignition_can_hook_test(const CANPacket_t *msg);
bool get_ignition_can(void);
void reset_ignition_can(void);
""")

class LibSafety:
Expand Down
17 changes: 17 additions & 0 deletions opendbc/safety/tests/libsafety/safety.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,23 @@ uint32_t microsecond_timer_get(void) {

#include "opendbc/safety/can.h"
#include "opendbc/safety/safety.h"
#include "opendbc/safety/ignition.h"

static bool test_ignition_can = false;
static uint32_t test_ignition_can_cnt = 0U;

void ignition_can_hook_test(const CANPacket_t *msg) {
ignition_can_hook(msg, &test_ignition_can, &test_ignition_can_cnt);
}

bool get_ignition_can(void) {
return test_ignition_can;
}

void reset_ignition_can(void) {
test_ignition_can = false;
test_ignition_can_cnt = 0U;
}

void safety_tick_current_safety_config() {
safety_tick(&current_safety_config);
Expand Down
90 changes: 90 additions & 0 deletions opendbc/safety/tests/test_ignition.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#!/usr/bin/env python3
import unittest

from opendbc.safety.tests.common import CANPackerSafety, make_msg
from opendbc.safety.tests.libsafety import libsafety_py


class TestIgnitionHook(unittest.TestCase):
TX_MSGS: list = []

def setUp(self):
self.safety = libsafety_py.libsafety
self.safety.reset_ignition_can()
self.gm_packer = CANPackerSafety("gm_global_a_powertrain_generated")
self.rivian_packer = CANPackerSafety("rivian_primary_actuator")
self.tesla_packer = CANPackerSafety("tesla_model3_party")

def _hook(self, msg):
self.safety.ignition_can_hook_test(msg)

def _ign(self):
return self.safety.get_ignition_can()

# GM: SystemPowerMode 2=Run, 3=Crank Request
def test_gm_ignition_on(self):
self._hook(self.gm_packer.make_can_msg_safety("BCMGeneralPlatformStatus", 0, {"SystemPowerMode": 2}))
self.assertTrue(self._ign())

def test_gm_ignition_off(self):
self._hook(self.gm_packer.make_can_msg_safety("BCMGeneralPlatformStatus", 0, {"SystemPowerMode": 2}))
self.assertTrue(self._ign())
self._hook(self.gm_packer.make_can_msg_safety("BCMGeneralPlatformStatus", 0, {"SystemPowerMode": 0}))
self.assertFalse(self._ign())

# Rivian: VDM_EpasPowerMode_Drive_On=1
def test_rivian_ignition_on(self):
for i in range(15):
self.safety.reset_ignition_can()
self._hook(self.rivian_packer.make_can_msg_safety("VDM_OutputSignals", 0,
{"VDM_OutputSigs_Counter": i, "VDM_EpasPowerMode": 1}))
self.assertFalse(self._ign())
self._hook(self.rivian_packer.make_can_msg_safety("VDM_OutputSignals", 0,
{"VDM_OutputSigs_Counter": (i + 1) % 15, "VDM_EpasPowerMode": 1}))
self.assertTrue(self._ign())

def test_rivian_ignition_off(self):
self._hook(self.rivian_packer.make_can_msg_safety("VDM_OutputSignals", 0,
{"VDM_OutputSigs_Counter": 0, "VDM_EpasPowerMode": 0}))
self._hook(self.rivian_packer.make_can_msg_safety("VDM_OutputSignals", 0,
{"VDM_OutputSigs_Counter": 1, "VDM_EpasPowerMode": 0}))
self.assertFalse(self._ign())

# Tesla: VEHICLE_POWER_STATE_DRIVE=3
def test_tesla_ignition_on(self):
self._hook(self.tesla_packer.make_can_msg_safety("VCFRONT_LVPowerState", 0,
{"VCFRONT_LVPowerStateCounter": 0, "VCFRONT_vehiclePowerState": 3}))
self.assertFalse(self._ign())
self._hook(self.tesla_packer.make_can_msg_safety("VCFRONT_LVPowerState", 0,
{"VCFRONT_LVPowerStateCounter": 1, "VCFRONT_vehiclePowerState": 3}))
self.assertTrue(self._ign())

def test_tesla_ignition_off(self):
self._hook(self.tesla_packer.make_can_msg_safety("VCFRONT_LVPowerState", 0,
{"VCFRONT_LVPowerStateCounter": 0, "VCFRONT_vehiclePowerState": 2}))
self._hook(self.tesla_packer.make_can_msg_safety("VCFRONT_LVPowerState", 0,
{"VCFRONT_LVPowerStateCounter": 1, "VCFRONT_vehiclePowerState": 2}))
self.assertFalse(self._ign())

# Mazda: 0x9E byte 0 high 3 bits == 6
def test_mazda_ignition_on(self):
self._hook(make_msg(0, 0x9E, dat=b"\xC0" + b"\x00" * 7))
self.assertTrue(self._ign())

def test_mazda_ignition_off(self):
self._hook(make_msg(0, 0x9E, dat=b"\xC0" + b"\x00" * 7))
self.assertTrue(self._ign())
self._hook(make_msg(0, 0x9E, dat=b"\x20" + b"\x00" * 7))
self.assertFalse(self._ign())

def test_wrong_bus_ignored(self):
self._hook(make_msg(1, 0x1F1, dat=b"\x02" + b"\x00" * 7))
self.assertFalse(self._ign())

def test_unknown_addr_ignored(self):
self._hook(make_msg(0, 0x123, dat=b"\xFF" * 8))
self.assertFalse(self._ign())


if __name__ == "__main__":
unittest.main()
Loading