Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor safety to support combinatoric number of configurations #1800

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
115 changes: 115 additions & 0 deletions opendbc/safety/safety/generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#!/usr/bin/env python3
import os
import re
import glob
import subprocess
import jinja2
import itertools
import pprint

from functools import partial
from jinja2 import Environment, StrictUndefined


generator_path = os.path.dirname(os.path.realpath(__file__))
include_pattern = re.compile(r'CM_ "IMPORT (.*?)";\n')
generated_suffix = '_generated.dbc'

# Require every template param to be defined when rendering.
def StrictTemplate(str):
return Environment(undefined=StrictUndefined).from_string(str)

def JinjaRenderer(template):
return StrictTemplate(template).render

def PythonRenderer(template):
def render(**kwargs):
rx_checks = []
ns = {
'rx_checks': rx_checks,
'add_rx_check': lambda check: rx_checks.append(check),
}
exec(template, ns, kwargs)
return '\n'.join(rx_checks)
return render

# Args:
# condition_groups: list[list[condition: str]]
# render_outcome: function()
EXPAND_CONDITIONS = StrictTemplate('''
{%- macro generate_conditions(levels, current_conditions) -%}
{%- if levels -%}
{%- set current_level = levels[0] -%}
{%- set remaining_levels = levels[1:] -%}
{%- for condition in current_level -%}
{%- set new_conditions = current_conditions.copy() -%}
{%- set _ = new_conditions.update({condition: True}) -%}
{%- for other in current_level -%}
{%- if other != condition -%}
{%- set _ = new_conditions.update({other: False}) -%}
{%- endif -%}
{%- endfor %}
if ({{ condition }}) {
{{ generate_conditions(remaining_levels, new_conditions) | indent(2) }}
}
{%- endfor %}
else {
{%- set new_conditions = current_conditions.copy() -%}
{%- for condition in current_level -%}
{%- set _ = new_conditions.update({condition: False}) -%}
{%- endfor -%}
{{ generate_conditions(remaining_levels, new_conditions) | indent(2) }}
}
{%- else %}
{{ render_outcome(current_conditions) }}
{%- endif -%}
{%- endmacro -%}

{{ generate_conditions(condition_groups, {}) | indent(2) }}
''')

def expand_conditions(condition_groups, render_outcome):
with_comments = render_with_comments(render_outcome)
return EXPAND_CONDITIONS.render(condition_groups=condition_groups,
render_outcome=with_comments)

def render_with_comments(render):
def wrapped(conditions):
condition_str = "\n".join([f"// {k}: {v}" for k, v in conditions.items()])
return f"{condition_str}\n" + render(**conditions)
return wrapped

def _remove_empty_lines(text):
return '\n'.join(line for line in text.splitlines() if line.strip())

# Generate C code to init a safety_config for any combination of conditions.
#
# Args:
# template: str
# jinja template for generating a safety config based on conditions.
# condition_groups: list[list[condition: str]]
# condition: an identifier of an existing boolean in C.
# These identifiers will be available in the template.
# list[condition]: mutually exclusive conditions.
# A default, naked else case is included automatically
# and does not need to be specified.
# e.g. [["boat_canfd", "boat_doip"], ["boat_long"]]
def generate_safety_config(template, condition_groups,
renderer=JinjaRenderer):
render = renderer(template)

output = expand_conditions(condition_groups=condition_groups,
render_outcome=render)

return _remove_empty_lines(output)


def generate_rx_checks(fn_name, condition_groups, template):
generate_safety_config(template, condition_groups)


def generate_init_header(filename):
output = ("// Generated by opendbc/safety/generator/generator.py\n\n" +
"" # TODO: fn content
)
return output
118 changes: 65 additions & 53 deletions opendbc/safety/safety/safety_hyundai_canfd.h
Original file line number Diff line number Diff line change
@@ -1,23 +1,11 @@
#pragma once

#include "safety_declarations.h"
#include "safety_generated.h"
#include "safety_hyundai_common.h"

// *** Addresses checked in rx hook ***
// EV, ICE, HYBRID: ACCELERATOR (0x35), ACCELERATOR_BRAKE_ALT (0x100), ACCELERATOR_ALT (0x105)
#define HYUNDAI_CANFD_COMMON_RX_CHECKS(pt_bus) \
{.msg = {{0x35, (pt_bus), 32, .check_checksum = true, .max_counter = 0xffU, .frequency = 100U}, \
{0x100, (pt_bus), 32, .check_checksum = true, .max_counter = 0xffU, .frequency = 100U}, \
{0x105, (pt_bus), 32, .check_checksum = true, .max_counter = 0xffU, .frequency = 100U}}}, \
{.msg = {{0x175, (pt_bus), 24, .check_checksum = true, .max_counter = 0xffU, .frequency = 50U}, { 0 }, { 0 }}}, \
{.msg = {{0xa0, (pt_bus), 24, .check_checksum = true, .max_counter = 0xffU, .frequency = 100U}, { 0 }, { 0 }}}, \
{.msg = {{0xea, (pt_bus), 24, .check_checksum = true, .max_counter = 0xffU, .frequency = 100U}, { 0 }, { 0 }}}, \
{.msg = {{0x1cf, (pt_bus), 8, .check_checksum = false, .max_counter = 0xfU, .frequency = 50U}, \
{0x1aa, (pt_bus), 16, .check_checksum = false, .max_counter = 0xffU, .frequency = 50U}, { 0 }}}, \

// SCC_CONTROL (from ADAS unit or camera)
#define HYUNDAI_CANFD_SCC_ADDR_CHECK(scc_bus) \
{.msg = {{0x1a0, (scc_bus), 32, .check_checksum = true, .max_counter = 0xffU, .frequency = 50U}, { 0 }, { 0 }}}, \
GENERATE_SAFETY_CFG_INIT_HEADER("hyundai_canfd_init_generated.h",
hyundai_canfd_init_rx_checks)

static bool hyundai_canfd_alt_buttons = false;
static bool hyundai_canfd_lka_steering_alt = false;
Expand Down Expand Up @@ -150,6 +138,7 @@ static bool hyundai_canfd_tx_hook(const CANPacket_t *to_send) {
}

// cruise buttons check
// TODO: Support ALT_BUTTONS.
if (addr == 0x1cf) {
int button = GET_BYTE(to_send, 2) & 0x7U;
bool is_cancel = (button == HYUNDAI_BTN_CANCEL);
Expand Down Expand Up @@ -272,57 +261,80 @@ static safety_config hyundai_canfd_init(uint16_t param) {
}

safety_config ret;
hyundai_canfd_init_rx_checks(&ret);

// TX checks.
if (hyundai_longitudinal) {
if (hyundai_canfd_lka_steering) {
static RxCheck hyundai_canfd_lka_steering_long_rx_checks[] = {
HYUNDAI_CANFD_COMMON_RX_CHECKS(1)
};

ret = BUILD_SAFETY_CFG(hyundai_canfd_lka_steering_long_rx_checks, HYUNDAI_CANFD_LKA_STEERING_LONG_TX_MSGS);
if (hyundai_canfd_lka_steering_alt) {
// TODO: Support this.
} else {
SET_TX_MSGS(HYUNDAI_CANFD_LKA_STEERING_LONG_TX_MSGS, ret);
}
} else {
// Longitudinal checks for LFA steering
static RxCheck hyundai_canfd_long_rx_checks[] = {
HYUNDAI_CANFD_COMMON_RX_CHECKS(0)
};

ret = BUILD_SAFETY_CFG(hyundai_canfd_long_rx_checks, HYUNDAI_CANFD_LFA_STEERING_TX_MSGS);
SET_TX_MSGS(HYUNDAI_CANFD_LFA_STEERING_TX_MSGS, ret);
}
} else {
if (hyundai_canfd_lka_steering) {
// *** LKA steering checks ***
// E-CAN is on bus 1, SCC messages are sent on cars with ADRV ECU.
// Does not use the alt buttons message
static RxCheck hyundai_canfd_lka_steering_rx_checks[] = {
HYUNDAI_CANFD_COMMON_RX_CHECKS(1)
HYUNDAI_CANFD_SCC_ADDR_CHECK(1)
};

ret = hyundai_canfd_lka_steering_alt ? BUILD_SAFETY_CFG(hyundai_canfd_lka_steering_rx_checks, HYUNDAI_CANFD_LKA_STEERING_ALT_TX_MSGS) : \
BUILD_SAFETY_CFG(hyundai_canfd_lka_steering_rx_checks, HYUNDAI_CANFD_LKA_STEERING_TX_MSGS);
} else if (!hyundai_camera_scc) {
// Radar sends SCC messages on these cars instead of camera
static RxCheck hyundai_canfd_radar_scc_rx_checks[] = {
HYUNDAI_CANFD_COMMON_RX_CHECKS(0)
HYUNDAI_CANFD_SCC_ADDR_CHECK(0)
};

ret = BUILD_SAFETY_CFG(hyundai_canfd_radar_scc_rx_checks, HYUNDAI_CANFD_LFA_STEERING_TX_MSGS);
if (hyundai_canfd_lka_steering_alt) {
SET_TX_MSGS(HYUNDAI_CANFD_LKA_STEERING_ALT_TX_MSGS, ret);
} else {
SET_TX_MSGS(HYUNDAI_CANFD_LKA_STEERING_TX_MSGS, ret);
}
} else {
// *** LFA steering checks ***
// Camera sends SCC messages on LFA steering cars.
// Both button messages exist on some platforms, so we ensure we track the correct one using flag
static RxCheck hyundai_canfd_rx_checks[] = {
HYUNDAI_CANFD_COMMON_RX_CHECKS(0)
HYUNDAI_CANFD_SCC_ADDR_CHECK(2)
};

ret = BUILD_SAFETY_CFG(hyundai_canfd_rx_checks, HYUNDAI_CANFD_LFA_STEERING_TX_MSGS);
SET_TX_MSGS(HYUNDAI_CANFD_LFA_STEERING_TX_MSGS, ret);
}
}

return ret;
}

static void hyundai_canfd_init_rx_checks(safety_config *cfg) {
/*
generate_rx_checks(

condition_groups = [
['hyundai_canfd_lka_steering', 'hyundai_camera_scc'],
['hyundai_ev_gas_signal', 'hyundai_hybrid_gas_signal'],
['hyundai_canfd_alt_buttons'],
['hyundai_longitudinal'],
],

template = """
{% set pt_bus = 1 if hyundai_canfd_lka_steering else 0 %}
{% set scc_bus = 1 if hyundai_canfd_lka_steering else (2 if hyundai_camera_scc else 0) %}

{#- RX Common checks. #}
{.msg = { {0x175, ({{pt_bus}}), 24, .check_checksum = true, .max_counter = 0xffU, .frequency = 50U}, { 0 }, { 0 }}},
{.msg = { {0xa0, ({{pt_bus}}), 24, .check_checksum = true, .max_counter = 0xffU, .frequency = 100U}, { 0 }, { 0 }}},
{.msg = { {0xea, ({{pt_bus}}), 24, .check_checksum = true, .max_counter = 0xffU, .frequency = 100U}, { 0 }, { 0 }}},

{#- Accel signals. -#}
{% if hyundai_ev_gas_signal %}
{.msg = { {0x35, ({{pt_bus}}), 32, .check_checksum = true, .max_counter = 0xffU, .frequency = 100U}, { 0 }, { 0 }}},
{% elif hyundai_hybrid_gas_signal %}
{.msg = { {0x105, ({{pt_bus}}), 32, .check_checksum = true, .max_counter = 0xffU, .frequency = 100U}, { 0 }, { 0 }}},
{% else %}
{.msg = { {0x100, ({{pt_bus}}), 32, .check_checksum = true, .max_counter = 0xffU, .frequency = 100U}, { 0 }, { 0 }}},
{% endif %}

{#- Cruise signals. -#}
{% if hyundai_canfd_alt_buttons %}
{.msg = { {0x1aa, ({{pt_bus}}), 16, .check_checksum = false, .max_counter = 0xffU, .frequency = 50U}, { 0 }, { 0 }}},
{% else %}
{.msg = { {0x1cf, ({{pt_bus}}), 8, .check_checksum = false, .max_counter = 0xfU, .frequency = 50U}, { 0 }, { 0 }}},
{% endif %}

{% if hyundai_longitudinal %}
{#- SCC_CONTROL sent, not read. -#}
{% else %}
{#- // SCC_CONTROL read. -#}
{.msg = { {0x1a0, ({{scc_bus}}), 32, .check_checksum = true, .max_counter = 0xffU, .frequency = 50U}, { 0 }, { 0 }}},
{% endif %}
""")
*/
}

const safety_hooks hyundai_canfd_hooks = {
.init = hyundai_canfd_init,
.rx = hyundai_canfd_rx_hook,
Expand Down
5 changes: 5 additions & 0 deletions opendbc/safety/safety_declarations.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@

#define BUILD_SAFETY_CFG(rx, tx) ((safety_config){(rx), (sizeof((rx)) / sizeof((rx)[0])), \
(tx), (sizeof((tx)) / sizeof((tx)[0]))})

// filename will be generated containing the two fns
// based on the comment between the following macros.
#define GENERATE_SAFETY_CFG_INIT_HEADER(filename, fn_name) filename

#define SET_RX_CHECKS(rx, config) \
do { \
(config).rx_checks = (rx); \
Expand Down
Loading
Loading