diff --git a/.gitignore b/.gitignore index 31cef9422..2b283d3b1 100644 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,7 @@ selfdrive/sensord/_sensord system/camerad/camerad system/camerad/test/ae_gray_test selfdrive/modeld/_modeld +selfdrive/modeld/_navmodeld selfdrive/modeld/_dmonitoringmodeld /src/ diff --git a/Jenkinsfile b/Jenkinsfile index c34d25358..30c045e41 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -11,6 +11,9 @@ export SOURCE_DIR=${env.SOURCE_DIR} export GIT_BRANCH=${env.GIT_BRANCH} export GIT_COMMIT=${env.GIT_COMMIT} export AZURE_TOKEN='${env.AZURE_TOKEN}' +export MAPBOX_TOKEN='${env.MAPBOX_TOKEN}' + +export GIT_SSH_COMMAND="ssh -i /data/gitkey" source ~/.bash_profile if [ -f /TICI ]; then @@ -47,20 +50,34 @@ pipeline { TEST_DIR = "/data/openpilot" SOURCE_DIR = "/data/openpilot_source/" AZURE_TOKEN = credentials('azure_token') + MAPBOX_TOKEN = credentials('mapbox_token') } options { - timeout(time: 4, unit: 'HOURS') + timeout(time: 3, unit: 'HOURS') + disableConcurrentBuilds(abortPrevious: env.BRANCH_NAME != 'master') } stages { - stage('build release3') { + stage('build release3-staging') { agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } when { branch 'devel-staging' } steps { phone_steps("tici-needs-can", [ - ["build release3-staging & dashcam3-staging", "PUSH=1 $SOURCE_DIR/release/build_release.sh"], + ["build release3-staging & dashcam3-staging", "RELEASE_BRANCH=release3-staging DASHCAM_BRANCH=dashcam3-staging $SOURCE_DIR/release/build_release.sh"], + ]) + } + } + + stage('build nightly') { + agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } + when { + branch 'master-ci' + } + steps { + phone_steps("tici-needs-can", [ + ["build nightly", "RELEASE_BRANCH=nightly $SOURCE_DIR/release/build_release.sh"], ]) } } @@ -78,6 +95,7 @@ pipeline { parallel { + /* stage('simulator') { agent { dockerfile { @@ -87,7 +105,8 @@ pipeline { } } steps { - sh "git config --global --add safe.directory ${WORKSPACE}" + sh "git config --global --add safe.directory '*'" + sh "git submodule update --init --recursive" sh "git lfs pull" lock(resource: "", label: "simulator", inversePrecedence: true, quantity: 1) { sh "${WORKSPACE}/tools/sim/build_container.sh" @@ -104,6 +123,7 @@ pipeline { } } } + */ stage('build') { agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } @@ -115,7 +135,6 @@ pipeline { ["build master-ci", "cd $SOURCE_DIR/release && TARGET_DIR=$TEST_DIR EXTRA_FILES='tools/' ./build_devel.sh"], ["build openpilot", "cd selfdrive/manager && ./build.py"], ["check dirty", "release/check-dirty.sh"], - ["test manager", "python selfdrive/manager/test/test_manager.py"], ["onroad tests", "cd selfdrive/test/ && ./test_onroad.py"], ["test car interfaces", "cd selfdrive/car/tests/ && ./test_car_interfaces.py"], ]) @@ -141,6 +160,7 @@ pipeline { ["test loggerd", "python selfdrive/loggerd/tests/test_loggerd.py"], ["test encoder", "LD_LIBRARY_PATH=/usr/local/lib python selfdrive/loggerd/tests/test_encoder.py"], ["test pigeond", "python selfdrive/sensord/tests/test_pigeond.py"], + ["test manager", "python selfdrive/manager/test/test_manager.py"], ]) } } @@ -148,7 +168,7 @@ pipeline { stage('camerad-ar') { agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } steps { - phone_steps("tici-ar0321", [ + phone_steps("tici-ar0231", [ ["build", "cd selfdrive/manager && ./build.py"], ["test camerad", "python system/camerad/test/test_camerad.py"], ["test exposure", "python system/camerad/test/test_exposure.py"], @@ -186,7 +206,7 @@ pipeline { steps { phone_steps("tici-common", [ ["build", "cd selfdrive/manager && ./build.py"], - ["model replay", "cd selfdrive/test/process_replay && ./model_replay.py"], + ["model replay", "cd selfdrive/test/process_replay && NO_NAV=1 ./model_replay.py"], ]) } } diff --git a/README.md b/README.md index cfeb625bf..4896d9ff7 100755 --- a/README.md +++ b/README.md @@ -103,7 +103,6 @@ Directory Structure ├── opendbc # Files showing how to interpret data from cars ├── panda # Code used to communicate on CAN ├── third_party # External libraries - ├── pyextra # Extra python packages └── system # Generic services ├── camerad # Driver to capture images from the camera sensors ├── clocksd # Broadcasts current time @@ -143,7 +142,4 @@ NO WARRANTY EXPRESSED OR IMPLIED.** [![openpilot tests](https://github.com/commaai/openpilot/workflows/openpilot%20tests/badge.svg?event=push)](https://github.com/commaai/openpilot/actions) -[![Total alerts](https://img.shields.io/lgtm/alerts/g/commaai/openpilot.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/commaai/openpilot/alerts/) -[![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/commaai/openpilot.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/commaai/openpilot/context:python) -[![Language grade: C/C++](https://img.shields.io/lgtm/grade/cpp/g/commaai/openpilot.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/commaai/openpilot/context:cpp) [![codecov](https://codecov.io/gh/commaai/openpilot/branch/master/graph/badge.svg)](https://codecov.io/gh/commaai/openpilot) diff --git a/RELEASES.md b/RELEASES.md index e15286c5f..f166e7d1c 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,24 @@ +Version 0.9.1 (2023-02-28) +======================== +* New driving model + * 30% improved height estimation resulting in better driving performance for tall cars +* Driver monitoring: removed timer resetting on user interaction if distracted +* UI updates + * Adjust alert volume using ambient noise level + * Driver monitoring icon shows driver's head pose + * German translation thanks to Vrabetz and CzokNorris! +* Cadillac Escalade 2017 support thanks to rickygilleland! +* Chevrolet Bolt EV 2022-23 support thanks to JasonJShuler! +* Genesis GV60 2023 support thanks to sunnyhaibin! +* Hyundai Tucson 2022-23 support +* Kia K5 Hybrid 2020 support thanks to sunnyhaibin! +* Kia Niro Hybrid 2023 support thanks to sunnyhaibin! +* Kia Sorento 2022-23 support thanks to sunnyhaibin! +* Kia Sorento Plug-in Hybrid 2022 support thanks to sunnyhaibin! +* Toyota C-HR 2021 support thanks to eFiniLan! +* Toyota C-HR Hybrid 2022 support thanks to Korben00! +* Volkswagen Crafter and MAN TGE 2017-23 support thanks to jyoung8607! + Version 0.9.0 (2022-11-21) ======================== * New driving model diff --git a/SConstruct b/SConstruct index 74680c059..007931b7f 100644 --- a/SConstruct +++ b/SConstruct @@ -71,10 +71,10 @@ if arch == "aarch64" and AGNOS: lenv = { "PATH": os.environ['PATH'], "LD_LIBRARY_PATH": [Dir(f"#third_party/acados/{arch}/lib").abspath], - "PYTHONPATH": Dir("#").abspath + ":" + Dir("#pyextra/").abspath, + "PYTHONPATH": Dir("#").abspath, "ACADOS_SOURCE_DIR": Dir("#third_party/acados/include/acados").abspath, - "ACADOS_PYTHON_INTERFACE_PATH": Dir("#pyextra/acados_template").abspath, + "ACADOS_PYTHON_INTERFACE_PATH": Dir("#third_party/acados/acados_template").abspath, "TERA_PATH": Dir("#").abspath + f"/third_party/acados/{arch}/t_renderer" } @@ -197,10 +197,6 @@ env = Environment( "#third_party/libyuv/include", "#third_party/json11", "#third_party/curl/include", - "#third_party/libgralloc/include", - "#third_party/android_frameworks_native/include", - "#third_party/android_hardware_libhardware/include", - "#third_party/android_system_core/include", "#third_party/linux/include", "#third_party/snpe/include", "#third_party/mapbox-gl-native-qt/include", @@ -282,7 +278,7 @@ Export('envCython') # Qt build environment qt_env = env.Clone() -qt_modules = ["Widgets", "Gui", "Core", "Network", "Concurrent", "Multimedia", "Quick", "Qml", "QuickWidgets", "Location", "Positioning", "DBus"] +qt_modules = ["Widgets", "Gui", "Core", "Network", "Concurrent", "Multimedia", "Quick", "Qml", "QuickWidgets", "Location", "Positioning", "DBus", "Xml"] qt_libs = [] if arch == "Darwin": @@ -298,16 +294,20 @@ if arch == "Darwin": qt_env["FRAMEWORKS"] += [f"Qt{m}" for m in qt_modules] + ["OpenGL"] qt_env.AppendENVPath('PATH', os.path.join(qt_env['QTDIR'], "bin")) else: - qt_env['QTDIR'] = "/usr" + qt_install_prefix = subprocess.check_output(['qmake', '-query', 'QT_INSTALL_PREFIX'], encoding='utf8').strip() + qt_install_headers = subprocess.check_output(['qmake', '-query', 'QT_INSTALL_HEADERS'], encoding='utf8').strip() + + qt_env['QTDIR'] = qt_install_prefix qt_dirs = [ - f"/usr/include/{real_arch}-linux-gnu/qt5", - f"/usr/include/{real_arch}-linux-gnu/qt5/QtGui/5.12.8/QtGui", + f"{qt_install_headers}", + f"{qt_install_headers}/QtGui/5.12.8/QtGui", ] - qt_dirs += [f"/usr/include/{real_arch}-linux-gnu/qt5/Qt{m}" for m in qt_modules] + qt_dirs += [f"{qt_install_headers}/Qt{m}" for m in qt_modules] qt_libs = [f"Qt5{m}" for m in qt_modules] if arch == "larch64": qt_libs += ["GLESv2", "wayland-client"] + qt_env.PrependENVPath('PATH', Dir("#third_party/qt5/larch64/bin/").abspath) elif arch != "Darwin": qt_libs += ["GL"] @@ -384,10 +384,10 @@ rednose_config = { if arch != "larch64": rednose_config['to_build'].update({ 'loc_4': ('#selfdrive/locationd/models/loc_kf.py', True, [], rednose_deps), + 'lane': ('#selfdrive/locationd/models/lane_kf.py', True, [], rednose_deps), 'pos_computer_4': ('#rednose/helpers/lst_sq_computer.py', False, [], []), 'pos_computer_5': ('#rednose/helpers/lst_sq_computer.py', False, [], []), 'feature_handler_5': ('#rednose/helpers/feature_handler.py', False, [], []), - 'lane': ('#xx/pipeline/lib/ekf/lane_kf.py', True, [], rednose_deps), }) Export('rednose_config') @@ -406,10 +406,10 @@ if arch != "Darwin": # build submodules SConscript([ - 'cereal/SConscript', 'body/board/SConscript', - 'panda/board/SConscript', + 'cereal/SConscript', 'opendbc/can/SConscript', + 'panda/SConscript', ]) SConscript(['third_party/SConscript']) @@ -419,7 +419,6 @@ SConscript(['common/transformations/SConscript']) SConscript(['selfdrive/modeld/SConscript']) -SConscript(['selfdrive/controls/lib/cluster/SConscript']) SConscript(['selfdrive/controls/lib/lateral_mpc_lib/SConscript']) SConscript(['selfdrive/controls/lib/longitudinal_mpc_lib/SConscript']) @@ -439,9 +438,6 @@ if arch in ['x86_64', 'Darwin'] or GetOption('extras'): Export('opendbc') SConscript(['tools/cabana/SConscript']) -if GetOption('test'): - SConscript('panda/tests/safety/SConscript') - external_sconscript = GetOption('external_sconscript') if external_sconscript: SConscript([external_sconscript]) diff --git a/body/board/boards.h b/body/board/boards.h index 2af4b3eb0..777232540 100644 --- a/body/board/boards.h +++ b/body/board/boards.h @@ -40,6 +40,7 @@ void board_detect(void) { board.led_portB = GPIOC; board.can_addr_offset = 0x0U; + board.uds_offset = 0x0U; } else if (hw_type == HW_TYPE_KNEE) { board.hall_left.hall_portA = GPIOC; @@ -77,6 +78,7 @@ void board_detect(void) { board.led_portB = GPIOB; board.can_addr_offset = KNEE_ADDR_OFFSET; + board.uds_offset = 0x10U; #ifndef BOOTSTUB MX_I2C_Init(); diff --git a/body/board/comms.h b/body/board/comms.h index b7b3b2986..342163c88 100644 --- a/body/board/comms.h +++ b/body/board/comms.h @@ -6,10 +6,10 @@ #define BROADCAST_ADDR 0x7DFU #define FALLBACK_ADDR 0x7E0U #define FALLBACK_R_ADDR (FALLBACK_ADDR + OFFSET) -#define ECU_ADDR 0x720U -#define ECU_R_ADDR (ECU_ADDR + OFFSET) -#define DEBUG_ADDR 0x721U -#define DEBUG_R_ADDR (DEBUG_ADDR + OFFSET) +#define ENGINE_ADDR 0x720U +#define ENGINE_R_ADDR (ENGINE_ADDR + OFFSET) +#define DEBUG_ADDR 0x721U +#define DEBUG_R_ADDR (DEBUG_ADDR + OFFSET) #include "drivers/llbxcan.h" #include "uds.h" @@ -24,6 +24,8 @@ extern board_t board; extern uint32_t enter_bootloader_mode; extern volatile uint32_t torque_cmd_timeout; +extern volatile uint32_t ignition_off_counter; + const uint8_t crc_poly = 0xD5U; // standard crc8 uint32_t current_idx = 0; @@ -84,8 +86,9 @@ void can_send_msg(uint32_t addr, uint32_t dhr, uint32_t dlr, uint8_t len) { } void process_can(void) { + __disable_irq(); CAN_FIFOMailBox_TypeDef to_send; - if (board.CAN->TSR & CAN_TSR_TME0) { + if ((board.CAN->TSR & CAN_TSR_TME0) == CAN_TSR_TME0) { if (can_pop(&can_tx_q, &to_send)) { board.CAN->sTxMailBox[0].TDLR = to_send.RDLR; board.CAN->sTxMailBox[0].TDHR = to_send.RDHR; @@ -93,12 +96,13 @@ void process_can(void) { board.CAN->sTxMailBox[0].TIR = to_send.RIR; } } + __enable_irq(); } void can_rx(void) { while ((board.CAN->RF0R & CAN_RF0R_FMP0) != 0) { - int address = board.CAN->sFIFOMailBox[0].RIR >> 21; - if (address == (int32_t)(0x250U + board.can_addr_offset)) { + uint32_t address = board.CAN->sFIFOMailBox[0].RIR >> 21; + if (address == (0x250U + board.can_addr_offset)) { if ((GET_MAILBOX_BYTES_04(&board.CAN->sFIFOMailBox[0]) == 0xdeadface) && (GET_MAILBOX_BYTES_48(&board.CAN->sFIFOMailBox[0]) == 0x0ab00b1e)) { enter_bootloader_mode = ENTER_SOFTLOADER_MAGIC; NVIC_SystemReset(); @@ -121,7 +125,7 @@ void can_rx(void) { current_idx = idx; } out_enable(LED_BLUE, true); - } else if (address == (int32_t)(0x251U + board.can_addr_offset)) { + } else if (address == (0x251U + board.can_addr_offset)) { #define MSG_SPD_LEN 5 uint8_t dat[MSG_TRQ_LEN]; for (int i=0; isFIFOMailBox[0])); out_enable(LED_BLUE, true); } else if ((hw_type == HW_TYPE_BASE) && (address == 0x203U + KNEE_ADDR_OFFSET)) { // detect knee by body and set flag for use with UDS message knee_detected = 1; + } else if ((hw_type == HW_TYPE_KNEE) && (address == 0x202U)) { // CAN based ignition for knee + ignition_off_counter = 0; } // next board.CAN->RF0R |= CAN_RF0R_RFOM0; diff --git a/body/board/config.h b/body/board/config.h index eb1e9231a..a6271ec74 100644 --- a/body/board/config.h +++ b/body/board/config.h @@ -5,23 +5,25 @@ #include "stm32f4xx_hal.h" #define CORE_FREQ 96000000U // MCU frequency in hertz -#define PWM_FREQ 16000 // PWM frequency in Hz / is also used for buzzer -#define DEAD_TIME 48 // PWM deadtime -#define DELAY_IN_MAIN_LOOP 5 // in ms. default 5. it is independent of all the timing critical stuff. do not touch if you do not know what you are doing. -#define A2BIT_CONV 50 // A to bit for current conversion on ADC. Example: 1 A = 50, 2 A = 100, etc +#define I2C_CLOCKSPEED 100 // I2C clock in kHz +#define PWM_FREQ 16000 // PWM frequency in Hz / is also used for buzzer +#define DEAD_TIME 48 // PWM deadtime +#define DELAY_IN_MAIN_LOOP 5 // in ms. default 5. it is independent of all the timing critical stuff. do not touch if you do not know what you are doing. +#define A2BIT_CONV 50 // A to bit for current conversion on ADC. Example: 1 A = 50, 2 A = 100, etc + +#define IGNITION_OFF_DELAY 5 // Stop sending CAN messages after 5 seconds #define ADC_CONV_CLOCK_CYCLES (ADC_SAMPLETIME_15CYCLES) #define ADC_CLOCK_DIV (4) #define ADC_TOTAL_CONV_TIME (ADC_CLOCK_DIV * ADC_CONV_CLOCK_CYCLES) // = ((SystemCoreClock / ADC_CLOCK_HZ) * ADC_CONV_CLOCK_CYCLES), where ADC_CLOCK_HZ = SystemCoreClock/ADC_CLOCK_DIV +#define KNEE_ADDR_OFFSET 0x100U #define ANGLE_TO_DEGREES 0.021972656 // Convert 14 bit angle sensor output to degrees #define GEARBOX_RATIO_LEFT 19 #define GEARBOX_RATIO_RIGHT 19 #define TRQ_LIMIT_LEFT 400 // Torque limit for knee gearbox(left) #define TRQ_LIMIT_RIGHT 200 // Torque limit for hip gearbox(right) -#define KNEE_ADDR_OFFSET 0x100 - #define BAT_FILT_COEF 655 // battery voltage filter coefficient in fixed-point. coef_fixedPoint = coef_floatingPoint * 2^16. In this case 655 = 0.01 * 2^16 #define BAT_CALIB_REAL_VOLTAGE 3192 // input voltage measured by multimeter (multiplied by 100). In this case 43.00 V * 100 = 4300 #define BAT_CALIB_ADC 1275 // adc-value measured by mainboard (value nr 5 on UART debug output) diff --git a/body/board/defines.h b/body/board/defines.h index cf6745679..bc7459a51 100644 --- a/body/board/defines.h +++ b/body/board/defines.h @@ -166,7 +166,8 @@ typedef struct { GPIO_TypeDef* ignition_port; uint16_t ignition_pin; - uint32_t can_addr_offset; + uint16_t can_addr_offset; + uint8_t uds_offset; GPIO_TypeDef* led_portR; uint16_t led_pinR; @@ -177,4 +178,11 @@ typedef struct { } board_t; +typedef struct { + uint8_t left_i2c : 1; + uint8_t left_angle : 1; + uint8_t right_i2c : 1; + uint8_t right_angle : 1; +} fault_status_t; + #endif // DEFINES_H diff --git a/body/board/drivers/angle_sensor.h b/body/board/drivers/angle_sensor.h index 4bf11ca1d..72f0a857e 100644 --- a/body/board/drivers/angle_sensor.h +++ b/body/board/drivers/angle_sensor.h @@ -19,14 +19,29 @@ extern I2C_HandleTypeDef hi2c1; const uint8_t init_imu_regaddr[] = {0x76, 0x4c, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53}; const uint8_t init_imu_data[] = {0x00, 0x12, 0x2f, 0x26, 0x67, 0x04, 0x00, 0x00}; +fault_status_t fault_status = {0}; void angle_sensor_read(uint16_t *sensor_angle) { + + if (fault_status.left_i2c && fault_status.right_i2c) { // Try to reinitialize halted I2C + if (HAL_I2C_Init(&hi2c1) == HAL_OK) { + fault_status.left_i2c = 0; + fault_status.right_i2c = 0; + } + } + uint8_t buf[2]; if (HAL_I2C_Mem_Read(&hi2c1, (AS5048_ADDRESS_LEFT<<1), AS5048B_ANGLMSB_REG, I2C_MEMADD_SIZE_8BIT, buf, 2, 10) == HAL_OK) { sensor_angle[0] = (buf[0] << 6) | (buf[1] & 0x3F); + fault_status.left_i2c = 0; + } else { + fault_status.left_i2c = 1; } if (HAL_I2C_Mem_Read(&hi2c1, (AS5048_ADDRESS_RIGHT<<1), AS5048B_ANGLMSB_REG, I2C_MEMADD_SIZE_8BIT, buf, 2, 10) == HAL_OK) { sensor_angle[1] = (buf[0] << 6) | (buf[1] & 0x3F); + fault_status.right_i2c = 0; + } else { + fault_status.right_i2c = 1; } } diff --git a/body/board/drivers/i2c_soft.h b/body/board/drivers/i2c_soft.h index 1ca60fc05..aa6280bd9 100644 --- a/body/board/drivers/i2c_soft.h +++ b/body/board/drivers/i2c_soft.h @@ -1,4 +1,4 @@ -#define SW_I2C_WAIT_TIME 22 +#define SW_I2C_WAIT_TIME 22 #define I2C_READ 0x01 #define READ_CMD 1 @@ -7,14 +7,14 @@ void SW_I2C_init(void) { - GPIO_InitTypeDef GPIO_InitStructure; + GPIO_InitTypeDef GPIO_InitStructure = { 0 }; GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH; GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; - + GPIO_InitStructure.Pin = SW_I2C1_SCL_PIN; HAL_GPIO_Init(SW_I2C1_SCL_GPIO, &GPIO_InitStructure); - GPIO_InitStructure.Pin = SW_I2C1_SDA_PIN; + GPIO_InitStructure.Pin = SW_I2C1_SDA_PIN; HAL_GPIO_Init(SW_I2C1_SDA_GPIO, &GPIO_InitStructure); } @@ -24,7 +24,7 @@ uint8_t SW_I2C_ReadVal_SDA(void) ret = (uint16_t)HAL_GPIO_ReadPin(SW_I2C1_SDA_GPIO, SW_I2C1_SDA_PIN); return ret; } - + void sda_high(void) { HAL_GPIO_WritePin(SW_I2C1_SDA_GPIO, SW_I2C1_SDA_PIN, GPIO_PIN_SET); @@ -59,23 +59,23 @@ void sda_out(uint8_t out) void sda_in_mode(void) { - GPIO_InitTypeDef GPIO_InitStructure; + GPIO_InitTypeDef GPIO_InitStructure = { 0 }; - GPIO_InitStructure.Speed=GPIO_SPEED_FREQ_HIGH; - GPIO_InitStructure.Mode=GPIO_MODE_INPUT; - - GPIO_InitStructure.Pin = SW_I2C1_SDA_PIN; + GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH; + GPIO_InitStructure.Mode = GPIO_MODE_INPUT; + + GPIO_InitStructure.Pin = SW_I2C1_SDA_PIN; HAL_GPIO_Init(SW_I2C1_SDA_GPIO, &GPIO_InitStructure); } void sda_out_mode(void) { - GPIO_InitTypeDef GPIO_InitStructure; + GPIO_InitTypeDef GPIO_InitStructure = { 0 }; - GPIO_InitStructure.Speed=GPIO_SPEED_FREQ_HIGH; - GPIO_InitStructure.Mode=GPIO_MODE_OUTPUT_OD; - - GPIO_InitStructure.Pin = SW_I2C1_SDA_PIN; + GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH; + GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_OD; + + GPIO_InitStructure.Pin = SW_I2C1_SDA_PIN; HAL_GPIO_Init(SW_I2C1_SDA_GPIO, &GPIO_InitStructure); } @@ -129,15 +129,15 @@ uint8_t i2c_check_ack(void) delay(SW_I2C_WAIT_TIME); for (i = 10; i > 0; i--) { - temp = !(SW_I2C_ReadVal_SDA()); - if (temp) + temp = !(SW_I2C_ReadVal_SDA()); + if (temp) { ack = 1; break; } } scl_low(); - sda_out_mode(); + sda_out_mode(); delay(SW_I2C_WAIT_TIME); return ack; diff --git a/body/board/drivers/llbxcan.h b/body/board/drivers/llbxcan.h index 6bdfe968b..15ebb27ed 100644 --- a/body/board/drivers/llbxcan.h +++ b/body/board/drivers/llbxcan.h @@ -1,13 +1,11 @@ -// maximum 500kbps! (or needs clock change) +// SAE 2284-3 : minimum 16 tq, SJW 3, sample point at 81.3% #define CAN_QUANTA 16U -#define CAN_SEQ1 13U // roundf(quanta * 0.875f) - 1; -#define CAN_SEQ2 2U // roundf(quanta * 0.125f); +#define CAN_SEQ1 12U +#define CAN_SEQ2 3U +#define CAN_SJW 3U #define CAN_PCLK (CORE_FREQ / 2U / 1000U) -// 333 = 33.3 kbps -// 5000 = 500 kbps #define can_speed_to_prescaler(x) (CAN_PCLK / CAN_QUANTA * 10U / (x)) - #define CAN_INIT_TIMEOUT_MS 500 bool llcan_set_speed(CAN_TypeDef *CAN_obj, uint32_t speed, bool loopback, bool silent) { @@ -30,7 +28,10 @@ bool llcan_set_speed(CAN_TypeDef *CAN_obj, uint32_t speed, bool loopback, bool s if(ret){ // set time quanta from defines - CAN_obj->BTR = ((CAN_BTR_TS1_0 * (CAN_SEQ1-1)) | (CAN_BTR_TS2_0 * (CAN_SEQ2-1)) | (can_speed_to_prescaler(speed) - 1U)); + CAN_obj->BTR = ((CAN_BTR_TS1_0 * (CAN_SEQ1-1)) | + (CAN_BTR_TS2_0 * (CAN_SEQ2-1)) | + (CAN_BTR_SJW_0 * (CAN_SJW-1)) | + (can_speed_to_prescaler(speed) - 1U)); // silent loopback mode for debugging if (loopback) { diff --git a/body/board/main.c b/body/board/main.c index d493c4a1a..74837b17e 100644 --- a/body/board/main.c +++ b/body/board/main.c @@ -58,6 +58,9 @@ extern board_t board; extern volatile uint32_t buzzerTimer; volatile uint32_t torque_cmd_timeout = 0U; volatile uint32_t ignition_off_counter = 0U; + +uint16_t cnt_press = 0; + int16_t batVoltageCalib; // global variable for calibrated battery voltage int16_t board_temp_deg_c; // global variable for calibrated temperature in degrees Celsius volatile int16_t cmdL; // global variable for Left Command @@ -66,9 +69,7 @@ volatile int16_t cmdR; // global variable for Right Command uint8_t hw_type; // type of the board detected(0 - base, 3 - knee) uint8_t ignition = 0; // global variable for ignition on SBU2 line uint8_t charger_connected = 0; // status of the charger port -uint8_t fault_status = 0; // fault status of the whole system uint8_t pkt_idx = 0; // For CAN msg counter - //------------------------------------------------------------------------ // Local variables //------------------------------------------------------------------------ @@ -120,6 +121,7 @@ int main(void) { } else { out_enable(POWERSWITCH, false); ignition = 1; + knee_detected = 1; } // Reset LEDs on startup out_enable(LED_RED, false); @@ -175,8 +177,10 @@ int main(void) { if (hw_type == HW_TYPE_KNEE) { // Safety to stop operation if angle sensor reading failed TODO: adjust sensivity and add lowpass to angle sensor? - if ((ABS((hall_angle_offset[0] + ((motPosL / 15 / GEARBOX_RATIO_LEFT) % 360)) - (sensor_angle[0] * ANGLE_TO_DEGREES)) > 5) || - (ABS((hall_angle_offset[1] + ((motPosR / 15 / GEARBOX_RATIO_RIGHT) % 360)) - (sensor_angle[1] * ANGLE_TO_DEGREES)) > 5)) { + fault_status.left_angle = (ABS((hall_angle_offset[0] + ((motPosL / 15 / GEARBOX_RATIO_LEFT) % 360)) - (sensor_angle[0] * ANGLE_TO_DEGREES)) > 5); + fault_status.right_angle = (ABS((hall_angle_offset[1] + ((motPosR / 15 / GEARBOX_RATIO_RIGHT) % 360)) - (sensor_angle[1] * ANGLE_TO_DEGREES)) > 5); + + if (fault_status.left_angle || fault_status.right_angle) { cmdL = cmdR = 0; } // Safety to stop movement when reaching dead angles, around 20 and 340 degrees @@ -211,7 +215,7 @@ int main(void) { } } - if (ignition_off_counter <= 10) { + if (ignition_off_counter <= IGNITION_OFF_DELAY) { // MOTORS_DATA: speed_L(2), speed_R(2), counter(1), checksum(1) uint8_t dat[8]; uint16_t speedL = rtY_Left.n_mot; @@ -287,19 +291,15 @@ int main(void) { // runs at 10Hz if ((HAL_GetTick() - (main_loop_10Hz - main_loop_10Hz_runtime)) >= 100) { main_loop_10Hz_runtime = HAL_GetTick(); - if (ignition_off_counter <= 10) { - // VAR_VALUES: fault_status(0:6), enable_motors(0:1), ignition(0:1), left motor error(1), right motor error(1), global fault status(1) + if (ignition_off_counter <= IGNITION_OFF_DELAY) { + // VAR_VALUES: fault_status(0:4), enable_motors(0:1), ignition(0:1), left motor error(1), right motor error(1) uint8_t dat[2]; - dat[0] = (((fault_status & 0x3F) << 2U) | (enable_motors << 1U) | ignition); + dat[0] = ((fault_status.right_angle << 5U) | (fault_status.right_i2c << 4U) | (fault_status.left_angle << 3U) | (fault_status.left_i2c << 2U) | (enable_motors << 1U) | ignition); dat[1] = rtY_Left.z_errCode; dat[2] = rtY_Right.z_errCode; can_send_msg((0x202U + board.can_addr_offset), 0x0U, ((dat[2] << 16U) | (dat[1] << 8U) | dat[0]), 3U); } out_enable(LED_GREEN, ignition); - - if (hw_type == HW_TYPE_BASE) { - poweroffPressCheck(); - } main_loop_10Hz_runtime = HAL_GetTick() - main_loop_10Hz_runtime; main_loop_10Hz = HAL_GetTick(); @@ -330,7 +330,7 @@ int main(void) { out_enable(LED_BLUE, false); // Reset LED after CAN RX out_enable(LED_GREEN, true); // Always use LED to show that body is on - if (ignition) { + if ((hw_type == HW_TYPE_BASE) && ignition) { ignition_off_counter = 0; } else { ignition_off_counter = (ignition_off_counter < MAX_uint32_T) ? (ignition_off_counter+1) : 0; @@ -341,6 +341,8 @@ int main(void) { } else if (rtY_Left.z_errCode || rtY_Right.z_errCode) { // 1 beep (low pitch): Motor error, disable motors enable_motors = 0; beepCount(1, 24, 1); + } else if (fault_status.left_angle || fault_status.left_i2c || fault_status.right_angle || fault_status.right_i2c) { // 2 beeps (low pitch): Motor error, disable motors + beepCount(2, 24, 1); } else if (TEMP_WARNING_ENABLE && board_temp_deg_c >= TEMP_WARNING) { // 5 beeps (low pitch): Mainboard temperature warning beepCount(5, 24, 1); } else if (batVoltage < BAT_LVL1) { // 1 beep fast (medium pitch): Low bat 1 @@ -357,6 +359,20 @@ int main(void) { main_loop_1Hz = HAL_GetTick(); } + if (hw_type == HW_TYPE_BASE) { + if (HAL_GPIO_ReadPin(BUTTON_PORT, BUTTON_PIN)) { + cnt_press += 1; + if (cnt_press == (2 * 1008)) { + poweroff(); + } + } else if (cnt_press >= 10) { + ignition = !ignition; + out_enable(IGNITION, ignition); + beepShort(5); + cnt_press = 0; + } + } + process_can(); tick_prev = HAL_GetTick(); } diff --git a/body/board/setup.h b/body/board/setup.h index 83e548f8e..898b2f393 100644 --- a/body/board/setup.h +++ b/body/board/setup.h @@ -8,7 +8,6 @@ TIM_HandleTypeDef htim_right; TIM_HandleTypeDef htim_left; ADC_HandleTypeDef hadc; I2C_HandleTypeDef hi2c1; -// SPI_HandleTypeDef hspi3; volatile adc_buf_t adc_buffer; extern board_t board; @@ -26,7 +25,7 @@ void MX_GPIO_Clocks_Init(void) { } void MX_GPIO_Common_Init(void) { - GPIO_InitTypeDef GPIO_InitStruct; + GPIO_InitTypeDef GPIO_InitStruct = { 0 }; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; @@ -167,7 +166,7 @@ void MX_GPIO_Common_Init(void) { void MX_I2C_Init(void) { - GPIO_InitTypeDef GPIO_InitStruct; + GPIO_InitTypeDef GPIO_InitStruct = { 0 }; GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7; GPIO_InitStruct.Mode = GPIO_MODE_AF_OD; @@ -179,7 +178,7 @@ void MX_I2C_Init(void) { __HAL_RCC_I2C1_CLK_ENABLE(); hi2c1.Instance = I2C1; - hi2c1.Init.ClockSpeed = 400000; + hi2c1.Init.ClockSpeed = (I2C_CLOCKSPEED * 1000); hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; hi2c1.Init.OwnAddress1 = 0; hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; diff --git a/body/board/uds.h b/body/board/uds.h index 5c3fa855c..f9e151be7 100644 --- a/body/board/uds.h +++ b/body/board/uds.h @@ -1,118 +1,125 @@ +extern uint8_t hw_type; void can_send_msg(uint32_t addr, uint32_t dhr, uint32_t dlr, uint8_t len); uint8_t uid[10]; -uint32_t uds_request = 0; +uint32_t uds_engine_request = 0; +uint32_t uds_debug_request = 0; uint8_t knee_detected = 0; +uint8_t sep_time = 0; void process_uds(uint32_t addr, uint32_t dlr) { memcpy(uid, (void *)0x1FFF7A10U, 0xAU); - if ((addr == BROADCAST_ADDR) || (addr == FALLBACK_ADDR)) { // OBD2 broadcast request, redirect to UDS? + if ((hw_type == HW_TYPE_BASE) && + ((addr == BROADCAST_ADDR) || + (addr == FALLBACK_ADDR))) { // OBD2 broadcast request, redirect to UDS? switch(dlr) { // VIN 09 OBD2 case 0x020902U: can_send_msg(FALLBACK_R_ADDR, 0x4D4F4301U, 0x02491410U, 8U); - uds_request = 0xF190U; + uds_engine_request = 0xF190U; break; // VIN : F190 on broadcast case 0x90F12203U: can_send_msg(FALLBACK_R_ADDR, 0x4D4F4390U, 0xF1621410U, 8U); break; // VIN continue - case 0x30U: - can_send_msg(FALLBACK_R_ADDR, 0x5659444FU, 0x42414D21U, 8U); - can_send_msg(FALLBACK_R_ADDR, 0x314E4F49U, 0x53524522U, 8U); + default: + if ((dlr & 0xFF) == 0x30U) { + sep_time = (dlr >> 16U) & 0xFF; + delay(sep_time); + can_send_msg(FALLBACK_R_ADDR, 0x5659444FU, 0x42414D21U, 8U); + can_send_msg(FALLBACK_R_ADDR, 0x314E4F49U, 0x53524522U, 8U); + } break; } - } else if (addr == ECU_ADDR) { // UDS request to "main" ECU + } else if (addr == (ENGINE_ADDR + board.uds_offset)) { // UDS request to "main" ECU switch(dlr) { // TESTER PRESENT case 0x3E02U: - can_send_msg(ECU_R_ADDR, 0x0U, 0x7E02U, 8U); + can_send_msg(ENGINE_R_ADDR + board.uds_offset, 0x0U, 0x7E02U, 8U); break; // DIAGNOSTIC SESSION CONTROL: DEFAULT case 0x011002U: - can_send_msg(ECU_R_ADDR, 0x0U, 0x015002U, 8U); + can_send_msg(ENGINE_R_ADDR + board.uds_offset, 0x0U, 0x015002U, 8U); break; // DIAGNOSTIC SESSION CONTROL: EXTENDED case 0x031002U: - can_send_msg(ECU_R_ADDR, 0x0U, 0x035002U, 8U); + can_send_msg(ENGINE_R_ADDR + board.uds_offset, 0x0U, 0x035002U, 8U); break; // APPLICATION SOFTWARE IDENTIFICATION : F181 (used for fingerprinting, firmware version) case 0x81F12203U: COMPILE_TIME_ASSERT(sizeof(version) == 6U); - can_send_msg(ECU_R_ADDR, ((version[2] << 24U) | (version[1] << 16U) | (version[0] << 8U) | 0x81U), 0xF1620910U, 8U); - uds_request = 0xF181U; + can_send_msg(ENGINE_R_ADDR + board.uds_offset, ((version[2] << 24U) | (version[1] << 16U) | (version[0] << 8U) | 0x81U), 0xF1620A10U, 8U); + uds_engine_request = 0xF181U; break; // ECU SERIAL NUMBER : F18C case 0x8CF12203U: - can_send_msg(ECU_R_ADDR, ((uid[2] << 24U) | (uid[1] << 16U) | (uid[0] << 8U) | 0x8CU), 0xF1620D10U, 8U); - uds_request = 0xF18CU; + can_send_msg(ENGINE_R_ADDR + board.uds_offset, ((uid[2] << 24U) | (uid[1] << 16U) | (uid[0] << 8U) | 0x8CU), 0xF1620D10U, 8U); + uds_engine_request = 0xF18CU; break; // VIN : F190 case 0x90F12203U: - can_send_msg(ECU_R_ADDR, 0x4D4F4390U, 0xF1621410U, 8U); - uds_request = 0xF190U; - break; - // SYSTEM NAME OR ENGINE TYPE : F197 - case 0x97F12203U: - can_send_msg(ECU_R_ADDR, 0x454C4597U, 0xF1620C10U, 8U); - uds_request = 0xF197U; + can_send_msg(ENGINE_R_ADDR + board.uds_offset, 0x4D4F4390U, 0xF1621410U, 8U); + uds_engine_request = 0xF190U; break; // FLOW CONTROL MESSAGE - case 0x30U: - switch(uds_request) { - // APPLICATION SOFTWARE IDENTIFICATION : F181 - case 0xF181U: - can_send_msg(ECU_R_ADDR, 0x0U, ((version[5] << 24U) | (version[4] << 16U) | (version[3] << 8U) | 0x21U), 8U); - uds_request = 0; - break; - // ECU SERIAL NUMBER : F18C - case 0xF18CU: - can_send_msg(ECU_R_ADDR, ((uid[9] << 24U) | (uid[8] << 16U) | (uid[7]<< 8U) | uid[6]), ((uid[5] << 24U) | (uid[4] << 16U) | (uid[3] << 8U) | 0x21U), 8U); - uds_request = 0; - break; - // VIN : F190 - case 0xF190U: - can_send_msg(ECU_R_ADDR, 0x5659444FU, 0x42414D21U, 8U); - can_send_msg(ECU_R_ADDR, 0x314E4F49U, 0x53524522U, 8U); - uds_request = 0; - break; - // SYSTEM NAME OR ENGINE TYPE : F197 - case 0xF197U: - can_send_msg(ECU_R_ADDR, (((knee_detected + 0x30) << 16U) | 0x4349U), 0x52544321U, 8U); - uds_request = 0; - break; + default: + if ((dlr & 0xFF) == 0x30U) { + sep_time = (dlr >> 16U) & 0xFF; + delay(sep_time); + switch(uds_engine_request) { + // APPLICATION SOFTWARE IDENTIFICATION : F181 + case 0xF181U: + can_send_msg(ENGINE_R_ADDR + board.uds_offset, (knee_detected + 0x61), ((version[5] << 24U) | (version[4] << 16U) | (version[3] << 8U) | 0x21U), 8U); + uds_engine_request = 0; + break; + // ECU SERIAL NUMBER : F18C + case 0xF18CU: + can_send_msg(ENGINE_R_ADDR + board.uds_offset, ((uid[9] << 24U) | (uid[8] << 16U) | (uid[7]<< 8U) | uid[6]), ((uid[5] << 24U) | (uid[4] << 16U) | (uid[3] << 8U) | 0x21U), 8U); + uds_engine_request = 0; + break; + // VIN : F190 + case 0xF190U: + can_send_msg(ENGINE_R_ADDR + board.uds_offset, 0x5659444FU, 0x42414D21U, 8U); + can_send_msg(ENGINE_R_ADDR + board.uds_offset, 0x314E4F49U, 0x53524522U, 8U); + uds_engine_request = 0; + break; + } } break; } - } else if (addr == DEBUG_ADDR) { // UDS request to "DEBUG" ECU + } else if (addr == (DEBUG_ADDR + board.uds_offset)) { // UDS request to "DEBUG" ECU switch(dlr) { // TESTER PRESENT case 0x3E02U: - can_send_msg(DEBUG_R_ADDR, 0x0U, 0x7E02U, 8U); + can_send_msg(DEBUG_R_ADDR + board.uds_offset, 0x0U, 0x7E02U, 8U); break; // DIAGNOSTIC SESSION CONTROL: DEFAULT case 0x011002U: - can_send_msg(DEBUG_R_ADDR, 0x0U, 0x015002U, 8U); + can_send_msg(DEBUG_R_ADDR + board.uds_offset, 0x0U, 0x015002U, 8U); break; // DIAGNOSTIC SESSION CONTROL: EXTENDED case 0x031002U: - can_send_msg(DEBUG_R_ADDR, 0x0U, 0x035002U, 8U); + can_send_msg(DEBUG_R_ADDR + board.uds_offset, 0x0U, 0x035002U, 8U); break; // APPLICATION SOFTWARE IDENTIFICATION : F181 (used for git hash logging) case 0x81F12203U: COMPILE_TIME_ASSERT(sizeof(gitversion) == 8U); - can_send_msg(DEBUG_R_ADDR, ((gitversion[2] << 24U) | (gitversion[1] << 16U) | (gitversion[0] << 8U) | 0x81U), 0xF1620B10U, 8U); - uds_request = 0xF181U; + can_send_msg((DEBUG_R_ADDR + board.uds_offset), ((gitversion[2] << 24U) | (gitversion[1] << 16U) | (gitversion[0] << 8U) | 0x81U), 0xF1620B10U, 8U); + uds_debug_request = 0xF181U; break; - case 0x30U: - switch(uds_request) { - // APPLICATION SOFTWARE IDENTIFICATION : F181 - case 0xF181U: - can_send_msg(DEBUG_R_ADDR, ((gitversion[7]<< 8U) | gitversion[6]), ((gitversion[5] << 24U) | (gitversion[4] << 16U) | (gitversion[3] << 8U) | 0x21U), 8U); - uds_request = 0; - break; + default: + if ((dlr & 0xFF) == 0x30U) { + sep_time = (dlr >> 16U) & 0xFF; + delay(sep_time); + switch(uds_debug_request) { + // APPLICATION SOFTWARE IDENTIFICATION : F181 + case 0xF181U: + can_send_msg((DEBUG_R_ADDR + board.uds_offset), ((gitversion[7]<< 8U) | gitversion[6]), ((gitversion[5] << 24U) | (gitversion[4] << 16U) | (gitversion[3] << 8U) | 0x21U), 8U); + uds_debug_request = 0; + break; + } } break; } diff --git a/body/board/util.c b/body/board/util.c index 5b3f0f5f0..c0b19ff19 100644 --- a/body/board/util.c +++ b/body/board/util.c @@ -175,22 +175,6 @@ void poweroff(void) { } } -void poweroffPressCheck(void) { - if(HAL_GPIO_ReadPin(BUTTON_PORT, BUTTON_PIN)) { - uint16_t cnt_press = 0; - while(HAL_GPIO_ReadPin(BUTTON_PORT, BUTTON_PIN)) { - HAL_Delay(10); - cnt_press++; - if (cnt_press == (2 * 100)) { poweroff(); } - } - if (cnt_press > 8) { - ignition = !ignition; - out_enable(IGNITION, ignition); - beepShort(5); - } - } -} - #define PULL_EFFECTIVE_DELAY 4096 uint8_t detect_with_pull(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, uint32_t mode) { GPIO_InitTypeDef GPIO_InitStruct; diff --git a/body/board/util.h b/body/board/util.h index d2bffff02..0eea10bd0 100644 --- a/body/board/util.h +++ b/body/board/util.h @@ -19,7 +19,6 @@ void calcAvgSpeed(void); // Poweroff Functions void poweroff(void); -void poweroffPressCheck(void); // GPIO functions uint8_t detect_with_pull(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, uint32_t mode); diff --git a/body/board/version.h b/body/board/version.h index 65b6f8869..2cc94a180 100644 --- a/body/board/version.h +++ b/body/board/version.h @@ -1 +1 @@ -const uint8_t version[6] = "0.0.02"; +const uint8_t version[6] = "0.3.00"; diff --git a/cereal/.gitignore b/cereal/.gitignore index 14cd02eff..d6ec0036c 100644 --- a/cereal/.gitignore +++ b/cereal/.gitignore @@ -1,6 +1,4 @@ -gen -node_modules -package-lock.json +/gen/ *.tmp *.pyc __pycache__ @@ -18,4 +16,3 @@ services.h .sconsign.dblite libcereal_shared.* .mypy_cache/ -catch2/ diff --git a/cereal/car.capnp b/cereal/car.capnp index 1b68892d1..1af634788 100644 --- a/cereal/car.capnp +++ b/cereal/car.capnp @@ -34,7 +34,7 @@ struct CarEvent @0x9b1657f34caf3ad3 { buttonCancel @11; buttonEnable @12; pedalPressed @13; # exits active state - pedalPressedPreEnable @73; # added during pre-enable state for either pedal + preEnableStandstill @73; # added during pre-enable state with brake gasPressedOverride @108; # added when user is pressing gas with no disengage on gas steerOverride @114; cruiseDisabled @14; @@ -46,7 +46,6 @@ struct CarEvent @0x9b1657f34caf3ad3 { controlsMismatch @22; pcmEnable @23; pcmDisable @24; - noTarget @25; radarFault @26; brakeHold @28; parkBrake @29; @@ -138,6 +137,7 @@ struct CarEvent @0x9b1657f34caf3ad3 { modelLagWarningDEPRECATED @93; startupOneplusDEPRECATED @82; startupFuzzyFingerprintDEPRECATED @97; + noTargetDEPRECATED @25; } } @@ -205,9 +205,6 @@ struct CarState { # clutch (manual transmission only) clutchPressed @28 :Bool; - # which packets this state came from - canMonoTimes @12: List(UInt64); - # blindspot sensors leftBlindspot @33 :Bool; # Is there something blocking the left lane change rightBlindspot @34 :Bool; # Is there something blocking the right lane change @@ -267,9 +264,11 @@ struct CarState { } } + # deprecated errorsDEPRECATED @0 :List(CarEvent.EventName); brakeLightsDEPRECATED @19 :Bool; steeringRateLimitedDEPRECATED @29 :Bool; + canMonoTimesDEPRECATED @12: List(UInt64); } # ******* radar state @ 20hz ******* @@ -278,9 +277,6 @@ struct RadarData @0x888ad6581cf0aacb { errors @0 :List(Error); points @1 :List(RadarPoint); - # which packets this state came from - canMonoTimes @2 :List(UInt64); - enum Error { canError @0; fault @1; @@ -304,6 +300,9 @@ struct RadarData @0x888ad6581cf0aacb { # some radars flag measurements VS estimates measured @6 :Bool; } + + # deprecated + canMonoTimesDEPRECATED @2 :List(UInt64); } # ******* car controls @ 100hz ******* @@ -317,6 +316,9 @@ struct CarControl { # Actuator commands as computed by controlsd actuators @6 :Actuators; + leftBlinker @15: Bool; + rightBlinker @16: Bool; + # Any car specific rate limits or quirks applied by # the CarController are reflected in actuatorsOutput # and matches what is sent to the car @@ -334,8 +336,12 @@ struct CarControl { brake @1: Float32; # range from -1.0 - 1.0 steer @2: Float32; + # value sent over can to the car + steerOutputCan @8: Float32; steeringAngleDeg @3: Float32; + curvature @7: Float32; + speed @6: Float32; # m/s accel @4: Float32; # m/s^2 longControlState @5: LongControlState; @@ -346,7 +352,6 @@ struct CarControl { stopping @2; starting @3; } - } struct CruiseControl { @@ -459,7 +464,7 @@ struct CarParams { vEgoStarting @59 :Float32; # Speed at which the car goes into starting state stoppingControl @31 :Bool; # Does the car allow full control even at lows speeds when stopping steerControlType @34 :SteerControlType; - radarOffCan @35 :Bool; # True when radar objects aren't visible on CAN + radarUnavailable @35 :Bool; # True when radar objects aren't visible on CAN or aren't parsed out stopAccel @60 :Float32; # Required acceleration to keep vehicle stationary stoppingDecelRate @52 :Float32; # m/s^2/s while trying to stop startAccel @32 :Float32; # Required acceleration to get car moving @@ -577,7 +582,7 @@ struct CarParams { subaruLegacy @22; # pre-Global platform hyundaiLegacy @23; hyundaiCommunity @24; - stellantisDEPRECATED @25; # Consolidated with Chrysler; may be recycled for the next new model + volkswagenMlb @25; hongqi @26; body @27; hyundaiCanfd @28; @@ -586,6 +591,7 @@ struct CarParams { enum SteerControlType { torque @0; angle @1; + curvature @2; } enum TransmissionType { @@ -605,6 +611,7 @@ struct CarParams { request @5 :List(Data); brand @6 :Text; bus @7 :UInt8; + logging @8 :Bool; } enum Ecu { diff --git a/cereal/log.capnp b/cereal/log.capnp index 72dcef700..4e657138c 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -165,7 +165,7 @@ struct FrameData { sensor @26 :ImageSensor; enum ImageSensor { unknown @0; - ar0321 @1; + ar0231 @1; ox03c10 @2; } @@ -543,7 +543,6 @@ struct PeripheralState { } struct RadarState @0x9a185389d6fdd05f { - canMonoTimes @10 :List(UInt64); mdMonoTime @6 :UInt64; carStateMonoTime @11 :UInt64; radarErrors @12 :List(Car.RadarData.Error); @@ -578,6 +577,7 @@ struct RadarState @0x9a185389d6fdd05f { calStatusDEPRECATED @2 :Int8; calCycleDEPRECATED @8 :Int32; calPercDEPRECATED @9 :Int8; + canMonoTimesDEPRECATED @10 :List(UInt64); } struct LiveCalibrationData { @@ -614,7 +614,6 @@ struct LiveTracks { struct ControlsState @0x97ff69c53601abf1 { startMonoTime @48 :UInt64; - canMonoTimes @21 :List(UInt64); longitudinalPlanMonoTime @28 :UInt64; lateralPlanMonoTime @50 :UInt64; @@ -654,10 +653,12 @@ struct ControlsState @0x97ff69c53601abf1 { lateralControlState :union { indiState @52 :LateralINDIState; pidState @53 :LateralPIDState; - lqrState @55 :LateralLQRState; angleState @58 :LateralAngleState; debugState @59 :LateralDebugState; torqueState @60 :LateralTorqueState; + curvatureState @65 :LateralCurvatureState; + + lqrStateDEPRECATED @55 :LateralLQRState; } enum OpenpilotState @0xdbe58b96d2d1ac61 { @@ -742,6 +743,18 @@ struct ControlsState @0x97ff69c53601abf1 { steeringAngleDesiredDeg @4 :Float32; } + struct LateralCurvatureState { + active @0 :Bool; + actualCurvature @1 :Float32; + desiredCurvature @2 :Float32; + error @3 :Float32; + p @4 :Float32; + i @5 :Float32; + f @6 :Float32; + output @7 :Float32; + saturated @8 :Bool; + } + struct LateralDebugState { active @0 :Bool; steeringAngleDeg @1 :Float32; @@ -778,6 +791,7 @@ struct ControlsState @0x97ff69c53601abf1 { jerkFactorDEPRECATED @12 :Float32; steerOverrideDEPRECATED @20 :Bool; steeringAngleDesiredDegDEPRECATED @29 :Float32; + canMonoTimesDEPRECATED @21 :List(UInt64); } struct ModelDataV2 { @@ -1153,16 +1167,16 @@ struct ProcLog { } struct GnssMeasurements { - ubloxMonoTime @0 :UInt64; + measTime @0 :UInt64; gpsWeek @1 :Int16; gpsTimeOfWeek @2 :Float64; correctedMeasurements @3 :List(CorrectedMeasurement); - positionECEF @4 :LiveLocationKalman.Measurement; - velocityECEF @5 :LiveLocationKalman.Measurement; - # Used for debugging: - positionFixECEF @6 :LiveLocationKalman.Measurement; + kalmanPositionECEF @4 :LiveLocationKalman.Measurement; + kalmanVelocityECEF @5 :LiveLocationKalman.Measurement; + positionECEF @6 :LiveLocationKalman.Measurement; + velocityECEF @7 :LiveLocationKalman.Measurement; # Todo sync this with timing pulse of ublox struct CorrectedMeasurement { @@ -1188,14 +1202,14 @@ struct GnssMeasurements { } enum ConstellationId { - # Satellite Constellation using the Ublox gnssid as index - gps @0; - sbas @1; - galileo @2; - beidou @3; - imes @4; - qznss @5; - glonass @6; + # Satellite Constellation using the Ublox gnssid as index + gps @0; + sbas @1; + galileo @2; + beidou @3; + imes @4; + qznss @5; + glonass @6; } enum EphemerisSourceType { @@ -1214,6 +1228,7 @@ struct UbloxGnss { ionoData @2 :IonoData; hwStatus @3 :HwStatus; hwStatus2 @4 :HwStatus2; + glonassEphemeris @5 :GlonassEphemeris; } struct MeasurementReport { @@ -1324,6 +1339,7 @@ struct UbloxGnss { ionoAlpha @38 :List(Float64); ionoBeta @39 :List(Float64); + towCount @40 :UInt32; } struct IonoData { @@ -1378,6 +1394,44 @@ struct UbloxGnss { flash @4; } } + + struct GlonassEphemeris { + svId @0 :UInt16; + year @1 :UInt16; + dayInYear @2 :UInt16; + hour @3 :UInt16; + minute @4 :UInt16; + second @5 :Float32; + + x @6 :Float64; + xVel @7 :Float64; + xAccel @8 :Float64; + y @9 :Float64; + yVel @10 :Float64; + yAccel @11 :Float64; + z @12 :Float64; + zVel @13 :Float64; + zAccel @14 :Float64; + + svType @15 :UInt8; + svURA @16 :Float32; + age @17 :UInt8; + + svHealth @18 :UInt8; + tk @19 :UInt16; + tb @20 :UInt16; + + tauN @21 :Float64; + deltaTauN @22 :Float64; + gammaN @23 :Float64; + + p1 @24 :UInt8; + p2 @25 :UInt8; + p3 @26 :UInt8; + p4 @27 :UInt8; + + freqNum @28 :UInt32; + } } struct QcomGnss @0xde94674b07ae51c1 { @@ -1951,6 +2005,30 @@ struct NavRoute { } } +struct MapRenderState { + locationMonoTime @0 :UInt64; + renderTime @1 :Float32; + frameId @2: UInt32; +} + +struct NavModelData { + frameId @0 :UInt32; + modelExecutionTime @1 :Float32; + dspExecutionTime @2 :Float32; + features @3 :List(Float32); + # predicted future position + position @4 :XYData; + desirePrediction @5 :List(Float32); + + # All SI units and in device frame + struct XYData { + x @0 :List(Float32); + y @1 :List(Float32); + xStd @2 :List(Float32); + yStd @3 :List(Float32); + } +} + struct EncodeData { idx @0 :EncodeIndex; data @1 :Data; @@ -1961,6 +2039,15 @@ struct EncodeData { struct UserFlag { } +struct Microphone { + soundPressure @0 :Float32; + + # uncalibrated, A-weighted + soundPressureWeighted @3 :Float32; + soundPressureWeightedDb @1 :Float32; + filteredSoundPressureWeightedDb @2 :Float32; +} + struct Event { logMonoTime @0 :UInt64; # nanoseconds valid @67 :Bool = true; @@ -2010,6 +2097,7 @@ struct Event { liveLocationKalman @72 :LiveLocationKalman; modelV2 @75 :ModelDataV2; driverStateV2 @92 :DriverStateV2; + navModel @104 :NavModelData; # camera stuff, each camera state has a matching encode idx roadCameraState @2 :FrameData; @@ -2020,6 +2108,9 @@ struct Event { wideRoadEncodeIdx @77 :EncodeIndex; qRoadEncodeIdx @90 :EncodeIndex; + # microphone data + microphone @103 :Microphone; + # systems stuff androidLog @20 :AndroidLogEntry; managerState @78 :ManagerState; @@ -2034,6 +2125,7 @@ struct Event { navInstruction @82 :NavInstruction; navRoute @83 :NavRoute; navThumbnail @84: Thumbnail; + mapRenderState @105: MapRenderState; # UI services userFlag @93 :UserFlag; diff --git a/cereal/messaging/bridge.cc b/cereal/messaging/bridge.cc index 0b72e067f..2ab751ce2 100644 --- a/cereal/messaging/bridge.cc +++ b/cereal/messaging/bridge.cc @@ -7,9 +7,9 @@ typedef void (*sighandler_t)(int sig); -#include "impl_msgq.h" -#include "impl_zmq.h" -#include "services.h" +#include "cereal/services.h" +#include "cereal/messaging/impl_msgq.h" +#include "cereal/messaging/impl_zmq.h" std::atomic do_exit = false; static void set_do_exit(int sig) { @@ -57,7 +57,7 @@ int main(int argc, char** argv) { } std::map sub2pub; - for (auto endpoint: get_services(whitelist_str, zmq_to_msgq)) { + for (auto endpoint : get_services(whitelist_str, zmq_to_msgq)) { PubSocket * pub_sock; SubSocket * sub_sock; if (zmq_to_msgq) { diff --git a/cereal/messaging/impl_msgq.cc b/cereal/messaging/impl_msgq.cc index 51353b437..18e936449 100644 --- a/cereal/messaging/impl_msgq.cc +++ b/cereal/messaging/impl_msgq.cc @@ -5,8 +5,8 @@ #include #include -#include "services.h" -#include "impl_msgq.h" +#include "cereal/services.h" +#include "cereal/messaging/impl_msgq.h" volatile sig_atomic_t msgq_do_exit = 0; diff --git a/cereal/messaging/impl_msgq.h b/cereal/messaging/impl_msgq.h index b67aae622..fdad52f9e 100644 --- a/cereal/messaging/impl_msgq.h +++ b/cereal/messaging/impl_msgq.h @@ -1,8 +1,10 @@ #pragma once -#include "messaging.h" -#include "msgq.h" -#include + #include +#include + +#include "cereal/messaging/messaging.h" +#include "cereal/messaging/msgq.h" #define MAX_POLLERS 128 diff --git a/cereal/messaging/impl_zmq.cc b/cereal/messaging/impl_zmq.cc index aeed176ae..5355c8110 100644 --- a/cereal/messaging/impl_zmq.cc +++ b/cereal/messaging/impl_zmq.cc @@ -4,10 +4,8 @@ #include #include -#include - -#include "services.h" -#include "impl_zmq.h" +#include "cereal/services.h" +#include "cereal/messaging/impl_zmq.h" static int get_port(std::string endpoint) { int port = -1; diff --git a/cereal/messaging/impl_zmq.h b/cereal/messaging/impl_zmq.h index bb232049e..deeac859c 100644 --- a/cereal/messaging/impl_zmq.h +++ b/cereal/messaging/impl_zmq.h @@ -1,7 +1,10 @@ #pragma once -#include "messaging.h" + #include #include +#include + +#include "cereal/messaging/messaging.h" #define MAX_POLLERS 128 diff --git a/cereal/messaging/messaging.cc b/cereal/messaging/messaging.cc index 69c0e80c2..48e405634 100644 --- a/cereal/messaging/messaging.cc +++ b/cereal/messaging/messaging.cc @@ -1,9 +1,9 @@ #include #include -#include "messaging.h" -#include "impl_zmq.h" -#include "impl_msgq.h" +#include "cereal/messaging/messaging.h" +#include "cereal/messaging/impl_zmq.h" +#include "cereal/messaging/impl_msgq.h" #ifdef __APPLE__ const bool MUST_USE_ZMQ = true; diff --git a/cereal/messaging/messaging.h b/cereal/messaging/messaging.h index 51ddb7af2..483b2465c 100644 --- a/cereal/messaging/messaging.h +++ b/cereal/messaging/messaging.h @@ -1,10 +1,15 @@ #pragma once + #include #include #include #include +#include +#include + #include -#include "../gen/cpp/log.capnp.h" + +#include "cereal/gen/cpp/log.capnp.h" #ifdef __APPLE__ #define CLOCK_BOOTTIME CLOCK_MONOTONIC @@ -18,7 +23,7 @@ class Context { public: virtual void * getRawContext() = 0; static Context * create(); - virtual ~Context(){}; + virtual ~Context(){} }; class Message { diff --git a/cereal/messaging/messaging.pxd b/cereal/messaging/messaging.pxd index de232da5f..38c6b09e1 100644 --- a/cereal/messaging/messaging.pxd +++ b/cereal/messaging/messaging.pxd @@ -6,7 +6,7 @@ from libcpp.vector cimport vector from libcpp cimport bool -cdef extern from "messaging.h": +cdef extern from "cereal/messaging/messaging.h": cdef cppclass Context: @staticmethod Context * create() diff --git a/cereal/messaging/msgq.cc b/cereal/messaging/msgq.cc index f527a5656..af93bbf60 100644 --- a/cereal/messaging/msgq.cc +++ b/cereal/messaging/msgq.cc @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -22,7 +23,7 @@ #include -#include "msgq.h" +#include "cereal/messaging/msgq.h" void sigusr2_handler(int signal) { assert(signal == SIGUSR2); @@ -30,7 +31,7 @@ void sigusr2_handler(int signal) { uint64_t msgq_get_uid(void){ std::random_device rd("/dev/urandom"); - std::uniform_int_distribution distribution(0,std::numeric_limits::max()); + std::uniform_int_distribution distribution(0, std::numeric_limits::max()); #ifdef __APPLE__ // TODO: this doesn't work @@ -76,7 +77,7 @@ void msgq_reset_reader(msgq_queue_t * q){ void msgq_wait_for_subscriber(msgq_queue_t *q){ while (*q->num_readers == 0){ - ; + // wait for subscriber } return; @@ -320,9 +321,11 @@ int msgq_msg_ready(msgq_queue_t * q){ uint32_t read_cycles, read_pointer; UNPACK64(read_cycles, read_pointer, *q->read_pointers[id]); + UNUSED(read_cycles); uint32_t write_cycles, write_pointer; UNPACK64(write_cycles, write_pointer, *q->write_pointer); + UNUSED(write_cycles); // Check if new message is available return (read_pointer != write_pointer); @@ -350,6 +353,7 @@ int msgq_msg_recv(msgq_msg_t * msg, msgq_queue_t * q){ uint32_t write_cycles, write_pointer; UNPACK64(write_cycles, write_pointer, *q->write_pointer); + UNUSED(write_cycles); char * p = q->data + read_pointer; diff --git a/cereal/messaging/msgq.h b/cereal/messaging/msgq.h index 8af069d9c..0a72a3864 100644 --- a/cereal/messaging/msgq.h +++ b/cereal/messaging/msgq.h @@ -1,4 +1,5 @@ #pragma once + #include #include #include @@ -8,8 +9,9 @@ #define NUM_READERS 12 #define ALIGN(n) ((n + (8 - 1)) & -8) +#define UNUSED(x) (void)x #define UNPACK64(higher, lower, input) do {uint64_t tmp = input; higher = tmp >> 32; lower = tmp & 0xFFFFFFFF;} while (0) -#define PACK64(output, higher, lower) output = ((uint64_t)higher << 32 ) | ((uint64_t)lower & 0xFFFFFFFF) +#define PACK64(output, higher, lower) output = ((uint64_t)higher << 32) | ((uint64_t)lower & 0xFFFFFFFF) struct msgq_header_t { uint64_t num_readers; diff --git a/cereal/messaging/socketmaster.cc b/cereal/messaging/socketmaster.cc index 2066559dd..50614be36 100644 --- a/cereal/messaging/socketmaster.cc +++ b/cereal/messaging/socketmaster.cc @@ -4,8 +4,8 @@ #include #include -#include "services.h" -#include "messaging.h" +#include "cereal/services.h" +#include "cereal/messaging/messaging.h" const bool SIMULATION = (getenv("SIMULATION") != nullptr) && (std::string(getenv("SIMULATION")) == "1"); @@ -116,7 +116,7 @@ void SubMaster::update(int timeout) { void SubMaster::update_msgs(uint64_t current_time, const std::vector> &messages){ if (++frame == UINT64_MAX) frame = 1; - for(auto &kv : messages) { + for (auto &kv : messages) { auto m_find = services_.find(kv.first); if (m_find == services_.end()){ continue; diff --git a/cereal/services.py b/cereal/services.py index 4a2f83dcf..fd1b34312 100755 --- a/cereal/services.py +++ b/cereal/services.py @@ -30,7 +30,7 @@ services = { "temperatureSensor": (True, 100., 100), "gpsNMEA": (True, 9.), "deviceState": (True, 2., 1), - "can": (True, 100.), + "can": (True, 100., 1223), # decimation gives ~5 msgs in a full segment "controlsState": (True, 100., 10), "pandaStates": (True, 2., 1), "peripheralState": (True, 2., 1), @@ -74,8 +74,11 @@ services = { "navInstruction": (True, 1., 10), "navRoute": (True, 0.), "navThumbnail": (True, 0.), + "navModel": (True, 2., 4.), + "mapRenderState": (True, 2., 1.), "qRoadEncodeIdx": (False, 20.), "userFlag": (True, 0., 1), + "microphone": (True, 10., 10), # debug "uiDebug": (True, 0., 1), diff --git a/cereal/visionipc/ipc.cc b/cereal/visionipc/ipc.cc index 29c4c9c37..c4ab9a475 100644 --- a/cereal/visionipc/ipc.cc +++ b/cereal/visionipc/ipc.cc @@ -15,7 +15,7 @@ #define getsocket() socket(AF_UNIX, SOCK_SEQPACKET, 0) #endif -#include "ipc.h" +#include "cereal/visionipc/ipc.h" int ipc_connect(const char* socket_path) { int err; diff --git a/cereal/visionipc/visionbuf.cc b/cereal/visionipc/visionbuf.cc index 3480e4037..e9e0ff397 100644 --- a/cereal/visionipc/visionbuf.cc +++ b/cereal/visionipc/visionbuf.cc @@ -1,4 +1,4 @@ -#include "visionbuf.h" +#include "cereal/visionipc/visionbuf.h" #define ALIGN(x, align) (((x) + (align)-1) & ~((align)-1)) diff --git a/cereal/visionipc/visionbuf.h b/cereal/visionipc/visionbuf.h index a01b584a0..e0e78f453 100644 --- a/cereal/visionipc/visionbuf.h +++ b/cereal/visionipc/visionbuf.h @@ -1,5 +1,6 @@ #pragma once -#include "visionipc.h" + +#include "cereal/visionipc/visionipc.h" #define CL_USE_DEPRECATED_OPENCL_1_2_APIS #ifdef __APPLE__ diff --git a/cereal/visionipc/visionbuf_cl.cc b/cereal/visionipc/visionbuf_cl.cc index 8de25fcb6..0315d8db9 100644 --- a/cereal/visionipc/visionbuf_cl.cc +++ b/cereal/visionipc/visionbuf_cl.cc @@ -1,4 +1,4 @@ -#include "visionbuf.h" +#include "cereal/visionipc/visionbuf.h" #include #include diff --git a/cereal/visionipc/visionbuf_ion.cc b/cereal/visionipc/visionbuf_ion.cc index c66b668c6..ed446abfb 100644 --- a/cereal/visionipc/visionbuf_ion.cc +++ b/cereal/visionipc/visionbuf_ion.cc @@ -14,7 +14,7 @@ #include -#include "visionbuf.h" +#include "cereal/visionipc/visionbuf.h" // keep trying if x gets interrupted by a signal #define HANDLE_EINTR(x) \ diff --git a/cereal/visionipc/visionipc.pxd b/cereal/visionipc/visionipc.pxd index 5c2d6f079..87999e27b 100644 --- a/cereal/visionipc/visionipc.pxd +++ b/cereal/visionipc/visionipc.pxd @@ -6,7 +6,7 @@ from libcpp.vector cimport vector from libc.stdint cimport uint32_t, uint64_t from libcpp cimport bool -cdef extern from "visionbuf.h": +cdef extern from "cereal/visionipc/visionbuf.h": cdef enum VisionStreamType: pass @@ -19,13 +19,13 @@ cdef extern from "visionbuf.h": size_t uv_offset void set_frame_id(uint64_t id) -cdef extern from "visionipc.h": +cdef extern from "cereal/visionipc/visionipc.h": struct VisionIpcBufExtra: uint32_t frame_id uint64_t timestamp_sof uint64_t timestamp_eof -cdef extern from "visionipc_server.h": +cdef extern from "cereal/visionipc/visionipc_server.h": cdef cppclass VisionIpcServer: VisionIpcServer(string, void*, void*) void create_buffers(VisionStreamType, size_t, bool, size_t, size_t) @@ -34,7 +34,7 @@ cdef extern from "visionipc_server.h": void send(VisionBuf *, VisionIpcBufExtra *, bool) void start_listener() -cdef extern from "visionipc_client.h": +cdef extern from "cereal/visionipc/visionipc_client.h": cdef cppclass VisionIpcClient: VisionIpcClient(string, VisionStreamType, bool, void*, void*) VisionBuf * recv(VisionIpcBufExtra *, int) diff --git a/cereal/visionipc/visionipc_client.cc b/cereal/visionipc/visionipc_client.cc index 573a47d09..0b86d9d6e 100644 --- a/cereal/visionipc/visionipc_client.cc +++ b/cereal/visionipc/visionipc_client.cc @@ -3,10 +3,21 @@ #include #include -#include "visionipc/ipc.h" -#include "visionipc/visionipc_client.h" -#include "visionipc/visionipc_server.h" -#include "logger/logger.h" +#include "cereal/visionipc/ipc.h" +#include "cereal/visionipc/visionipc_client.h" +#include "cereal/visionipc/visionipc_server.h" +#include "cereal/logger/logger.h" + +static int connect_to_vipc_server(const std::string &name, bool blocking) { + std::string path = "/tmp/visionipc_" + name; + int socket_fd = ipc_connect(path.c_str()); + while (socket_fd < 0 && blocking) { + std::cout << "VisionIpcClient connecting" << std::endl; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + socket_fd = ipc_connect(path.c_str()); + } + return socket_fd; +} VisionIpcClient::VisionIpcClient(std::string name, VisionStreamType type, bool conflate, cl_device_id device_id, cl_context ctx) : name(name), type(type), device_id(device_id), ctx(ctx) { msg_ctx = Context::create(); @@ -29,23 +40,10 @@ bool VisionIpcClient::connect(bool blocking){ num_buffers = 0; - // Connect to server socket and ask for all FDs of type - std::string path = "/tmp/visionipc_" + name; - - int socket_fd = -1; - while (socket_fd < 0) { - socket_fd = ipc_connect(path.c_str()); - - if (socket_fd < 0) { - if (blocking){ - std::cout << "VisionIpcClient connecting" << std::endl; - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - } else { - return false; - } - } + int socket_fd = connect_to_vipc_server(name, blocking); + if (socket_fd < 0) { + return false; } - // Send stream type to server to request FDs int r = ipc_sendrecv_with_fds(true, socket_fd, &type, sizeof(type), nullptr, 0, nullptr); assert(r == sizeof(type)); @@ -114,7 +112,22 @@ VisionBuf * VisionIpcClient::recv(VisionIpcBufExtra * extra, const int timeout_m return buf; } +std::set VisionIpcClient::getAvailableStreams(const std::string &name, bool blocking) { + int socket_fd = connect_to_vipc_server(name, blocking); + if (socket_fd < 0) { + return {}; + } + // Send VISION_STREAM_MAX to server to request available streams + int request = VISION_STREAM_MAX; + int r = ipc_sendrecv_with_fds(true, socket_fd, &request, sizeof(request), nullptr, 0, nullptr); + assert(r == sizeof(request)); + VisionStreamType available_streams[VISION_STREAM_MAX] = {}; + r = ipc_sendrecv_with_fds(false, socket_fd, &available_streams, sizeof(available_streams), nullptr, 0, nullptr); + assert(r >= sizeof(VisionStreamType) && r % sizeof(VisionStreamType) == 0); + close(socket_fd); + return std::set(available_streams, available_streams + r / sizeof(VisionStreamType)); +} VisionIpcClient::~VisionIpcClient(){ for (size_t i = 0; i < num_buffers; i++){ diff --git a/cereal/visionipc/visionipc_client.h b/cereal/visionipc/visionipc_client.h index a6426ce92..8dff9b2e0 100644 --- a/cereal/visionipc/visionipc_client.h +++ b/cereal/visionipc/visionipc_client.h @@ -1,11 +1,13 @@ #pragma once -#include + +#include #include +#include #include -#include "messaging/messaging.h" -#include "visionipc/visionipc.h" -#include "visionipc/visionbuf.h" +#include "cereal/messaging/messaging.h" +#include "cereal/visionipc/visionipc.h" +#include "cereal/visionipc/visionbuf.h" class VisionIpcClient { private: @@ -14,8 +16,6 @@ private: SubSocket * sock; Poller * poller; - VisionStreamType type; - cl_device_id device_id = nullptr; cl_context ctx = nullptr; @@ -23,6 +23,7 @@ private: public: bool connected = false; + VisionStreamType type; int num_buffers = 0; VisionBuf buffers[VISIONIPC_MAX_FDS]; VisionIpcClient(std::string name, VisionStreamType type, bool conflate, cl_device_id device_id=nullptr, cl_context ctx=nullptr); @@ -30,4 +31,5 @@ public: VisionBuf * recv(VisionIpcBufExtra * extra=nullptr, const int timeout_ms=100); bool connect(bool blocking=true); bool is_connected() { return connected; } + static std::set getAvailableStreams(const std::string &name, bool blocking = true); }; diff --git a/cereal/visionipc/visionipc_server.cc b/cereal/visionipc/visionipc_server.cc index cbe36663a..24d8bb9bb 100644 --- a/cereal/visionipc/visionipc_server.cc +++ b/cereal/visionipc/visionipc_server.cc @@ -2,15 +2,16 @@ #include #include #include +#include #include #include #include -#include "messaging/messaging.h" -#include "visionipc/ipc.h" -#include "visionipc/visionipc_server.h" -#include "logger/logger.h" +#include "cereal/messaging/messaging.h" +#include "cereal/visionipc/ipc.h" +#include "cereal/visionipc/visionipc_server.h" +#include "cereal/logger/logger.h" std::string get_endpoint_name(std::string name, VisionStreamType type){ if (messaging_use_zmq()){ @@ -25,7 +26,7 @@ VisionIpcServer::VisionIpcServer(std::string name, cl_device_id device_id, cl_co msg_ctx = Context::create(); std::random_device rd("/dev/urandom"); - std::uniform_int_distribution distribution(0,std::numeric_limits::max()); + std::uniform_int_distribution distribution(0, std::numeric_limits::max()); server_id = distribution(rd); } @@ -111,6 +112,19 @@ void VisionIpcServer::listener(){ VisionStreamType type = VisionStreamType::VISION_STREAM_MAX; int r = ipc_sendrecv_with_fds(false, fd, &type, sizeof(type), nullptr, 0, nullptr); assert(r == sizeof(type)); + + // send available stream types + if (type == VisionStreamType::VISION_STREAM_MAX) { + std::vector available_stream_types; + for (auto& [stream_type, _] : buffers) { + available_stream_types.push_back(stream_type); + } + r = ipc_sendrecv_with_fds(true, fd, available_stream_types.data(), available_stream_types.size() * sizeof(VisionStreamType), nullptr, 0, nullptr); + assert(r == available_stream_types.size() * sizeof(VisionStreamType)); + close(fd); + continue; + } + if (buffers.count(type) <= 0) { std::cout << "got request for invalid buffer type: " << type << std::endl; close(fd); @@ -174,7 +188,7 @@ VisionIpcServer::~VisionIpcServer(){ listener_thread.join(); // VisionBuf cleanup - for( auto const& [type, buf] : buffers ) { + for (auto const& [type, buf] : buffers) { for (VisionBuf* b : buf){ if (b->free() != 0) { LOGE("Failed to free buffer"); @@ -184,7 +198,7 @@ VisionIpcServer::~VisionIpcServer(){ } // Messaging cleanup - for( auto const& [type, sock] : sockets ) { + for (auto const& [type, sock] : sockets) { delete sock; } delete msg_ctx; diff --git a/cereal/visionipc/visionipc_server.h b/cereal/visionipc/visionipc_server.h index 5c644023b..4c23ff6f0 100644 --- a/cereal/visionipc/visionipc_server.h +++ b/cereal/visionipc/visionipc_server.h @@ -5,9 +5,9 @@ #include #include -#include "messaging/messaging.h" -#include "visionipc/visionipc.h" -#include "visionipc/visionbuf.h" +#include "cereal/messaging/messaging.h" +#include "cereal/visionipc/visionipc.h" +#include "cereal/visionipc/visionbuf.h" std::string get_endpoint_name(std::string name, VisionStreamType type); diff --git a/cereal/visionipc/visionipc_tests.cc b/cereal/visionipc/visionipc_tests.cc index b10e33256..4a081df11 100644 --- a/cereal/visionipc/visionipc_tests.cc +++ b/cereal/visionipc/visionipc_tests.cc @@ -2,8 +2,8 @@ #include #include "catch2/catch.hpp" -#include "visionipc_server.h" -#include "visionipc_client.h" +#include "cereal/visionipc/visionipc_server.h" +#include "cereal/visionipc/visionipc_client.h" static void zmq_sleep(int milliseconds=1000){ if (messaging_use_zmq()){ @@ -22,6 +22,17 @@ TEST_CASE("Connecting"){ REQUIRE(client.connected); } +TEST_CASE("getAvailableStreams"){ + VisionIpcServer server("camerad"); + server.create_buffers(VISION_STREAM_ROAD, 1, false, 100, 100); + server.create_buffers(VISION_STREAM_WIDE_ROAD, 1, false, 100, 100); + server.start_listener(); + auto available_streams = VisionIpcClient::getAvailableStreams("camerad"); + REQUIRE(available_streams.size() == 2); + REQUIRE(available_streams.count(VISION_STREAM_ROAD) == 1); + REQUIRE(available_streams.count(VISION_STREAM_WIDE_ROAD) == 1); +} + TEST_CASE("Check buffers"){ size_t width = 100, height = 200, num_buffers = 5; VisionIpcServer server("camerad"); diff --git a/common/SConscript b/common/SConscript index 8aee6f42a..5d6170611 100644 --- a/common/SConscript +++ b/common/SConscript @@ -10,11 +10,13 @@ common_libs = [ 'statlog.cc', 'swaglog.cc', 'util.cc', - 'gpio.cc', 'i2c.cc', 'watchdog.cc', ] +if arch != "Darwin": + common_libs.append('gpio.cc') + _common = fxn('common', common_libs, LIBS="json11") files = [ diff --git a/common/clutil.cc b/common/clutil.cc index 9d3447d80..3cfc8a8c8 100644 --- a/common/clutil.cc +++ b/common/clutil.cc @@ -5,6 +5,7 @@ #include #include "common/util.h" +#include "common/swaglog.h" namespace { // helper functions @@ -31,14 +32,14 @@ void cl_print_info(cl_platform_id platform, cl_device_id device) { case CL_DEVICE_TYPE_ACCELERATOR: type_str = "CL_DEVICE_TYPE_ACCELERATOR"; break; } - std::cout << "vendor: " << get_platform_info(platform, CL_PLATFORM_VENDOR) << std::endl - << "platform version: " << get_platform_info(platform, CL_PLATFORM_VERSION) << std::endl - << "profile: " << get_platform_info(platform, CL_PLATFORM_PROFILE) << std::endl - << "extensions: " << get_platform_info(platform, CL_PLATFORM_EXTENSIONS) << std::endl - << "name :" << get_device_info(device, CL_DEVICE_NAME) << std::endl - << "device version :" << get_device_info(device, CL_DEVICE_VERSION) << std::endl - << "max work group size :" << work_group_size << std::endl - << "type = " << device_type << " = " << type_str << std::endl; + LOGD("vendor: %s", get_platform_info(platform, CL_PLATFORM_VENDOR).c_str()); + LOGD("platform version: %s", get_platform_info(platform, CL_PLATFORM_VERSION).c_str()); + LOGD("profile: %s", get_platform_info(platform, CL_PLATFORM_PROFILE).c_str()); + LOGD("extensions: %s", get_platform_info(platform, CL_PLATFORM_EXTENSIONS).c_str()); + LOGD("name: %s", get_device_info(device, CL_DEVICE_NAME).c_str()); + LOGD("device version: %s", get_device_info(device, CL_DEVICE_VERSION).c_str()); + LOGD("max work group size: %d", work_group_size); + LOGD("type = %d = ", device_type, type_str); } void cl_print_build_errors(cl_program program, cl_device_id device) { @@ -49,7 +50,7 @@ void cl_print_build_errors(cl_program program, cl_device_id device) { std::string log(log_size, '\0'); clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, log_size, &log[0], NULL); - std::cout << "build failed; status=" << status << ", log:" << std::endl << log << std::endl; + LOGE("build failed; status=%d, log: %s", status, log.c_str()); } } // namespace @@ -61,14 +62,15 @@ cl_device_id cl_get_device_id(cl_device_type device_type) { CL_CHECK(clGetPlatformIDs(num_platforms, &platform_ids[0], NULL)); for (size_t i = 0; i < num_platforms; ++i) { - std::cout << "platform[" << i << "] CL_PLATFORM_NAME: " << get_platform_info(platform_ids[i], CL_PLATFORM_NAME) << std::endl; + LOGD("platform[%d] CL_PLATFORM_NAME: %s", i, get_platform_info(platform_ids[i], CL_PLATFORM_NAME).c_str()); + // Get first device if (cl_device_id device_id = NULL; clGetDeviceIDs(platform_ids[i], device_type, 1, &device_id, NULL) == 0 && device_id) { cl_print_info(platform_ids[i], device_id); return device_id; } } - std::cout << "No valid openCL platform found" << std::endl; + LOGE("No valid openCL platform found"); assert(0); return nullptr; } diff --git a/common/gpio.cc b/common/gpio.cc index 9f5c211a4..aa65c0bd9 100644 --- a/common/gpio.cc +++ b/common/gpio.cc @@ -1,5 +1,20 @@ #include "common/gpio.h" +#ifdef __APPLE__ +int gpio_init(int pin_nr, bool output) { + return 0; +} + +int gpio_set(int pin_nr, bool high) { + return 0; +} + +int gpiochip_get_ro_value_fd(const char* consumer_label, int gpiochiop_id, int pin_nr) { + return 0; +} + +#else + #include #include @@ -63,3 +78,5 @@ int gpiochip_get_ro_value_fd(const char* consumer_label, int gpiochiop_id, int p close(fd); return rq.fd; } + +#endif diff --git a/common/params.cc b/common/params.cc index 9e3e32d58..2ba4f7e8a 100644 --- a/common/params.cc +++ b/common/params.cc @@ -104,13 +104,14 @@ std::unordered_map keys = { {"DisablePowerDown", PERSISTENT}, {"ExperimentalMode", PERSISTENT}, {"ExperimentalModeConfirmed", PERSISTENT}, - {"ExperimentalLongitudinalEnabled", PERSISTENT}, // WARNING: THIS MAY DISABLE AEB + {"ExperimentalLongitudinalEnabled", PERSISTENT}, {"DisableUpdates", PERSISTENT}, {"DisengageOnAccelerator", PERSISTENT}, {"DongleId", PERSISTENT}, {"DoReboot", CLEAR_ON_MANAGER_START}, {"DoShutdown", CLEAR_ON_MANAGER_START}, {"DoUninstall", CLEAR_ON_MANAGER_START}, + {"FirmwareObdQueryDone", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, {"ForcePowerDown", CLEAR_ON_MANAGER_START}, {"GitBranch", PERSISTENT}, {"GitCommit", PERSISTENT}, @@ -134,9 +135,10 @@ std::unordered_map keys = { {"IsRhdDetected", PERSISTENT}, {"IsTakingSnapshot", CLEAR_ON_MANAGER_START}, {"IsTestedBranch", CLEAR_ON_MANAGER_START}, + {"IsReleaseBranch", CLEAR_ON_MANAGER_START}, {"IsUpdateAvailable", CLEAR_ON_MANAGER_START}, {"JoystickDebugMode", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF}, - {"LaikadEphemeris", PERSISTENT | DONT_LOG}, + {"LaikadEphemerisV2", PERSISTENT | DONT_LOG}, {"LanguageSetting", PERSISTENT}, {"LastAthenaPingTime", CLEAR_ON_MANAGER_START}, {"LastGPSPosition", PERSISTENT}, @@ -149,9 +151,11 @@ std::unordered_map keys = { {"LiveTorqueCarParams", PERSISTENT}, {"LiveTorqueParameters", PERSISTENT | DONT_LOG}, {"NavDestination", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF}, + {"NavDestinationWaypoints", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF}, {"NavSettingTime24h", PERSISTENT}, {"NavSettingLeftSide", PERSISTENT}, {"NavdRender", PERSISTENT}, + {"ObdMultiplexingDisabled", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, {"OpenpilotEnabledToggle", PERSISTENT}, {"PandaHeartbeatLost", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF}, {"PandaSignatures", CLEAR_ON_MANAGER_START}, @@ -168,7 +172,7 @@ std::unordered_map keys = { {"Timezone", PERSISTENT}, {"TrainingVersion", PERSISTENT}, {"UbloxAvailable", PERSISTENT}, - {"UpdateAvailable", CLEAR_ON_MANAGER_START}, + {"UpdateAvailable", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, {"UpdateFailedCount", CLEAR_ON_MANAGER_START}, {"UpdaterState", CLEAR_ON_MANAGER_START}, {"UpdaterFetchAvailable", CLEAR_ON_MANAGER_START}, diff --git a/common/prefix.h b/common/prefix.h new file mode 100644 index 000000000..f5abe14b2 --- /dev/null +++ b/common/prefix.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +#include "common/params.h" +#include "common/util.h" + +class OpenpilotPrefix { +public: + OpenpilotPrefix(std::string prefix = {}) { + if (prefix.empty()) { + prefix = util::random_string(15); + } + msgq_path = "/dev/shm/" + prefix; + bool ret = util::create_directories(msgq_path, 0777); + assert(ret); + setenv("OPENPILOT_PREFIX", prefix.c_str(), 1); + } + + ~OpenpilotPrefix() { + auto param_path = Params().getParamPath(); + if (util::file_exists(param_path)) { + std::string real_path = util::readlink(param_path); + system(util::string_format("rm %s -rf", real_path.c_str()).c_str()); + unlink(param_path.c_str()); + } + system(util::string_format("rm %s -rf", msgq_path.c_str()).c_str()); + unsetenv("OPENPILOT_PREFIX"); + } + +private: + std::string msgq_path; +}; diff --git a/common/util.cc b/common/util.cc index 010fe8a11..10dff6a9e 100644 --- a/common/util.cc +++ b/common/util.cc @@ -10,6 +10,7 @@ #include #include #include +#include #include #ifdef __linux__ @@ -228,6 +229,18 @@ std::string hexdump(const uint8_t* in, const size_t size) { return ss.str(); } +std::string random_string(std::string::size_type length) { + const char* chrs = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + std::mt19937 rg{std::random_device{}()}; + std::uniform_int_distribution pick(0, sizeof(chrs) - 2); + std::string s; + s.reserve(length); + while (length--) { + s += chrs[pick(rg)]; + } + return s; +} + std::string dir_name(std::string const &path) { size_t pos = path.find_last_of("/"); if (pos == std::string::npos) return ""; diff --git a/common/util.h b/common/util.h index b46f7bde4..028074384 100644 --- a/common/util.h +++ b/common/util.h @@ -75,6 +75,7 @@ int getenv(const char* key, int default_val); float getenv(const char* key, float default_val); std::string hexdump(const uint8_t* in, const size_t size); +std::string random_string(std::string::size_type length); std::string dir_name(std::string const& path); // **** file fhelpers ***** diff --git a/common/version.h b/common/version.h index 74a56a7c1..7b5764785 100644 --- a/common/version.h +++ b/common/version.h @@ -1 +1 @@ -#define COMMA_VERSION "0.9.0" +#define COMMA_VERSION "0.9.1" diff --git a/docs/CARS.md b/docs/CARS.md index 59b814555..886029936 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,236 +4,259 @@ A supported vehicle is one that just works when you install a comma three. All supported cars provide a better experience than any stock system. -# 215 Supported Cars +# 237 Supported Cars -|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Harness| -|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:| -|Acura|ILX 2016-19|AcuraWatch Plus|openpilot|25 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| -|Acura|RDX 2016-18|AcuraWatch Plus|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| -|Acura|RDX 2019-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| -|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Audi|Q3 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Cadillac|Escalade ESV 2016[3](#footnotes)|Adaptive Cruise Control (ACC) & LKAS|openpilot|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II| -|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM| -|Chevrolet|Silverado 1500 2020-21|Safety Package II|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM| -|Chevrolet|Volt 2017-18[3](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II| -|Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| -|Chrysler|Pacifica 2019-20|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| -|Chrysler|Pacifica 2021|All|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| -|Chrysler|Pacifica Hybrid 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| -|Chrysler|Pacifica Hybrid 2019-22|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| -|comma|body|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|None| -|Genesis|G70 2018-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F| -|Genesis|G70 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F| -|Genesis|G80 2017-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| -|Genesis|G90 2017-18|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| -|Genesis|GV70 2022-23|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| -|GMC|Acadia 2018[3](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II| -|GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM| -|Honda|Accord 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| -|Honda|Accord Hybrid 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| -|Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| -|Honda|Civic 2019-21|All|openpilot available[1](#footnotes)|0 mph|2 mph[4](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| -|Honda|Civic 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch B| -|Honda|Civic Hatchback 2017-21|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| -|Honda|Civic Hatchback 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch B| -|Honda|CR-V 2015-16|Touring Trim|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| -|Honda|CR-V 2017-22|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| -|Honda|CR-V Hybrid 2017-19|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| -|Honda|e 2020|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| -|Honda|Fit 2018-20|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| -|Honda|Freed 2020|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| -|Honda|HR-V 2019-22|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| -|Honda|Insight 2019-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| -|Honda|Inspire 2018|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| -|Honda|Odyssey 2018-20|Honda Sensing|openpilot|25 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| -|Honda|Passport 2019-21|All|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| -|Honda|Pilot 2016-22|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| -|Honda|Ridgeline 2017-22|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| -|Hyundai|Elantra 2017-19|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai B| -|Hyundai|Elantra 2021-22|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| -|Hyundai|Elantra GT 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| -|Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| -|Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai J| -|Hyundai|i30 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| -|Hyundai|Ioniq 5 (with HDA II) 2022-23|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai Q| -|Hyundai|Ioniq 5 (without HDA II) 2022-23|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| -|Hyundai|Ioniq Electric 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| -|Hyundai|Ioniq Electric 2020|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| -|Hyundai|Ioniq Hybrid 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| -|Hyundai|Ioniq Hybrid 2020-22|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| -|Hyundai|Ioniq Plug-in Hybrid 2019|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| -|Hyundai|Ioniq Plug-in Hybrid 2020-21|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| -|Hyundai|Kona 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai B| -|Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai G| -|Hyundai|Kona Electric 2022|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai O| -|Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai I| -|Hyundai|Palisade 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| -|Hyundai|Santa Cruz 2021-22|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N| -|Hyundai|Santa Fe 2019-20|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai D| -|Hyundai|Santa Fe 2021-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| -|Hyundai|Santa Fe Hybrid 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| -|Hyundai|Santa Fe Plug-in Hybrid 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| -|Hyundai|Sonata 2018-19|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| -|Hyundai|Sonata 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A| -|Hyundai|Sonata Hybrid 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A| -|Hyundai|Tucson 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| -|Hyundai|Tucson Diesel 2019|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| -|Hyundai|Tucson Hybrid 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N| -|Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| -|Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| -|Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| -|Kia|Ceed 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| -|Kia|EV6 (with HDA II) 2022|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai P| -|Kia|EV6 (without HDA II) 2022|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| -|Kia|Forte 2019-21|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai G| -|Kia|K5 2021-22|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A| -|Kia|Niro EV 2019|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| -|Kia|Niro EV 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F| -|Kia|Niro EV 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| -|Kia|Niro EV 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| -|Kia|Niro Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F| -|Kia|Niro Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| -|Kia|Niro Plug-in Hybrid 2018-19|All|openpilot available[1](#footnotes)|10 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| -|Kia|Optima 2017|Advanced Smart Cruise Control|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai B| -|Kia|Optima 2019-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai G| -|Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A| -|Kia|Sorento 2018|Advanced Smart Cruise Control|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| -|Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| -|Kia|Sportage 2023|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N| -|Kia|Sportage Hybrid 2023|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N| -|Kia|Stinger 2018-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| -|Kia|Stinger 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| -|Kia|Telluride 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| -|Lexus|CT Hybrid 2017-18|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Lexus|ES 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Lexus|ES Hybrid 2017-18|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Lexus|ES Hybrid 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Lexus|IS 2017-19|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Lexus|NX 2018-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Lexus|NX 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Lexus|NX Hybrid 2018-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Lexus|NX Hybrid 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Lexus|RC 2017-20|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Lexus|RX 2016|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Lexus|RX 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Lexus|RX 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Lexus|RX Hybrid 2016|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Lexus|RX Hybrid 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Lexus|RX Hybrid 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Lexus|UX Hybrid 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Mazda|CX-5 2022-23|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Mazda| -|Mazda|CX-9 2021-22|All|Stock|0 mph|28 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Mazda| -|Nissan|Altima 2019-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Nissan B| -|Nissan|Leaf 2018-22|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Nissan A| -|Nissan|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Nissan A| -|Nissan|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Nissan A| -|Ram|1500 2019-22|Adaptive Cruise Control (ACC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Ram| -|SEAT|Ateca 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Subaru|Ascent 2019-21|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| -|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| -|Subaru|Crosstrek 2020-21|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| -|Subaru|Forester 2019-21|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| -|Subaru|Impreza 2017-19|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| -|Subaru|Impreza 2020-22|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| -|Subaru|Legacy 2020-22|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru B| -|Subaru|Outback 2020-22|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru B| -|Subaru|XV 2018-19|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| -|Subaru|XV 2020-21|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| -|Škoda|Kamiq 2021[6](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533[9](#footnotes)| -|Škoda|Karoq 2019-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Škoda|Kodiaq 2018-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Škoda|Octavia 2015, 2018-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Škoda|Octavia RS 2016|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Škoda|Scala 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533[9](#footnotes)| -|Škoda|Superb 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Avalon 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|Avalon 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|Avalon 2019-21|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|Avalon 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Avalon Hybrid 2019-21|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|Avalon Hybrid 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|C-HR 2017-21|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|C-HR Hybrid 2017-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|Camry 2018-20|All|Stock|0 mph[5](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|Camry 2021-22|All|openpilot|0 mph[5](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Camry Hybrid 2018-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|Camry Hybrid 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Corolla 2017-19|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|Corolla 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Corolla Cross (Non-US only) 2020-23|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Corolla Cross Hybrid (Non-US only) 2020-22|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Corolla Hatchback 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Corolla Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Highlander 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Highlander 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Highlander Hybrid 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Highlander Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Mirai 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Prius 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|Prius 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|Prius 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Prius Prime 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|Prius Prime 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Prius v 2017|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|RAV4 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|RAV4 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|RAV4 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|RAV4 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|RAV4 Hybrid 2017-18|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|RAV4 Hybrid 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|RAV4 Hybrid 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|Sienna 2018-20|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Volkswagen|Arteon 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Arteon eHybrid 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Arteon R 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Atlas Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|California 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Jetta 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Jetta GLI 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Passat 2015-22[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Polo 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533[9](#footnotes)| -|Volkswagen|Polo GTI 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533[9](#footnotes)| -|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533[9](#footnotes)| -|Volkswagen|T-Roc 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533[9](#footnotes)| -|Volkswagen|Taos 2022|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Tiguan 2019-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Touran 2017|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,8](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| +|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Harness|Video| +|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:| +|Acura|ILX 2016-19|AcuraWatch Plus|openpilot|25 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|| +|Acura|RDX 2016-18|AcuraWatch Plus|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|| +|Acura|RDX 2019-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|| +|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Audi|Q3 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Cadillac|Escalade 2017[3](#footnotes)|Driver Assist Package|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II|| +|Cadillac|Escalade ESV 2016[3](#footnotes)|Adaptive Cruise Control (ACC) & LKAS|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II|| +|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM|| +|Chevrolet|Bolt EV 2022-23|2LT Trim with Adaptive Cruise Control Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM|| +|Chevrolet|Silverado 1500 2020-21|Safety Package II|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM|| +|Chevrolet|Volt 2017-18[3](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II|| +|Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA|| +|Chrysler|Pacifica 2019-20|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA|| +|Chrysler|Pacifica 2021|All|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA|| +|Chrysler|Pacifica Hybrid 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA|| +|Chrysler|Pacifica Hybrid 2019-22|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA|| +|comma|body|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|None|| +|Genesis|G70 2018-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F|| +|Genesis|G70 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F|| +|Genesis|G80 2017|All|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai J|| +|Genesis|G80 2018-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H|| +|Genesis|G90 2017-18|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C|| +|Genesis|GV60 (Advanced Trim) 2023[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A|| +|Genesis|GV60 (Performance Trim) 2023[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K|| +|Genesis|GV70 2022-23[5](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L|| +|GMC|Acadia 2018[3](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II|| +|GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM|| +|Honda|Accord 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|| +|Honda|Accord Hybrid 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|| +|Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|| +|Honda|Civic 2019-21|All|openpilot available[1](#footnotes)|0 mph|2 mph[4](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|| +|Honda|Civic 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch B|| +|Honda|Civic Hatchback 2017-21|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|| +|Honda|Civic Hatchback 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch B|| +|Honda|CR-V 2015-16|Touring Trim|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|| +|Honda|CR-V 2017-22|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|| +|Honda|CR-V Hybrid 2017-19|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|| +|Honda|e 2020|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|| +|Honda|Fit 2018-20|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|| +|Honda|Freed 2020|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|| +|Honda|HR-V 2019-22|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|| +|Honda|Insight 2019-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|| +|Honda|Inspire 2018|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|| +|Honda|Odyssey 2018-20|Honda Sensing|openpilot|25 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|| +|Honda|Passport 2019-21|All|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|| +|Honda|Pilot 2016-22|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|| +|Honda|Ridgeline 2017-22|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|| +|Hyundai|Elantra 2017-19|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai B|| +|Hyundai|Elantra 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K|| +|Hyundai|Elantra GT 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E|| +|Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K|| +|Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai J|| +|Hyundai|i30 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E|| +|Hyundai|Ioniq 5 (Southeast Asia only) 2022-23[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai Q|| +|Hyundai|Ioniq 5 (with HDA II) 2022-23[5](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai Q|| +|Hyundai|Ioniq 5 (without HDA II) 2022-23[5](#footnotes)|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K|| +|Hyundai|Ioniq Electric 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C|| +|Hyundai|Ioniq Electric 2020|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H|| +|Hyundai|Ioniq Hybrid 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C|| +|Hyundai|Ioniq Hybrid 2020-22|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H|| +|Hyundai|Ioniq Plug-in Hybrid 2019|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C|| +|Hyundai|Ioniq Plug-in Hybrid 2020-21|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H|| +|Hyundai|Kona 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai B|| +|Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai G|| +|Hyundai|Kona Electric 2022|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai O|| +|Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai I|| +|Hyundai|Palisade 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H|| +|Hyundai|Santa Cruz 2021-22[5](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N|| +|Hyundai|Santa Fe 2019-20|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai D|| +|Hyundai|Santa Fe 2021-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L|| +|Hyundai|Santa Fe Hybrid 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L|| +|Hyundai|Santa Fe Plug-in Hybrid 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L|| +|Hyundai|Sonata 2018-19|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E|| +|Hyundai|Sonata 2020-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A|| +|Hyundai|Sonata Hybrid 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A|| +|Hyundai|Tucson 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L|| +|Hyundai|Tucson 2022[5](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N|| +|Hyundai|Tucson 2023[5](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N|| +|Hyundai|Tucson Diesel 2019|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L|| +|Hyundai|Tucson Hybrid 2022[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N|| +|Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E|| +|Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA|| +|Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA|| +|Kia|Ceed 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E|| +|Kia|EV6 (Southeast Asia only) 2022-23[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai P|| +|Kia|EV6 (with HDA II) 2022[5](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai P|| +|Kia|EV6 (without HDA II) 2022[5](#footnotes)|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L|| +|Kia|Forte 2019-21|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai G|| +|Kia|K5 2021-22|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A|| +|Kia|K5 Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A|| +|Kia|Niro EV 2019|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H|| +|Kia|Niro EV 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F|| +|Kia|Niro EV 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C|| +|Kia|Niro EV 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H|| +|Kia|Niro Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F|| +|Kia|Niro Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H|| +|Kia|Niro Hybrid 2023[5](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A|| +|Kia|Niro Plug-in Hybrid 2018-19|All|openpilot available[1](#footnotes)|10 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C|| +|Kia|Niro Plug-in Hybrid 2020|All|openpilot available[1](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai D|| +|Kia|Optima 2017|Advanced Smart Cruise Control|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai B|| +|Kia|Optima 2019-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai G|| +|Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A|| +|Kia|Sorento 2018|Advanced Smart Cruise Control|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C|| +|Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E|| +|Kia|Sorento 2022-23[5](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K|| +|Kia|Sorento Plug-in Hybrid 2022-23[5](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A|| +|Kia|Sportage 2023[5](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N|| +|Kia|Sportage Hybrid 2023[5](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N|| +|Kia|Stinger 2018-20|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C|| +|Kia|Stinger 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K|| +|Kia|Telluride 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H|| +|Lexus|CT Hybrid 2017-18|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Lexus|ES 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Lexus|ES Hybrid 2017-18|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Lexus|ES Hybrid 2019-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Lexus|IS 2017-19|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Lexus|NX 2018-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Lexus|NX 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Lexus|NX Hybrid 2018-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Lexus|NX Hybrid 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Lexus|RC 2017-20|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Lexus|RX 2016|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Lexus|RX 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Lexus|RX 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Lexus|RX Hybrid 2016|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Lexus|RX Hybrid 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Lexus|RX Hybrid 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Lexus|UX Hybrid 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|MAN|eTGE 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|MAN|TGE 2017-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Mazda|CX-5 2022-23|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Mazda|| +|Mazda|CX-9 2021-23|All|Stock|0 mph|28 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Mazda|| +|Nissan|Altima 2019-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Nissan B|| +|Nissan|Leaf 2018-22|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Nissan A|| +|Nissan|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Nissan A|| +|Nissan|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Nissan A|| +|Ram|1500 2019-22|Adaptive Cruise Control (ACC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Ram|| +|SEAT|Ateca 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Subaru|Ascent 2019-21|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A|| +|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A|| +|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A|| +|Subaru|Forester 2019-21|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A|| +|Subaru|Impreza 2017-19|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A|| +|Subaru|Impreza 2020-22|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A|| +|Subaru|Legacy 2020-22|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru B|| +|Subaru|Outback 2020-22|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru B|| +|Subaru|XV 2018-19|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A|| +|Subaru|XV 2020-21|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A|| +|Škoda|Kamiq 2021[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533[10](#footnotes)|| +|Škoda|Karoq 2019-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Škoda|Kodiaq 2018-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Škoda|Octavia 2015, 2018-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Škoda|Octavia RS 2016|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Škoda|Scala 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533[10](#footnotes)|| +|Škoda|Superb 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|Avalon 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|Avalon 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|Avalon 2019-21|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|Avalon 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|Avalon Hybrid 2019-21|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|Avalon Hybrid 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|C-HR 2017-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|C-HR 2021|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|C-HR Hybrid 2017-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|C-HR Hybrid 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|Camry 2018-20|All|Stock|0 mph[6](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|Camry 2021-22|All|openpilot|0 mph[6](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|Camry Hybrid 2018-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|Camry Hybrid 2021-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|Corolla 2017-19|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|Corolla 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|Corolla Cross (Non-US only) 2020-23|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|Corolla Cross Hybrid (Non-US only) 2020-22|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|Corolla Hatchback 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|Corolla Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|Corolla Hybrid (Non-US only) 2020-23|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|Highlander 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|Highlander 2020-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|Highlander Hybrid 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|Highlander Hybrid 2020-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|Mirai 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|Prius 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|Prius 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|Prius 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|Prius Prime 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|Prius Prime 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|Prius v 2017|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|RAV4 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|RAV4 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|RAV4 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|RAV4 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|RAV4 Hybrid 2017-18|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|RAV4 Hybrid 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|RAV4 Hybrid 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|Sienna 2018-20|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Volkswagen|Arteon 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Arteon eHybrid 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Arteon R 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Atlas Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|California 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Crafter 2017-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|e-Crafter 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Grand California 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Jetta 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Jetta GLI 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Passat 2015-22[8](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Polo 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533[10](#footnotes)|| +|Volkswagen|Polo GTI 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533[10](#footnotes)|| +|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533[10](#footnotes)|| +|Volkswagen|T-Roc 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533[10](#footnotes)|| +|Volkswagen|Taos 2022|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Tiguan 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Touran 2017|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| -1Experimental openpilot longitudinal control is available behind a toggle; the toggle is only available in non-release branches such as `master-ci`. Using openpilot longitudinal may disable Automatic Emergency Braking (AEB).
-2When the Driver Support Unit (DSU) is disconnected, openpilot Adaptive Cruise Control (ACC) will replace stock Adaptive Cruise Control (ACC). NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).
-3Requires a community built ASCM harness. NOTE: disconnecting the ASCM disables Automatic Emergency Braking (AEB).
+1Experimental openpilot longitudinal control is available behind a toggle; the toggle is only available in non-release branches such as `devel` or `master-ci`.
+2By default, this car will use the stock Adaptive Cruise Control (ACC) for longitudinal control. If the Driver Support Unit (DSU) is disconnected, openpilot ACC will replace stock ACC. NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).
+3Requires a community built ASCM harness. NOTE: disconnecting the ASCM disables Automatic Emergency Braking (AEB).
42019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph.
-5openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.
-6Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform.
-7Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets.
-8Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness are limited to using stock ACC.
-9Model-years 2022 and beyond may have a combined CAN gateway and BCM, which is supported by openpilot in software, but doesn't yet have a harness available from the comma store.
+5Requires a red panda for this CAN FD car. All the hardware needed is sold in the CAN FD kit.
+6openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.
+7Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform.
+8Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets.
+9Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness are limited to using stock ACC.
+10Model-years 2022 and beyond may have a combined CAN gateway and BCM, which is supported by openpilot in software, but doesn't yet have a harness available from the comma store.
## Community Maintained Cars Although they're not upstream, the community has openpilot running on other makes and models. See the 'Community Supported Models' section of each make [on our wiki](https://wiki.comma.ai/). diff --git a/docs/INTEGRATION.md b/docs/INTEGRATION.md index 97b72e39d..ba6291c1e 100644 --- a/docs/INTEGRATION.md +++ b/docs/INTEGRATION.md @@ -8,4 +8,4 @@ Additionally, on specific supported cars (see ACC column in [supported cars](CAR * Stock ACC is replaced by openpilot ACC. * openpilot FCW operates in addition to stock FCW. -openpilot should preserve all other vehicle's stock features, including, but are not limited to: FCW, Automatic Emergency Braking (AEB), auto high-beam, blind spot warning, and side collision warning. +openpilot should preserve all other vehicle's stock features, including, but not limited to: FCW, Automatic Emergency Braking (AEB), auto high-beam, blind spot warning, and side collision warning. diff --git a/laika/astro_dog.py b/laika/astro_dog.py index ea32e350d..348c3959a 100644 --- a/laika/astro_dog.py +++ b/laika/astro_dog.py @@ -48,13 +48,13 @@ class AstroDog: self.valid_ephem_types = valid_ephem_types self.orbit_fetched_times = TimeRangeHolder() - self.nav_fetched_times = TimeRangeHolder() + self.navs_fetched_times = TimeRangeHolder() self.dcbs_fetched_times = TimeRangeHolder() self.dgps_delays = [] self.ionex_maps: List[IonexMap] = [] self.orbits: DefaultDict[str, List[PolyEphemeris]] = defaultdict(list) - self.nav: DefaultDict[str, List[Union[GPSEphemeris, GLONASSEphemeris]]] = defaultdict(list) + self.navs: DefaultDict[str, List[Union[GPSEphemeris, GLONASSEphemeris]]] = defaultdict(list) self.dcbs: DefaultDict[str, List[DCB]] = defaultdict(list) self.cached_ionex: Optional[IonexMap] = None @@ -73,8 +73,8 @@ class AstroDog: return ionex def get_nav(self, prn, time): - skip_download = time in self.nav_fetched_times - nav = self._get_latest_valid_data(self.nav[prn], self.cached_nav[prn], self.get_nav_data, time, skip_download) + skip_download = time in self.navs_fetched_times + nav = self._get_latest_valid_data(self.navs[prn], self.cached_nav[prn], self.get_nav_data, time, skip_download) if nav is not None: self.cached_nav[prn] = nav return nav @@ -97,9 +97,9 @@ class AstroDog: return result def get_navs(self, time): - if time not in self.nav_fetched_times and self.auto_update: + if time not in self.navs_fetched_times: self.get_nav_data(time) - return AstroDog._select_valid_temporal_items(self.nav, time, self.cached_nav) + return AstroDog._select_valid_temporal_items(self.navs, time, self.cached_nav) def get_orbit(self, prn: str, time: GPSTime): skip_download = time in self.orbit_fetched_times @@ -133,7 +133,7 @@ class AstroDog: self._add_ephems(new_ephems, self.orbits, self.orbit_fetched_times) def add_navs(self, new_ephems: Dict[str, List[Ephemeris]]): - self._add_ephems(new_ephems, self.nav, self.nav_fetched_times) + self._add_ephems(new_ephems, self.navs, self.navs_fetched_times) def _add_ephems(self, new_ephems: Dict[str, List[Ephemeris]], ephems_dict, fetched_times): for k, v in new_ephems.items(): @@ -172,7 +172,7 @@ class AstroDog: if sum([len(v) for v in fetched_ephems.values()]) == 0: begin_day = GPSTime(time.week, SECS_IN_DAY * (time.tow // SECS_IN_DAY)) end_day = GPSTime(time.week, SECS_IN_DAY * (1 + (time.tow // SECS_IN_DAY))) - self.nav_fetched_times.add(begin_day, end_day) + self.navs_fetched_times.add(begin_day, end_day) def download_parse_orbit(self, gps_time: GPSTime, skip_before_epoch=None) -> Dict[str, List[PolyEphemeris]]: # Download multiple days to be able to polyfit at the start-end of the day @@ -265,7 +265,6 @@ class AstroDog: eph = self.get_orbit(prn, time) if not eph and self.pull_nav: eph = self.get_nav(prn, time) - if eph: return eph.get_sat_info(time) return None diff --git a/laika/dgps.py b/laika/dgps.py index 54c13d8cf..53111d90d 100644 --- a/laika/dgps.py +++ b/laika/dgps.py @@ -5,6 +5,7 @@ from datetime import datetime from .gps_time import GPSTime from .constants import SECS_IN_YEAR from . import raw_gnss as raw +from . import opt from .rinex_file import RINEXFile from .downloader import download_cors_coords from .helpers import get_constellation @@ -107,8 +108,9 @@ def parse_dgps(station_id, station_obs_file_path, dog, max_distance=100000, requ station_delays[signal] = {} for i, proc_measurement in enumerate(proc_measurements): times.append(proc_measurement[0].recv_time) - Fx_pos = raw.pr_residual(proc_measurement, signal=signal) - residual = -np.array(Fx_pos(list(station_pos) + [0, 0])) + Fx_pos = opt.pr_residual(proc_measurement, signal=signal) + residual, _ = Fx_pos(list(station_pos) + [0,0]) + residual = -np.array(residual) for j, m in enumerate(proc_measurement): prn = m.prn if prn not in station_delays[signal]: diff --git a/laika/downloader.py b/laika/downloader.py index 1658e7f7d..bd456af20 100644 --- a/laika/downloader.py +++ b/laika/downloader.py @@ -7,18 +7,18 @@ import urllib.error import pycurl import re import time -import tempfile import socket from datetime import datetime, timedelta from urllib.parse import urlparse from io import BytesIO +from ftplib import FTP_TLS from atomicwrites import atomic_write from laika.ephemeris import EphemerisType from .constants import SECS_IN_HR, SECS_IN_DAY, SECS_IN_WEEK -from .gps_time import GPSTime +from .gps_time import GPSTime, tow_to_datetime from .helpers import ConstellationId dir_path = os.path.dirname(os.path.realpath(__file__)) @@ -177,24 +177,6 @@ def http_download_files(url_base, folder_path, cacheDir, filenames): def https_download_file(url): - if 'nasa.gov/' not in url: - netrc_path = None - f = None - elif os.path.isfile(dir_path + '/.netrc'): - netrc_path = dir_path + '/.netrc' - f = None - else: - try: - username = os.environ['NASA_USERNAME'] - password = os.environ['NASA_PASSWORD'] - f = tempfile.NamedTemporaryFile() - netrc = f"machine urs.earthdata.nasa.gov login {username} password {password}" - f.write(netrc.encode()) - f.flush() - netrc_path = f.name - except KeyError: - raise DownloadFailed('Could not find .netrc file and no NASA_USERNAME and NASA_PASSWORD in environment for urs.earthdata.nasa.gov authentication') - crl = pycurl.Curl() crl.setopt(crl.CAINFO, certifi.where()) crl.setopt(crl.URL, url) @@ -202,17 +184,12 @@ def https_download_file(url): crl.setopt(crl.SSL_CIPHER_LIST, 'DEFAULT@SECLEVEL=1') crl.setopt(crl.COOKIEJAR, '/tmp/cddis_cookies') crl.setopt(pycurl.CONNECTTIMEOUT, 10) - if netrc_path is not None: - crl.setopt(crl.NETRC_FILE, netrc_path) - crl.setopt(crl.NETRC, 2) - + buf = BytesIO() crl.setopt(crl.WRITEDATA, buf) crl.perform() response = crl.getinfo(pycurl.RESPONSE_CODE) crl.close() - if f is not None: - f.close() if response != 200: raise DownloadFailed('HTTPS error ' + str(response)) @@ -228,6 +205,17 @@ def ftp_download_file(url): except urllib.error.URLError as e: raise DownloadFailed(e) +def ftps_download_file(url): + parsed = urlparse(url) + try: + buf = BytesIO() + with FTP_TLS(parsed.hostname) as ftps: + ftps.login(user='anonymous') + ftps.prot_p() + ftps.retrbinary('RETR ' + parsed.path, buf.write) + return buf.getvalue() + except ftplib.all_errors as e: + raise DownloadFailed(e) @retryable def download_files(url_base, folder_path, cacheDir, filenames): @@ -242,11 +230,13 @@ def download_files(url_base, folder_path, cacheDir, filenames): def download_file(url_base, folder_path, filename_zipped): url = url_base + folder_path + filename_zipped print('Downloading ' + url) - if url.startswith('https'): + if url.startswith('https://'): return https_download_file(url) - if url.startswith('ftp'): + elif url.startswith('ftp://'): return ftp_download_file(url) - raise NotImplementedError('Did find ftp or https preamble') + elif url.startswith('sftp://'): + return ftps_download_file(url) + raise NotImplementedError('Did not find supported url scheme') def download_and_cache_file_return_first_success(url_bases, folder_and_file_names, cache_dir, compression='', overwrite=False, raise_error=False): @@ -308,26 +298,51 @@ def download_nav(time: GPSTime, cache_dir, constellation: ConstellationId): if GPSTime.from_datetime(datetime.utcnow()) - time > SECS_IN_DAY: url_bases = ( 'https://github.com/commaai/gnss-data/raw/master/gnss/data/daily/', - 'https://cddis.nasa.gov/archive/gnss/data/daily/', + 'sftp://gdc.cddis.eosdis.nasa.gov/gnss/data/daily/', ) filename = t.strftime(f"brdc%j0.%y{c}") folder_path = t.strftime(f'%Y/%j/%y{c}/') compression = '.gz' if folder_path >= '2020/335/' else '.Z' return download_and_cache_file(url_bases, folder_path, cache_dir+'daily_nav/', filename, compression) - elif constellation == ConstellationId.GPS: - url_base = 'https://cddis.nasa.gov/archive/gnss/data/hourly/' - filename = t.strftime(f"hour%j0.%y{c}") - folder_path = t.strftime('%Y/%j/') - compression = '.gz' if folder_path >= '2020/336/' else '.Z' - return download_and_cache_file(url_base, folder_path, cache_dir+'hourly_nav/', filename, compression) + else: + url_bases = ( + 'https://github.com/commaai/gnss-data-hourly/raw/master/', + 'sftp://gdc.cddis.eosdis.nasa.gov/gnss/data/hourly/', + ) + times = [t, (t - timedelta(hours=1))] + folder_and_filenames = [(t.strftime('%Y/%j/'), t.strftime(f"hour%j0.%y{c}")) for t in times] + compression = '.gz' if folder_and_filenames[0][0] >= '2020/336/' else '.Z' + # always overwrite as this file is appended + return download_and_cache_file_return_first_success(url_bases, + folder_and_filenames, cache_dir+'hourly_nav/', compression, overwrite=True) except DownloadFailed: pass +def download_orbits_gps_cod0(time, cache_dir, ephem_types): + url_bases = ( + 'https://github.com/commaai/gnss-data/raw/master/gnss/products/', + 'sftp://gdc.cddis.eosdis.nasa.gov/gnss/products/', + ) + + if EphemerisType.ULTRA_RAPID_ORBIT not in ephem_types: + # TODO: raise error here + return None + + tm = tow_to_datetime(time.tow, time.week).timetuple() + doy = str(tm.tm_yday).zfill(3) + filename = f"COD0OPSULT_{tm.tm_year}{doy}0000_02D_05M_ORB.SP3" + # TODO: add hour management + + folder_path = "%i/" % time.week + folder_file_names = [(folder_path, filename)] + return download_and_cache_file_return_first_success(url_bases, folder_file_names, cache_dir+'cddis_products/', compression='.gz') + + def download_orbits_gps(time, cache_dir, ephem_types): url_bases = ( 'https://github.com/commaai/gnss-data/raw/master/gnss/products/', - 'https://cddis.nasa.gov/archive/gnss/products/', + 'sftp://gdc.cddis.eosdis.nasa.gov/gnss/products/', 'ftp://igs.ign.fr/pub/igs/products/', ) folder_path = "%i/" % time.week @@ -344,7 +359,12 @@ def download_orbits_gps(time, cache_dir, ephem_types): f"igu{time_str}_06.sp3", f"igu{time_str}_00.sp3"]) folder_file_names = [(folder_path, filename) for filename in filenames] - return download_and_cache_file_return_first_success(url_bases, folder_file_names, cache_dir+'cddis_products/', compression='.Z') + ret = download_and_cache_file_return_first_success(url_bases, folder_file_names, cache_dir+'cddis_products/', compression='.Z') + if ret is not None: + return ret + + # fallback to COD0 Ultra Rapid Orbits + return download_orbits_gps_cod0(time, cache_dir, ephem_types) def download_prediction_orbits_russia_src(gps_time, cache_dir): @@ -406,7 +426,7 @@ def download_ionex(time, cache_dir): t = time.as_datetime() url_bases = ( 'https://github.com/commaai/gnss-data/raw/master/gnss/products/ionex/', - 'https://cddis.nasa.gov/archive/gnss/products/ionex/', + 'sftp://gdc.cddis.eosdis.nasa.gov/gnss/products/ionex/', 'ftp://igs.ensg.ign.fr/pub/igs/products/ionosphere/', 'ftp://gssc.esa.int/gnss/products/ionex/', ) @@ -422,7 +442,7 @@ def download_dcb(time, cache_dir): folder_paths = [] url_bases = ( 'https://github.com/commaai/gnss-data/raw/master/gnss/products/bias/', - 'https://cddis.nasa.gov/archive/gnss/products/bias/', + 'sftp://gdc.cddis.eosdis.nasa.gov/gnss/products/bias/', 'ftp://igs.ign.fr/pub/igs/products/mgex/dcb/', ) # seem to be a lot of data missing, so try many days diff --git a/laika/ephemeris.py b/laika/ephemeris.py index 12f9700f1..059fc931c 100644 --- a/laika/ephemeris.py +++ b/laika/ephemeris.py @@ -7,7 +7,7 @@ from typing import Dict, List, Optional import numpy as np import numpy.polynomial.polynomial as poly -from datetime import datetime +from datetime import datetime, timedelta from math import sin, cos, sqrt, fabs, atan2 from .gps_time import GPSTime, utc_to_gpst @@ -24,7 +24,7 @@ def read4(f, rinex_ver): return float(line[4:23]), float(line[23:42]), float(line[42:61]), float(line[61:80]) -def convert_ublox_ephem(ublox_ephem, current_time: Optional[datetime] = None): +def convert_ublox_gps_ephem(ublox_ephem, current_time: Optional[datetime] = None): # Week time of ephemeris gps msg has a roll-over period of 10 bits (19.6 years) # The latest roll-over was on 2019-04-07 week = ublox_ephem.gpsWeek @@ -38,6 +38,12 @@ def convert_ublox_ephem(ublox_ephem, current_time: Optional[datetime] = None): roll_overs = GPSTime.from_datetime(current_time).week // 1024 week += (roll_overs - (week // 1024)) * 1024 + # GPS week refers to current week, the ephemeris can be valid for the next + # if toe equals 0, this can be verified by the TOW count if it is within the + # last 2 hours of the week (gps ephemeris valid for 4hours) + if ublox_ephem.toe == 0 and ublox_ephem.towCount*6 >= (SECS_IN_WEEK - 2*SECS_IN_HR): + week += 1 + ephem = {} ephem['sv_id'] = ublox_ephem.svId ephem['toe'] = GPSTime(week, ublox_ephem.toe) @@ -65,10 +71,48 @@ def convert_ublox_ephem(ublox_ephem, current_time: Optional[datetime] = None): ephem['omegadot'] = ublox_ephem.omegaDot ephem['omega0'] = ublox_ephem.omega0 + ephem['healthy'] = ublox_ephem.svHealth == 0.0 + epoch = ephem['toe'] return GPSEphemeris(ephem, epoch) +def convert_ublox_glonass_ephem(ublox_ephem, current_time: Optional[datetime] = None): + ephem = {} + ephem['prn'] = 'R%02i' % ublox_ephem.svId + + etime = datetime.strptime(f"{ublox_ephem.year}-{ublox_ephem.dayInYear}", "%Y-%j") + # glonass time: UTC + 3h + time_in_day = timedelta(hours=ublox_ephem.hour, minutes=ublox_ephem.minute, seconds=ublox_ephem.second) + ephem['toc'] = GPSTime.from_datetime(etime + time_in_day - timedelta(hours=3)) + ephem['toe'] = GPSTime.from_datetime(etime + timedelta(minutes=(ublox_ephem.tb*15 - 180))) + + ephem['x'] = ublox_ephem.x # km + ephem['x_vel'] = ublox_ephem.xVel # km/s + ephem['x_acc'] = ublox_ephem.xAccel # km/s*s + + ephem['y'] = ublox_ephem.y # km + ephem['y_vel'] = ublox_ephem.yVel # km/s + ephem['y_acc'] = ublox_ephem.yAccel # km/s*s + + ephem['z'] = ublox_ephem.z # km + ephem['z_vel'] = ublox_ephem.zVel # km/s + ephem['z_acc'] = ublox_ephem.zAccel # km/s*s + + ephem['healthy'] = ublox_ephem.svHealth == 0.0 + ephem['age'] = ublox_ephem.age # age of information [days] + + # tauN compared to ephemeris from gdc.cddis.eosdis.nasa.gov is times -1 + ephem['min_tauN'] = ublox_ephem.tauN * (-1) # time correction relative to GLONASS tc + ephem['GammaN'] = ublox_ephem.gammaN + + # TODO: channel is in string 7, which is not parsed + ephem['freq_num'] = "1" + + # NOTE: ublox_ephem.tk is in a different format than rinex tk + return GLONASSEphemeris(ephem, ephem['toe']) + + class EphemerisType(IntEnum): # Matches the enum in log.capnp NAV = 0 @@ -87,7 +131,7 @@ class EphemerisType(IntEnum): return EphemerisType.FINAL_ORBIT if "/rapid" in file_name or "/igr" in file_name: return EphemerisType.RAPID_ORBIT - if "/ultra" in file_name or "/igu" in file_name: + if "/ultra" in file_name or "/igu" in file_name or "COD0OPSULT" in file_name: return EphemerisType.ULTRA_RAPID_ORBIT raise RuntimeError(f"Ephemeris type not found in filename: {file_name}") @@ -158,8 +202,8 @@ class EphemerisSerializer(json.JSONEncoder): class GLONASSEphemeris(Ephemeris): - def __init__(self, data, epoch, healthy=True, file_name=None): - super().__init__(data['prn'], data, epoch, EphemerisType.NAV, healthy, max_time_diff=25*SECS_IN_MIN, file_name=file_name) + def __init__(self, data, epoch, file_name=None): + super().__init__(data['prn'], data, epoch, EphemerisType.NAV, data['healthy'], max_time_diff=25*SECS_IN_MIN, file_name=file_name) self.channel = data['freq_num'] self.to_json() @@ -168,9 +212,7 @@ class GLONASSEphemeris(Ephemeris): # http://gauss.gge.unb.ca/GLONASS.ICD.pdf eph = self.data - # TODO should handle leap seconds better - toc_gps_time = utc_to_gpst(eph['toc']) - tdiff = time - toc_gps_time + tdiff = time - utc_to_gpst(eph['toe']) # Clock correction (except for general relativity which is applied later) clock_err = eph['min_tauN'] + tdiff * (eph['GammaN']) @@ -227,8 +269,9 @@ class GLONASSEphemeris(Ephemeris): class PolyEphemeris(Ephemeris): def __init__(self, prn: str, data, epoch: GPSTime, ephem_type: EphemerisType, - file_epoch: GPSTime=None, file_name: str=None, healthy=True, tgd=0): - super().__init__(prn, data, epoch, ephem_type, healthy, max_time_diff=SECS_IN_HR, file_epoch=file_epoch, file_name=file_name) + file_epoch: GPSTime=None, file_name: str=None, healthy=True, tgd=0, + max_time_diff: int=SECS_IN_HR): + super().__init__(prn, data, epoch, ephem_type, healthy, max_time_diff=max_time_diff, file_epoch=file_epoch, file_name=file_name) self.tgd = tgd self.to_json() @@ -247,8 +290,8 @@ class PolyEphemeris(Ephemeris): class GPSEphemeris(Ephemeris): - def __init__(self, data, epoch, healthy=True, file_name=None): - super().__init__('G%02i' % data['sv_id'], data, epoch, EphemerisType.NAV, healthy, max_time_diff=2*SECS_IN_HR, file_name=file_name) + def __init__(self, data, epoch, file_name=None): + super().__init__('G%02i' % data['sv_id'], data, epoch, EphemerisType.NAV, data['healthy'], max_time_diff=2*SECS_IN_HR, file_name=file_name) self.max_time_diff_tgd = SECS_IN_DAY self.to_json() @@ -403,7 +446,7 @@ def read_prn_data(data, prn, deg=16, deg_t=1): measurements = np_data_prn[i:i + deg + 1, 1:5] times = (measurements[:, 0] - epoch).astype(float) - if (np.diff(times) != 900).any(): + if not (np.diff(times) != 900).any() and not (np.diff(times) != 300).any(): continue poly_data = {} @@ -516,7 +559,7 @@ def parse_rinex_nav_msg_glonass(file_name): line = line.replace('D', 'E') # Handle bizarro float format e = {'epoch': epoch, 'prn': prn} - e['toc'] = epoch + e['toe'] = epoch e['min_tauN'] = float(line[23:42]) e['GammaN'] = float(line[42:61]) e['tk'] = float(line[61:80]) @@ -526,7 +569,6 @@ def parse_rinex_nav_msg_glonass(file_name): e['z'], e['z_vel'], e['z_acc'], e['age'] = read4(f, rinex_ver) e['healthy'] = (e['health'] == 0.0) - ephems[prn].append(GLONASSEphemeris(e, epoch, file_name=file_name)) f.close() return ephems @@ -553,4 +595,4 @@ def parse_qcom_ephem(qcom_poly, current_week): poly_data['clock'] = [1e-3*data.other[3], 1e-3*data.other[2], 1e-3*data.other[1], 1e-3*data.other[0]] poly_data['deg'] = 3 poly_data['deg_t'] = 3 - return PolyEphemeris(prn, poly_data, epoch, ephem_type=EphemerisType.QCOM_POLY) + return PolyEphemeris(prn, poly_data, epoch, ephem_type=EphemerisType.QCOM_POLY, max_time_diff=180) diff --git a/laika/gps_time.py b/laika/gps_time.py index 3f0b1f91c..b9492f52d 100644 --- a/laika/gps_time.py +++ b/laika/gps_time.py @@ -62,8 +62,7 @@ def get_leap_seconds(time): return 15 elif time <= GPSTime.from_datetime(datetime.datetime(2015, 7, 1)): return 16 - # TODO is this correct? - elif time <= GPSTime.from_datetime(datetime.datetime(2017, 7, 1)): + elif time <= GPSTime.from_datetime(datetime.datetime(2017, 1, 1)): return 17 else: return 18 diff --git a/laika/opt.py b/laika/opt.py new file mode 100644 index 000000000..1a76f353c --- /dev/null +++ b/laika/opt.py @@ -0,0 +1,191 @@ +import sympy +import numpy as np +from typing import List + +from .constants import EARTH_ROTATION_RATE, SPEED_OF_LIGHT +from .helpers import ConstellationId +from .raw_gnss import GNSSMeasurement + + +def gauss_newton(fun, b, xtol=1e-8, max_n=25): + for _ in range(max_n): + # Compute function and jacobian on current estimate + r, J = fun(b) + + # Update estimate + delta = np.linalg.pinv(J) @ r + b -= delta + + # Check step size for stopping condition + if np.linalg.norm(delta) < xtol: + break + return b + + +def calc_pos_fix(measurements, posfix_functions=None, x0=None, no_weight=False, signal='C1C', min_measurements=6): + ''' + Calculates gps fix using gauss newton method + To solve the problem a minimal of 4 measurements are required. + If Glonass is included 5 are required to solve for the additional free variable. + returns: + 0 -> list with positions + 1 -> pseudorange errs + ''' + if x0 is None: + x0 = [0, 0, 0, 0, 0] + + if len(measurements) < min_measurements: + return [],[] + + Fx_pos = pr_residual(measurements, posfix_functions, signal=signal, no_weight=no_weight, no_nans=True) + x = gauss_newton(Fx_pos, x0) + residual, _ = Fx_pos(x, no_weight=True) + return x.tolist(), residual.tolist() + + +def calc_vel_fix(measurements, est_pos, velfix_function=None, v0=None, no_weight=False, signal='D1C', min_measurements=6): + ''' + Calculates gps velocity fix using gauss newton method + returns: + 0 -> list with velocities + 1 -> pseudorange_rate errs + ''' + if v0 is None: + v0 = [0, 0, 0, 0] + + if len(measurements) < min_measurements: + return [], [] + + Fx_vel = prr_residual(measurements, est_pos, velfix_function, signal=signal, no_weight=no_weight, no_nans=True) + v = gauss_newton(Fx_vel, v0) + residual, _ = Fx_vel(v, no_weight=True) + return v.tolist(), residual.tolist() + + +def get_posfix_sympy_fun(constellation): + # Unknowns + x, y, z = sympy.Symbol('x'), sympy.Symbol('y'), sympy.Symbol('z') + bc = sympy.Symbol('bc') + bg = sympy.Symbol('bg') + zero_theta = sympy.Symbol('zero_theta') + var = [x, y, z, bc, bg] + + # Knowns + pr = sympy.Symbol('pr') + sat_x, sat_y, sat_z = sympy.Symbol('sat_x'), sympy.Symbol('sat_y'), sympy.Symbol('sat_z') + weight = sympy.Symbol('weight') + + theta = (EARTH_ROTATION_RATE * (pr - bc) / SPEED_OF_LIGHT)*zero_theta + val = sympy.sqrt( + (sat_x * sympy.cos(theta) + sat_y * sympy.sin(theta) - x) ** 2 + + (sat_y * sympy.cos(theta) - sat_x * sympy.sin(theta) - y) ** 2 + + (sat_z - z) ** 2 + ) + + if constellation == ConstellationId.GLONASS: + res = weight * (val - (pr - bc - bg)) + elif constellation == ConstellationId.GPS: + res = weight * (val - (pr - bc)) + else: + raise NotImplementedError(f"Constellation {constellation} not supported") + + res = [res] + [sympy.diff(res, v) for v in var] + + return sympy.lambdify([x, y, z, bc, bg, pr, zero_theta, sat_x, sat_y, sat_z, weight], res, modules=["numpy"]) + + +def get_velfix_sympy_func(): + # implementing this without sympy.Matrix gives a 2x speedup at generation + + # knowns, receiver position, satellite position, satellite velocity + ep_x, ep_y, ep_z = sympy.Symbol('ep_x'), sympy.Symbol('ep_y'), sympy.Symbol('ep_z') + est_pos = np.array([ep_x, ep_y, ep_z]) + sp_x, sp_y, sp_z = sympy.Symbol('sp_x'), sympy.Symbol('sp_y'), sympy.Symbol('sp_z') + sat_pos = np.array([sp_x, sp_y, sp_z]) + sv_x, sv_y, sv_z = sympy.Symbol('sv_x'), sympy.Symbol('sv_y'), sympy.Symbol('sv_z') + sat_vel = np.array([sv_x, sv_y, sv_z]) + observables = sympy.Symbol('observables') + weight = sympy.Symbol('weight') + + # unknown, receiver velocity + v_x, v_y, v_z = sympy.Symbol('v_x'), sympy.Symbol('v_y'), sympy.Symbol('v_z') + vel = np.array([v_x, v_y, v_z]) + vel_o = sympy.Symbol('vel_o') + + loss = sat_pos - est_pos + loss /= sympy.sqrt(loss.dot(loss)) + + nv = loss.dot(sat_vel - vel) + ov = (observables - vel_o) + res = (nv - ov)*weight + + res = [res] + [sympy.diff(res, v) for v in [v_x, v_y, v_z, vel_o]] + + return sympy.lambdify([ + ep_x, ep_y, ep_z, sp_x, sp_y, sp_z, + sv_x, sv_y, sv_z, observables, weight, + v_x, v_y, v_z, vel_o + ], + res, modules=["numpy"]) + + +def pr_residual(measurements: List[GNSSMeasurement], posfix_functions=None, signal='C1C', no_weight=False, no_nans=False): + + if posfix_functions is None: + posfix_functions = {constellation: get_posfix_sympy_fun(constellation) for constellation in (ConstellationId.GPS, ConstellationId.GLONASS)} + + def Fx_pos(inp, no_weight=no_weight): + vals, gradients = [], [] + + for meas in measurements: + if signal in meas.observables_final and np.isfinite(meas.observables_final[signal]): + pr = meas.observables_final[signal] + sat_pos = meas.sat_pos_final + zero_theta = 0 + elif signal in meas.observables and np.isfinite(meas.observables[signal]) and meas.processed: + pr = meas.observables[signal] + pr += meas.sat_clock_err * SPEED_OF_LIGHT + sat_pos = meas.sat_pos + zero_theta = 1 + else: + if not no_nans: + vals.append(np.nan) + gradients.append(np.nan) + continue + + w = 1.0 if no_weight or meas.observables_std[signal] == 0 else (1 / meas.observables_std[signal]) + val, *gradient = posfix_functions[meas.constellation_id](*inp, pr, zero_theta, *sat_pos, w) + vals.append(val) + gradients.append(gradient) + return np.asarray(vals), np.asarray(gradients) + return Fx_pos + + +def prr_residual(measurements: List[GNSSMeasurement], est_pos, velfix_function=None, signal='D1C', no_weight=False, no_nans=False): + + if velfix_function is None: + velfix_function = get_velfix_sympy_func() + + def Fx_vel(vel, no_weight=no_weight): + vals, gradients = [], [] + + for meas in measurements: + if signal not in meas.observables or not np.isfinite(meas.observables[signal]): + if not no_nans: + vals.append(np.nan) + gradients.append(np.nan) + continue + + sat_pos = meas.sat_pos_final if meas.corrected else meas.sat_pos + weight = 1.0 if no_weight or meas.observables_std[signal] == 0 else (1 / meas.observables_std[signal]) + + val, *gradient = velfix_function(est_pos[0], est_pos[1], est_pos[2], + sat_pos[0], sat_pos[1], sat_pos[2], + meas.sat_vel[0], meas.sat_vel[1], meas.sat_vel[2], + meas.observables[signal], weight, + vel[0], vel[1], vel[2], vel[3]) + vals.append(val) + gradients.append(gradient) + + return np.asarray(vals), np.asarray(gradients) + return Fx_vel diff --git a/laika/raw_gnss.py b/laika/raw_gnss.py index 39eabda45..b1f60ab9b 100644 --- a/laika/raw_gnss.py +++ b/laika/raw_gnss.py @@ -92,11 +92,11 @@ class GNSSMeasurement: self.processed = True return True - def correct(self, est_pos, dog, allow_incomplete_delay=False): + def correct(self, est_pos, dog): for obs in self.observables: if obs[0] == 'C': # or obs[0] == 'L': delay = dog.get_delay(self.prn, self.recv_time, est_pos, signal=obs) - if delay is not None and (allow_incomplete_delay or delay != 0): + if delay is not None: self.observables_final[obs] = (self.observables[obs] + self.sat_clock_err*constants.SPEED_OF_LIGHT - delay) @@ -147,10 +147,10 @@ def process_measurements(measurements: List[GNSSMeasurement], dog) -> List[GNSSM return proc_measurements -def correct_measurements(measurements: List[GNSSMeasurement], est_pos, dog, allow_incomplete_delay=False) -> List[GNSSMeasurement]: +def correct_measurements(measurements: List[GNSSMeasurement], est_pos, dog) -> List[GNSSMeasurement]: corrected_measurements = [] for meas in measurements: - if meas.correct(est_pos, dog, allow_incomplete_delay): + if meas.correct(est_pos, dog): corrected_measurements.append(meas) return corrected_measurements @@ -292,108 +292,6 @@ def read_rinex_obs(obsdata) -> List[List[GNSSMeasurement]]: return measurements -def calc_pos_fix(measurements, x0=[0, 0, 0, 0, 0], no_weight=False, signal='C1C', min_measurements=6): - ''' - Calculates gps fix with WLS optimizer - - returns: - 0 -> list with positions - 1 -> pseudorange errs - ''' - import scipy.optimize as opt # Only use scipy here - - n = len(measurements) - if n < min_measurements: - return [] - - Fx_pos = pr_residual(measurements, signal=signal, no_weight=no_weight, no_nans=True) - opt_pos = opt.least_squares(Fx_pos, x0).x - return opt_pos, Fx_pos(opt_pos, no_weight=True) - - -def calc_vel_fix(measurements, est_pos, v0=[0, 0, 0, 0], no_weight=False, signal='D1C'): - ''' - Calculates gps velocity fix with WLS optimizer - - returns: - 0 -> list with velocities - 1 -> pseudorange_rate errs - ''' - import scipy.optimize as opt # Only use scipy here - - n = len(measurements) - if n < 6: - return [] - - Fx_vel = prr_residual(measurements, est_pos, signal=signal, no_weight=no_weight, no_nans=True) - opt_vel = opt.least_squares(Fx_vel, v0).x - return opt_vel, Fx_vel(opt_vel, no_weight=True) - - -def pr_residual(measurements: List[GNSSMeasurement], signal='C1C', no_weight=False, no_nans=False): - # solve for pos - def Fx_pos(xxx_todo_changeme, no_weight=no_weight): - (x, y, z, bc, bg) = xxx_todo_changeme - rows = [] - - for meas in measurements: - if signal in meas.observables_final and np.isfinite(meas.observables_final[signal]): - pr = meas.observables_final[signal] - sat_pos = meas.sat_pos_final - theta = 0 - elif signal in meas.observables and np.isfinite(meas.observables[signal]) and meas.processed: - pr = meas.observables[signal] - pr += meas.sat_clock_err * constants.SPEED_OF_LIGHT - sat_pos = meas.sat_pos - theta = constants.EARTH_ROTATION_RATE * (pr - bc) / constants.SPEED_OF_LIGHT - else: - if not no_nans: - rows.append(np.nan) - continue - if no_weight: - weight = 1 - else: - weight = (1 / meas.observables_std[signal]) - - val = np.sqrt( - (sat_pos[0] * np.cos(theta) + sat_pos[1] * np.sin(theta) - x) ** 2 + - (sat_pos[1] * np.cos(theta) - sat_pos[0] * np.sin(theta) - y) ** 2 + - (sat_pos[2] - z) ** 2 - ) - if meas.constellation_id == ConstellationId.GLONASS: - rows.append(weight * (val - (pr - bc - bg))) - elif meas.constellation_id == ConstellationId.GPS: - rows.append(weight * (val - (pr - bc))) - return rows - return Fx_pos - - -def prr_residual(measurements, est_pos, signal='D1C', no_weight=False, no_nans=False): - # solve for vel - def Fx_vel(vel, no_weight=no_weight): - rows = [] - for meas in measurements: - if signal not in meas.observables or not np.isfinite(meas.observables[signal]): - if not no_nans: - rows.append(np.nan) - continue - if meas.corrected: - sat_pos = meas.sat_pos_final - else: - sat_pos = meas.sat_pos - if no_weight: - weight = 1 - else: - weight = (1 / meas.observables[signal]) - los_vector = (sat_pos - est_pos[0:3] - ) / np.linalg.norm(sat_pos - est_pos[0:3]) - rows.append( - weight * ((meas.sat_vel - vel[0:3]).dot(los_vector) - - (meas.observables[signal] - vel[3]))) - return rows - return Fx_vel - - def get_Q(recv_pos, sat_positions): local = LocalCoord.from_ecef(recv_pos) sat_positions_rel = local.ecef2ned(sat_positions) diff --git a/laika/trop.py b/laika/trop.py index 8315e91bb..ff224663f 100644 --- a/laika/trop.py +++ b/laika/trop.py @@ -16,7 +16,7 @@ def saast(pos, el, humi=0.75, temp0=15.0): :return: tropospheric delay (m) """ pos_rad = ecef2geodetic(pos, radians=True) - if pos_rad[2] < -100.0 or 1E4 < pos_rad[2] or el <= 0: + if pos_rad[2] < -1E3 or 1E4 < pos_rad[2] or el <= 0: return 0.0 # /* standard atmosphere */ diff --git a/launch_chffrplus.sh b/launch_chffrplus.sh index 911774a4e..9fe9b1bd1 100755 --- a/launch_chffrplus.sh +++ b/launch_chffrplus.sh @@ -74,7 +74,7 @@ function launch { # handle pythonpath ln -sfn $(pwd) /data/pythonpath - export PYTHONPATH="$PWD:$PWD/pyextra" + export PYTHONPATH="$PWD" # hardware specific init agnos_init diff --git a/opendbc/can/common.cc b/opendbc/can/common.cc index 3712b155a..0df9e04b9 100644 --- a/opendbc/can/common.cc +++ b/opendbc/can/common.cc @@ -1,4 +1,5 @@ -#include "common.h" +#include "opendbc/can/common.h" + unsigned int honda_checksum(uint32_t address, const Signal &sig, const std::vector &d) { int s = 0; @@ -34,7 +35,7 @@ unsigned int subaru_checksum(uint32_t address, const Signal &sig, const std::vec } unsigned int chrysler_checksum(uint32_t address, const Signal &sig, const std::vector &d) { - /* jeep chrysler canbus checksum from http://illmatics.com/Remote%20Car%20Hacking.pdf */ + // jeep chrysler canbus checksum from http://illmatics.com/Remote%20Car%20Hacking.pdf uint8_t checksum = 0xFF; for (int j = 0; j < (d.size() - 1); j++) { uint8_t shift = 0x80; @@ -123,64 +124,64 @@ unsigned int volkswagen_mqb_checksum(uint32_t address, const Signal &sig, const // Look up and apply the magic final CRC padding byte, which permutes by CAN // address, and additionally (for SOME addresses) by the message counter. uint8_t counter = d[1] & 0x0F; - switch(address) { + switch (address) { case 0x86: // LWI_01 Steering Angle - crc ^= (uint8_t[]){0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86}[counter]; + crc ^= (uint8_t[]){0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86}[counter]; break; case 0x9F: // LH_EPS_03 Electric Power Steering - crc ^= (uint8_t[]){0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5,0xF5}[counter]; + crc ^= (uint8_t[]){0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5, 0xF5}[counter]; break; case 0xAD: // Getriebe_11 Automatic Gearbox - crc ^= (uint8_t[]){0x3F,0x69,0x39,0xDC,0x94,0xF9,0x14,0x64,0xD8,0x6A,0x34,0xCE,0xA2,0x55,0xB5,0x2C}[counter]; + crc ^= (uint8_t[]){0x3F, 0x69, 0x39, 0xDC, 0x94, 0xF9, 0x14, 0x64, 0xD8, 0x6A, 0x34, 0xCE, 0xA2, 0x55, 0xB5, 0x2C}[counter]; break; case 0xFD: // ESP_21 Electronic Stability Program - crc ^= (uint8_t[]){0xB4,0xEF,0xF8,0x49,0x1E,0xE5,0xC2,0xC0,0x97,0x19,0x3C,0xC9,0xF1,0x98,0xD6,0x61}[counter]; + crc ^= (uint8_t[]){0xB4, 0xEF, 0xF8, 0x49, 0x1E, 0xE5, 0xC2, 0xC0, 0x97, 0x19, 0x3C, 0xC9, 0xF1, 0x98, 0xD6, 0x61}[counter]; break; case 0x106: // ESP_05 Electronic Stability Program - crc ^= (uint8_t[]){0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07}[counter]; + crc ^= (uint8_t[]){0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07}[counter]; break; case 0x117: // ACC_10 Automatic Cruise Control - crc ^= (uint8_t[]){0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16,0x16}[counter]; + crc ^= (uint8_t[]){0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16}[counter]; break; case 0x120: // TSK_06 Drivetrain Coordinator - crc ^= (uint8_t[]){0xC4,0xE2,0x4F,0xE4,0xF8,0x2F,0x56,0x81,0x9F,0xE5,0x83,0x44,0x05,0x3F,0x97,0xDF}[counter]; + crc ^= (uint8_t[]){0xC4, 0xE2, 0x4F, 0xE4, 0xF8, 0x2F, 0x56, 0x81, 0x9F, 0xE5, 0x83, 0x44, 0x05, 0x3F, 0x97, 0xDF}[counter]; break; case 0x121: // Motor_20 Driver Throttle Inputs - crc ^= (uint8_t[]){0xE9,0x65,0xAE,0x6B,0x7B,0x35,0xE5,0x5F,0x4E,0xC7,0x86,0xA2,0xBB,0xDD,0xEB,0xB4}[counter]; + crc ^= (uint8_t[]){0xE9, 0x65, 0xAE, 0x6B, 0x7B, 0x35, 0xE5, 0x5F, 0x4E, 0xC7, 0x86, 0xA2, 0xBB, 0xDD, 0xEB, 0xB4}[counter]; break; case 0x122: // ACC_06 Automatic Cruise Control - crc ^= (uint8_t[]){0x37,0x7D,0xF3,0xA9,0x18,0x46,0x6D,0x4D,0x3D,0x71,0x92,0x9C,0xE5,0x32,0x10,0xB9}[counter]; + crc ^= (uint8_t[]){0x37, 0x7D, 0xF3, 0xA9, 0x18, 0x46, 0x6D, 0x4D, 0x3D, 0x71, 0x92, 0x9C, 0xE5, 0x32, 0x10, 0xB9}[counter]; break; case 0x126: // HCA_01 Heading Control Assist - crc ^= (uint8_t[]){0xDA,0xDA,0xDA,0xDA,0xDA,0xDA,0xDA,0xDA,0xDA,0xDA,0xDA,0xDA,0xDA,0xDA,0xDA,0xDA}[counter]; + crc ^= (uint8_t[]){0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA}[counter]; break; case 0x12B: // GRA_ACC_01 Steering wheel controls for ACC - crc ^= (uint8_t[]){0x6A,0x38,0xB4,0x27,0x22,0xEF,0xE1,0xBB,0xF8,0x80,0x84,0x49,0xC7,0x9E,0x1E,0x2B}[counter]; + crc ^= (uint8_t[]){0x6A, 0x38, 0xB4, 0x27, 0x22, 0xEF, 0xE1, 0xBB, 0xF8, 0x80, 0x84, 0x49, 0xC7, 0x9E, 0x1E, 0x2B}[counter]; break; case 0x12E: // ACC_07 Automatic Cruise Control - crc ^= (uint8_t[]){0xF8,0xE5,0x97,0xC9,0xD6,0x07,0x47,0x21,0x66,0xDD,0xCF,0x6F,0xA1,0x94,0x74,0x63}[counter]; + crc ^= (uint8_t[]){0xF8, 0xE5, 0x97, 0xC9, 0xD6, 0x07, 0x47, 0x21, 0x66, 0xDD, 0xCF, 0x6F, 0xA1, 0x94, 0x74, 0x63}[counter]; break; case 0x187: // EV_Gearshift "Gear" selection data for EVs with no gearbox - crc ^= (uint8_t[]){0x7F,0xED,0x17,0xC2,0x7C,0xEB,0x44,0x21,0x01,0xFA,0xDB,0x15,0x4A,0x6B,0x23,0x05}[counter]; + crc ^= (uint8_t[]){0x7F, 0xED, 0x17, 0xC2, 0x7C, 0xEB, 0x44, 0x21, 0x01, 0xFA, 0xDB, 0x15, 0x4A, 0x6B, 0x23, 0x05}[counter]; break; case 0x30C: // ACC_02 Automatic Cruise Control - crc ^= (uint8_t[]){0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F,0x0F}[counter]; + crc ^= (uint8_t[]){0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F}[counter]; break; case 0x30F: // SWA_01 Lane Change Assist (SpurWechselAssistent) - crc ^= (uint8_t[]){0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C}[counter]; + crc ^= (uint8_t[]){0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C}[counter]; break; case 0x324: // ACC_04 Automatic Cruise Control - crc ^= (uint8_t[]){0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27,0x27}[counter]; + crc ^= (uint8_t[]){0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27}[counter]; break; case 0x3C0: // Klemmen_Status_01 ignition and starting status - crc ^= (uint8_t[]){0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3,0xC3}[counter]; + crc ^= (uint8_t[]){0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3}[counter]; break; case 0x65D: // ESP_20 Electronic Stability Program - crc ^= (uint8_t[]){0xAC,0xB3,0xAB,0xEB,0x7A,0xE1,0x3B,0xF7,0x73,0xBA,0x7C,0x9E,0x06,0x5F,0x02,0xD9}[counter]; + crc ^= (uint8_t[]){0xAC, 0xB3, 0xAB, 0xEB, 0x7A, 0xE1, 0x3B, 0xF7, 0x73, 0xBA, 0x7C, 0x9E, 0x06, 0x5F, 0x02, 0xD9}[counter]; break; default: // As-yet undefined CAN message, CRC check expected to fail printf("Attempt to CRC check undefined Volkswagen message 0x%02X\n", address); - crc ^= (uint8_t[]){0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}[counter]; + crc ^= (uint8_t[]){0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}[counter]; break; } crc = crc8_lut_8h2f[crc]; @@ -221,7 +222,6 @@ unsigned int pedal_checksum(uint32_t address, const Signal &sig, const std::vect } unsigned int hkg_can_fd_checksum(uint32_t address, const Signal &sig, const std::vector &d) { - uint16_t crc = 0; for (int i = 2; i < d.size(); i++) { diff --git a/opendbc/can/common.h b/opendbc/can/common.h index bcd04f65d..ba9ce0234 100644 --- a/opendbc/can/common.h +++ b/opendbc/can/common.h @@ -1,10 +1,11 @@ #pragma once -#include #include +#include +#include #include +#include -#include "common_dbc.h" #include #include @@ -12,6 +13,8 @@ #include "cereal/gen/cpp/log.capnp.h" #endif +#include "opendbc/can/common_dbc.h" + #define INFO printf #define WARN printf #define DEBUG(...) diff --git a/opendbc/can/common.pxd b/opendbc/can/common.pxd index 1d34a2606..c863b65a7 100644 --- a/opendbc/can/common.pxd +++ b/opendbc/can/common.pxd @@ -61,6 +61,7 @@ cdef extern from "common_dbc.h": cdef struct SignalValue: uint32_t address + uint64_t ts_nanos string name double value vector[double] all_values diff --git a/opendbc/can/common_dbc.h b/opendbc/can/common_dbc.h index 4e2fa32fc..20a8ef3fd 100644 --- a/opendbc/can/common_dbc.h +++ b/opendbc/can/common_dbc.h @@ -24,6 +24,7 @@ struct MessageParseOptions { struct SignalValue { uint32_t address; + uint64_t ts_nanos; std::string name; double value; // latest value std::vector all_values; // all values from this cycle diff --git a/opendbc/can/dbc.cc b/opendbc/can/dbc.cc index 16ca328e8..11231ebd7 100644 --- a/opendbc/can/dbc.cc +++ b/opendbc/can/dbc.cc @@ -8,9 +8,10 @@ #include #include #include +#include -#include "common.h" -#include "common_dbc.h" +#include "opendbc/can/common.h" +#include "opendbc/can/common_dbc.h" std::regex bo_regexp(R"(^BO_ (\w+) (\w+) *: (\w+) (\w+))"); std::regex sg_regexp(R"(^SG_ (\w+) : (\d+)\|(\d+)@(\d+)([\+|\-]) \(([0-9.+\-eE]+),([0-9.+\-eE]+)\) \[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\] \"(.*)\" (.*))"); diff --git a/opendbc/can/packer.cc b/opendbc/can/packer.cc index 5525349d9..6cee2eb1b 100644 --- a/opendbc/can/packer.cc +++ b/opendbc/can/packer.cc @@ -4,7 +4,7 @@ #include #include -#include "common.h" +#include "opendbc/can/common.h" void set_value(std::vector &msg, const Signal &sig, int64_t ival) { diff --git a/opendbc/can/packer.py b/opendbc/can/packer.py index fc22cce00..024cf3045 100644 --- a/opendbc/can/packer.py +++ b/opendbc/can/packer.py @@ -1,3 +1,2 @@ -# pylint: skip-file -from opendbc.can.packer_pyx import CANPacker +from opendbc.can.packer_pyx import CANPacker # pylint: disable=no-name-in-module, import-error assert CANPacker diff --git a/opendbc/can/parser.cc b/opendbc/can/parser.cc index 984df1115..754c09df9 100644 --- a/opendbc/can/parser.cc +++ b/opendbc/can/parser.cc @@ -32,7 +32,6 @@ int64_t get_raw_value(const std::vector &msg, const Signal &sig) { bool MessageState::parse(uint64_t sec, const std::vector &dat) { - for (int i = 0; i < parse_sigs.size(); i++) { auto &sig = parse_sigs[i]; @@ -94,7 +93,6 @@ CANParser::CANParser(int abus, const std::string& dbc_name, const std::vector &options, const std::vector &sigoptions) : bus(abus), aligned_buf(kj::heapArray(1024)) { - dbc = dbc_lookup(dbc_name); assert(dbc); init_crc_lookup_tables(); @@ -312,6 +310,7 @@ std::vector CANParser::query_latest() { const Signal &sig = state.parse_sigs[i]; ret.push_back((SignalValue){ .address = state.address, + .ts_nanos = state.last_seen_nanos, .name = sig.name, .value = state.vals[i], .all_values = state.all_vals[i], diff --git a/opendbc/can/parser_pyx.pyx b/opendbc/can/parser_pyx.pyx index 3a8733fae..9867305ef 100644 --- a/opendbc/can/parser_pyx.pyx +++ b/opendbc/can/parser_pyx.pyx @@ -26,6 +26,7 @@ cdef class CANParser: cdef readonly: dict vl dict vl_all + dict ts_nanos string dbc_name def __init__(self, dbc_name, signals, checks=None, bus=0, enforce_checks=True): @@ -39,6 +40,7 @@ cdef class CANParser: self.vl = {} self.vl_all = {} + self.ts_nanos = {} msg_name_to_address = {} for i in range(self.dbc[0].msgs.size()): @@ -51,6 +53,8 @@ cdef class CANParser: self.vl[name] = self.vl[msg.address] self.vl_all[msg.address] = defaultdict(list) self.vl_all[name] = self.vl_all[msg.address] + self.ts_nanos[msg.address] = {} + self.ts_nanos[name] = self.ts_nanos[msg.address] # Convert message names into addresses for i in range(len(signals)): @@ -108,6 +112,7 @@ cdef class CANParser: cv_name = cv.name self.vl[cv.address][cv_name] = cv.value self.vl_all[cv.address][cv_name].extend(cv.all_values) + self.ts_nanos[cv.address][cv_name] = cv.ts_nanos updated_addrs.insert(cv.address) return updated_addrs diff --git a/opendbc/chrysler_pacifica_2017_hybrid_generated.dbc b/opendbc/chrysler_pacifica_2017_hybrid_generated.dbc index a93ae1d34..3036bc728 100644 --- a/opendbc/chrysler_pacifica_2017_hybrid_generated.dbc +++ b/opendbc/chrysler_pacifica_2017_hybrid_generated.dbc @@ -81,6 +81,7 @@ BO_ 544 EPS_2: 8 XXX SG_ TORQUE_OVERLAY_STATUS : 6|4@0+ (1,0) [0|15] "" XXX SG_ EPS_TORQUE_MOTOR_RAW : 19|12@0+ (1,-2048) [-2048|2047] "" XXX SG_ EPS_TORQUE_MOTOR : 34|11@0+ (1,-1024) [-1024|1023] "" XXX + SG_ LKAS_TEMPORARY_FAULT : 38|1@0+ (1,0) [0|1] "" XXX SG_ AUTO_PARK_HAS_CONTROL_2 : 51|1@0+ (1,0) [0|1] "" XXX SG_ COUNTER : 55|4@0+ (1,0) [0|15] "" XXX SG_ CHECKSUM : 63|8@0+ (1,0) [0|255] "" XXX diff --git a/opendbc/chrysler_ram_dt_generated.dbc b/opendbc/chrysler_ram_dt_generated.dbc index 0736a52c9..2eb5aecf3 100644 --- a/opendbc/chrysler_ram_dt_generated.dbc +++ b/opendbc/chrysler_ram_dt_generated.dbc @@ -83,6 +83,7 @@ BO_ 49 EPS_2: 8 XXX SG_ TORQUE_OVERLAY_STATUS : 6|4@0+ (1,0) [0|15] "" XXX SG_ EPS_TORQUE_MOTOR_RAW : 19|12@0+ (1,-2048) [-2048|2047] "" XXX SG_ EPS_TORQUE_MOTOR : 34|11@0+ (1,-1024) [-1024|1023] "" XXX + SG_ LKAS_TEMPORARY_FAULT : 38|1@0+ (1,0) [0|1] "" XXX SG_ AUTO_PARK_HAS_CONTROL_2 : 51|1@0+ (1,0) [0|1] "" XXX SG_ COUNTER : 55|4@0+ (1,0) [0|15] "" XXX SG_ CHECKSUM : 63|8@0+ (1,0) [0|255] "" XXX diff --git a/opendbc/chrysler_ram_hd_generated.dbc b/opendbc/chrysler_ram_hd_generated.dbc index 248121388..82cf0d365 100644 --- a/opendbc/chrysler_ram_hd_generated.dbc +++ b/opendbc/chrysler_ram_hd_generated.dbc @@ -83,6 +83,7 @@ BO_ 544 EPS_2: 8 XXX SG_ TORQUE_OVERLAY_STATUS : 6|4@0+ (1,0) [0|15] "" XXX SG_ EPS_TORQUE_MOTOR_RAW : 19|12@0+ (1,-2048) [-2048|2047] "" XXX SG_ EPS_TORQUE_MOTOR : 34|11@0+ (1,-1024) [-1024|1023] "" XXX + SG_ LKAS_TEMPORARY_FAULT : 38|1@0+ (1,0) [0|1] "" XXX SG_ AUTO_PARK_HAS_CONTROL_2 : 51|1@0+ (1,0) [0|1] "" XXX SG_ COUNTER : 55|4@0+ (1,0) [0|15] "" XXX SG_ CHECKSUM : 63|8@0+ (1,0) [0|255] "" XXX diff --git a/opendbc/ford_lincoln_base_pt.dbc b/opendbc/ford_lincoln_base_pt.dbc index 1f2560179..ebe7a4321 100644 --- a/opendbc/ford_lincoln_base_pt.dbc +++ b/opendbc/ford_lincoln_base_pt.dbc @@ -153,7 +153,7 @@ VAL_TABLE_ ChrgCordLck_D_Stat 7 "Faulty" 6 "LockFail" 5 "UnlockFail" 4 "LockInPr VAL_TABLE_ LkaActvStats_D2_Req 7 "NotUsed" 6 "LkaIncrIntervRight" 5 "LkaSupprRight" 4 "LkaStandIntervRight" 3 "LkaSupprLeft" 2 "LkaStandIntervLeft" 1 "LkaIncrIntervLeft" 0 "LkaNoInterv"; VAL_TABLE_ OffOnNoDataFaulty 3 "Faulty" 2 "No_Data_Exists" 1 "On" 0 "Off"; VAL_TABLE_ NoMessage_Msg1_3 3 "Message3" 2 "Message2" 1 "Message1" 0 "NoMessage"; -VAL_TABLE_ TrnRng_D_RqGsm 15 "Fault" 14 "UnknownPosition" 13 "Undefined_2" 12 "Undefined_1" 11 "_6" 10 "_5" 9 "_4" 8 "_3" 7 "_2" 6 "_1" 5 "Low" 4 "Sport_DriveSport" 3 "Drive" 2 "Neutral" 1 "Reverse" 0 "Park"; +VAL_TABLE_ TrnRng_D_RqGsm 15 "Fault" 14 "UnknownPosition" 13 "Undefined_2" 12 "Undefined_1" 11 "_6" 10 "_5" 9 "_4" 8 "_3" 7 "_2" 6 "_1" 5 "Low" 4 "Sport" 3 "Drive" 2 "Neutral" 1 "Reverse" 0 "Park"; VAL_TABLE_ PrkBrkActv_D_RqGsmGear 3 "NotUsed" 2 "RequestParkBrakeEngage" 1 "NoRequest" 0 "Null"; VAL_TABLE_ TrnPrkSys_D_Actl 15 "Faulty" 14 "NotUsed_5" 13 "NotUsed_4" 12 "NotUsed_3" 11 "NotUsed_2" 10 "NotUsed_1" 9 "FrequencyError" 8 "OutOfRangeHigh" 7 "OutOfRangeLow" 6 "Override" 5 "OutOfPark" 4 "TransitionCloseToOutOfPark" 3 "AtNoSpring" 2 "TransitionCloseToPark" 1 "Park" 0 "NotKnown"; VAL_TABLE_ OKLowUnknownInvalid 3 "Invalid" 2 "Unknown" 1 "Low" 0 "OK"; diff --git a/opendbc/gm_global_a_powertrain_generated.dbc b/opendbc/gm_global_a_powertrain_generated.dbc index a251bb96e..916a0a254 100644 --- a/opendbc/gm_global_a_powertrain_generated.dbc +++ b/opendbc/gm_global_a_powertrain_generated.dbc @@ -160,6 +160,7 @@ BO_ 452 AcceleratorPedal2: 8 XXX BO_ 481 ASCMSteeringButton: 7 K124_ASCM SG_ DistanceButton : 22|1@0+ (1,0) [0|0] "" NEO SG_ LKAButton : 23|1@0+ (1,0) [0|0] "" NEO + SG_ ACCAlwaysOne : 24|1@0+ (1,0) [0|1] "" XXX SG_ ACCButtons : 46|3@0+ (1,0) [0|0] "" NEO SG_ DriveModeButton : 39|1@0+ (1,0) [0|1] "" XXX SG_ RollingCounter : 33|2@0+ (1,0) [0|3] "" NEO @@ -193,6 +194,13 @@ BO_ 532 BRAKE_RELATED: 6 XXX BO_ 560 EPBStatus: 8 EPB SG_ EPBClosed : 12|1@0+ (1,0) [0|1] "" NEO +BO_ 562 EBCMFrictionBrakeStatus: 8 XXX + SG_ FrictionBrakeUnavailable : 46|1@0+ (1,0) [0|1] "" XXX + +BO_ 608 SPEED_RELATED: 8 XXX + SG_ RollingCounter : 5|2@0+ (1,0) [0|0] "" XXX + SG_ ClusterSpeed : 31|8@0+ (1,0) [0|0] "" XXX + BO_ 711 BECMBatteryVoltageCurrent: 6 K17_EBCM SG_ HVBatteryVoltage : 31|12@0+ (0.125,0) [0|511.875] "V" NEO SG_ HVBatteryCurrent : 12|13@0- (0.15,0) [-614.4|614.25] "A" NEO @@ -245,6 +253,7 @@ BO_ 842 EBCMWheelSpdRear: 5 K17_EBCM BO_ 869 ASCM_365: 4 K124_ASCM BO_ 880 ASCMActiveCruiseControlStatus: 6 K124_ASCM + SG_ ACCCruiseState : 8|3@1+ (1,0) [0|7] "" XXX SG_ ACCLeadCar : 44|1@0+ (1,0) [0|0] "" Vector__XXX SG_ ACCAlwaysOne2 : 32|1@0+ (1,0) [0|0] "" Vector__XXX SG_ ACCAlwaysOne : 0|1@0+ (1,0) [0|0] "" Vector__XXX @@ -304,9 +313,12 @@ CM_ BO_ 190 "Length varies from 6 to 8 bytes by car"; CM_ SG_ 190 GasPedalAndAcc "ACC baseline is 62"; CM_ SG_ 352 Ignition "Non-zero when ignition is on"; CM_ SG_ 451 GasPedalAndAcc2 "ACC baseline is 62"; +CM_ SG_ 481 ACCAlwaysOne "Usually 1 if the car is equipped with ACC"; +CM_ SG_ 562 FrictionBrakeUnavailable "1 when ACC brake control is unavailable. Stays high if brake command messages are blocked for a period of time"; CM_ SG_ 497 Ignition "Describes ignition + preconditioning mode, noisy"; CM_ SG_ 501 PRNDL2 "When ManualMode is Active, Value is 13=L1 12=L2 11=L3 ... 4=L10"; CM_ SG_ 532 UserBrakePressure "can be lower than other brake position signals when the brakes are pre-filled from ACC braking and the user presses on the brakes. user-only pressure?"; +CM_ SG_ 608 ClusterSpeed "Cluster speed signal seems to match dash on newer cars, but is a lower rate and can be noisier."; CM_ SG_ 761 UserBrakePressure2 "Similar to BRAKE_RELATED->UserBrakePressure"; CM_ SG_ 1001 VehicleSpeed "Spinouts show here on 2wd. Speed derived from right front wheel (drive tire)"; BA_DEF_ "UseGMParameterIDs" INT 0 0; @@ -326,6 +338,7 @@ VAL_ 452 CruiseState 4 "Standstill" 3 "Faulted" 1 "Active" 0 "Off" ; VAL_ 309 PRNDL 3 "R" 2 "D" 1 "N" 0 "P" ; VAL_ 309 ESPButton 1 "Active" 0 "Inactive" ; VAL_ 384 LKASteeringCmdActive 1 "Active" 0 "Inactive" ; +VAL_ 880 ACCCruiseState 2 "Adaptive" 3 "Adaptive" 4 "Non-adaptive" 5 "Non-adaptive" ; VAL_ 880 ACCLeadCar 1 "Present" 0 "Not Present" ; VAL_ 880 ACCGapLevel 3 "Far" 2 "Med" 1 "Near" 0 "Inactive" ; VAL_ 880 ACCResumeButton 1 "Pressed" 0 "Depressed" ; diff --git a/opendbc/honda_civic_ex_2022_can_generated.dbc b/opendbc/honda_civic_ex_2022_can_generated.dbc index 2b06a4a49..b72f9aa6b 100644 --- a/opendbc/honda_civic_ex_2022_can_generated.dbc +++ b/opendbc/honda_civic_ex_2022_can_generated.dbc @@ -447,7 +447,7 @@ BO_ 432 STANDSTILL: 7 VSA BO_ 456 ACC_CONTROL: 8 XXX SG_ ACCEL_COMMAND : 7|12@0- (0.01,0) [0|0] "m/s^2" XXX - SG_ CONTROL_OFF : 8|1@0+ (1,0) [0|1] "" XXX + SG_ IDLESTOP_ALLOW : 8|1@0+ (1,0) [0|1] "" XXX SG_ STANDSTILL : 9|1@0+ (1,0) [0|1] "" XXX SG_ CONTROL_ON : 10|1@0+ (1,0) [0|1] "" XXX SG_ BOH : 23|1@0+ (1,0) [0|1] "" XXX @@ -496,7 +496,7 @@ BO_ 254913108 LKAS_HUD_2: 8 ADAS SG_ COUNTER : 61|2@0+ (1,0) [0|3] "" XXX SG_ CHECKSUM : 59|4@0+ (1,0) [0|15] "" XXX -CM_ BO_ 456 "not sure which bit enables cruise"; +CM_ SG_ 456 IDLESTOP_ALLOW "allows car to turn off engine at a standstill"; CM_ SG_ 456 STANDSTILL "set to 1 when camera requests -4.0 m/s^2"; VAL_ 401 GEAR_SHIFTER 32 "L" 16 "S" 8 "D" 4 "N" 2 "R" 1 "P"; diff --git a/opendbc/hyundai_canfd.dbc b/opendbc/hyundai_canfd.dbc index 0977f9aef..f88afe298 100644 --- a/opendbc/hyundai_canfd.dbc +++ b/opendbc/hyundai_canfd.dbc @@ -33,7 +33,7 @@ NS_ : BS_: -BU_: XXX CAMERA FRONT_RADAR ADRV +BU_: XXX CAMERA FRONT_RADAR ADRV APRK BO_ 53 ACCELERATOR: 32 XXX @@ -86,6 +86,11 @@ BO_ 101 BRAKE: 32 XXX SG_ BRAKE_POSITION : 40|16@1- (1,0) [0|65535] "" XXX SG_ BRAKE_PRESSED : 57|1@1+ (1,0) [0|3] "" XXX +BO_ 112 GEAR_ALT_2: 32 XXX + SG_ CHECKSUM : 0|16@1+ (1,0) [0|65535] "" XXX + SG_ COUNTER : 16|8@1+ (1,0) [0|255] "" XXX + SG_ GEAR : 60|3@1+ (1,0) [0|7] "" XXX + BO_ 160 WHEEL_SPEEDS: 24 XXX SG_ CHECKSUM : 0|16@1+ (1,0) [0|65535] "" XXX SG_ COUNTER : 16|8@1+ (1,0) [0|255] "" XXX @@ -144,6 +149,17 @@ BO_ 304 GEAR_SHIFTER: 16 XXX SG_ GEAR : 64|3@1+ (1,0) [0|7] "" XXX SG_ KNOB_POSITION : 40|3@1+ (1,0) [0|3] "" XXX +BO_ 357 SPAS1: 24 APRK + SG_ CHECKSUM : 0|16@1+ (1,0) [0|65535] "" XXX + SG_ COUNTER : 16|8@1+ (1,0) [0|0] "" XXX + SG_ NEW_SIGNAL_1 : 96|16@1- (0.1,0) [0|0] "" XXX + SG_ NEW_SIGNAL_2 : 90|3@1+ (1,0) [0|0] "" XXX + +BO_ 362 SPAS2: 32 APRK + SG_ CHECKSUM : 0|16@1+ (1,0) [0|65535] "" XXX + SG_ COUNTER : 16|8@1+ (1,0) [0|0] "" XXX + SG_ BLINKER_CONTROL : 133|3@1+ (1,0) [0|0] "" XXX + BO_ 373 TCS: 24 XXX SG_ CHECKSUM : 0|16@1+ (1,0) [0|65535] "" XXX SG_ COUNTER : 16|8@1+ (1,0) [0|255] "" XXX @@ -162,6 +178,7 @@ BO_ 373 TCS: 24 XXX SG_ DriverBrakingLowSens : 84|1@1+ (1,0) [0|1] "" XXX SG_ ACC_REQ : 68|1@0+ (1,0) [0|1] "" XXX SG_ ACCEL_REF_ACC : 48|11@1- (1,0) [0|1023] "" XXX + SG_ ACCEnable : 67|2@0+ (1,0) [0|3] "" XXX BO_ 352 ADRV_0x160: 16 ADRV SG_ CHECKSUM : 0|16@1+ (1,0) [0|65535] "" XXX @@ -200,7 +217,6 @@ BO_ 416 SCC_CONTROL: 32 ADRV SG_ CHECKSUM : 0|16@1+ (1,0) [0|65535] "" XXX SG_ NEW_SIGNAL_1 : 64|2@1+ (1,0) [0|3] "" XXX SG_ NEW_SIGNAL_8 : 170|4@1+ (1,0) [0|15] "" XXX - SG_ NEW_SIGNAL_10 : 156|5@0+ (1,0) [0|3] "" XXX SG_ ZEROS : 215|48@0+ (1,0) [0|281474976710655] "" XXX SG_ ZEROS_3 : 191|7@0+ (1,0) [0|127] "" XXX SG_ ZEROS_4 : 183|4@0+ (1,0) [0|63] "" XXX @@ -225,7 +241,6 @@ BO_ 416 SCC_CONTROL: 32 ADRV SG_ aReqRaw : 140|11@1+ (0.01,-10.23) [-10.23|10.24] "m/s^2" XXX SG_ aReqValue : 128|11@1+ (0.01,-10.23) [-10.23|10.24] "m/s^2" XXX SG_ ZEROS_7 : 63|8@0+ (1,0) [0|255] "" XXX - SG_ NEW_SIGNAL_4 : 158|2@0+ (1,0) [0|3] "" XXX SG_ ACCMode : 68|3@1+ (1,0) [0|7] "" XXX SG_ NEW_SIGNAL_12 : 35|9@1+ (0.1,0) [0|255] "" XXX SG_ JerkLowerLimit : 166|7@0+ (0.1,0) [0|12.7] "m/s^3" XXX @@ -233,12 +248,14 @@ BO_ 416 SCC_CONTROL: 32 ADRV SG_ NEW_SIGNAL_15 : 192|11@1+ (0.1,0) [0|204.7] "m" XXX SG_ VSetDis : 103|8@0+ (1,0) [0|255] "km/h or mph" XXX SG_ MainMode_ACC : 66|1@1+ (1,0) [0|1] "" XXX + SG_ JerkUpperLimit : 158|7@0+ (0.1,0) [0|0] "" XXX BO_ 426 CRUISE_BUTTONS_ALT: 16 XXX SG_ CHECKSUM : 0|16@1+ (1,0) [0|65535] "" XXX SG_ COUNTER : 16|8@1+ (1,0) [0|255] "" XXX SG_ NEW_SIGNAL_1 : 24|4@1+ (1,0) [0|15] "" XXX - SG_ SET_ME_1 : 28|3@1+ (1,0) [0|7] "" XXX + SG_ SET_ME_1 : 28|2@1+ (1,0) [0|3] "" XXX + SG_ DISTANCE_UNIT : 30|1@1+ (1,0) [0|1] "" XXX SG_ NEW_SIGNAL_2 : 31|3@1+ (1,0) [0|7] "" XXX SG_ ADAPTIVE_CRUISE_MAIN_BTN : 34|1@1+ (1,0) [0|1] "" XXX SG_ NEW_SIGNAL_3 : 35|1@1+ (1,0) [0|1] "" XXX @@ -516,7 +533,7 @@ BO_ 485 BLINDSPOTS_FRONT_CORNER_1: 16 XXX SG_ NEW_SIGNAL_3 : 88|1@0+ (1,0) [0|1] "" XXX SG_ NEW_SIGNAL_4 : 80|1@0+ (1,0) [0|1] "" XXX SG_ NEW_SIGNAL_5 : 31|2@0+ (1,0) [0|3] "" XXX - SG_ NEW_SIGNAL_6 : 24|1@0+ (1,0) [0|1] "" XXX + SG_ REVERSING : 24|1@0+ (1,0) [0|1] "" XXX SG_ NEW_SIGNAL_7 : 32|2@1+ (1,0) [0|3] "" XXX SG_ NEW_SIGNAL_8 : 47|8@0+ (1,0) [0|255] "" XXX SG_ NEW_SIGNAL_9 : 55|8@0+ (1,0) [0|255] "" XXX @@ -539,6 +556,7 @@ BO_ 506 CLUSTER_SPEED_LIMIT: 32 XXX CM_ SG_ 96 BRAKE_PRESSURE "User applied brake pedal pressure. Ramps from computer applied pressure on falling edge of cruise. Cruise cancels if !=0"; CM_ SG_ 101 BRAKE_POSITION "User applied brake pedal position, max is ~700. Signed on some vehicles"; CM_ SG_ 373 PROBABLY_EQUIP "aeb equip?"; +CM_ SG_ 373 ACCEnable "Likely a copy of CAN's TCS13->ACCEnable"; CM_ SG_ 373 DriverBraking "Likely derived from BRAKE->BRAKE_POSITION"; CM_ SG_ 373 DriverBrakingLowSens "Higher threshold version of DriverBraking"; CM_ SG_ 352 SET_ME_9 "has something to do with AEB settings"; @@ -549,6 +567,7 @@ CM_ SG_ 1043 COUNTER_ALT "only increments on change"; VAL_ 53 GEAR 0 "P" 5 "D" 6 "N" 7 "R" ; VAL_ 64 GEAR 0 "P" 5 "D" 6 "N" 7 "R" ; VAL_ 69 GEAR 0 "P" 5 "D" 6 "N" 7 "R" ; +VAL_ 112 GEAR 0 "P" 5 "D" 6 "N" 7 "R" ; VAL_ 80 LKA_ICON 0 "hidden" 1 "grey" 2 "green" 3 "flashing green" ; VAL_ 80 LKA_MODE 1 "warning only" 2 "assist" 6 "off" ; VAL_ 234 LKA_FAULT 0 "ok" 1 "lka fault" ; @@ -556,6 +575,8 @@ VAL_ 298 LKA_ICON 0 "hidden" 1 "grey" 2 "green" 3 "flashing green" ; VAL_ 298 LKA_MODE 1 "warning only" 2 "assist" 6 "off" ; VAL_ 304 GEAR 1 "P" 2 "R" 3 "N" 4 "D" ; VAL_ 352 AEB_SETTING 1 "off" 2 "warning only" 3 "active assist" ; +VAL_ 362 BLINKER_CONTROL 1 "hazards" 2 "hazards button backlight" 3 "left blinkers" 4 "right blinkers"; +VAL_ 373 ACCEnable 0 "SCC ready" 1 "SCC temp fault" 2 "SCC permanent fault" 3 "SCC permanent fault, communication issue"; VAL_ 416 ACCMode 0 "off" 1 "enabled" 2 "driver_override" 3 "off_maybe_fault" 4 "cancelled" ; VAL_ 426 CRUISE_BUTTONS 0 "none" 1 "res_accel" 2 "set_decel" 3 "gap_distance" 4 "pause_resume" ; VAL_ 463 CRUISE_BUTTONS 0 "none" 1 "res_accel" 2 "set_decel" 3 "gap_distance" 4 "pause_resume" ; diff --git a/opendbc/mazda_2017.dbc b/opendbc/mazda_2017.dbc index fbd11162a..cc1f339aa 100644 --- a/opendbc/mazda_2017.dbc +++ b/opendbc/mazda_2017.dbc @@ -352,12 +352,10 @@ BO_ 130 STEER: 8 XXX SG_ CHKSUM_MAYBE : 39|8@0+ (1,0) [0|255] "" XXX BO_ 120 BRAKE: 8 XXX - SG_ NEW_SIGNAL_1 : 7|8@0+ (1,0) [0|255] "" XXX - SG_ NEW_SIGNAL_2 : 15|8@0+ (1,0) [0|255] "" XXX - SG_ NEW_SIGNAL_3 : 23|8@0+ (1,0) [0|255] "" XXX - SG_ NEW_SIGNAL_4 : 31|8@0+ (1,0) [0|255] "" XXX SG_ CTR : 55|8@0+ (1,0) [0|255] "" XXX SG_ BRAKE_PRESSURE : 39|8@0+ (1,0) [0|255] "" XXX + SG_ VEHICLE_ACC_X : 5|13@0+ (0.01,-40) [-40|40] "m/s^2" XXX + SG_ VEHICLE_ACC_Y : 8|13@0+ (0.001,-4.096) [-4.096|4.096] "m/s^2" XXX BO_ 304 GEAR_RELATED: 8 XXX SG_ NEW_SIGNAL_1 : 55|8@0+ (1,0) [0|255] "" XXX @@ -556,8 +554,6 @@ BO_ 535 CURVE_CTRS: 8 XXX BO_ 540 CRZ_CTRL: 8 XXX SG_ NEW_SIGNAL_6 : 10|1@0+ (1,0) [0|1] "" XXX - SG_ NEW_SIGNAL_9 : 31|1@0+ (1,0) [0|255] "" XXX - SG_ ACC_GAS_MAYBE2 : 29|1@0+ (1,0) [0|1] "" XXX SG_ HANDS_OFF_STEERING : 48|1@0+ (1,0) [0|1] "" XXX SG_ HANDS_ON_STEER_WARN : 59|4@0+ (1,0) [0|255] "" XXX SG_ CRZ_ACTIVE : 3|1@0+ (1,0) [0|1] "" XXX @@ -565,13 +561,13 @@ BO_ 540 CRZ_CTRL: 8 XXX SG_ DISTANCE_SETTING : 20|3@0+ (1,0) [0|7] "" XXX SG_ MSG_1_INV : 1|1@0+ (1,0) [0|1] "" XXX SG_ MSG_1_COPY : 9|1@0+ (1,0) [0|1] "" XXX - SG_ ACC_GAS_MAYBE : 23|1@0+ (1,0) [0|31] "" XXX SG_ ACC_ACTIVE_2 : 52|1@0+ (1,0) [0|1] "" XXX - SG_ NEW_SIGNAL_10 : 30|1@0+ (1,0) [0|1] "" XXX SG_ MSG_1 : 0|1@0+ (1,0) [0|3] "" XXX SG_ 5_SEC_DISABLE_TIMER : 45|3@0+ (1,0) [0|7] "" XXX SG_ NEW_SIGNAL_3 : 13|1@0+ (1,0) [0|3] "" XXX SG_ MSG_1_INV_COPY : 8|1@0+ (1,0) [0|7] "" XXX + SG_ RADAR_HAS_LEAD : 23|1@0+ (1,0) [0|1] "" XXX + SG_ RADAR_LEAD_RELATIVE_DISTANCE : 31|3@0+ (1,0) [0|5] "" XXX BO_ 539 CRZ_INFO: 8 XXX SG_ CTR1 : 55|8@0+ (1,0) [0|255] "" XXX @@ -765,6 +761,8 @@ CM_ SG_ 1157 LANEE_DEPARTURE_ALERT "1 off, 2 on"; CM_ SG_ 1157 WARNING "1 Rare, 0 often"; CM_ SG_ 1088 LANE_LINES "0 LKAS disabled, 1 no lines, 2 two lines, 3 left line, 4 right line"; CM_ SG_ 1045 ABS_MALFUNCTION "off: 0, solid: 1, slow blink: 2, fast blink: 3"; +CM_ SG_ 120 VEHICLE_ACC_X "Vehicle acceleration of X-axis wrt. NED frame."; +CM_ SG_ 120 VEHICLE_ACC_Y "Vehicle acceleration of Y-axis wrt. NED frame."; CM_ SG_ 157 CAN_OFF "Disengage Cruise if enabled, if already disabled TURN it OFF "; CM_ SG_ 552 MORE_GEAR ""; CM_ SG_ 552 GEAR "0 Shifting, 1 P, 2 R, 3 N, 4 D"; @@ -773,3 +771,5 @@ CM_ SG_ 540 HANDS_ON_STEER_WARN "0 no warning, b warning"; CM_ SG_ 540 DISTANCE_SETTING "Radar distance 0: disabled, 1: 4 bars, 2: 3 bars, 3: 2 bars, 4: 1 bar"; CM_ SG_ 1143 REAR_CT_ALERT "Rear Cross Traffic Alert"; VAL_ 552 GEAR 1 "P" 2 "R" 3 "N" 4 "D" ; +VAL_ 540 RADAR_HAS_LEAD 0 "NO LEAD" 1 "HAS LEAD" ; +VAL_ 540 RADAR_LEAD_RELATIVE_DISTANCE 0 "NO LEAD" 1 "FARTHEST" 2 "4" 3 "3" 4 "2" 5 "NEAREST" ; diff --git a/opendbc/toyota_new_mc_pt_generated.dbc b/opendbc/toyota_new_mc_pt_generated.dbc index 708560f64..d611c1d84 100644 --- a/opendbc/toyota_new_mc_pt_generated.dbc +++ b/opendbc/toyota_new_mc_pt_generated.dbc @@ -106,7 +106,7 @@ BO_ 180 SPEED: 8 XXX SG_ CHECKSUM : 63|8@0+ (1,0) [0|255] "" XXX BO_ 295 GEAR_PACKET_HYBRID: 8 XXX - SG_ CAR_MOVEMENT : 39|8@0- (1,0) [0|255] "" XXX + SG_ CAR_MOVEMENT : 25|10@0- (1,0) [0|255] "" XXX SG_ COUNTER : 55|8@0+ (1,0) [0|255] "" XXX SG_ CHECKSUM : 63|8@0+ (1,0) [0|255] "" XXX SG_ GEAR : 47|4@0+ (1,0) [0|15] "" XXX @@ -128,9 +128,10 @@ BO_ 466 PCM_CRUISE: 8 XXX BO_ 467 PCM_CRUISE_2: 8 XXX SG_ PCM_FOLLOW_DISTANCE : 12|2@0+ (1,0) [0|3] "" XXX - SG_ MAIN_ON : 15|1@0+ (1,0) [0|1] "" XXX SG_ LOW_SPEED_LOCKOUT : 14|2@0+ (1,0) [0|3] "" XXX + SG_ MAIN_ON : 15|1@0+ (1,0) [0|1] "" XXX SG_ SET_SPEED : 23|8@0+ (1,0) [0|255] "km/h" XXX + SG_ ACC_FAULTED : 47|1@0+ (1,0) [0|1] "" XXX SG_ CHECKSUM : 63|8@0+ (1,0) [0|255] "" XXX BO_ 552 ACCELEROMETER: 8 XXX @@ -209,8 +210,9 @@ BO_ 836 PRE_COLLISION_2: 8 DSU SG_ CHECKSUM : 63|8@0+ (1,0) [0|0] "" XXX BO_ 865 CLUTCH: 8 XXX - SG_ CLUTCH_RELEASED : 38|1@0+ (1,0) [0|1] "" XXX + SG_ ACC_FAULTED : 32|1@0+ (1,0) [0|1] "" XXX SG_ GAS_PEDAL_ALT : 23|8@0+ (0.005,0) [0|1] "" XXX + SG_ CLUTCH_RELEASED : 38|1@0+ (1,0) [0|1] "" XXX BO_ 869 DSU_CRUISE : 7 DSU SG_ RES_BTN : 3|1@0+ (1,0) [0|0] "" XXX @@ -225,6 +227,7 @@ BO_ 921 PCM_CRUISE_SM: 8 XXX SG_ MAIN_ON : 4|1@0+ (1,0) [0|1] "" XXX SG_ CRUISE_CONTROL_STATE : 11|4@0+ (1,0) [0|15] "" XXX SG_ DISTANCE_LINES : 14|2@0+ (1,0) [0|3] "" XXX + SG_ TEMP_ACC_FAULTED : 15|1@0+ (1,0) [0|1] "" XXX SG_ UI_SET_SPEED : 31|8@0+ (1,0) [0|255] "" XXX BO_ 951 ESP_CONTROL: 8 ESP @@ -377,8 +380,7 @@ BO_ 1552 BODY_CONTROL_STATE_2: 8 XXX BO_ 1553 UI_SETTING: 8 XXX SG_ UNITS : 26|2@0+ (1,0) [0|3] "" XXX - SG_ ODOMETER : 43|20@0+ (1,0) [0|1048575] "" XXX - + SG_ ODOMETER : 39|32@0+ (1,0) [0|1048575] "" XXX BO_ 1556 BLINKERS_STATE: 8 XXX SG_ TURN_SIGNALS : 29|2@0+ (1,0) [0|3] "" XXX SG_ HAZARD_LIGHT : 27|1@0+ (1,0) [0|1] "" XXX @@ -416,6 +418,7 @@ CM_ SG_ 37 STEER_FRACTION "1/15th of the signal STEER_ANGLE, which is 1.5 deg; n CM_ SG_ 37 STEER_RATE "factor is tbd"; CM_ SG_ 466 ACCEL_NET "net acceleration produced by the system, given ACCEL_CMD, road grade and other factors"; CM_ SG_ 466 CRUISE_STATE "Active state is 8, if standstill is requested will switch to state 11(3 sec timer), after timer is elapsed will switch into state 7(standstill). If plus button was pressed - status 9, minus button pressed - status 10"; +CM_ SG_ 467 ACC_FAULTED "1 when ACC is faulted and the PCM disallows engagement"; CM_ SG_ 467 SET_SPEED "43 km/h are shown as 28 mph, so conversion isn't perfect"; CM_ SG_ 467 LOW_SPEED_LOCKOUT "in low speed lockout, system would always disengage below 28mph"; CM_ SG_ 560 BRAKE_PRESSED "another brake pressed?"; @@ -437,7 +440,9 @@ CM_ SG_ 835 PERMIT_BRAKING "Original ACC has this going high when a car in front CM_ SG_ 835 ACCEL_CMD_ALT "Copy of main ACCEL_CMD, but across 8 bits instead of 16 bits like ACCEL_CMD. Unsure if only informational or has an effect. Likely informational as existing openpilot sets this to 0 and no loss of functionality observed. Originally 'AT_RAW' in leaked toyota_2017_ref_pt.dbc file."; CM_ SG_ 865 GAS_PEDAL_ALT "copy of main GAS_PEDAL. Both use 8 bits. Might indicate that this message is for pedals."; CM_ SG_ 865 CLUTCH_RELEASED "boolean of clutch for 6MT."; +CM_ SG_ 865 ACC_FAULTED "1 when ACC is faulted and the PCM disallows engagement"; CM_ SG_ 921 UI_SET_SPEED "set speed shown in the vehicle's UI with the vehicle's unit"; +CM_ SG_ 921 TEMP_ACC_FAULTED "1 when the UI is displaying or playing fault-related alerts or sounds. Also 1 when pressing main on."; CM_ SG_ 951 BRAKE_LIGHTS_ACC "brake lights when ACC commands decel"; CM_ SG_ 956 GEAR "on 6MT, only R shows."; CM_ SG_ 1009 UI_SET_SPEED "units seem to be whatever the car is set to"; diff --git a/opendbc/toyota_nodsu_pt_generated.dbc b/opendbc/toyota_nodsu_pt_generated.dbc index 0c6e1cf9d..c51b56c05 100644 --- a/opendbc/toyota_nodsu_pt_generated.dbc +++ b/opendbc/toyota_nodsu_pt_generated.dbc @@ -106,7 +106,7 @@ BO_ 180 SPEED: 8 XXX SG_ CHECKSUM : 63|8@0+ (1,0) [0|255] "" XXX BO_ 295 GEAR_PACKET_HYBRID: 8 XXX - SG_ CAR_MOVEMENT : 39|8@0- (1,0) [0|255] "" XXX + SG_ CAR_MOVEMENT : 25|10@0- (1,0) [0|255] "" XXX SG_ COUNTER : 55|8@0+ (1,0) [0|255] "" XXX SG_ CHECKSUM : 63|8@0+ (1,0) [0|255] "" XXX SG_ GEAR : 47|4@0+ (1,0) [0|15] "" XXX @@ -128,9 +128,10 @@ BO_ 466 PCM_CRUISE: 8 XXX BO_ 467 PCM_CRUISE_2: 8 XXX SG_ PCM_FOLLOW_DISTANCE : 12|2@0+ (1,0) [0|3] "" XXX - SG_ MAIN_ON : 15|1@0+ (1,0) [0|1] "" XXX SG_ LOW_SPEED_LOCKOUT : 14|2@0+ (1,0) [0|3] "" XXX + SG_ MAIN_ON : 15|1@0+ (1,0) [0|1] "" XXX SG_ SET_SPEED : 23|8@0+ (1,0) [0|255] "km/h" XXX + SG_ ACC_FAULTED : 47|1@0+ (1,0) [0|1] "" XXX SG_ CHECKSUM : 63|8@0+ (1,0) [0|255] "" XXX BO_ 552 ACCELEROMETER: 8 XXX @@ -209,8 +210,9 @@ BO_ 836 PRE_COLLISION_2: 8 DSU SG_ CHECKSUM : 63|8@0+ (1,0) [0|0] "" XXX BO_ 865 CLUTCH: 8 XXX - SG_ CLUTCH_RELEASED : 38|1@0+ (1,0) [0|1] "" XXX + SG_ ACC_FAULTED : 32|1@0+ (1,0) [0|1] "" XXX SG_ GAS_PEDAL_ALT : 23|8@0+ (0.005,0) [0|1] "" XXX + SG_ CLUTCH_RELEASED : 38|1@0+ (1,0) [0|1] "" XXX BO_ 869 DSU_CRUISE : 7 DSU SG_ RES_BTN : 3|1@0+ (1,0) [0|0] "" XXX @@ -225,6 +227,7 @@ BO_ 921 PCM_CRUISE_SM: 8 XXX SG_ MAIN_ON : 4|1@0+ (1,0) [0|1] "" XXX SG_ CRUISE_CONTROL_STATE : 11|4@0+ (1,0) [0|15] "" XXX SG_ DISTANCE_LINES : 14|2@0+ (1,0) [0|3] "" XXX + SG_ TEMP_ACC_FAULTED : 15|1@0+ (1,0) [0|1] "" XXX SG_ UI_SET_SPEED : 31|8@0+ (1,0) [0|255] "" XXX BO_ 951 ESP_CONTROL: 8 ESP @@ -377,8 +380,7 @@ BO_ 1552 BODY_CONTROL_STATE_2: 8 XXX BO_ 1553 UI_SETTING: 8 XXX SG_ UNITS : 26|2@0+ (1,0) [0|3] "" XXX - SG_ ODOMETER : 43|20@0+ (1,0) [0|1048575] "" XXX - + SG_ ODOMETER : 39|32@0+ (1,0) [0|1048575] "" XXX BO_ 1556 BLINKERS_STATE: 8 XXX SG_ TURN_SIGNALS : 29|2@0+ (1,0) [0|3] "" XXX SG_ HAZARD_LIGHT : 27|1@0+ (1,0) [0|1] "" XXX @@ -416,6 +418,7 @@ CM_ SG_ 37 STEER_FRACTION "1/15th of the signal STEER_ANGLE, which is 1.5 deg; n CM_ SG_ 37 STEER_RATE "factor is tbd"; CM_ SG_ 466 ACCEL_NET "net acceleration produced by the system, given ACCEL_CMD, road grade and other factors"; CM_ SG_ 466 CRUISE_STATE "Active state is 8, if standstill is requested will switch to state 11(3 sec timer), after timer is elapsed will switch into state 7(standstill). If plus button was pressed - status 9, minus button pressed - status 10"; +CM_ SG_ 467 ACC_FAULTED "1 when ACC is faulted and the PCM disallows engagement"; CM_ SG_ 467 SET_SPEED "43 km/h are shown as 28 mph, so conversion isn't perfect"; CM_ SG_ 467 LOW_SPEED_LOCKOUT "in low speed lockout, system would always disengage below 28mph"; CM_ SG_ 560 BRAKE_PRESSED "another brake pressed?"; @@ -437,7 +440,9 @@ CM_ SG_ 835 PERMIT_BRAKING "Original ACC has this going high when a car in front CM_ SG_ 835 ACCEL_CMD_ALT "Copy of main ACCEL_CMD, but across 8 bits instead of 16 bits like ACCEL_CMD. Unsure if only informational or has an effect. Likely informational as existing openpilot sets this to 0 and no loss of functionality observed. Originally 'AT_RAW' in leaked toyota_2017_ref_pt.dbc file."; CM_ SG_ 865 GAS_PEDAL_ALT "copy of main GAS_PEDAL. Both use 8 bits. Might indicate that this message is for pedals."; CM_ SG_ 865 CLUTCH_RELEASED "boolean of clutch for 6MT."; +CM_ SG_ 865 ACC_FAULTED "1 when ACC is faulted and the PCM disallows engagement"; CM_ SG_ 921 UI_SET_SPEED "set speed shown in the vehicle's UI with the vehicle's unit"; +CM_ SG_ 921 TEMP_ACC_FAULTED "1 when the UI is displaying or playing fault-related alerts or sounds. Also 1 when pressing main on."; CM_ SG_ 951 BRAKE_LIGHTS_ACC "brake lights when ACC commands decel"; CM_ SG_ 956 GEAR "on 6MT, only R shows."; CM_ SG_ 1009 UI_SET_SPEED "units seem to be whatever the car is set to"; @@ -539,10 +544,11 @@ BO_ 401 STEERING_LTA: 8 XXX SG_ SETME_X64 : 47|8@0+ (1,0) [0|255] "" XXX SG_ ANGLE : 55|8@0- (0.5,0) [0|255] "" XXX SG_ STEER_ANGLE_CMD : 15|16@0- (0.0573,0) [-540|540] "" XXX - SG_ STEER_REQUEST : 25|1@0+ (1,0) [0|1] "" XXX + SG_ STEER_REQUEST_2 : 25|1@0+ (1,0) [0|1] "" XXX + SG_ LKA_ACTIVE : 26|1@0+ (1,0) [0|1] "" XXX SG_ BIT : 30|1@0+ (1,0) [0|1] "" XXX SG_ COUNTER : 6|6@0+ (1,0) [0|255] "" XXX - SG_ STEER_REQUEST_2 : 0|1@0+ (1,0) [0|1] "" XXX + SG_ STEER_REQUEST : 0|1@0+ (1,0) [0|1] "" XXX SG_ SETME_X1 : 7|1@0+ (1,0) [0|1] "" XXX BO_ 550 BRAKE_MODULE: 8 XXX @@ -569,9 +575,10 @@ CM_ SG_ 401 PERCENTAGE "driver override percentage (0-100), very close to steeri CM_ SG_ 401 SETME_X64 "ramps to 0 smoothly then back on falling edge of STEER_REQUEST if BIT isn't 1"; CM_ SG_ 401 ANGLE "angle of car relative to lane center on LTA camera"; CM_ SG_ 401 STEER_ANGLE_CMD "desired angle, OEM steers up to 95 degrees, no angle limit but torque will bottom out"; -CM_ SG_ 401 STEER_REQUEST "enable bit for steering, 1 to steer, 0 to not"; CM_ SG_ 401 BIT "has correlation to STEER_REQUEST"; +CM_ SG_ 401 STEER_REQUEST "enable bit for steering, 1 to steer, 0 to not"; CM_ SG_ 401 STEER_REQUEST_2 "enable bit for steering, 1 to steer, 0 to not"; +CM_ SG_ 401 LKA_ACTIVE "1 when using LTA for LKA"; CM_ SG_ 550 BRAKE_PRESSURE "seems prop to pedal force"; CM_ SG_ 550 BRAKE_POSITION "seems proportional to pedal displacement, unclear the max value of 0x1c8"; CM_ SG_ 610 TYPE "seems 1 on Corolla, 0 on all others"; @@ -583,5 +590,5 @@ CM_ SG_ 1014 ADJACENT_ENABLED "when BSM is enabled in settings, this is on along CM_ SG_ 1014 APPROACHING_ENABLED "when BSM is enabled in settings, this is on along with ADJACENT_ENABLED. this controls bsm alert visibility"; VAL_ 610 IPAS_STATE 5 "override" 3 "enabled" 1 "disabled"; -VAL_ 610 LKA_STATE 25 "temporary_fault" 17 "permanent_fault" 9 "temporary_fault2" 5 "active" 1 "standby"; -VAL_ 610 LTA_STATE 25 "temporary_fault" 9 "temporary_fault2" 5 "active" 1 "standby"; +VAL_ 610 LKA_STATE 25 "temporary_fault" 17 "permanent_fault" 11 "lka_missing_unavailable2" 9 "temporary_fault2" 5 "active" 3 "lka_missing_unavailable" 1 "standby"; +VAL_ 610 LTA_STATE 25 "temporary_fault" 9 "temporary_fault2" 5 "active" 3 "lta_missing_unavailable" 1 "standby"; diff --git a/opendbc/toyota_tnga_k_pt_generated.dbc b/opendbc/toyota_tnga_k_pt_generated.dbc index 2eaef636e..cf20ec697 100644 --- a/opendbc/toyota_tnga_k_pt_generated.dbc +++ b/opendbc/toyota_tnga_k_pt_generated.dbc @@ -106,7 +106,7 @@ BO_ 180 SPEED: 8 XXX SG_ CHECKSUM : 63|8@0+ (1,0) [0|255] "" XXX BO_ 295 GEAR_PACKET_HYBRID: 8 XXX - SG_ CAR_MOVEMENT : 39|8@0- (1,0) [0|255] "" XXX + SG_ CAR_MOVEMENT : 25|10@0- (1,0) [0|255] "" XXX SG_ COUNTER : 55|8@0+ (1,0) [0|255] "" XXX SG_ CHECKSUM : 63|8@0+ (1,0) [0|255] "" XXX SG_ GEAR : 47|4@0+ (1,0) [0|15] "" XXX @@ -128,9 +128,10 @@ BO_ 466 PCM_CRUISE: 8 XXX BO_ 467 PCM_CRUISE_2: 8 XXX SG_ PCM_FOLLOW_DISTANCE : 12|2@0+ (1,0) [0|3] "" XXX - SG_ MAIN_ON : 15|1@0+ (1,0) [0|1] "" XXX SG_ LOW_SPEED_LOCKOUT : 14|2@0+ (1,0) [0|3] "" XXX + SG_ MAIN_ON : 15|1@0+ (1,0) [0|1] "" XXX SG_ SET_SPEED : 23|8@0+ (1,0) [0|255] "km/h" XXX + SG_ ACC_FAULTED : 47|1@0+ (1,0) [0|1] "" XXX SG_ CHECKSUM : 63|8@0+ (1,0) [0|255] "" XXX BO_ 552 ACCELEROMETER: 8 XXX @@ -209,8 +210,9 @@ BO_ 836 PRE_COLLISION_2: 8 DSU SG_ CHECKSUM : 63|8@0+ (1,0) [0|0] "" XXX BO_ 865 CLUTCH: 8 XXX - SG_ CLUTCH_RELEASED : 38|1@0+ (1,0) [0|1] "" XXX + SG_ ACC_FAULTED : 32|1@0+ (1,0) [0|1] "" XXX SG_ GAS_PEDAL_ALT : 23|8@0+ (0.005,0) [0|1] "" XXX + SG_ CLUTCH_RELEASED : 38|1@0+ (1,0) [0|1] "" XXX BO_ 869 DSU_CRUISE : 7 DSU SG_ RES_BTN : 3|1@0+ (1,0) [0|0] "" XXX @@ -225,6 +227,7 @@ BO_ 921 PCM_CRUISE_SM: 8 XXX SG_ MAIN_ON : 4|1@0+ (1,0) [0|1] "" XXX SG_ CRUISE_CONTROL_STATE : 11|4@0+ (1,0) [0|15] "" XXX SG_ DISTANCE_LINES : 14|2@0+ (1,0) [0|3] "" XXX + SG_ TEMP_ACC_FAULTED : 15|1@0+ (1,0) [0|1] "" XXX SG_ UI_SET_SPEED : 31|8@0+ (1,0) [0|255] "" XXX BO_ 951 ESP_CONTROL: 8 ESP @@ -377,8 +380,7 @@ BO_ 1552 BODY_CONTROL_STATE_2: 8 XXX BO_ 1553 UI_SETTING: 8 XXX SG_ UNITS : 26|2@0+ (1,0) [0|3] "" XXX - SG_ ODOMETER : 43|20@0+ (1,0) [0|1048575] "" XXX - + SG_ ODOMETER : 39|32@0+ (1,0) [0|1048575] "" XXX BO_ 1556 BLINKERS_STATE: 8 XXX SG_ TURN_SIGNALS : 29|2@0+ (1,0) [0|3] "" XXX SG_ HAZARD_LIGHT : 27|1@0+ (1,0) [0|1] "" XXX @@ -416,6 +418,7 @@ CM_ SG_ 37 STEER_FRACTION "1/15th of the signal STEER_ANGLE, which is 1.5 deg; n CM_ SG_ 37 STEER_RATE "factor is tbd"; CM_ SG_ 466 ACCEL_NET "net acceleration produced by the system, given ACCEL_CMD, road grade and other factors"; CM_ SG_ 466 CRUISE_STATE "Active state is 8, if standstill is requested will switch to state 11(3 sec timer), after timer is elapsed will switch into state 7(standstill). If plus button was pressed - status 9, minus button pressed - status 10"; +CM_ SG_ 467 ACC_FAULTED "1 when ACC is faulted and the PCM disallows engagement"; CM_ SG_ 467 SET_SPEED "43 km/h are shown as 28 mph, so conversion isn't perfect"; CM_ SG_ 467 LOW_SPEED_LOCKOUT "in low speed lockout, system would always disengage below 28mph"; CM_ SG_ 560 BRAKE_PRESSED "another brake pressed?"; @@ -437,7 +440,9 @@ CM_ SG_ 835 PERMIT_BRAKING "Original ACC has this going high when a car in front CM_ SG_ 835 ACCEL_CMD_ALT "Copy of main ACCEL_CMD, but across 8 bits instead of 16 bits like ACCEL_CMD. Unsure if only informational or has an effect. Likely informational as existing openpilot sets this to 0 and no loss of functionality observed. Originally 'AT_RAW' in leaked toyota_2017_ref_pt.dbc file."; CM_ SG_ 865 GAS_PEDAL_ALT "copy of main GAS_PEDAL. Both use 8 bits. Might indicate that this message is for pedals."; CM_ SG_ 865 CLUTCH_RELEASED "boolean of clutch for 6MT."; +CM_ SG_ 865 ACC_FAULTED "1 when ACC is faulted and the PCM disallows engagement"; CM_ SG_ 921 UI_SET_SPEED "set speed shown in the vehicle's UI with the vehicle's unit"; +CM_ SG_ 921 TEMP_ACC_FAULTED "1 when the UI is displaying or playing fault-related alerts or sounds. Also 1 when pressing main on."; CM_ SG_ 951 BRAKE_LIGHTS_ACC "brake lights when ACC commands decel"; CM_ SG_ 956 GEAR "on 6MT, only R shows."; CM_ SG_ 1009 UI_SET_SPEED "units seem to be whatever the car is set to"; diff --git a/panda/.gitignore b/panda/.gitignore index 3a8ead1e0..403d034ee 100644 --- a/panda/.gitignore +++ b/panda/.gitignore @@ -18,3 +18,6 @@ examples/output.csv nosetests.xml .mypy_cache/ .sconsign.dblite + +# CTU info files generated by Cppcheck +*.*.ctu-info diff --git a/panda/SConscript b/panda/SConscript new file mode 100644 index 000000000..ed3aff754 --- /dev/null +++ b/panda/SConscript @@ -0,0 +1,6 @@ +# panda fw +SConscript('board/SConscript') + +# test files +if GetOption('test'): + SConscript('tests/libpanda/SConscript') diff --git a/panda/__init__.py b/panda/__init__.py index 6b2d73a87..8b43e9938 100644 --- a/panda/__init__.py +++ b/panda/__init__.py @@ -1,10 +1,5 @@ -# flake8: noqa -# pylint: skip-file -from .python import Panda, PandaDFU, flash_release, \ - BASEDIR, ensure_st_up_to_date, PandaSerial, pack_can_buffer, unpack_can_buffer, \ - DEFAULT_FW_FN, DEFAULT_H7_FW_FN, MCU_TYPE_H7, MCU_TYPE_F4, DLC_TO_LEN, LEN_TO_DLC, \ - ALTERNATIVE_EXPERIENCE - -from .python.config import BOOTSTUB_ADDRESS, BLOCK_SIZE_FX, APP_ADDRESS_FX, \ - BLOCK_SIZE_H7, APP_ADDRESS_H7, DEVICE_SERIAL_NUMBER_ADDR_H7, \ - DEVICE_SERIAL_NUMBER_ADDR_FX +from .python.constants import McuType, BASEDIR # noqa: F401 +from .python.serial import PandaSerial # noqa: F401 +from .python import (Panda, PandaDFU, # noqa: F401 + pack_can_buffer, unpack_can_buffer, calculate_checksum, + DLC_TO_LEN, LEN_TO_DLC, ALTERNATIVE_EXPERIENCE, USBPACKET_MAX_SIZE, CANPACKET_HEAD_SIZE) diff --git a/panda/board/README.md b/panda/board/README.md index 4bd4b89a2..4434d28af 100644 --- a/panda/board/README.md +++ b/panda/board/README.md @@ -1,20 +1,3 @@ -Dependencies --------- - -**Mac** - -``` -xcode-select --install -./get_sdk_mac.sh -``` - -**Debian / Ubuntu** - -``` -./get_sdk.sh -``` - - Programming ---- diff --git a/panda/board/boards/black.h b/panda/board/boards/black.h index 043df7ed4..7c7894874 100644 --- a/panda/board/boards/black.h +++ b/panda/board/boards/black.h @@ -17,7 +17,7 @@ void black_enable_can_transceiver(uint8_t transceiver, bool enabled) { set_gpio_output(GPIOB, 10, !enabled); break; default: - puts("Invalid CAN transceiver ("); puth(transceiver); puts("): enabling failed\n"); + print("Invalid CAN transceiver ("); puth(transceiver); print("): enabling failed\n"); break; } } @@ -74,7 +74,7 @@ void black_set_gps_mode(uint8_t mode) { set_gpio_output(GPIOC, 5, 0); break; default: - puts("Invalid GPS mode\n"); + print("Invalid GPS mode\n"); break; } } @@ -102,7 +102,7 @@ void black_set_can_mode(uint8_t mode){ } break; default: - puts("Tried to set unsupported CAN mode: "); puth(mode); puts("\n"); + print("Tried to set unsupported CAN mode: "); puth(mode); print("\n"); break; } } @@ -202,6 +202,6 @@ const board board_black = { .set_fan_enabled = unused_set_fan_enabled, .set_ir_power = unused_set_ir_power, .set_phone_power = unused_set_phone_power, - .set_clock_source_mode = unused_set_clock_source_mode, - .set_siren = unused_set_siren -}; + .set_siren = unused_set_siren, + .read_som_gpio = unused_read_som_gpio +}; \ No newline at end of file diff --git a/panda/board/boards/board_declarations.h b/panda/board/boards/board_declarations.h index b00f39fc3..4ca1fb4fe 100644 --- a/panda/board/boards/board_declarations.h +++ b/panda/board/boards/board_declarations.h @@ -10,9 +10,9 @@ typedef uint32_t (*board_read_current)(void); typedef void (*board_set_ir_power)(uint8_t percentage); typedef void (*board_set_fan_enabled)(bool enabled); typedef void (*board_set_phone_power)(bool enabled); -typedef void (*board_set_clock_source_mode)(uint8_t mode); typedef void (*board_set_siren)(bool enabled); typedef void (*board_board_tick)(bool ignition, bool usb_enum, bool heartbeat_seen); +typedef bool (*board_read_som_gpio)(void); struct board { const char *board_type; @@ -36,9 +36,9 @@ struct board { board_set_ir_power set_ir_power; board_set_fan_enabled set_fan_enabled; board_set_phone_power set_phone_power; - board_set_clock_source_mode set_clock_source_mode; board_set_siren set_siren; board_board_tick board_tick; + board_read_som_gpio read_som_gpio; }; // ******************* Definitions ******************** diff --git a/panda/board/boards/dos.h b/panda/board/boards/dos.h index 630a30952..d2e8f3df2 100644 --- a/panda/board/boards/dos.h +++ b/panda/board/boards/dos.h @@ -17,7 +17,7 @@ void dos_enable_can_transceiver(uint8_t transceiver, bool enabled) { set_gpio_output(GPIOB, 10, !enabled); break; default: - puts("Invalid CAN transceiver ("); puth(transceiver); puts("): enabling failed\n"); + print("Invalid CAN transceiver ("); puth(transceiver); print("): enabling failed\n"); break; } } @@ -88,7 +88,7 @@ void dos_set_can_mode(uint8_t mode){ } break; default: - puts("Tried to set unsupported CAN mode: "); puth(mode); puts("\n"); + print("Tried to set unsupported CAN mode: "); puth(mode); print("\n"); break; } } @@ -110,14 +110,14 @@ void dos_set_fan_enabled(bool enabled){ set_gpio_output(GPIOA, 1, enabled); } -void dos_set_clock_source_mode(uint8_t mode){ - clock_source_init(mode); -} - void dos_set_siren(bool enabled){ set_gpio_output(GPIOC, 12, enabled); } +bool dos_read_som_gpio (void){ + return (get_gpio_input(GPIOC, 2) != 0); +} + void dos_init(void) { common_init_gpio(); @@ -151,15 +151,15 @@ void dos_init(void) { // C8: FAN PWM aka TIM3_CH3 set_gpio_alternate(GPIOC, 8, GPIO_AF2_TIM3); + // C2: SOM GPIO used as input (fan control at boot) + set_gpio_mode(GPIOC, 2, MODE_INPUT); + set_gpio_pullup(GPIOC, 2, PULL_DOWN); + // Initialize IR PWM and set to 0% set_gpio_alternate(GPIOB, 7, GPIO_AF2_TIM4); pwm_init(TIM4, 2); dos_set_ir_power(0U); - // Initialize fan and set to 0% - fan_init(); - dos_set_fan_enabled(false); - // Initialize harness harness_init(); @@ -186,7 +186,7 @@ void dos_init(void) { } // Init clock source (camera strobe) using PWM - dos_set_clock_source_mode(CLOCK_SOURCE_MODE_PWM); + clock_source_init(); } const harness_configuration dos_harness_config = { @@ -230,6 +230,6 @@ const board board_dos = { .set_fan_enabled = dos_set_fan_enabled, .set_ir_power = dos_set_ir_power, .set_phone_power = unused_set_phone_power, - .set_clock_source_mode = dos_set_clock_source_mode, - .set_siren = dos_set_siren + .set_siren = dos_set_siren, + .read_som_gpio = dos_read_som_gpio }; diff --git a/panda/board/boards/grey.h b/panda/board/boards/grey.h index 94d66bf9f..1ee5efb0a 100644 --- a/panda/board/boards/grey.h +++ b/panda/board/boards/grey.h @@ -28,7 +28,7 @@ void grey_set_gps_mode(uint8_t mode) { set_gpio_output(GPIOC, 5, 0); break; default: - puts("Invalid ESP/GPS mode\n"); + print("Invalid ESP/GPS mode\n"); break; } } @@ -56,6 +56,6 @@ const board board_grey = { .set_fan_enabled = unused_set_fan_enabled, .set_ir_power = unused_set_ir_power, .set_phone_power = unused_set_phone_power, - .set_clock_source_mode = unused_set_clock_source_mode, - .set_siren = unused_set_siren + .set_siren = unused_set_siren, + .read_som_gpio = unused_read_som_gpio }; diff --git a/panda/board/boards/pedal.h b/panda/board/boards/pedal.h index fe9a8ff44..a2c51f3d5 100644 --- a/panda/board/boards/pedal.h +++ b/panda/board/boards/pedal.h @@ -8,7 +8,7 @@ void pedal_enable_can_transceiver(uint8_t transceiver, bool enabled) { set_gpio_output(GPIOB, 3, !enabled); break; default: - puts("Invalid CAN transceiver ("); puth(transceiver); puts("): enabling failed\n"); + print("Invalid CAN transceiver ("); puth(transceiver); print("): enabling failed\n"); break; } } @@ -32,7 +32,7 @@ void pedal_set_led(uint8_t color, bool enabled) { void pedal_set_gps_mode(uint8_t mode) { UNUSED(mode); - puts("Trying to set ESP/GPS mode on pedal. This is not supported.\n"); + print("Trying to set ESP/GPS mode on pedal. This is not supported.\n"); } void pedal_set_can_mode(uint8_t mode){ @@ -40,7 +40,7 @@ void pedal_set_can_mode(uint8_t mode){ case CAN_MODE_NORMAL: break; default: - puts("Tried to set unsupported CAN mode: "); puth(mode); puts("\n"); + print("Tried to set unsupported CAN mode: "); puth(mode); print("\n"); break; } } @@ -94,6 +94,6 @@ const board board_pedal = { .set_fan_enabled = unused_set_fan_enabled, .set_ir_power = unused_set_ir_power, .set_phone_power = unused_set_phone_power, - .set_clock_source_mode = unused_set_clock_source_mode, - .set_siren = unused_set_siren + .set_siren = unused_set_siren, + .read_som_gpio = unused_read_som_gpio }; diff --git a/panda/board/boards/red.h b/panda/board/boards/red.h index 76f06faa7..0a333cdf4 100644 --- a/panda/board/boards/red.h +++ b/panda/board/boards/red.h @@ -49,10 +49,6 @@ void red_set_led(uint8_t color, bool enabled) { } } -void red_set_usb_load_switch(bool enabled) { - set_gpio_output(GPIOB, 14, !enabled); -} - void red_set_can_mode(uint8_t mode) { switch (mode) { case CAN_MODE_NORMAL: @@ -123,16 +119,15 @@ void red_init(void) { set_gpio_pullup(GPIOB, 4, PULL_NONE); set_gpio_mode(GPIOB, 4, MODE_OUTPUT); - // B14: usb load switch - set_gpio_pullup(GPIOB, 14, PULL_NONE); - set_gpio_mode(GPIOB, 14, MODE_OUTPUT); - //B1: 5VOUT_S set_gpio_pullup(GPIOB, 1, PULL_NONE); set_gpio_mode(GPIOB, 1, MODE_ANALOG); - // Turn on USB load switch. - red_set_usb_load_switch(true); + // B14: usb load switch, enabled by pull resistor on board, obsolete for red panda + set_gpio_output_type(GPIOB, 14, OUTPUT_TYPE_OPEN_DRAIN); + set_gpio_pullup(GPIOB, 14, PULL_UP); + set_gpio_mode(GPIOB, 14, MODE_OUTPUT); + set_gpio_output(GPIOB, 14, 1); // Initialize harness harness_init(); @@ -194,6 +189,6 @@ const board board_red = { .set_fan_enabled = unused_set_fan_enabled, .set_ir_power = unused_set_ir_power, .set_phone_power = unused_set_phone_power, - .set_clock_source_mode = unused_set_clock_source_mode, - .set_siren = unused_set_siren + .set_siren = unused_set_siren, + .read_som_gpio = unused_read_som_gpio }; diff --git a/panda/board/boards/red_chiplet.h b/panda/board/boards/red_chiplet.h index 3eb09832d..52e52c338 100644 --- a/panda/board/boards/red_chiplet.h +++ b/panda/board/boards/red_chiplet.h @@ -35,23 +35,23 @@ void red_chiplet_enable_can_transceivers(bool enabled) { } } -void red_chiplet_set_usb_load_switch(bool enabled) { +void red_chiplet_set_fan_or_usb_load_switch(bool enabled) { set_gpio_output(GPIOD, 3, enabled); } void red_chiplet_init(void) { common_init_gpio(); - //A8, A9 : OBD_SBU1_RELAY, OBD_SBU2_RELAY + // A8, A3: OBD_SBU1_RELAY, OBD_SBU2_RELAY set_gpio_output_type(GPIOA, 8, OUTPUT_TYPE_OPEN_DRAIN); set_gpio_pullup(GPIOA, 8, PULL_NONE); set_gpio_mode(GPIOA, 8, MODE_OUTPUT); set_gpio_output(GPIOA, 8, 1); - set_gpio_output_type(GPIOA, 9, OUTPUT_TYPE_OPEN_DRAIN); - set_gpio_pullup(GPIOA, 9, PULL_NONE); - set_gpio_mode(GPIOA, 9, MODE_OUTPUT); - set_gpio_output(GPIOA, 9, 1); + set_gpio_output_type(GPIOA, 3, OUTPUT_TYPE_OPEN_DRAIN); + set_gpio_pullup(GPIOA, 3, PULL_NONE); + set_gpio_mode(GPIOA, 3, MODE_OUTPUT); + set_gpio_output(GPIOA, 3, 1); // G11,B10,D7,B11: transceiver enable set_gpio_pullup(GPIOG, 11, PULL_NONE); @@ -75,7 +75,7 @@ void red_chiplet_init(void) { set_gpio_mode(GPIOB, 0, MODE_ANALOG); // Turn on USB load switch. - red_chiplet_set_usb_load_switch(true); + red_chiplet_set_fan_or_usb_load_switch(true); // Initialize harness harness_init(); @@ -109,7 +109,7 @@ const harness_configuration red_chiplet_harness_config = { .pin_SBU1 = 4, .pin_SBU2 = 1, .pin_relay_SBU1 = 8, - .pin_relay_SBU2 = 9, + .pin_relay_SBU2 = 3, .adc_channel_SBU1 = 4, // ADC12_INP4 .adc_channel_SBU2 = 17 // ADC1_INP17 }; diff --git a/panda/board/boards/red_v2.h b/panda/board/boards/red_v2.h index 8527432ee..5b613358e 100644 --- a/panda/board/boards/red_v2.h +++ b/panda/board/boards/red_v2.h @@ -25,6 +25,5 @@ const board board_red_v2 = { .set_fan_enabled = unused_set_fan_enabled, .set_ir_power = unused_set_ir_power, .set_phone_power = unused_set_phone_power, - .set_clock_source_mode = unused_set_clock_source_mode, .set_siren = unused_set_siren }; diff --git a/panda/board/boards/tres.h b/panda/board/boards/tres.h index 095783a86..c1b792c41 100644 --- a/panda/board/boards/tres.h +++ b/panda/board/boards/tres.h @@ -2,6 +2,47 @@ // Tres + Harness // // ///////////////// +bool tres_ir_enabled; +bool tres_fan_enabled; +void tres_update_fan_ir_power(void) { + red_chiplet_set_fan_or_usb_load_switch(tres_ir_enabled || tres_fan_enabled); +} + +void tres_set_ir_power(uint8_t percentage){ + tres_ir_enabled = (percentage > 0U); + tres_update_fan_ir_power(); + pwm_set(TIM3, 4, percentage); +} + +void tres_set_bootkick(bool enabled){ + set_gpio_output(GPIOA, 0, !enabled); +} + +bool tres_ignition_prev = false; +void tres_board_tick(bool ignition, bool usb_enum, bool heartbeat_seen) { + UNUSED(usb_enum); + if (ignition && !tres_ignition_prev) { + // enable bootkick on rising edge of ignition + tres_set_bootkick(true); + } else if (heartbeat_seen) { + // disable once openpilot is up + tres_set_bootkick(false); + } else { + + } + tres_ignition_prev = ignition; +} + +void tres_set_fan_enabled(bool enabled) { + // NOTE: fan controller reset doesn't work on a tres if IR is enabled + tres_fan_enabled = enabled; + tres_update_fan_ir_power(); +} + +bool tres_read_som_gpio (void){ + return (get_gpio_input(GPIOC, 2) != 0); +} + void tres_init(void) { // Enable USB 3.3V LDO for USB block register_set_bits(&(PWR->CR3), PWR_CR3_USBREGEN); @@ -10,17 +51,44 @@ void tres_init(void) { red_chiplet_init(); + // C2: SOM GPIO used as input (fan control at boot) + set_gpio_mode(GPIOC, 2, MODE_INPUT); + set_gpio_pullup(GPIOC, 2, PULL_DOWN); + + tres_set_bootkick(true); + + // SOM debugging UART + gpio_uart7_init(); + uart_init(&uart_ring_som_debug, 115200); + // SPI init set_gpio_alternate(GPIOE, 11, GPIO_AF5_SPI4); set_gpio_alternate(GPIOE, 12, GPIO_AF5_SPI4); set_gpio_alternate(GPIOE, 13, GPIO_AF5_SPI4); set_gpio_alternate(GPIOE, 14, GPIO_AF5_SPI4); register_set_bits(&(GPIOE->OSPEEDR), GPIO_OSPEEDR_OSPEED11 | GPIO_OSPEEDR_OSPEED12 | GPIO_OSPEEDR_OSPEED13 | GPIO_OSPEEDR_OSPEED14); + + // fan setup + set_gpio_alternate(GPIOC, 8, GPIO_AF2_TIM3); + + // Initialize IR PWM and set to 0% + set_gpio_alternate(GPIOC, 9, GPIO_AF2_TIM3); + pwm_init(TIM3, 4); + tres_set_ir_power(0U); + + // Fake siren + set_gpio_alternate(GPIOC, 10, GPIO_AF4_I2C5); + set_gpio_alternate(GPIOC, 11, GPIO_AF4_I2C5); + register_set_bits(&(GPIOC->OTYPER), GPIO_OTYPER_OT10 | GPIO_OTYPER_OT11); // open drain + fake_siren_init(); + + // Clock source + clock_source_init(); } const board board_tres = { .board_type = "Tres", - .board_tick = unused_board_tick, + .board_tick = tres_board_tick, .harness_config = &red_chiplet_harness_config, .has_gps = false, .has_hw_gmlan = false, @@ -29,7 +97,7 @@ const board board_tres = { .has_spi = true, .has_canfd = true, .has_rtc_battery = true, - .fan_max_rpm = 0U, + .fan_max_rpm = 6500U, // TODO: verify this, copied from dos .init = tres_init, .enable_can_transceiver = red_chiplet_enable_can_transceiver, .enable_can_transceivers = red_chiplet_enable_can_transceivers, @@ -38,9 +106,9 @@ const board board_tres = { .set_can_mode = red_set_can_mode, .check_ignition = red_check_ignition, .read_current = unused_read_current, - .set_fan_enabled = unused_set_fan_enabled, - .set_ir_power = unused_set_ir_power, + .set_fan_enabled = tres_set_fan_enabled, + .set_ir_power = tres_set_ir_power, .set_phone_power = unused_set_phone_power, - .set_clock_source_mode = unused_set_clock_source_mode, - .set_siren = unused_set_siren + .set_siren = fake_siren_set, + .read_som_gpio = tres_read_som_gpio }; diff --git a/panda/board/boards/uno.h b/panda/board/boards/uno.h index d4461b497..feb784dd3 100644 --- a/panda/board/boards/uno.h +++ b/panda/board/boards/uno.h @@ -19,7 +19,7 @@ void uno_enable_can_transceiver(uint8_t transceiver, bool enabled) { set_gpio_output(GPIOB, 10, !enabled); break; default: - puts("Invalid CAN transceiver ("); puth(transceiver); puts("): enabling failed\n"); + print("Invalid CAN transceiver ("); puth(transceiver); print("): enabling failed\n"); break; } } @@ -93,7 +93,7 @@ void uno_set_gps_mode(uint8_t mode) { uno_set_gps_load_switch(true); break; default: - puts("Invalid ESP/GPS mode\n"); + print("Invalid ESP/GPS mode\n"); break; } } @@ -121,7 +121,7 @@ void uno_set_can_mode(uint8_t mode){ } break; default: - puts("Tried to set unsupported CAN mode: "); puth(mode); puts("\n"); + print("Tried to set unsupported CAN mode: "); puth(mode); print("\n"); break; } } @@ -192,10 +192,6 @@ void uno_init(void) { pwm_init(TIM4, 2); uno_set_ir_power(0U); - // Initialize fan and set to 0% - fan_init(); - uno_set_fan_enabled(false); - // Initialize harness harness_init(); @@ -266,6 +262,6 @@ const board board_uno = { .set_fan_enabled = uno_set_fan_enabled, .set_ir_power = uno_set_ir_power, .set_phone_power = uno_set_phone_power, - .set_clock_source_mode = unused_set_clock_source_mode, - .set_siren = unused_set_siren + .set_siren = unused_set_siren, + .read_som_gpio = unused_read_som_gpio }; diff --git a/panda/board/boards/unused_funcs.h b/panda/board/boards/unused_funcs.h index be675d89f..d3b4a4c6c 100644 --- a/panda/board/boards/unused_funcs.h +++ b/panda/board/boards/unused_funcs.h @@ -14,10 +14,6 @@ void unused_set_phone_power(bool enabled) { UNUSED(enabled); } -void unused_set_clock_source_mode(uint8_t mode) { - UNUSED(mode); -} - void unused_set_siren(bool enabled) { UNUSED(enabled); } @@ -30,4 +26,8 @@ void unused_board_tick(bool ignition, bool usb_enum, bool heartbeat_seen) { UNUSED(ignition); UNUSED(usb_enum); UNUSED(heartbeat_seen); +} + +bool unused_read_som_gpio(void) { + return false; } \ No newline at end of file diff --git a/panda/board/boards/white.h b/panda/board/boards/white.h index 15335494c..0fe817db1 100644 --- a/panda/board/boards/white.h +++ b/panda/board/boards/white.h @@ -14,7 +14,7 @@ void white_enable_can_transceiver(uint8_t transceiver, bool enabled) { set_gpio_output(GPIOA, 0, !enabled); break; default: - puts("Invalid CAN transceiver ("); puth(transceiver); puts("): enabling failed\n"); + print("Invalid CAN transceiver ("); puth(transceiver); print("): enabling failed\n"); break; } } @@ -60,7 +60,7 @@ void white_set_usb_power_mode(uint8_t mode){ set_gpio_output(GPIOA, 13, 0); break; default: - puts("Invalid usb power mode\n"); + print("Invalid usb power mode\n"); break; } } @@ -77,7 +77,7 @@ void white_set_gps_mode(uint8_t mode) { set_gpio_output(GPIOC, 5, 0); break; default: - puts("Invalid ESP/GPS mode\n"); + print("Invalid ESP/GPS mode\n"); break; } } @@ -136,7 +136,7 @@ void white_set_can_mode(uint8_t mode){ set_gpio_alternate(GPIOB, 6, GPIO_AF9_CAN2); break; default: - puts("Tried to set unsupported CAN mode: "); puth(mode); puts("\n"); + print("Tried to set unsupported CAN mode: "); puth(mode); print("\n"); break; } } @@ -258,6 +258,6 @@ const board board_white = { .set_fan_enabled = unused_set_fan_enabled, .set_ir_power = unused_set_ir_power, .set_phone_power = unused_set_phone_power, - .set_clock_source_mode = unused_set_clock_source_mode, - .set_siren = unused_set_siren + .set_siren = unused_set_siren, + .read_som_gpio = unused_read_som_gpio }; diff --git a/panda/board/bootstub_declarations.h b/panda/board/bootstub_declarations.h index 06a24a2b9..ae115d2eb 100644 --- a/panda/board/bootstub_declarations.h +++ b/panda/board/bootstub_declarations.h @@ -1,5 +1,5 @@ // ******************** Prototypes ******************** -void puts(const char *a){ UNUSED(a); } +void print(const char *a){ UNUSED(a); } void puth(uint8_t i){ UNUSED(i); } void puth2(uint8_t i){ UNUSED(i); } void puth4(uint8_t i){ UNUSED(i); } @@ -10,6 +10,10 @@ typedef struct harness_configuration harness_configuration; void can_flip_buses(uint8_t bus1, uint8_t bus2){UNUSED(bus1); UNUSED(bus2);} void pwm_init(TIM_TypeDef *TIM, uint8_t channel); void pwm_set(TIM_TypeDef *TIM, uint8_t channel, uint8_t percentage); +// No UART support in bootloader +typedef struct uart_ring {} uart_ring; +uart_ring uart_ring_som_debug; +void uart_init(uart_ring *q, int baud) { UNUSED(q); UNUSED(baud); } // ********************* Globals ********************** uint8_t hw_type = 0; diff --git a/panda/board/can_comms.h b/panda/board/can_comms.h new file mode 100644 index 000000000..02bc0aef3 --- /dev/null +++ b/panda/board/can_comms.h @@ -0,0 +1,117 @@ +/* + CAN transactions to and from the host come in the form of + a certain number of CANPacket_t. The transaction is split + into multiple transfers or chunks. + + * comms_can_read outputs this buffer in chunks of a specified length. + chunks are always the given length, except the last one. + * comms_can_write reads in this buffer in chunks. + * both functions maintain an overflow buffer for a partial CANPacket_t that + spans multiple transfers/chunks. + * the overflow buffers are reset by a dedicated control transfer handler, + which is sent by the host on each start of a connection. +*/ + +typedef struct { + uint32_t ptr; + uint32_t tail_size; + uint8_t data[72]; +} asm_buffer; + +asm_buffer can_read_buffer = {.ptr = 0U, .tail_size = 0U}; + +int comms_can_read(uint8_t *data, uint32_t max_len) { + uint32_t pos = 0U; + + // Send tail of previous message if it is in buffer + if (can_read_buffer.ptr > 0U) { + uint32_t overflow_len = MIN(max_len - pos, can_read_buffer.ptr); + (void)memcpy(&data[pos], can_read_buffer.data, overflow_len); + pos += overflow_len; + (void)memcpy(can_read_buffer.data, &can_read_buffer.data[overflow_len], can_read_buffer.ptr - overflow_len); + can_read_buffer.ptr -= overflow_len; + } + + if (can_read_buffer.ptr == 0U) { + // Fill rest of buffer with new data + CANPacket_t can_packet; + while ((pos < max_len) && can_pop(&can_rx_q, &can_packet)) { + uint32_t pckt_len = CANPACKET_HEAD_SIZE + dlc_to_len[can_packet.data_len_code]; + if ((pos + pckt_len) <= max_len) { + (void)memcpy(&data[pos], &can_packet, pckt_len); + pos += pckt_len; + } else { + (void)memcpy(&data[pos], &can_packet, max_len - pos); + can_read_buffer.ptr += pckt_len - (max_len - pos); + // cppcheck-suppress objectIndex + (void)memcpy(can_read_buffer.data, &((uint8_t*)&can_packet)[(max_len - pos)], can_read_buffer.ptr); + pos = max_len; + } + } + } + + return pos; +} + +asm_buffer can_write_buffer = {.ptr = 0U, .tail_size = 0U}; + +// send on CAN +void comms_can_write(uint8_t *data, uint32_t len) { + uint32_t pos = 0U; + + // Assembling can message with data from buffer + if (can_write_buffer.ptr != 0U) { + if (can_write_buffer.tail_size <= (len - pos)) { + // we have enough data to complete the buffer + CANPacket_t to_push; + (void)memcpy(&can_write_buffer.data[can_write_buffer.ptr], &data[pos], can_write_buffer.tail_size); + can_write_buffer.ptr += can_write_buffer.tail_size; + pos += can_write_buffer.tail_size; + + // send out + (void)memcpy(&to_push, can_write_buffer.data, can_write_buffer.ptr); + can_send(&to_push, to_push.bus, false); + + // reset overflow buffer + can_write_buffer.ptr = 0U; + can_write_buffer.tail_size = 0U; + } else { + // maybe next time + uint32_t data_size = len - pos; + (void) memcpy(&can_write_buffer.data[can_write_buffer.ptr], &data[pos], data_size); + can_write_buffer.tail_size -= data_size; + can_write_buffer.ptr += data_size; + pos += data_size; + } + } + + // rest of the message + while (pos < len) { + uint32_t pckt_len = CANPACKET_HEAD_SIZE + dlc_to_len[(data[pos] >> 4U)]; + if ((pos + pckt_len) <= len) { + CANPacket_t to_push; + (void)memcpy(&to_push, &data[pos], pckt_len); + can_send(&to_push, to_push.bus, false); + pos += pckt_len; + } else { + (void)memcpy(can_write_buffer.data, &data[pos], len - pos); + can_write_buffer.ptr = len - pos; + can_write_buffer.tail_size = pckt_len - can_write_buffer.ptr; + pos += can_write_buffer.ptr; + } + } +} + +void comms_can_reset(void) { + can_write_buffer.ptr = 0U; + can_write_buffer.tail_size = 0U; + can_read_buffer.ptr = 0U; + can_read_buffer.tail_size = 0U; +} + +// TODO: make this more general! +void usb_cb_ep3_out_complete(void) { + if (can_tx_check_min_slots_free(MAX_CAN_MSGS_PER_BULK_TRANSFER)) { + usb_outep3_resume_if_paused(); + } +} diff --git a/panda/board/can_definitions.h b/panda/board/can_definitions.h index 0de2da8a3..daae4d044 100644 --- a/panda/board/can_definitions.h +++ b/panda/board/can_definitions.h @@ -1,25 +1,34 @@ -#include "dlc_to_len.h" +#pragma once + +const uint8_t PANDA_CAN_CNT = 3U; +const uint8_t PANDA_BUS_CNT = 4U; + +// bump this when changing the CAN packet +#define CAN_PACKET_VERSION 4 + +#define CANPACKET_HEAD_SIZE 6U + +#if !defined(STM32F4) && !defined(STM32F2) + #define CANFD + #define CANPACKET_DATA_SIZE_MAX 64U +#else + #define CANPACKET_DATA_SIZE_MAX 8U +#endif -#define CAN_PACKET_VERSION 2 typedef struct { unsigned char reserved : 1; unsigned char bus : 3; - unsigned char data_len_code : 4; + unsigned char data_len_code : 4; // lookup length with dlc_to_len unsigned char rejected : 1; unsigned char returned : 1; unsigned char extended : 1; unsigned int addr : 29; + unsigned char checksum; unsigned char data[CANPACKET_DATA_SIZE_MAX]; } __attribute__((packed, aligned(4))) CANPacket_t; +const unsigned char dlc_to_len[] = {0U, 1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 12U, 16U, 20U, 24U, 32U, 48U, 64U}; + #define GET_BUS(msg) ((msg)->bus) #define GET_LEN(msg) (dlc_to_len[(msg)->data_len_code]) #define GET_ADDR(msg) ((msg)->addr) - -// Flasher and pedal use raw mailbox access -#define GET_MAILBOX_BYTE(msg, b) (((int)(b) > 3) ? (((msg)->RDHR >> (8U * ((unsigned int)(b) % 4U))) & 0xFFU) : (((msg)->RDLR >> (8U * (unsigned int)(b))) & 0xFFU)) -#define GET_MAILBOX_BYTES_04(msg) ((msg)->RDLR) -#define GET_MAILBOX_BYTES_48(msg) ((msg)->RDHR) - -#define WORD_TO_BYTE_ARRAY(dst8, src32) 0[dst8] = ((src32) & 0xFFU); 1[dst8] = (((src32) >> 8U) & 0xFFU); 2[dst8] = (((src32) >> 16U) & 0xFFU); 3[dst8] = (((src32) >> 24U) & 0xFFU) -#define BYTE_ARRAY_TO_WORD(dst32, src8) ((dst32) = 0[src8] | (1[src8] << 8U) | (2[src8] << 16U) | (3[src8] << 24U)) diff --git a/panda/board/comms_definitions.h b/panda/board/comms_definitions.h index 66ffe7e55..d074514de 100644 --- a/panda/board/comms_definitions.h +++ b/panda/board/comms_definitions.h @@ -9,3 +9,4 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp); void comms_endpoint2_write(uint8_t *data, uint32_t len); void comms_can_write(uint8_t *data, uint32_t len); int comms_can_read(uint8_t *data, uint32_t max_len); +void comms_can_reset(void); diff --git a/panda/board/config.h b/panda/board/config.h index cc54df256..1eb7de69b 100644 --- a/panda/board/config.h +++ b/panda/board/config.h @@ -1,5 +1,6 @@ -#ifndef PANDA_CONFIG_H -#define PANDA_CONFIG_H +#pragma once + +#include //#define DEBUG //#define DEBUG_UART @@ -8,31 +9,27 @@ //#define DEBUG_FAULTS //#define DEBUG_COMMS +#define CAN_INIT_TIMEOUT_MS 500U #define DEEPSLEEP_WAKEUP_DELAY 3U +#define USBPACKET_MAX_SIZE 0x40U +#define MAX_CAN_MSGS_PER_BULK_TRANSFER 51U -#define NULL ((void*)0) -#define COMPILE_TIME_ASSERT(pred) ((void)sizeof(char[1 - (2 * ((int)(!(pred))))])) +// USB definitions +#define USB_VID 0xBBAAU -#define MIN(a,b) \ - ({ __typeof__ (a) _a = (a); \ - __typeof__ (b) _b = (b); \ - (_a < _b) ? _a : _b; }) +#ifdef BOOTSTUB + #define USB_PID 0xDDEEU +#else + #define USB_PID 0xDDCCU +#endif -#define MAX(a,b) \ - ({ __typeof__ (a) _a = (a); \ - __typeof__ (b) _b = (b); \ - (_a > _b) ? _a : _b; }) - -#define ABS(a) \ - ({ __typeof__ (a) _a = (a); \ - (_a > 0) ? _a : (-_a); }) - -#include -#include "panda.h" +// platform includes #ifdef STM32H7 #include "stm32h7/stm32h7_config.h" -#else +#elif defined(STM32F2) || defined(STM32F4) #include "stm32fx/stm32fx_config.h" -#endif - +#else + // TODO: uncomment this, cppcheck complains + // building for tests + //#include "fake_stm.h" #endif diff --git a/panda/board/dlc_to_len.h b/panda/board/dlc_to_len.h deleted file mode 100644 index 82d743ffb..000000000 --- a/panda/board/dlc_to_len.h +++ /dev/null @@ -1 +0,0 @@ -unsigned char dlc_to_len[] = {0U, 1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 12U, 16U, 20U, 24U, 32U, 48U, 64U}; diff --git a/panda/board/drivers/bxcan.h b/panda/board/drivers/bxcan.h index 9cab16412..8c4a3fc46 100644 --- a/panda/board/drivers/bxcan.h +++ b/panda/board/drivers/bxcan.h @@ -27,9 +27,9 @@ void can_set_gmlan(uint8_t bus) { switch (prev_bus) { case 1: case 2: - puts("Disable GMLAN on CAN"); + print("Disable GMLAN on CAN"); puth(prev_bus + 1U); - puts("\n"); + print("\n"); current_board->set_can_mode(CAN_MODE_NORMAL); bus_config[prev_bus].bus_lookup = prev_bus; bus_config[prev_bus].can_num_lookup = prev_bus; @@ -47,9 +47,9 @@ void can_set_gmlan(uint8_t bus) { switch (bus) { case 1: case 2: - puts("Enable GMLAN on CAN"); + print("Enable GMLAN on CAN"); puth(bus + 1U); - puts("\n"); + print("\n"); current_board->set_can_mode((bus == 1U) ? CAN_MODE_GMLAN_CAN2 : CAN_MODE_GMLAN_CAN3); bus_config[bus].bus_lookup = 3; bus_config[bus].can_num_lookup = -1; @@ -60,11 +60,11 @@ void can_set_gmlan(uint8_t bus) { case 0xFF: //-1 unsigned break; default: - puts("GMLAN can only be set on CAN2 or CAN3\n"); + print("GMLAN can only be set on CAN2 or CAN3\n"); break; } } else { - puts("GMLAN not available on black panda\n"); + print("GMLAN not available on black panda\n"); } } @@ -127,6 +127,7 @@ void process_can(uint8_t can_number) { to_push.bus = bus_number; WORD_TO_BYTE_ARRAY(&to_push.data[0], CAN->sTxMailBox[0].TDLR); WORD_TO_BYTE_ARRAY(&to_push.data[4], CAN->sTxMailBox[0].TDHR); + can_set_checksum(&to_push); rx_buffer_overflow += can_push(&can_rx_q, &to_push) ? 0U : 1U; } @@ -137,14 +138,18 @@ void process_can(uint8_t can_number) { } if (can_pop(can_queues[bus_number], &to_send)) { - can_health[can_number].total_tx_cnt += 1U; - // only send if we have received a packet - CAN->sTxMailBox[0].TIR = ((to_send.extended != 0U) ? (to_send.addr << 3) : (to_send.addr << 21)) | (to_send.extended << 2); - CAN->sTxMailBox[0].TDTR = to_send.data_len_code; - BYTE_ARRAY_TO_WORD(CAN->sTxMailBox[0].TDLR, &to_send.data[0]); - BYTE_ARRAY_TO_WORD(CAN->sTxMailBox[0].TDHR, &to_send.data[4]); - // Send request TXRQ - CAN->sTxMailBox[0].TIR |= 0x1U; + if (can_check_checksum(&to_send)) { + can_health[can_number].total_tx_cnt += 1U; + // only send if we have received a packet + CAN->sTxMailBox[0].TIR = ((to_send.extended != 0U) ? (to_send.addr << 3) : (to_send.addr << 21)) | (to_send.extended << 2); + CAN->sTxMailBox[0].TDTR = to_send.data_len_code; + BYTE_ARRAY_TO_WORD(CAN->sTxMailBox[0].TDLR, &to_send.data[0]); + BYTE_ARRAY_TO_WORD(CAN->sTxMailBox[0].TDHR, &to_send.data[4]); + // Send request TXRQ + CAN->sTxMailBox[0].TIR |= 0x1U; + } else { + can_health[can_number].total_tx_checksum_error_cnt += 1U; + } usb_cb_ep3_out_complete(); } @@ -183,6 +188,7 @@ void can_rx(uint8_t can_number) { to_push.bus = bus_number; WORD_TO_BYTE_ARRAY(&to_push.data[0], CAN->sFIFOMailBox[0].RDLR); WORD_TO_BYTE_ARRAY(&to_push.data[4], CAN->sFIFOMailBox[0].RDHR); + can_set_checksum(&to_push); // forwarding (panda only) int bus_fwd_num = safety_fwd_hook(bus_number, &to_push); @@ -196,6 +202,8 @@ void can_rx(uint8_t can_number) { to_send.bus = to_push.bus; to_send.data_len_code = to_push.data_len_code; (void)memcpy(to_send.data, to_push.data, dlc_to_len[to_push.data_len_code]); + can_set_checksum(&to_send); + can_send(&to_send, bus_fwd_num, true); can_health[can_number].total_fwd_cnt += 1U; } diff --git a/panda/board/drivers/can_common.h b/panda/board/drivers/can_common.h index 0af0b41b0..9fd2743d6 100644 --- a/panda/board/drivers/can_common.h +++ b/panda/board/drivers/can_common.h @@ -66,6 +66,10 @@ can_buffer(tx3_q, 0x1A0) // cppcheck-suppress misra-c2012-9.3 can_ring *can_queues[] = {&can_tx1_q, &can_tx2_q, &can_tx3_q, &can_txgmlan_q}; +// helpers +#define WORD_TO_BYTE_ARRAY(dst8, src32) 0[dst8] = ((src32) & 0xFFU); 1[dst8] = (((src32) >> 8U) & 0xFFU); 2[dst8] = (((src32) >> 16U) & 0xFFU); 3[dst8] = (((src32) >> 24U) & 0xFFU) +#define BYTE_ARRAY_TO_WORD(dst32, src8) ((dst32) = 0[src8] | (1[src8] << 8U) | (2[src8] << 16U) | (3[src8] << 24U)) + // ********************* interrupt safe queue ********************* bool can_pop(can_ring *q, CANPacket_t *elem) { bool ret = 0; @@ -103,21 +107,21 @@ bool can_push(can_ring *q, CANPacket_t *elem) { EXIT_CRITICAL(); if (!ret) { #ifdef DEBUG - puts("can_push to "); + print("can_push to "); if (q == &can_rx_q) { - puts("can_rx_q"); + print("can_rx_q"); } else if (q == &can_tx1_q) { - puts("can_tx1_q"); + print("can_tx1_q"); } else if (q == &can_tx2_q) { - puts("can_tx2_q"); + print("can_tx2_q"); } else if (q == &can_tx3_q) { - puts("can_tx3_q"); + print("can_tx3_q"); } else if (q == &can_txgmlan_q) { - puts("can_txgmlan_q"); + print("can_txgmlan_q"); } else { - puts("unknown"); + print("unknown"); } - puts(" failed!\n"); + print(" failed!\n"); #endif } return ret; @@ -169,7 +173,7 @@ bus_config_t bus_config[] = { void can_init_all(void) { bool ret = true; - for (uint8_t i=0U; i < CAN_CNT; i++) { + for (uint8_t i=0U; i < PANDA_CAN_CNT; i++) { if (!current_board->has_canfd) { bus_config[i].can_data_speed = 0U; } @@ -191,24 +195,25 @@ void ignition_can_hook(CANPacket_t *to_push) { int addr = GET_ADDR(to_push); int len = GET_LEN(to_push); - ignition_can_cnt = 0U; // reset counter - if (bus == 0) { // GM exception if ((addr == 0x160) && (len == 5)) { // this message isn't all zeros when ignition is on ignition_can = GET_BYTES_04(to_push) != 0U; + ignition_can_cnt = 0U; } // Tesla exception if ((addr == 0x348) && (len == 8)) { // GTW_status ignition_can = (GET_BYTE(to_push, 0) & 0x1U) != 0U; + ignition_can_cnt = 0U; } // Mazda exception if ((addr == 0x9E) && (len == 8)) { ignition_can = (GET_BYTE(to_push, 0) >> 5) == 0x6U; + ignition_can_cnt = 0U; } } @@ -222,9 +227,26 @@ bool can_tx_check_min_slots_free(uint32_t min) { (can_slots_empty(&can_txgmlan_q) >= min); } +uint8_t calculate_checksum(uint8_t *dat, uint32_t len) { + uint8_t checksum = 0U; + for (uint32_t i = 0U; i < len; i++) { + checksum ^= dat[i]; + } + return checksum; +} + +void can_set_checksum(CANPacket_t *packet) { + packet->checksum = 0U; + packet->checksum = calculate_checksum((uint8_t *) packet, CANPACKET_HEAD_SIZE + GET_LEN(packet)); +} + +bool can_check_checksum(CANPacket_t *packet) { + return (calculate_checksum((uint8_t *) packet, CANPACKET_HEAD_SIZE + GET_LEN(packet)) == 0U); +} + void can_send(CANPacket_t *to_push, uint8_t bus_number, bool skip_tx_hook) { if (skip_tx_hook || safety_tx_hook(to_push) != 0) { - if (bus_number < BUS_CNT) { + if (bus_number < PANDA_BUS_CNT) { // add CAN packet to send queue if ((bus_number == 3U) && (bus_config[3].can_num_lookup == 0xFFU)) { gmlan_send_errs += bitbang_gmlan(to_push) ? 0U : 1U; @@ -236,6 +258,9 @@ void can_send(CANPacket_t *to_push, uint8_t bus_number, bool skip_tx_hook) { } else { safety_tx_blocked += 1U; to_push->rejected = 1U; + + // data changed + can_set_checksum(to_push); rx_buffer_overflow += can_push(&can_rx_q, to_push) ? 0U : 1U; } } diff --git a/panda/board/drivers/clock_source.h b/panda/board/drivers/clock_source.h new file mode 100644 index 000000000..d1b5724f1 --- /dev/null +++ b/panda/board/drivers/clock_source.h @@ -0,0 +1,33 @@ +#define CLOCK_SOURCE_PERIOD_MS 50U +#define CLOCK_SOURCE_PULSE_LEN_MS 2U + +void clock_source_init(void) { + // Setup timer + register_set(&(TIM1->PSC), ((APB2_TIMER_FREQ*100U)-1U), 0xFFFFU); // Tick on 0.1 ms + register_set(&(TIM1->ARR), ((CLOCK_SOURCE_PERIOD_MS*10U) - 1U), 0xFFFFU); // Period + register_set(&(TIM1->CCMR1), 0U, 0xFFFFU); // No output on compare + register_set(&(TIM1->CCER), TIM_CCER_CC1E, 0xFFFFU); // Enable compare 1 + register_set(&(TIM1->CCR1), (CLOCK_SOURCE_PULSE_LEN_MS*10U), 0xFFFFU); // Compare 1 value + register_set(&(TIM1->CCR2), (CLOCK_SOURCE_PULSE_LEN_MS*10U), 0xFFFFU); // Compare 1 value + register_set(&(TIM1->CCR3), (CLOCK_SOURCE_PULSE_LEN_MS*10U), 0xFFFFU); // Compare 1 value + register_set_bits(&(TIM1->DIER), TIM_DIER_UIE | TIM_DIER_CC1IE); // Enable interrupts + register_set(&(TIM1->CR1), TIM_CR1_CEN, 0x3FU); // Enable timer + + // No interrupts + NVIC_DisableIRQ(TIM1_UP_TIM10_IRQn); + NVIC_DisableIRQ(TIM1_CC_IRQn); + + // Set GPIO as timer channels + set_gpio_alternate(GPIOB, 14, GPIO_AF1_TIM1); + set_gpio_alternate(GPIOB, 15, GPIO_AF1_TIM1); + + // Set PWM mode + register_set(&(TIM1->CCMR1), (0b110 << TIM_CCMR1_OC2M_Pos), 0xFFFFU); + register_set(&(TIM1->CCMR2), (0b110 << TIM_CCMR2_OC3M_Pos), 0xFFFFU); + + // Enable output + register_set(&(TIM1->BDTR), TIM_BDTR_MOE, 0xFFFFU); + + // Enable complementary compares + register_set_bits(&(TIM1->CCER), TIM_CCER_CC2NE | TIM_CCER_CC3NE); +} diff --git a/panda/board/drivers/fake_siren.h b/panda/board/drivers/fake_siren.h new file mode 100644 index 000000000..e6fd78f99 --- /dev/null +++ b/panda/board/drivers/fake_siren.h @@ -0,0 +1,74 @@ +#include "stm32h7/lli2c.h" + +#define CODEC_I2C_ADDR 0x10 +// 1Vpp sine wave with 1V offset +const uint8_t fake_siren_lut[360] = { 134U, 135U, 137U, 138U, 139U, 140U, 141U, 143U, 144U, 145U, 146U, 148U, 149U, 150U, 151U, 152U, 154U, 155U, 156U, 157U, 158U, 159U, 160U, 162U, 163U, 164U, 165U, 166U, 167U, 168U, 169U, 170U, 171U, 172U, 174U, 175U, 176U, 177U, 177U, 178U, 179U, 180U, 181U, 182U, 183U, 184U, 185U, 186U, 186U, 187U, 188U, 189U, 190U, 190U, 191U, 192U, 193U, 193U, 194U, 195U, 195U, 196U, 196U, 197U, 197U, 198U, 199U, 199U, 199U, 200U, 200U, 201U, 201U, 202U, 202U, 202U, 203U, 203U, 203U, 203U, 204U, 204U, 204U, 204U, 204U, 204U, 204U, 205U, 205U, 205U, 205U, 205U, 205U, 205U, 204U, 204U, 204U, 204U, 204U, 204U, 204U, 203U, 203U, 203U, 203U, 202U, 202U, 202U, 201U, 201U, 200U, 200U, 199U, 199U, 199U, 198U, 197U, 197U, 196U, 196U, 195U, 195U, 194U, 193U, 193U, 192U, 191U, 190U, 190U, 189U, 188U, 187U, 186U, 186U, 185U, 184U, 183U, 182U, 181U, 180U, 179U, 178U, 177U, 177U, 176U, 175U, 174U, 172U, 171U, 170U, 169U, 168U, 167U, 166U, 165U, 164U, 163U, 162U, 160U, 159U, 158U, 157U, 156U, 155U, 154U, 152U, 151U, 150U, 149U, 148U, 146U, 145U, 144U, 143U, 141U, 140U, 139U, 138U, 137U, 135U, 134U, 133U, 132U, 130U, 129U, 128U, 127U, 125U, 124U, 123U, 122U, 121U, 119U, 118U, 117U, 116U, 115U, 113U, 112U, 111U, 110U, 109U, 108U, 106U, 105U, 104U, 103U, 102U, 101U, 100U, 99U, 98U, 97U, 96U, 95U, 94U, 93U, 92U, 91U, 90U, 89U, 88U, 87U, 86U, 85U, 84U, 83U, 82U, 82U, 81U, 80U, 79U, 78U, 78U, 77U, 76U, 76U, 75U, 74U, 74U, 73U, 72U, 72U, 71U, 71U, 70U, 70U, 69U, 69U, 68U, 68U, 67U, 67U, 67U, 66U, 66U, 66U, 65U, 65U, 65U, 65U, 64U, 64U, 64U, 64U, 64U, 64U, 64U, 64U, 64U, 63U, 64U, 64U, 64U, 64U, 64U, 64U, 64U, 64U, 64U, 65U, 65U, 65U, 65U, 66U, 66U, 66U, 67U, 67U, 67U, 68U, 68U, 69U, 69U, 70U, 70U, 71U, 71U, 72U, 72U, 73U, 74U, 74U, 75U, 76U, 76U, 77U, 78U, 78U, 79U, 80U, 81U, 82U, 82U, 83U, 84U, 85U, 86U, 87U, 88U, 89U, 90U, 91U, 92U, 93U, 94U, 95U, 96U, 97U, 98U, 99U, 100U, 101U, 102U, 103U, 104U, 105U, 106U, 108U, 109U, 110U, 111U, 112U, 113U, 115U, 116U, 117U, 118U, 119U, 121U, 122U, 123U, 124U, 125U, 127U, 128U, 129U, 130U, 132U, 133U }; + +bool fake_siren_enabled = false; + +void fake_siren_codec_enable(bool enabled) { + if(enabled) { + bool success = false; + success &= i2c_set_reg_bits(I2C5, CODEC_I2C_ADDR, 0x2B, (1U << 1)); // Left speaker mix from INA1 + success &= i2c_set_reg_bits(I2C5, CODEC_I2C_ADDR, 0x2C, (1U << 1)); // Right speaker mix from INA1 + success &= i2c_set_reg_mask(I2C5, CODEC_I2C_ADDR, 0x3D, 0x1F, 0b11111); // Left speaker volume + success &= i2c_set_reg_mask(I2C5, CODEC_I2C_ADDR, 0x3E, 0x1F, 0b11111); // Right speaker volume + success &= i2c_set_reg_mask(I2C5, CODEC_I2C_ADDR, 0x37, 0b101, 0b111); // INA gain + success &= i2c_set_reg_bits(I2C5, CODEC_I2C_ADDR, 0x4C, (1U << 7)); // Enable INA + success &= i2c_set_reg_bits(I2C5, CODEC_I2C_ADDR, 0x51, (1U << 7)); // Disable global shutdown + if (!success) { + print("Failed to setup the codec for fake siren\n"); + } + } else { + // Disable INA input. Make sure to retry a few times if the I2C bus is busy. + for(uint8_t i=0U; i<10U; i++) { + if (i2c_clear_reg_bits(I2C5, CODEC_I2C_ADDR, 0x4C, (1U << 7))) { + break; + } + } + } +} + + +void fake_siren_set(bool enabled) { + if (enabled != fake_siren_enabled) { + fake_siren_codec_enable(enabled); + } + + if (enabled) { + register_set_bits(&DMA1_Stream1->CR, DMA_SxCR_EN); + } else { + register_clear_bits(&DMA1_Stream1->CR, DMA_SxCR_EN); + } + fake_siren_enabled = enabled; +} + +void fake_siren_init(void) { + // Init DAC + register_set(&DAC1->MCR, 0U, 0xFFFFFFFFU); + register_set(&DAC1->CR, DAC_CR_TEN1 | (6U << DAC_CR_TSEL1_Pos) | DAC_CR_DMAEN1, 0xFFFFFFFFU); + register_set_bits(&DAC1->CR, DAC_CR_EN1); + + // Setup DMAMUX (DAC_CH1_DMA as input) + register_set(&DMAMUX1_Channel1->CCR, 67U, DMAMUX_CxCR_DMAREQ_ID_Msk); + + // Setup DMA + register_set(&DMA1_Stream1->M0AR, (uint32_t) fake_siren_lut, 0xFFFFFFFFU); + register_set(&DMA1_Stream1->PAR, (uint32_t) &(DAC1->DHR8R1), 0xFFFFFFFFU); + DMA1_Stream1->NDTR = sizeof(fake_siren_lut); + register_set(&DMA1_Stream1->FCR, 0U, 0xFFFFFFFFU); + DMA1_Stream1->CR = (0b11 << DMA_SxCR_PL_Pos); + DMA1_Stream1->CR |= DMA_SxCR_MINC | DMA_SxCR_CIRC | (1 << DMA_SxCR_DIR_Pos); + + // Init trigger timer (around 2.5kHz) + register_set(&TIM7->PSC, 0U, 0xFFFFU); + register_set(&TIM7->ARR, 133U, 0xFFFFU); + register_set(&TIM7->CR2, (0b10 << TIM_CR2_MMS_Pos), TIM_CR2_MMS_Msk); + register_set(&TIM7->CR1, TIM_CR1_ARPE | TIM_CR1_URS, 0x088EU); + TIM7->SR = 0U; + TIM7->CR1 |= TIM_CR1_CEN; + + // Enable the I2C to the codec + i2c_init(I2C5); + fake_siren_codec_enable(false); +} \ No newline at end of file diff --git a/panda/board/drivers/fan.h b/panda/board/drivers/fan.h index cb5fbabe6..d5e32ae5f 100644 --- a/panda/board/drivers/fan.h +++ b/panda/board/drivers/fan.h @@ -27,7 +27,7 @@ void fan_tick(void){ current_board->set_fan_enabled(fan_state.target_rpm > 0U); // Stall detection - if(fan_state.power > 0U) { + if (fan_state.power > 0U) { if (fan_rpm_fast == 0U) { fan_state.stall_counter = MIN(fan_state.stall_counter + 1U, 255U); } else { diff --git a/panda/board/drivers/fdcan.h b/panda/board/drivers/fdcan.h index 0d3aa1687..d8793fd66 100644 --- a/panda/board/drivers/fdcan.h +++ b/panda/board/drivers/fdcan.h @@ -29,7 +29,7 @@ bool can_set_speed(uint8_t can_number) { void can_set_gmlan(uint8_t bus) { UNUSED(bus); - puts("GMLAN not available on red panda\n"); + print("GMLAN not available on red panda\n"); } // ***************************** CAN ***************************** @@ -84,37 +84,43 @@ void process_can(uint8_t can_number) { if ((CANx->TXFQS & FDCAN_TXFQS_TFQF) == 0) { CANPacket_t to_send; if (can_pop(can_queues[bus_number], &to_send)) { - can_health[can_number].total_tx_cnt += 1U; + if (can_check_checksum(&to_send)) { + can_health[can_number].total_tx_cnt += 1U; - uint32_t TxFIFOSA = FDCAN_START_ADDRESS + (can_number * FDCAN_OFFSET) + (FDCAN_RX_FIFO_0_EL_CNT * FDCAN_RX_FIFO_0_EL_SIZE); - uint8_t tx_index = (CANx->TXFQS >> FDCAN_TXFQS_TFQPI_Pos) & 0x1F; - // only send if we have received a packet - canfd_fifo *fifo; - fifo = (canfd_fifo *)(TxFIFOSA + (tx_index * FDCAN_TX_FIFO_EL_SIZE)); + uint32_t TxFIFOSA = FDCAN_START_ADDRESS + (can_number * FDCAN_OFFSET) + (FDCAN_RX_FIFO_0_EL_CNT * FDCAN_RX_FIFO_0_EL_SIZE); + uint8_t tx_index = (CANx->TXFQS >> FDCAN_TXFQS_TFQPI_Pos) & 0x1F; + // only send if we have received a packet + canfd_fifo *fifo; + fifo = (canfd_fifo *)(TxFIFOSA + (tx_index * FDCAN_TX_FIFO_EL_SIZE)); - fifo->header[0] = (to_send.extended << 30) | ((to_send.extended != 0U) ? (to_send.addr) : (to_send.addr << 18)); - fifo->header[1] = (to_send.data_len_code << 16) | (bus_config[can_number].canfd_enabled << 21) | (bus_config[can_number].brs_enabled << 20); + fifo->header[0] = (to_send.extended << 30) | ((to_send.extended != 0U) ? (to_send.addr) : (to_send.addr << 18)); + fifo->header[1] = (to_send.data_len_code << 16) | (bus_config[can_number].canfd_enabled << 21) | (bus_config[can_number].brs_enabled << 20); - uint8_t data_len_w = (dlc_to_len[to_send.data_len_code] / 4U); - data_len_w += ((dlc_to_len[to_send.data_len_code] % 4U) > 0U) ? 1U : 0U; - for (unsigned int i = 0; i < data_len_w; i++) { - BYTE_ARRAY_TO_WORD(fifo->data_word[i], &to_send.data[i*4U]); + uint8_t data_len_w = (dlc_to_len[to_send.data_len_code] / 4U); + data_len_w += ((dlc_to_len[to_send.data_len_code] % 4U) > 0U) ? 1U : 0U; + for (unsigned int i = 0; i < data_len_w; i++) { + BYTE_ARRAY_TO_WORD(fifo->data_word[i], &to_send.data[i*4U]); + } + + CANx->TXBAR = (1UL << tx_index); + + // Send back to USB + CANPacket_t to_push; + + to_push.returned = 1U; + to_push.rejected = 0U; + to_push.extended = to_send.extended; + to_push.addr = to_send.addr; + to_push.bus = to_send.bus; + to_push.data_len_code = to_send.data_len_code; + (void)memcpy(to_push.data, to_send.data, dlc_to_len[to_push.data_len_code]); + can_set_checksum(&to_push); + + rx_buffer_overflow += can_push(&can_rx_q, &to_push) ? 0U : 1U; + } else { + can_health[can_number].total_tx_checksum_error_cnt += 1U; } - CANx->TXBAR = (1UL << tx_index); - - // Send back to USB - CANPacket_t to_push; - - to_push.returned = 1U; - to_push.rejected = 0U; - to_push.extended = to_send.extended; - to_push.addr = to_send.addr; - to_push.bus = to_send.bus; - to_push.data_len_code = to_send.data_len_code; - (void)memcpy(to_push.data, to_send.data, dlc_to_len[to_push.data_len_code]); - rx_buffer_overflow += can_push(&can_rx_q, &to_push) ? 0U : 1U; - usb_cb_ep3_out_complete(); } } @@ -164,6 +170,7 @@ void can_rx(uint8_t can_number) { for (unsigned int i = 0; i < data_len_w; i++) { WORD_TO_BYTE_ARRAY(&to_push.data[i*4U], fifo->data_word[i]); } + can_set_checksum(&to_push); // forwarding (panda only) int bus_fwd_num = safety_fwd_hook(bus_number, &to_push); @@ -177,6 +184,8 @@ void can_rx(uint8_t can_number) { to_send.bus = to_push.bus; to_send.data_len_code = to_push.data_len_code; (void)memcpy(to_send.data, to_push.data, dlc_to_len[to_push.data_len_code]); + can_set_checksum(&to_send); + can_send(&to_send, bus_fwd_num, true); can_health[can_number].total_fwd_cnt += 1U; } diff --git a/panda/board/drivers/gmlan_alt.h b/panda/board/drivers/gmlan_alt.h index 2e5225691..266a683e6 100644 --- a/panda/board/drivers/gmlan_alt.h +++ b/panda/board/drivers/gmlan_alt.h @@ -161,9 +161,9 @@ void gmlan_switch_init(int timeout_enable) { void set_gmlan_digital_output(int to_set) { inverted_bit_to_send = to_set; /* - puts("Writing "); + print("Writing "); puth(inverted_bit_to_send); - puts("\n"); + print("\n"); */ } @@ -207,12 +207,12 @@ void TIM12_IRQ_Handler(void) { if ((gmlan_sending > 0) && // not first bit ((read == 0) && (pkt_stuffed[gmlan_sending-1] == 1)) && // bus wrongly dominant (gmlan_sending != (gmlan_sendmax - 11))) { //not ack bit - puts("GMLAN ERR: bus driven at "); + print("GMLAN ERR: bus driven at "); puth(gmlan_sending); - puts("\n"); + print("\n"); retry = 1; } else if ((read == 1) && (gmlan_sending == (gmlan_sendmax - 11))) { // recessive during ACK - puts("GMLAN ERR: didn't recv ACK\n"); + print("GMLAN ERR: didn't recv ACK\n"); retry = 1; } else { // do not retry @@ -224,7 +224,7 @@ void TIM12_IRQ_Handler(void) { gmlan_sending = 0; gmlan_fail_count++; if (gmlan_fail_count == MAX_FAIL_COUNT) { - puts("GMLAN ERR: giving up send\n"); + print("GMLAN ERR: giving up send\n"); gmlan_send_ok = false; } } else { diff --git a/panda/board/drivers/harness.h b/panda/board/drivers/harness.h index 7d0f0ae3e..c2b5ac95f 100644 --- a/panda/board/drivers/harness.h +++ b/panda/board/drivers/harness.h @@ -21,9 +21,9 @@ struct harness_configuration { void set_intercept_relay(bool intercept) { if (car_harness_status != HARNESS_STATUS_NC) { if (intercept) { - puts("switching harness to intercept (relay on)\n"); + print("switching harness to intercept (relay on)\n"); } else { - puts("switching harness to passthrough (relay off)\n"); + print("switching harness to passthrough (relay off)\n"); } if(car_harness_status == HARNESS_STATUS_NORMAL){ @@ -80,7 +80,7 @@ void harness_init(void) { // try to detect orientation uint8_t ret = harness_detect_orientation(); if (ret != HARNESS_STATUS_NC) { - puts("detected car harness with orientation "); puth2(ret); puts("\n"); + print("detected car harness with orientation "); puth2(ret); print("\n"); car_harness_status = ret; // set the SBU lines to be inputs before using the relay. The lines are not 5V tolerant in ADC mode! @@ -90,6 +90,6 @@ void harness_init(void) { // keep busses connected by default set_intercept_relay(false); } else { - puts("failed to detect car harness!\n"); + print("failed to detect car harness!\n"); } } diff --git a/panda/board/drivers/interrupts.h b/panda/board/drivers/interrupts.h index a39498562..ad1ec5c02 100644 --- a/panda/board/drivers/interrupts.h +++ b/panda/board/drivers/interrupts.h @@ -11,7 +11,7 @@ uint32_t microsecond_timer_get(void); void unused_interrupt_handler(void) { // Something is wrong if this handler is called! - puts("Unused interrupt handler called!\n"); + print("Unused interrupt handler called!\n"); fault_occurred(FAULT_UNUSED_INTERRUPT_HANDLED); } @@ -47,7 +47,7 @@ void handle_interrupt(IRQn_Type irq_type){ // Check that the interrupts don't fire too often if(check_interrupt_rate && (interrupts[irq_type].call_counter > interrupts[irq_type].max_call_rate)){ - puts("Interrupt 0x"); puth(irq_type); puts(" fired too often (0x"); puth(interrupts[irq_type].call_counter); puts("/s)!\n"); + print("Interrupt 0x"); puth(irq_type); print(" fired too often (0x"); puth(interrupts[irq_type].call_counter); print("/s)!\n"); fault_occurred(interrupts[irq_type].call_rate_fault); } diff --git a/panda/board/drivers/registers.h b/panda/board/drivers/registers.h index 4595a5686..a5f8b0280 100644 --- a/panda/board/drivers/registers.h +++ b/panda/board/drivers/registers.h @@ -35,7 +35,7 @@ void register_set(volatile uint32_t *addr, uint32_t val, uint32_t mask){ register_map[hash].check_mask |= mask; } else { #ifdef DEBUG_FAULTS - puts("Hash collision: address 0x"); puth((uint32_t) addr); puts("!\n"); + print("Hash collision: address 0x"); puth((uint32_t) addr); print("!\n"); #endif } EXIT_CRITICAL() @@ -60,11 +60,11 @@ void check_registers(void){ ENTER_CRITICAL() if((*(register_map[i].address) & register_map[i].check_mask) != (register_map[i].value & register_map[i].check_mask)){ #ifdef DEBUG_FAULTS - puts("Register at address 0x"); puth((uint32_t) register_map[i].address); puts(" is divergent!"); - puts(" Map: 0x"); puth(register_map[i].value); - puts(" Register: 0x"); puth(*(register_map[i].address)); - puts(" Mask: 0x"); puth(register_map[i].check_mask); - puts("\n"); + print("Register at address 0x"); puth((uint32_t) register_map[i].address); print(" is divergent!"); + print(" Map: 0x"); puth(register_map[i].value); + print(" Register: 0x"); puth(*(register_map[i].address)); + print(" Mask: 0x"); puth(register_map[i].check_mask); + print("\n"); #endif fault_occurred(FAULT_REGISTER_DIVERGENT); } diff --git a/panda/board/drivers/rtc.h b/panda/board/drivers/rtc.h index 7f1854374..552b86758 100644 --- a/panda/board/drivers/rtc.h +++ b/panda/board/drivers/rtc.h @@ -36,7 +36,7 @@ void rtc_init(void){ // Initialize RTC module and clock if not done already. if((RCC->BDCR & bdcr_mask) != bdcr_opts){ - puts("Initializing RTC\n"); + print("Initializing RTC\n"); // Reset backup domain register_set_bits(&(RCC->BDCR), RCC_BDCR_BDRST); @@ -55,7 +55,7 @@ void rtc_init(void){ } void rtc_set_time(timestamp_t time){ - puts("Setting RTC time\n"); + print("Setting RTC time\n"); // Disable write protection disable_bdomain_protection(); diff --git a/panda/board/drivers/spi.h b/panda/board/drivers/spi.h index 422678556..f7382e469 100644 --- a/panda/board/drivers/spi.h +++ b/panda/board/drivers/spi.h @@ -1,6 +1,7 @@ #pragma once #define SPI_BUF_SIZE 1024U +#define SPI_TIMEOUT_US 10000U #ifdef STM32H7 __attribute__((section(".ram_d1"))) uint8_t spi_buf_rx[SPI_BUF_SIZE]; @@ -75,7 +76,7 @@ void spi_handle_rx(void) { next_rx_state = SPI_STATE_HEADER_ACK; } else { // response: NACK and reset state machine - puts("- incorrect header sync or checksum "); hexdump(spi_buf_rx, SPI_HEADER_SIZE); + print("- incorrect header sync or checksum "); hexdump(spi_buf_rx, SPI_HEADER_SIZE); spi_buf_tx[0] = SPI_NACK; next_rx_state = SPI_STATE_HEADER_NACK; } @@ -92,14 +93,14 @@ void spi_handle_rx(void) { response_len = comms_control_handler(&ctrl, &spi_buf_tx[3]); reponse_ack = true; } else { - puts("SPI: insufficient data for control handler\n"); + print("SPI: insufficient data for control handler\n"); } } else if ((spi_endpoint == 1U) || (spi_endpoint == 0x81U)) { if (spi_data_len_mosi == 0U) { response_len = comms_can_read(&(spi_buf_tx[3]), spi_data_len_miso); reponse_ack = true; } else { - puts("SPI: did not expect data for can_read\n"); + print("SPI: did not expect data for can_read\n"); } } else if (spi_endpoint == 2U) { comms_endpoint2_write(&spi_buf_rx[SPI_HEADER_SIZE], spi_data_len_mosi); @@ -109,15 +110,15 @@ void spi_handle_rx(void) { comms_can_write(&spi_buf_rx[SPI_HEADER_SIZE], spi_data_len_mosi); reponse_ack = true; } else { - puts("SPI: did expect data for can_write\n"); + print("SPI: did expect data for can_write\n"); } } else { - puts("SPI: unexpected endpoint"); puth(spi_endpoint); puts("\n"); + print("SPI: unexpected endpoint"); puth(spi_endpoint); print("\n"); } } else { // Checksum was incorrect reponse_ack = false; - puts("- incorrect data checksum\n"); + print("- incorrect data checksum\n"); } // Setup response header @@ -137,21 +138,21 @@ void spi_handle_rx(void) { next_rx_state = SPI_STATE_DATA_TX; } else { - puts("SPI: RX unexpected state: "); puth(spi_state); puts("\n"); + print("SPI: RX unexpected state: "); puth(spi_state); print("\n"); } spi_state = next_rx_state; } -void spi_handle_tx(void) { - if (spi_state == SPI_STATE_HEADER_ACK) { - // ACK was sent, queue up the RX buf for the data + checksum - spi_state = SPI_STATE_DATA_RX; - llspi_mosi_dma(&spi_buf_rx[SPI_HEADER_SIZE], spi_data_len_mosi + 1U); - } else if (spi_state == SPI_STATE_HEADER_NACK) { +void spi_handle_tx(bool timed_out) { + if ((spi_state == SPI_STATE_HEADER_NACK) || timed_out) { // Reset state spi_state = SPI_STATE_HEADER; llspi_mosi_dma(spi_buf_rx, SPI_HEADER_SIZE); + } else if (spi_state == SPI_STATE_HEADER_ACK) { + // ACK was sent, queue up the RX buf for the data + checksum + spi_state = SPI_STATE_DATA_RX; + llspi_mosi_dma(&spi_buf_rx[SPI_HEADER_SIZE], spi_data_len_mosi + 1U); } else if (spi_state == SPI_STATE_DATA_TX) { // Reset state spi_state = SPI_STATE_HEADER; @@ -159,6 +160,6 @@ void spi_handle_tx(void) { } else { spi_state = SPI_STATE_HEADER; llspi_mosi_dma(spi_buf_rx, SPI_HEADER_SIZE); - puts("SPI: TX unexpected state: "); puth(spi_state); puts("\n"); + print("SPI: TX unexpected state: "); puth(spi_state); print("\n"); } } diff --git a/panda/board/drivers/timers.h b/panda/board/drivers/timers.h index eb0539886..d7f3f01fd 100644 --- a/panda/board/drivers/timers.h +++ b/panda/board/drivers/timers.h @@ -6,7 +6,7 @@ void timer_init(TIM_TypeDef *TIM, int psc) { } void microsecond_timer_init(void) { - MICROSECOND_TIMER->PSC = (APB1_FREQ)-1U; + MICROSECOND_TIMER->PSC = (APB1_TIMER_FREQ - 1U); MICROSECOND_TIMER->CR1 = TIM_CR1_CEN; MICROSECOND_TIMER->EGR = TIM_EGR_UG; } @@ -18,7 +18,7 @@ uint32_t microsecond_timer_get(void) { void interrupt_timer_init(void) { enable_interrupt_timer(); REGISTER_INTERRUPT(INTERRUPT_TIMER_IRQ, interrupt_timer_handler, 1, FAULT_INTERRUPT_RATE_INTERRUPTS) - register_set(&(INTERRUPT_TIMER->PSC), ((uint16_t)(15.25*APB1_FREQ)-1U), 0xFFFFU); + register_set(&(INTERRUPT_TIMER->PSC), ((uint16_t)(15.25*APB1_TIMER_FREQ)-1U), 0xFFFFU); register_set(&(INTERRUPT_TIMER->DIER), TIM_DIER_UIE, 0x5F5FU); register_set(&(INTERRUPT_TIMER->CR1), TIM_CR1_CEN, 0x3FU); INTERRUPT_TIMER->SR = 0; @@ -26,6 +26,6 @@ void interrupt_timer_init(void) { } void tick_timer_init(void) { - timer_init(TICK_TIMER, (uint16_t)((15.25*APB2_FREQ)/8U)); + timer_init(TICK_TIMER, (uint16_t)((15.25*APB2_TIMER_FREQ)/8U)); NVIC_EnableIRQ(TICK_TIMER_IRQ); } diff --git a/panda/board/drivers/uart.h b/panda/board/drivers/uart.h index 2caa44fa5..b0fc2ed1b 100644 --- a/panda/board/drivers/uart.h +++ b/panda/board/drivers/uart.h @@ -53,6 +53,14 @@ UART_BUFFER(lin2, FIFO_SIZE_INT, FIFO_SIZE_INT, USART3, NULL, false) // debug = USART2 UART_BUFFER(debug, FIFO_SIZE_INT, FIFO_SIZE_INT, USART2, debug_ring_callback, false) +// SOM debug = UART7 +#ifdef STM32H7 + UART_BUFFER(som_debug, FIFO_SIZE_INT, FIFO_SIZE_INT, UART7, NULL, false) +#else + // UART7 is not available on F4 + UART_BUFFER(som_debug, 1U, 1U, NULL, NULL, false) +#endif + uart_ring *get_ring_by_number(int a) { uart_ring *ring = NULL; switch(a) { @@ -68,6 +76,9 @@ uart_ring *get_ring_by_number(int a) { case 3: ring = &uart_ring_lin2; break; + case 4: + ring = &uart_ring_som_debug; + break; default: ring = NULL; break; @@ -154,7 +165,7 @@ void putch(const char a) { (void)injectc(&uart_ring_debug, a); } -void puts(const char *a) { +void print(const char *a) { for (const char *in = a; *in; in++) { if (*in == '\n') putch('\r'); putch(*in); @@ -172,7 +183,7 @@ void putui(uint32_t i) { idx--; i_copy /= 10; } while (i_copy != 0U); - puts(&str[idx + 1U]); + print(&str[idx + 1U]); } void puthx(uint32_t i, uint8_t len) { @@ -197,10 +208,10 @@ void puth4(unsigned int i) { void hexdump(const void *a, int l) { if (a != NULL) { for (int i=0; i < l; i++) { - if ((i != 0) && ((i & 0xf) == 0)) puts("\n"); + if ((i != 0) && ((i & 0xf) == 0)) print("\n"); puth2(((const unsigned char*)a)[i]); - puts(" "); + print(" "); } } - puts("\n"); + print("\n"); } diff --git a/panda/board/drivers/usb.h b/panda/board/drivers/usb.h index f8017adde..1ac5b21b3 100644 --- a/panda/board/drivers/usb.h +++ b/panda/board/drivers/usb.h @@ -352,7 +352,7 @@ uint8_t winusb_20_desc[WINUSB_PLATFORM_DESCRIPTOR_LENGTH] = { // current packet USB_Setup_TypeDef setup; -uint8_t usbdata[0x100]; +uint8_t usbdata[0x100] __attribute__((aligned(4))); uint8_t* ep0_txdata = NULL; uint16_t ep0_txlen = 0; bool outep3_processing = false; @@ -375,7 +375,7 @@ void *USB_ReadPacket(void *dest, uint16_t len) { void USB_WritePacket(const void *src, uint16_t len, uint32_t ep) { #ifdef DEBUG_USB - puts("writing "); + print("writing "); hexdump(src, len); #endif @@ -402,7 +402,7 @@ void USB_WritePacket(const void *src, uint16_t len, uint32_t ep) { // so use TX FIFO empty interrupt to send larger amounts of data void USB_WritePacket_EP0(uint8_t *src, uint16_t len) { #ifdef DEBUG_USB - puts("writing "); + print("writing "); hexdump(src, len); #endif @@ -505,7 +505,7 @@ void usb_setup(void) { USBx_DEVICE->DCFG |= ((setup.b.wValue.w & 0x7fU) << 4); #ifdef DEBUG_USB - puts(" set address\n"); + print(" set address\n"); #endif USB_WritePacket(0, 0, 0); @@ -515,7 +515,7 @@ void usb_setup(void) { case USB_REQ_GET_DESCRIPTOR: switch (setup.b.wValue.bw.lsb) { case USB_DESC_TYPE_DEVICE: - //puts(" writing device descriptor\n"); + //print(" writing device descriptor\n"); // set bcdDevice to hardware type device_desc[13] = hw_type; @@ -523,7 +523,7 @@ void usb_setup(void) { USB_WritePacket(device_desc, MIN(sizeof(device_desc), setup.b.wLength.w), 0); USBx_OUTEP(0)->DOEPCTL |= USB_OTG_DOEPCTL_CNAK; - //puts("D"); + //print("D"); break; case USB_DESC_TYPE_CONFIGURATION: USB_WritePacket(configuration_desc, MIN(sizeof(configuration_desc), setup.b.wLength.w), 0); @@ -660,21 +660,21 @@ void usb_irqhandler(void) { // gintsts SUSPEND? 04008428 #ifdef DEBUG_USB puth(gintsts); - puts(" "); + print(" "); /*puth(USBx->GCCFG); - puts(" ");*/ + print(" ");*/ puth(gotgint); - puts(" ep "); + print(" ep "); puth(daint); - puts(" USB interrupt!\n"); + print(" USB interrupt!\n"); #endif if ((gintsts & USB_OTG_GINTSTS_CIDSCHG) != 0) { - puts("connector ID status change\n"); + print("connector ID status change\n"); } if ((gintsts & USB_OTG_GINTSTS_ESUSP) != 0) { - puts("ESUSP detected\n"); + print("ESUSP detected\n"); } if ((gintsts & USB_OTG_GINTSTS_EOPF) != 0) { @@ -682,7 +682,7 @@ void usb_irqhandler(void) { } if ((gintsts & USB_OTG_GINTSTS_USBRST) != 0) { - puts("USB reset\n"); + print("USB reset\n"); usb_enumerated = false; usb_reset(); } @@ -692,16 +692,16 @@ void usb_irqhandler(void) { } if ((gintsts & USB_OTG_GINTSTS_ENUMDNE) != 0) { - puts("enumeration done"); + print("enumeration done"); // Full speed, ENUMSPD //puth(USBx_DEVICE->DSTS); - puts("\n"); + print("\n"); } if ((gintsts & USB_OTG_GINTSTS_OTGINT) != 0) { - puts("OTG int:"); + print("OTG int:"); puth(USBx->GOTGINT); - puts("\n"); + print("\n"); // getting ADTOCHG //USBx->GOTGINT = USBx->GOTGINT; @@ -714,13 +714,13 @@ void usb_irqhandler(void) { int status = (rxst & USB_OTG_GRXSTSP_PKTSTS) >> 17; #ifdef DEBUG_USB - puts(" RX FIFO:"); + print(" RX FIFO:"); puth(rxst); - puts(" status: "); + print(" status: "); puth(status); - puts(" len: "); + print(" len: "); puth((rxst & USB_OTG_GRXSTSP_BCNT) >> 4); - puts("\n"); + print("\n"); #endif if (status == STS_DATA_UPDT) { @@ -728,9 +728,9 @@ void usb_irqhandler(void) { int len = (rxst & USB_OTG_GRXSTSP_BCNT) >> 4; (void)USB_ReadPacket(&usbdata, len); #ifdef DEBUG_USB - puts(" data "); + print(" data "); puth(len); - puts("\n"); + print("\n"); hexdump(&usbdata, len); #endif @@ -745,9 +745,9 @@ void usb_irqhandler(void) { } else if (status == STS_SETUP_UPDT) { (void)USB_ReadPacket(&setup, 8); #ifdef DEBUG_USB - puts(" setup "); + print(" setup "); hexdump(&setup, 8); - puts("\n"); + print("\n"); #endif } else { // status is neither STS_DATA_UPDT or STS_SETUP_UPDT, skip @@ -756,9 +756,9 @@ void usb_irqhandler(void) { /*if (gintsts & USB_OTG_GINTSTS_HPRTINT) { // host - puts("HPRT:"); + print("HPRT:"); puth(USBx_HOST_PORT->HPRT); - puts("\n"); + print("\n"); if (USBx_HOST_PORT->HPRT & USB_OTG_HPRT_PCDET) { USBx_HOST_PORT->HPRT |= USB_OTG_HPRT_PRST; USBx_HOST_PORT->HPRT |= USB_OTG_HPRT_PCDET; @@ -769,16 +769,16 @@ void usb_irqhandler(void) { if ((gintsts & USB_OTG_GINTSTS_BOUTNAKEFF) || (gintsts & USB_OTG_GINTSTS_GINAKEFF)) { // no global NAK, why is this getting set? #ifdef DEBUG_USB - puts("GLOBAL NAK\n"); + print("GLOBAL NAK\n"); #endif USBx_DEVICE->DCTL |= USB_OTG_DCTL_CGONAK | USB_OTG_DCTL_CGINAK; } if ((gintsts & USB_OTG_GINTSTS_SRQINT) != 0) { // we want to do "A-device host negotiation protocol" since we are the A-device - /*puts("start request\n"); + /*print("start request\n"); puth(USBx->GOTGCTL); - puts("\n");*/ + print("\n");*/ //USBx->GUSBCFG |= USB_OTG_GUSBCFG_FDMOD; //USBx_HOST_PORT->HPRT = USB_OTG_HPRT_PPWR | USB_OTG_HPRT_PENA; //USBx->GOTGCTL |= USB_OTG_GOTGCTL_SRQ; @@ -787,22 +787,22 @@ void usb_irqhandler(void) { // out endpoint hit if ((gintsts & USB_OTG_GINTSTS_OEPINT) != 0) { #ifdef DEBUG_USB - puts(" 0:"); + print(" 0:"); puth(USBx_OUTEP(0)->DOEPINT); - puts(" 2:"); + print(" 2:"); puth(USBx_OUTEP(2)->DOEPINT); - puts(" 3:"); + print(" 3:"); puth(USBx_OUTEP(3)->DOEPINT); - puts(" "); + print(" "); puth(USBx_OUTEP(3)->DOEPCTL); - puts(" 4:"); + print(" 4:"); puth(USBx_OUTEP(4)->DOEPINT); - puts(" OUT ENDPOINT\n"); + print(" OUT ENDPOINT\n"); #endif if ((USBx_OUTEP(2)->DOEPINT & USB_OTG_DOEPINT_XFRC) != 0) { #ifdef DEBUG_USB - puts(" OUT2 PACKET XFRC\n"); + print(" OUT2 PACKET XFRC\n"); #endif USBx_OUTEP(2)->DOEPTSIZ = (1U << 19) | 0x40U; USBx_OUTEP(2)->DOEPCTL |= USB_OTG_DOEPCTL_EPENA | USB_OTG_DOEPCTL_CNAK; @@ -810,14 +810,14 @@ void usb_irqhandler(void) { if ((USBx_OUTEP(3)->DOEPINT & USB_OTG_DOEPINT_XFRC) != 0) { #ifdef DEBUG_USB - puts(" OUT3 PACKET XFRC\n"); + print(" OUT3 PACKET XFRC\n"); #endif // NAK cleared by process_can (if tx buffers have room) outep3_processing = false; usb_cb_ep3_out_complete(); } else if ((USBx_OUTEP(3)->DOEPINT & 0x2000) != 0) { #ifdef DEBUG_USB - puts(" OUT3 PACKET WTF\n"); + print(" OUT3 PACKET WTF\n"); #endif // if NAK was set trigger this, unknown interrupt // TODO: why was this here? fires when TX buffers when we can't clear NAK @@ -825,9 +825,9 @@ void usb_irqhandler(void) { // USBx_OUTEP(3)->DOEPCTL |= USB_OTG_DOEPCTL_CNAK; } else if ((USBx_OUTEP(3)->DOEPINT) != 0) { #ifdef DEBUG_USB - puts("OUTEP3 error "); + print("OUTEP3 error "); puth(USBx_OUTEP(3)->DOEPINT); - puts("\n"); + print("\n"); #endif } else { // USBx_OUTEP(3)->DOEPINT is 0, ok to skip @@ -851,11 +851,11 @@ void usb_irqhandler(void) { // interrupt endpoint hit (Page 1221) if ((gintsts & USB_OTG_GINTSTS_IEPINT) != 0) { #ifdef DEBUG_USB - puts(" "); + print(" "); puth(USBx_INEP(0)->DIEPINT); - puts(" "); + print(" "); puth(USBx_INEP(1)->DIEPINT); - puts(" IN ENDPOINT\n"); + print(" IN ENDPOINT\n"); #endif // Should likely check the EP of the IN request even if there is @@ -876,7 +876,7 @@ void usb_irqhandler(void) { // *** IN token received when TxFIFO is empty if ((USBx_INEP(1)->DIEPINT & USB_OTG_DIEPMSK_ITTXFEMSK) != 0) { #ifdef DEBUG_USB - puts(" IN PACKET QUEUE\n"); + print(" IN PACKET QUEUE\n"); #endif // TODO: always assuming max len, can we get the length? USB_WritePacket((void *)resp, comms_can_read(resp, 0x40), 1); @@ -887,7 +887,7 @@ void usb_irqhandler(void) { // *** IN token received when TxFIFO is empty if ((USBx_INEP(1)->DIEPINT & USB_OTG_DIEPMSK_ITTXFEMSK) != 0) { #ifdef DEBUG_USB - puts(" IN PACKET QUEUE\n"); + print(" IN PACKET QUEUE\n"); #endif // TODO: always assuming max len, can we get the length? int len = comms_can_read(resp, 0x40); @@ -897,13 +897,13 @@ void usb_irqhandler(void) { } break; default: - puts("current_int0_alt_setting value invalid\n"); + print("current_int0_alt_setting value invalid\n"); break; } if ((USBx_INEP(0)->DIEPINT & USB_OTG_DIEPMSK_ITTXFEMSK) != 0) { #ifdef DEBUG_USB - puts(" IN PACKET QUEUE\n"); + print(" IN PACKET QUEUE\n"); #endif if ((ep0_txlen != 0U) && ((USBx_INEP(0)->DTXFSTS & USB_OTG_DTXFSTS_INEPTFSAV) >= 0x40U)) { diff --git a/panda/board/fake_stm.h b/panda/board/fake_stm.h new file mode 100644 index 000000000..b73a4e898 --- /dev/null +++ b/panda/board/fake_stm.h @@ -0,0 +1,33 @@ +// minimal code to fake a panda for tests +#include +#include +#include + +#include "utils.h" + +#define CANFD +#define ALLOW_DEBUG +#define PANDA + +#define ENTER_CRITICAL() 0 +#define EXIT_CRITICAL() 0 + +void print(const char *a) { + printf("%s", a); +} + +void puth(unsigned int i) { + printf("%u", i); +} + +typedef struct { + uint32_t CNT; +} TIM_TypeDef; + +TIM_TypeDef timer; +TIM_TypeDef *MICROSECOND_TIMER = &timer; +uint32_t microsecond_timer_get(void); + +uint32_t microsecond_timer_get(void) { + return MICROSECOND_TIMER->CNT; +} diff --git a/panda/board/faults.h b/panda/board/faults.h index ad938b9d0..c5df1131b 100644 --- a/panda/board/faults.h +++ b/panda/board/faults.h @@ -27,6 +27,7 @@ #define FAULT_INTERRUPT_RATE_TICK (1U << 21) #define FAULT_INTERRUPT_RATE_EXTI (1U << 22) #define FAULT_INTERRUPT_RATE_SPI (1U << 23) +#define FAULT_INTERRUPT_RATE_UART_7 (1U << 24) // Permanent faults #define PERMANENT_FAULTS 0U @@ -37,10 +38,10 @@ uint32_t faults = 0U; void fault_occurred(uint32_t fault) { faults |= fault; if((PERMANENT_FAULTS & fault) != 0U){ - puts("Permanent fault occurred: 0x"); puth(fault); puts("\n"); + print("Permanent fault occurred: 0x"); puth(fault); print("\n"); fault_status = FAULT_STATUS_PERMANENT; } else { - puts("Temporary fault occurred: 0x"); puth(fault); puts("\n"); + print("Temporary fault occurred: 0x"); puth(fault); print("\n"); fault_status = FAULT_STATUS_TEMPORARY; } } @@ -49,6 +50,6 @@ void fault_recovered(uint32_t fault) { if((PERMANENT_FAULTS & fault) == 0U){ faults &= ~fault; } else { - puts("Cannot recover from a permanent fault!\n"); + print("Cannot recover from a permanent fault!\n"); } } diff --git a/panda/board/flasher.h b/panda/board/flasher.h index 45b9d123d..f1a4f645e 100644 --- a/panda/board/flasher.h +++ b/panda/board/flasher.h @@ -41,6 +41,13 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { resp[1] = 0xff; } break; + // **** 0xc3: fetch MCU UID + case 0xc3: + #ifdef UID_BASE + (void)memcpy(resp, ((uint8_t *)UID_BASE), 12); + resp_len = 12; + #endif + break; // **** 0xd0: fetch serial number case 0xd0: #ifndef STM32F2 @@ -59,12 +66,12 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { // this allows reflashing of the bootstub switch (req->param1) { case 0: - puts("-> entering bootloader\n"); + print("-> entering bootloader\n"); enter_bootloader_mode = ENTER_BOOTLOADER_MAGIC; NVIC_SystemReset(); break; case 1: - puts("-> entering softloader\n"); + print("-> entering softloader\n"); enter_bootloader_mode = ENTER_SOFTLOADER_MAGIC; NVIC_SystemReset(); break; @@ -113,11 +120,11 @@ void comms_endpoint2_write(uint8_t *data, uint32_t len) { int spi_cb_rx(uint8_t *data, int len, uint8_t *data_out) { UNUSED(len); ControlPacket_t control_req; - + int resp_len = 0; switch (data[0]) { case 0: - // control transfer + // control transfer control_req.request = ((USB_Setup_TypeDef *)(data+4))->b.bRequest; control_req.param1 = ((USB_Setup_TypeDef *)(data+4))->b.wValue.w; control_req.param2 = ((USB_Setup_TypeDef *)(data+4))->b.wIndex.w; @@ -256,7 +263,7 @@ void soft_flasher_start(void) { REGISTER_INTERRUPT(CAN1_SCE_IRQn, CAN1_SCE_IRQ_Handler, CAN_INTERRUPT_RATE, FAULT_INTERRUPT_RATE_CAN_1) #endif - puts("\n\n\n************************ FLASHER START ************************\n"); + print("\n\n\n************************ FLASHER START ************************\n"); enter_bootloader_mode = 0; diff --git a/panda/board/get_sdk.sh b/panda/board/get_sdk.sh deleted file mode 100755 index c0d05651a..000000000 --- a/panda/board/get_sdk.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -sudo apt-get install dfu-util gcc-arm-none-eabi python3-pip -sudo pip install libusb1 pycryptodome requests diff --git a/panda/board/get_sdk_mac.sh b/panda/board/get_sdk_mac.sh deleted file mode 100755 index 9e4b71590..000000000 --- a/panda/board/get_sdk_mac.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -# Need formula for gcc -sudo easy_install pip -/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" -brew tap ArmMbed/homebrew-formulae -brew install python dfu-util arm-none-eabi-gcc -pip install --user libusb1 pycryptodome requests diff --git a/panda/board/health.h b/panda/board/health.h index 32b9bdd14..afe4f7254 100644 --- a/panda/board/health.h +++ b/panda/board/health.h @@ -28,7 +28,7 @@ struct __attribute__((packed)) health_t { uint8_t safety_rx_checks_invalid; }; -#define CAN_HEALTH_PACKET_VERSION 3 +#define CAN_HEALTH_PACKET_VERSION 4 typedef struct __attribute__((packed)) { uint8_t bus_off; uint32_t bus_off_cnt; @@ -46,6 +46,7 @@ typedef struct __attribute__((packed)) { uint32_t total_tx_cnt; uint32_t total_rx_cnt; uint32_t total_fwd_cnt; // Messages forwarded from one bus to another + uint32_t total_tx_checksum_error_cnt; uint16_t can_speed; uint16_t can_data_speed; uint8_t canfd_enabled; diff --git a/panda/board/main.c b/panda/board/main.c index 5b978fea3..4620a6157 100644 --- a/panda/board/main.c +++ b/panda/board/main.c @@ -24,6 +24,7 @@ #include "obj/gitversion.h" +#include "can_comms.h" #include "main_comms.h" @@ -63,11 +64,11 @@ void set_safety_mode(uint16_t mode, uint16_t param) { uint16_t mode_copy = mode; int err = set_safety_hooks(mode_copy, param); if (err == -1) { - puts("Error: safety set mode failed. Falling back to SILENT\n"); + print("Error: safety set mode failed. Falling back to SILENT\n"); mode_copy = SAFETY_SILENT; err = set_safety_hooks(mode_copy, 0U); if (err == -1) { - puts("Error: Failed setting SILENT mode. Hanging\n"); + print("Error: Failed setting SILENT mode. Hanging\n"); while (true) { // TERMINAL ERROR: we can't continue if SILENT safety mode isn't succesfully set } @@ -154,18 +155,18 @@ void tick_handler(void) { if (loop_counter == 0U) { can_live = pending_can_live; - //puth(usart1_dma); puts(" "); puth(DMA2_Stream5->M0AR); puts(" "); puth(DMA2_Stream5->NDTR); puts("\n"); + //puth(usart1_dma); print(" "); puth(DMA2_Stream5->M0AR); print(" "); puth(DMA2_Stream5->NDTR); print("\n"); // reset this every 16th pass if ((uptime_cnt & 0xFU) == 0U) { pending_can_live = 0; } #ifdef DEBUG - puts("** blink "); - puts("rx:"); puth4(can_rx_q.r_ptr); puts("-"); puth4(can_rx_q.w_ptr); puts(" "); - puts("tx1:"); puth4(can_tx1_q.r_ptr); puts("-"); puth4(can_tx1_q.w_ptr); puts(" "); - puts("tx2:"); puth4(can_tx2_q.r_ptr); puts("-"); puth4(can_tx2_q.w_ptr); puts(" "); - puts("tx3:"); puth4(can_tx3_q.r_ptr); puts("-"); puth4(can_tx3_q.w_ptr); puts("\n"); + print("** blink "); + print("rx:"); puth4(can_rx_q.r_ptr); print("-"); puth4(can_rx_q.w_ptr); print(" "); + print("tx1:"); puth4(can_tx1_q.r_ptr); print("-"); puth4(can_tx1_q.w_ptr); print(" "); + print("tx2:"); puth4(can_tx2_q.r_ptr); print("-"); puth4(can_tx2_q.w_ptr); print(" "); + print("tx3:"); puth4(can_tx3_q.r_ptr); print("-"); puth4(can_tx3_q.w_ptr); print("\n"); #endif // set green LED to be controls allowed @@ -214,9 +215,9 @@ void tick_handler(void) { if (!heartbeat_disabled) { // if the heartbeat has been gone for a while, go to SILENT safety mode and enter power save if (heartbeat_counter >= (check_started() ? HEARTBEAT_IGNITION_CNT_ON : HEARTBEAT_IGNITION_CNT_OFF)) { - puts("device hasn't sent a heartbeat for 0x"); + print("device hasn't sent a heartbeat for 0x"); puth(heartbeat_counter); - puts(" seconds. Safety is set to SILENT mode.\n"); + print(" seconds. Safety is set to SILENT mode.\n"); if (controls_allowed_countdown > 0U) { siren_countdown = 5U; @@ -231,6 +232,7 @@ void tick_handler(void) { if (current_safety_mode != SAFETY_SILENT) { set_safety_mode(SAFETY_SILENT, 0U); } + if (power_save_status != POWER_SAVE_STATUS_ENABLED) { set_power_save_state(POWER_SAVE_STATUS_ENABLED); } @@ -238,8 +240,10 @@ void tick_handler(void) { // Also disable IR when the heartbeat goes missing current_board->set_ir_power(0U); - // If enumerated but no heartbeat (phone up, boardd not running), turn the fan on to cool the device - if(usb_enumerated){ + // TODO: need a SPI equivalent + // If enumerated but no heartbeat (phone up, boardd not running), or when the SOM GPIO is pulled high by the ABL, + // turn the fan on to cool the device + if(usb_enumerated || current_board->read_som_gpio()){ fan_set_power(50U); } else { fan_set_power(0U); @@ -316,16 +320,16 @@ int main(void) { adc_init(); // print hello - puts("\n\n\n************************ MAIN START ************************\n"); + print("\n\n\n************************ MAIN START ************************\n"); // check for non-supported board types if(hw_type == HW_TYPE_UNKNOWN){ - puts("Unsupported board type\n"); + print("Unsupported board type\n"); while (1) { /* hang */ } } - puts("Config:\n"); - puts(" Board type: "); puts(current_board->board_type); puts("\n"); + print("Config:\n"); + print(" Board type: "); print(current_board->board_type); print("\n"); // init board current_board->init(); @@ -340,7 +344,7 @@ int main(void) { uart_init(&uart_ring_gps, 115200); } - if(current_board->has_lin){ + if (current_board->has_lin) { // enable LIN uart_init(&uart_ring_lin1, 10400); UART5->CR2 |= USART_CR2_LINEN; @@ -348,6 +352,10 @@ int main(void) { USART3->CR2 |= USART_CR2_LINEN; } + if (current_board->fan_max_rpm > 0U) { + llfan_init(); + } + microsecond_timer_init(); // init to SILENT and can silent @@ -361,7 +369,7 @@ int main(void) { tick_timer_init(); #ifdef DEBUG - puts("DEBUG ENABLED\n"); + print("DEBUG ENABLED\n"); #endif // enable USB (right before interrupts or enum can fail!) usb_init(); @@ -372,7 +380,7 @@ int main(void) { } #endif - puts("**** INTERRUPTS ON ****\n"); + print("**** INTERRUPTS ON ****\n"); enable_interrupts(); // LED should keep on blinking all the time diff --git a/panda/board/main_comms.h b/panda/board/main_comms.h index 3c41596c3..0d3a97aa3 100644 --- a/panda/board/main_comms.h +++ b/panda/board/main_comms.h @@ -47,115 +47,11 @@ int get_rtc_pkt(void *dat) { return sizeof(t); } -typedef struct { - uint32_t ptr; - uint32_t tail_size; - uint8_t data[72]; - uint8_t counter; -} asm_buffer; - -asm_buffer can_read_buffer = {.ptr = 0U, .tail_size = 0U, .counter = 0U}; -uint32_t total_rx_size = 0U; - -int comms_can_read(uint8_t *data, uint32_t max_len) { - uint32_t pos = 1; - data[0] = can_read_buffer.counter; - // Send tail of previous message if it is in buffer - if (can_read_buffer.ptr > 0U) { - if (can_read_buffer.ptr <= 63U) { - (void)memcpy(&data[pos], can_read_buffer.data, can_read_buffer.ptr); - pos += can_read_buffer.ptr; - can_read_buffer.ptr = 0U; - } else { - (void)memcpy(&data[pos], can_read_buffer.data, 63U); - can_read_buffer.ptr = can_read_buffer.ptr - 63U; - (void)memcpy(can_read_buffer.data, &can_read_buffer.data[63], can_read_buffer.ptr); - pos += 63U; - } - } - - if (total_rx_size > MAX_EP1_CHUNK_PER_BULK_TRANSFER) { - total_rx_size = 0U; - can_read_buffer.counter = 0U; - } else { - CANPacket_t can_packet; - while ((pos < max_len) && can_pop(&can_rx_q, &can_packet)) { - uint32_t pckt_len = CANPACKET_HEAD_SIZE + dlc_to_len[can_packet.data_len_code]; - if ((pos + pckt_len) <= max_len) { - (void)memcpy(&data[pos], &can_packet, pckt_len); - pos += pckt_len; - } else { - (void)memcpy(&data[pos], &can_packet, max_len - pos); - can_read_buffer.ptr = pckt_len - (max_len - pos); - // cppcheck-suppress objectIndex - (void)memcpy(can_read_buffer.data, &((uint8_t*)&can_packet)[(max_len - pos)], can_read_buffer.ptr); - pos = max_len; - } - } - can_read_buffer.counter++; - total_rx_size += pos; - } - if (pos != max_len) { - can_read_buffer.counter = 0U; - total_rx_size = 0U; - } - if (pos <= 1U) { pos = 0U; } - return pos; -} - -asm_buffer can_write_buffer = {.ptr = 0U, .tail_size = 0U, .counter = 0U}; - -// send on CAN -void comms_can_write(uint8_t *data, uint32_t len) { - // Got first packet from a stream, resetting buffer and counter - if (data[0] == 0U) { - can_write_buffer.counter = 0U; - can_write_buffer.ptr = 0U; - can_write_buffer.tail_size = 0U; - } - // Assembling can message with data from buffer - if (data[0] == can_write_buffer.counter) { - uint32_t pos = 1U; - can_write_buffer.counter++; - if (can_write_buffer.ptr != 0U) { - if (can_write_buffer.tail_size <= 63U) { - CANPacket_t to_push; - (void)memcpy(&can_write_buffer.data[can_write_buffer.ptr], &data[pos], can_write_buffer.tail_size); - (void)memcpy(&to_push, can_write_buffer.data, can_write_buffer.ptr + can_write_buffer.tail_size); - can_send(&to_push, to_push.bus, false); - pos += can_write_buffer.tail_size; - can_write_buffer.ptr = 0U; - can_write_buffer.tail_size = 0U; - } else { - (void)memcpy(&can_write_buffer.data[can_write_buffer.ptr], &data[pos], len - pos); - can_write_buffer.tail_size -= 63U; - can_write_buffer.ptr += 63U; - pos += 63U; - } - } - - while (pos < len) { - uint32_t pckt_len = CANPACKET_HEAD_SIZE + dlc_to_len[(data[pos] >> 4U)]; - if ((pos + pckt_len) <= len) { - CANPacket_t to_push; - (void)memcpy(&to_push, &data[pos], pckt_len); - can_send(&to_push, to_push.bus, false); - pos += pckt_len; - } else { - (void)memcpy(can_write_buffer.data, &data[pos], len - pos); - can_write_buffer.ptr = len - pos; - can_write_buffer.tail_size = pckt_len - can_write_buffer.ptr; - pos += can_write_buffer.ptr; - } - } - } -} - // send on serial, first byte to select the ring void comms_endpoint2_write(uint8_t *data, uint32_t len) { uart_ring *ur = get_ring_by_number(data[0]); if ((len != 0U) && (ur != NULL)) { - if ((data[0] < 2U) || safety_tx_lin_hook(data[0] - 2U, &data[1], len - 1U)) { + if ((data[0] < 2U) || (data[0] >= 4U) || safety_tx_lin_hook(data[0] - 2U, &data[1], len - 1U)) { for (uint32_t i = 1; i < len; i++) { while (!putc(ur, data[i])) { // wait @@ -165,23 +61,17 @@ void comms_endpoint2_write(uint8_t *data, uint32_t len) { } } -// TODO: make this more general! -void usb_cb_ep3_out_complete(void) { - if (can_tx_check_min_slots_free(MAX_CAN_MSGS_PER_BULK_TRANSFER)) { - usb_outep3_resume_if_paused(); - } -} - int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { unsigned int resp_len = 0; uart_ring *ur = NULL; timestamp_t t; + uint32_t time; #ifdef DEBUG_COMMS - puts("raw control request: "); hexdump(req, sizeof(ControlPacket_t)); puts("\n"); - puts("- request "); puth(req->request); puts("\n"); - puts("- param1 "); puth(req->param1); puts("\n"); - puts("- param2 "); puth(req->param2); puts("\n"); + print("raw control request: "); hexdump(req, sizeof(ControlPacket_t)); print("\n"); + print("- request "); puth(req->request); print("\n"); + print("- param1 "); puth(req->param1); print("\n"); + print("- param2 "); puth(req->param2); print("\n"); #endif switch (req->request) { @@ -231,6 +121,15 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { t.second = req->param1; rtc_set_time(t); break; + // **** 0xa8: get microsecond timer + case 0xa8: + time = microsecond_timer_get(); + resp[0] = (time & 0x000000FFU); + resp[1] = ((time & 0x0000FF00U) >> 8U); + resp[2] = ((time & 0x00FF0000U) >> 16U); + resp[3] = ((time & 0xFF000000U) >> 24U); + resp_len = 4U; + break; // **** 0xb0: set IR power case 0xb0: current_board->set_ir_power(req->param1); @@ -249,12 +148,16 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { case 0xb3: current_board->set_phone_power(req->param1 > 0U); break; + // **** 0xc0: reset communications + case 0xc0: + comms_can_reset(); + break; // **** 0xc1: get hardware type case 0xc1: resp[0] = hw_type; resp_len = 1; break; - // **** 0xd0: fetch serial number + // **** 0xc2: CAN health stats case 0xc2: COMPILE_TIME_ASSERT(sizeof(can_health_t) <= USBPACKET_MAX_SIZE); if (req->param1 < 3U) { @@ -267,6 +170,12 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { (void)memcpy(resp, &can_health[req->param1], resp_len); } break; + // **** 0xc3: fetch MCU UID + case 0xc3: + (void)memcpy(resp, ((uint8_t *)UID_BASE), 12); + resp_len = 12; + break; + // **** 0xd0: fetch serial (aka the provisioned dongle ID) case 0xd0: // addresses are OTP if (req->param1 == 1U) { @@ -284,18 +193,18 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { case 0: // only allow bootloader entry on debug builds #ifdef ALLOW_DEBUG - puts("-> entering bootloader\n"); + print("-> entering bootloader\n"); enter_bootloader_mode = ENTER_BOOTLOADER_MAGIC; NVIC_SystemReset(); #endif break; case 1: - puts("-> entering softloader\n"); + print("-> entering softloader\n"); enter_bootloader_mode = ENTER_SOFTLOADER_MAGIC; NVIC_SystemReset(); break; default: - puts("Bootloader mode invalid\n"); + print("Bootloader mode invalid\n"); break; } break; @@ -371,7 +280,7 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { } else if (req->param2 == 2U) { can_set_gmlan(2); } else { - puts("Invalid bus num for GMLAN CAN set\n"); + print("Invalid bus num for GMLAN CAN set\n"); } } else { can_set_gmlan(-1); @@ -392,7 +301,7 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { break; // **** 0xde: set can bitrate case 0xde: - if ((req->param1 < BUS_CNT) && is_speed_valid(req->param2, speeds, sizeof(speeds)/sizeof(speeds[0]))) { + if ((req->param1 < PANDA_BUS_CNT) && is_speed_valid(req->param2, speeds, sizeof(speeds)/sizeof(speeds[0]))) { bus_config[req->param1].can_speed = req->param2; bool ret = can_init(CAN_NUM_FROM_BUS_NUM(req->param1)); UNUSED(ret); @@ -486,13 +395,13 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { // **** 0xf1: Clear CAN ring buffer. case 0xf1: if (req->param1 == 0xFFFFU) { - puts("Clearing CAN Rx queue\n"); + print("Clearing CAN Rx queue\n"); can_clear(&can_rx_q); - } else if (req->param1 < BUS_CNT) { - puts("Clearing CAN Tx queue\n"); + } else if (req->param1 < PANDA_BUS_CNT) { + print("Clearing CAN Tx queue\n"); can_clear(can_queues[req->param1]); } else { - puts("Clearing CAN CAN ring buffer failed: wrong bus number\n"); + print("Clearing CAN CAN ring buffer failed: wrong bus number\n"); } break; // **** 0xf2: Clear UART ring buffer. @@ -500,7 +409,7 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { { uart_ring * rb = get_ring_by_number(req->param1); if (rb != NULL) { - puts("Clearing UART queue.\n"); + print("Clearing UART queue.\n"); clear_uart_buff(rb); } break; @@ -525,10 +434,6 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { } } break; - // **** 0xf5: set clock source mode - case 0xf5: - current_board->set_clock_source_mode(req->param1); - break; // **** 0xf6: set siren enabled case 0xf6: siren_enabled = (req->param1 != 0U); @@ -545,7 +450,7 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { break; // **** 0xf9: set CAN FD data bitrate case 0xf9: - if ((req->param1 < CAN_CNT) && + if ((req->param1 < PANDA_CAN_CNT) && current_board->has_canfd && is_speed_valid(req->param2, data_speeds, sizeof(data_speeds)/sizeof(data_speeds[0]))) { bus_config[req->param1].can_data_speed = req->param2; @@ -561,16 +466,16 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { break; // **** 0xfc: set CAN FD non-ISO mode case 0xfc: - if ((req->param1 < CAN_CNT) && current_board->has_canfd) { + if ((req->param1 < PANDA_CAN_CNT) && current_board->has_canfd) { bus_config[req->param1].canfd_non_iso = (req->param2 != 0U); bool ret = can_init(CAN_NUM_FROM_BUS_NUM(req->param1)); UNUSED(ret); } break; default: - puts("NO HANDLER "); + print("NO HANDLER "); puth(req->request); - puts("\n"); + print("\n"); break; } return resp_len; diff --git a/panda/board/main_declarations.h b/panda/board/main_declarations.h index 208afadfb..4570c6e67 100644 --- a/panda/board/main_declarations.h +++ b/panda/board/main_declarations.h @@ -1,5 +1,5 @@ // ******************** Prototypes ******************** -void puts(const char *a); +void print(const char *a); void puth(unsigned int i); void puth2(unsigned int i); void puth4(unsigned int i); diff --git a/panda/board/panda.h b/panda/board/panda.h deleted file mode 100644 index b6d611b2c..000000000 --- a/panda/board/panda.h +++ /dev/null @@ -1,26 +0,0 @@ -// USB definitions -#define USB_VID 0xBBAAU - -#ifdef BOOTSTUB - #define USB_PID 0xDDEEU -#else - #define USB_PID 0xDDCCU -#endif - -#define USBPACKET_MAX_SIZE 0x40U - -#define MAX_CAN_MSGS_PER_BULK_TRANSFER 51U -#define MAX_EP1_CHUNK_PER_BULK_TRANSFER 16256U // max data stream chunk in bytes, shouldn't be higher than 16320 or counter will overflow - -// CAN definitions -#define CANPACKET_HEAD_SIZE 5U - -#if !defined(STM32F4) && !defined(STM32F2) - #define CANPACKET_DATA_SIZE_MAX 64U -#else - #define CANPACKET_DATA_SIZE_MAX 8U -#endif - -#define CAN_CNT 3U -#define BUS_CNT 4U -#define CAN_INIT_TIMEOUT_MS 500U diff --git a/panda/board/pedal/main.c b/panda/board/pedal/main.c index 5e9c1eac2..71df7fe4e 100644 --- a/panda/board/pedal/main.c +++ b/panda/board/pedal/main.c @@ -11,7 +11,7 @@ #include "drivers/usb.h" #else // no serial either - void puts(const char *a) { + void print(const char *a) { UNUSED(a); } void puth(unsigned int i) { @@ -78,9 +78,9 @@ int comms_control_handler(ControlPacket_t *req, uint8_t *resp) { } break; default: - puts("NO HANDLER "); + print("NO HANDLER "); puth(req->request); - puts("\n"); + print("\n"); break; } return resp_len; @@ -122,7 +122,7 @@ const uint8_t crc_poly = 0xD5U; // standard crc8 void CAN1_RX0_IRQ_Handler(void) { while ((CAN->RF0R & CAN_RF0R_FMP0) != 0) { #ifdef DEBUG - puts("CAN RX\n"); + print("CAN RX\n"); #endif int address = CAN->sFIFOMailBox[0].RIR >> 21; if (address == CAN_GAS_INPUT) { @@ -135,7 +135,7 @@ void CAN1_RX0_IRQ_Handler(void) { enter_bootloader_mode = ENTER_BOOTLOADER_MAGIC; NVIC_SystemReset(); } else { - puts("Failed entering Softloader or Bootloader\n"); + print("Failed entering Softloader or Bootloader\n"); } } @@ -151,9 +151,9 @@ void CAN1_RX0_IRQ_Handler(void) { if (crc_checksum(dat, CAN_GAS_SIZE - 1, crc_poly) == dat[5]) { if (((current_index + 1U) & COUNTER_CYCLE) == index) { #ifdef DEBUG - puts("setting gas "); + print("setting gas "); puth(value_0); - puts("\n"); + print("\n"); #endif if (enable) { gas_set_0 = value_0; @@ -196,11 +196,11 @@ int led_value = 0; void TIM3_IRQ_Handler(void) { #ifdef DEBUG puth(TIM3->CNT); - puts(" "); + print(" "); puth(pdl0); - puts(" "); + print(" "); puth(pdl1); - puts("\n"); + print("\n"); #endif // check timer for sending the user pedal and clearing the CAN @@ -222,7 +222,7 @@ void TIM3_IRQ_Handler(void) { // old can packet hasn't sent! state = FAULT_SEND; #ifdef DEBUG - puts("CAN MISS\n"); + print("CAN MISS\n"); #endif } @@ -292,7 +292,7 @@ int main(void) { // init can bool llcan_speed_set = llcan_set_speed(CAN1, 5000, false, false); if (!llcan_speed_set) { - puts("Failed to set llcan speed"); + print("Failed to set llcan speed"); } bool ret = llcan_init(CAN1); @@ -304,7 +304,7 @@ int main(void) { watchdog_init(); - puts("**** INTERRUPTS ON ****\n"); + print("**** INTERRUPTS ON ****\n"); enable_interrupts(); // main pedal loop diff --git a/panda/board/pedal/main_declarations.h b/panda/board/pedal/main_declarations.h index 339a67d35..33e422735 100644 --- a/panda/board/pedal/main_declarations.h +++ b/panda/board/pedal/main_declarations.h @@ -1,5 +1,5 @@ // ******************** Prototypes ******************** -void puts(const char *a); +void print(const char *a); void puth(unsigned int i); void puth2(unsigned int i); void puth4(unsigned int i); diff --git a/panda/board/power_saving.h b/panda/board/power_saving.h index 52c65028a..cc271e5da 100644 --- a/panda/board/power_saving.h +++ b/panda/board/power_saving.h @@ -12,14 +12,14 @@ void set_power_save_state(int state) { if (is_valid_state && (state != power_save_status)) { bool enable = false; if (state == POWER_SAVE_STATUS_ENABLED) { - puts("enable power savings\n"); + print("enable power savings\n"); if (current_board->has_gps) { const char UBLOX_SLEEP_MSG[] = "\xb5\x62\x06\x04\x04\x00\x01\x00\x08\x00\x17\x78"; uart_ring *ur = get_ring_by_number(1); for (unsigned int i = 0; i < sizeof(UBLOX_SLEEP_MSG) - 1U; i++) while (!putc(ur, UBLOX_SLEEP_MSG[i])); } } else { - puts("disable power savings\n"); + print("disable power savings\n"); if (current_board->has_gps) { const char UBLOX_WAKE_MSG[] = "\xb5\x62\x06\x04\x04\x00\x01\x00\x09\x00\x18\x7a"; uart_ring *ur = get_ring_by_number(1); diff --git a/panda/board/provision.h b/panda/board/provision.h index 842179442..e56205bff 100644 --- a/panda/board/provision.h +++ b/panda/board/provision.h @@ -1,3 +1,6 @@ +// this is where we manage the dongle ID assigned during our +// manufacturing. aside from this, there's a UID for the MCU + #define PROVISION_CHUNK_LEN 0x20 void get_provision_chunk(uint8_t *resp) { diff --git a/panda/board/safety.h b/panda/board/safety.h index d9f0753e8..474c3c663 100644 --- a/panda/board/safety.h +++ b/panda/board/safety.h @@ -1,4 +1,5 @@ #include "safety_declarations.h" +#include "can_definitions.h" // include the safety policies. #include "safety/safety_defaults.h" @@ -18,10 +19,6 @@ #include "safety/safety_elm327.h" #include "safety/safety_body.h" -#ifdef STM32H7 -#define CANFD -#endif - // CAN-FD only safety modes #ifdef CANFD #include "safety/safety_hyundai_canfd.h" @@ -73,7 +70,7 @@ int safety_rx_hook(CANPacket_t *to_push) { } int safety_tx_hook(CANPacket_t *to_send) { - return (relay_malfunction ? -1 : current_hooks->tx(to_send, get_longitudinal_allowed())); + return (relay_malfunction ? -1 : current_hooks->tx(to_send)); } int safety_tx_lin_hook(int lin_num, uint8_t *data, int len) { @@ -486,6 +483,41 @@ float interpolate(struct lookup_t xy, float x) { return ret; } +// Safety checks for longitudinal actuation +bool longitudinal_accel_checks(int desired_accel, const LongitudinalLimits limits) { + bool violation = false; + if (!get_longitudinal_allowed()) { + violation |= desired_accel != limits.inactive_accel; + } else { + violation |= max_limit_check(desired_accel, limits.max_accel, limits.min_accel); + } + return violation; +} + +bool longitudinal_speed_checks(int desired_speed, const LongitudinalLimits limits) { + return !get_longitudinal_allowed() && (desired_speed != limits.inactive_speed); +} + +bool longitudinal_gas_checks(int desired_gas, const LongitudinalLimits limits) { + bool violation = false; + if (!get_longitudinal_allowed()) { + violation |= desired_gas != limits.inactive_gas; + } else { + violation |= max_limit_check(desired_gas, limits.max_gas, limits.min_gas); + } + return violation; +} + +bool longitudinal_brake_checks(int desired_brake, const LongitudinalLimits limits) { + bool violation = false; + violation |= !get_longitudinal_allowed() && (desired_brake != 0); + violation |= desired_brake > limits.max_brake; + return violation; +} + +bool longitudinal_interceptor_checks(CANPacket_t *to_send) { + return !get_longitudinal_allowed() && (GET_BYTE(to_send, 0) || GET_BYTE(to_send, 1)); +} // Safety checks for torque-based steering commands bool steer_torque_cmd_checks(int desired_torque, int steer_req, const SteeringLimits limits) { @@ -573,6 +605,35 @@ bool steer_torque_cmd_checks(int desired_torque, int steer_req, const SteeringLi return violation; } +// Safety checks for angle-based steering commands +bool steer_angle_cmd_checks(int desired_angle, bool steer_control_enabled, const SteeringLimits limits) { + bool violation = false; + + if (controls_allowed && steer_control_enabled) { + // convert floating point angle rate limits to integers in the scale of the desired angle on CAN, + // add 1 to not false trigger the violation + int delta_angle_up = (interpolate(limits.angle_rate_up_lookup, vehicle_speed) * limits.angle_deg_to_can) + 1.; + int delta_angle_down = (interpolate(limits.angle_rate_down_lookup, vehicle_speed) * limits.angle_deg_to_can) + 1.; + + int highest_desired_angle = desired_angle_last + ((desired_angle_last > 0) ? delta_angle_up : delta_angle_down); + int lowest_desired_angle = desired_angle_last - ((desired_angle_last >= 0) ? delta_angle_down : delta_angle_up); + + // check for violation; + violation |= max_limit_check(desired_angle, highest_desired_angle, lowest_desired_angle); + } + desired_angle_last = desired_angle; + + // Angle should be the same as current angle while not steering + violation |= (!controls_allowed && + ((desired_angle < (angle_meas.min - 1)) || + (desired_angle > (angle_meas.max + 1)))); + + // No angle control allowed when controls are not allowed + violation |= !controls_allowed && steer_control_enabled; + + return violation; +} + void pcm_cruise_check(bool cruise_engaged) { // Enter controls on rising edge of stock ACC, exit controls if stock ACC disengages if (!cruise_engaged) { diff --git a/panda/board/safety/safety_body.h b/panda/board/safety/safety_body.h index 473946a6d..d9a9fcd33 100644 --- a/panda/board/safety/safety_body.h +++ b/panda/board/safety/safety_body.h @@ -16,8 +16,7 @@ static int body_rx_hook(CANPacket_t *to_push) { return valid; } -static int body_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { - UNUSED(longitudinal_allowed); +static int body_tx_hook(CANPacket_t *to_send) { int tx = 0; int addr = GET_ADDR(to_send); @@ -30,8 +29,8 @@ static int body_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { if (msg_allowed(to_send, BODY_TX_MSGS, sizeof(BODY_TX_MSGS)/sizeof(BODY_TX_MSGS[0])) && controls_allowed) { tx = 1; } - - // Allow going into CAN flashing mode even if controls are not allowed + + // Allow going into CAN flashing mode even if controls are not allowed if (!controls_allowed && ((uint32_t)GET_BYTES_04(to_send) == 0xdeadfaceU) && ((uint32_t)GET_BYTES_48(to_send) == 0x0ab00b1eU)) { tx = 1; } diff --git a/panda/board/safety/safety_chrysler.h b/panda/board/safety/safety_chrysler.h index b158cf8a4..7c444bc0e 100644 --- a/panda/board/safety/safety_chrysler.h +++ b/panda/board/safety/safety_chrysler.h @@ -226,8 +226,7 @@ static int chrysler_rx_hook(CANPacket_t *to_push) { return valid; } -static int chrysler_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { - UNUSED(longitudinal_allowed); +static int chrysler_tx_hook(CANPacket_t *to_send) { int tx = 1; int addr = GET_ADDR(to_send); diff --git a/panda/board/safety/safety_defaults.h b/panda/board/safety/safety_defaults.h index e10f369d3..41c886295 100644 --- a/panda/board/safety/safety_defaults.h +++ b/panda/board/safety/safety_defaults.h @@ -15,9 +15,8 @@ static const addr_checks* nooutput_init(uint16_t param) { return &default_rx_checks; } -static int nooutput_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { +static int nooutput_tx_hook(CANPacket_t *to_send) { UNUSED(to_send); - UNUSED(longitudinal_allowed); return false; } @@ -54,9 +53,8 @@ static const addr_checks* alloutput_init(uint16_t param) { return &default_rx_checks; } -static int alloutput_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { +static int alloutput_tx_hook(CANPacket_t *to_send) { UNUSED(to_send); - UNUSED(longitudinal_allowed); return true; } diff --git a/panda/board/safety/safety_elm327.h b/panda/board/safety/safety_elm327.h index 5535471ea..6b1e72eac 100644 --- a/panda/board/safety/safety_elm327.h +++ b/panda/board/safety/safety_elm327.h @@ -1,5 +1,4 @@ -static int elm327_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { - UNUSED(longitudinal_allowed); +static int elm327_tx_hook(CANPacket_t *to_send) { int tx = 1; int addr = GET_ADDR(to_send); diff --git a/panda/board/safety/safety_ford.h b/panda/board/safety/safety_ford.h index 6556d957d..746c685d1 100644 --- a/panda/board/safety/safety_ford.h +++ b/panda/board/safety/safety_ford.h @@ -30,6 +30,10 @@ AddrCheckStruct ford_addr_checks[] = { #define FORD_ADDR_CHECK_LEN (sizeof(ford_addr_checks) / sizeof(ford_addr_checks[0])) addr_checks ford_rx_checks = {ford_addr_checks, FORD_ADDR_CHECK_LEN}; +#define INACTIVE_CURVATURE 1000U +#define INACTIVE_CURVATURE_RATE 4096U +#define INACTIVE_PATH_OFFSET 512U +#define INACTIVE_PATH_ANGLE 1000U static bool ford_lkas_msg_check(int addr) { return (addr == MSG_ACCDATA_3) @@ -76,8 +80,7 @@ static int ford_rx_hook(CANPacket_t *to_push) { return valid; } -static int ford_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { - UNUSED(longitudinal_allowed); +static int ford_tx_hook(CANPacket_t *to_send) { int tx = 1; int addr = GET_ADDR(to_send); @@ -117,9 +120,18 @@ static int ford_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { if (addr == MSG_LateralMotionControl) { // Signal: LatCtl_D_Rq unsigned int steer_control_type = (GET_BYTE(to_send, 4) >> 2) & 0x7U; - bool steer_control_enabled = steer_control_type != 0U; + unsigned int curvature = (GET_BYTE(to_send, 0) << 3) | (GET_BYTE(to_send, 1) >> 5); + unsigned int curvature_rate = ((GET_BYTE(to_send, 1) & 0x1FU) << 8) | GET_BYTE(to_send, 2); + unsigned int path_angle = (GET_BYTE(to_send, 3) << 3) | (GET_BYTE(to_send, 4) >> 5); + unsigned int path_offset = (GET_BYTE(to_send, 5) << 2) | (GET_BYTE(to_send, 6) >> 6); + + // These signals are not yet tested with the current safety limits + if ((curvature_rate != INACTIVE_CURVATURE_RATE) || (path_angle != INACTIVE_PATH_ANGLE) || (path_offset != INACTIVE_PATH_OFFSET)) { + tx = 0; + } // No steer control allowed when controls are not allowed + bool steer_control_enabled = (steer_control_type != 0U) || (curvature != INACTIVE_CURVATURE); if (!controls_allowed && steer_control_enabled) { tx = 0; } diff --git a/panda/board/safety/safety_gm.h b/panda/board/safety/safety_gm.h index b703f4721..3e7c2d3b9 100644 --- a/panda/board/safety/safety_gm.h +++ b/panda/board/safety/safety_gm.h @@ -1,43 +1,29 @@ -// board enforces -// in-state -// accel set/resume -// out-state -// cancel button -// regen paddle -// accel rising edge -// brake rising edge -// brake > 0mph +const SteeringLimits GM_STEERING_LIMITS = { + .max_steer = 300, + .max_rate_up = 10, + .max_rate_down = 25, + .driver_torque_allowance = 50, + .driver_torque_factor = 4, + .max_rt_delta = 128, + .max_rt_interval = 250000, + .type = TorqueDriverLimited, +}; -const int GM_MAX_STEER = 300; -const int GM_MAX_RT_DELTA = 128; // max delta torque allowed for real time checks -const uint32_t GM_RT_INTERVAL = 250000; // 250ms between real time checks -const int GM_MAX_RATE_UP = 7; -const int GM_MAX_RATE_DOWN = 17; -const int GM_DRIVER_TORQUE_ALLOWANCE = 50; -const int GM_DRIVER_TORQUE_FACTOR = 4; - -typedef struct { - const int max_gas; - const int max_regen; - const int inactive_regen; - const int max_brake; -} GmLongLimits; - -const GmLongLimits GM_ASCM_LONG_LIMITS = { +const LongitudinalLimits GM_ASCM_LONG_LIMITS = { .max_gas = 3072, - .max_regen = 1404, - .inactive_regen = 1404, + .min_gas = 1404, + .inactive_gas = 1404, .max_brake = 400, }; -const GmLongLimits GM_CAM_LONG_LIMITS = { +const LongitudinalLimits GM_CAM_LONG_LIMITS = { .max_gas = 3400, - .max_regen = 1514, - .inactive_regen = 1554, + .min_gas = 1514, + .inactive_gas = 1554, .max_brake = 400, }; -const GmLongLimits *gm_long_limits; +const LongitudinalLimits *gm_long_limits; const int GM_STANDSTILL_THRSLD = 10; // 0.311kph @@ -161,7 +147,7 @@ static int gm_rx_hook(CANPacket_t *to_push) { // else // block all commands that produce actuation -static int gm_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { +static int gm_tx_hook(CANPacket_t *to_send) { int tx = 1; int addr = GET_ADDR(to_send); @@ -180,12 +166,7 @@ static int gm_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { if (addr == 789) { int brake = ((GET_BYTE(to_send, 0) & 0xFU) << 8) + GET_BYTE(to_send, 1); brake = (0x1000 - brake) & 0xFFF; - if (!longitudinal_allowed) { - if (brake != 0) { - tx = 0; - } - } - if (brake > gm_long_limits->max_brake) { + if (longitudinal_brake_checks(brake, *gm_long_limits)) { tx = 0; } } @@ -193,70 +174,24 @@ static int gm_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { // LKA STEER: safety check if (addr == 384) { int desired_torque = ((GET_BYTE(to_send, 0) & 0x7U) << 8) + GET_BYTE(to_send, 1); - uint32_t ts = microsecond_timer_get(); - bool violation = 0; desired_torque = to_signed(desired_torque, 11); - if (controls_allowed) { - - // *** global torque limit check *** - violation |= max_limit_check(desired_torque, GM_MAX_STEER, -GM_MAX_STEER); - - // *** torque rate limit check *** - violation |= driver_limit_check(desired_torque, desired_torque_last, &torque_driver, - GM_MAX_STEER, GM_MAX_RATE_UP, GM_MAX_RATE_DOWN, - GM_DRIVER_TORQUE_ALLOWANCE, GM_DRIVER_TORQUE_FACTOR); - - // used next time - desired_torque_last = desired_torque; - - // *** torque real time rate limit check *** - violation |= rt_rate_limit_check(desired_torque, rt_torque_last, GM_MAX_RT_DELTA); - - // every RT_INTERVAL set the new limits - uint32_t ts_elapsed = get_ts_elapsed(ts, ts_torque_check_last); - if (ts_elapsed > GM_RT_INTERVAL) { - rt_torque_last = desired_torque; - ts_torque_check_last = ts; - } - } - - // 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) { - desired_torque_last = 0; - rt_torque_last = 0; - ts_torque_check_last = ts; - } - - if (violation) { + if (steer_torque_cmd_checks(desired_torque, -1, GM_STEERING_LIMITS)) { tx = 0; } } // GAS/REGEN: safety check if (addr == 715) { + bool apply = GET_BIT(to_send, 0U) != 0U; int gas_regen = ((GET_BYTE(to_send, 2) & 0x7FU) << 5) + ((GET_BYTE(to_send, 3) & 0xF8U) >> 3); - // Disabled message is !engaged with gas - // value that corresponds to inactive regen. - if (!longitudinal_allowed) { - if (gas_regen != gm_long_limits->inactive_regen) { - tx = 0; - } - } - // Need to allow apply bit in pre-enabled and overriding states - if (!controls_allowed) { - bool apply = GET_BIT(to_send, 0U) != 0U; - if (apply) { - tx = 0; - } - } - // Enforce gas/regen actuation limits (max_regen <= gas_regen <= max_gas) - if ((gas_regen < gm_long_limits->max_regen) || (gas_regen > gm_long_limits->max_gas)) { + + bool violation = false; + // Allow apply bit in pre-enabled and overriding states + violation |= !controls_allowed && apply; + violation |= longitudinal_gas_checks(gas_regen, *gm_long_limits); + + if (violation) { tx = 0; } } diff --git a/panda/board/safety/safety_honda.h b/panda/board/safety/safety_honda.h index d1f4daa86..b8af7cac2 100644 --- a/panda/board/safety/safety_honda.h +++ b/panda/board/safety/safety_honda.h @@ -16,9 +16,22 @@ const CanMsg HONDA_BOSCH_LONG_TX_MSGS[] = {{0xE4, 1, 5}, {0x1DF, 1, 8}, {0x1EF, // Threshold calculated from DBC gains: round(((83.3 / 0.253984064) + (83.3 / 0.126992032)) / 2) = 492 const int HONDA_GAS_INTERCEPTOR_THRESHOLD = 492; #define HONDA_GET_INTERCEPTOR(msg) (((GET_BYTE((msg), 0) << 8) + GET_BYTE((msg), 1) + (GET_BYTE((msg), 2) << 8) + GET_BYTE((msg), 3)) / 2U) // avg between 2 tracks -const int HONDA_BOSCH_NO_GAS_VALUE = -30000; // value sent when not requesting gas -const int HONDA_BOSCH_GAS_MAX = 2000; -const int HONDA_BOSCH_ACCEL_MIN = -350; // max braking == -3.5m/s2 + +const LongitudinalLimits HONDA_BOSCH_LONG_LIMITS = { + .max_accel = 200, // accel is used for brakes + .min_accel = -350, + + .max_gas = 2000, + .min_gas = -30000, + .inactive_gas = -30000, +}; + +const LongitudinalLimits HONDA_NIDEC_LONG_LIMITS = { + .max_gas = 198, // 0xc6 + .max_brake = 255, + + .inactive_speed = 0, +}; // Nidec and bosch radarless has the powertrain bus on bus 0 AddrCheckStruct honda_common_addr_checks[] = { @@ -247,7 +260,7 @@ static int honda_rx_hook(CANPacket_t *to_push) { // else // block all commands that produce actuation -static int honda_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { +static int honda_tx_hook(CANPacket_t *to_send) { int tx = 1; int addr = GET_ADDR(to_send); @@ -270,22 +283,19 @@ static int honda_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { if ((addr == 0x30C) && (bus == bus_pt)) { int pcm_speed = (GET_BYTE(to_send, 0) << 8) | GET_BYTE(to_send, 1); int pcm_gas = GET_BYTE(to_send, 2); - if (!longitudinal_allowed) { - if ((pcm_speed != 0) || (pcm_gas != 0)) { - tx = 0; - } + + bool violation = false; + violation |= longitudinal_speed_checks(pcm_speed, HONDA_NIDEC_LONG_LIMITS); + violation |= longitudinal_gas_checks(pcm_gas, HONDA_NIDEC_LONG_LIMITS); + if (violation) { + tx = 0; } } // BRAKE: safety check (nidec) if ((addr == 0x1FA) && (bus == bus_pt)) { honda_brake = (GET_BYTE(to_send, 0) << 2) + ((GET_BYTE(to_send, 1) >> 6) & 0x3U); - if (!longitudinal_allowed) { - if (honda_brake != 0) { - tx = 0; - } - } - if (honda_brake > 255) { + if (longitudinal_brake_checks(honda_brake, HONDA_NIDEC_LONG_LIMITS)) { tx = 0; } if (honda_fwd_brake) { @@ -297,23 +307,14 @@ static int honda_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { if ((addr == 0x1DF) && (bus == bus_pt)) { int accel = (GET_BYTE(to_send, 3) << 3) | ((GET_BYTE(to_send, 4) >> 5) & 0x7U); accel = to_signed(accel, 11); - if (!longitudinal_allowed) { - if (accel != 0) { - tx = 0; - } - } - if (accel < HONDA_BOSCH_ACCEL_MIN) { - tx = 0; - } int gas = (GET_BYTE(to_send, 0) << 8) | GET_BYTE(to_send, 1); gas = to_signed(gas, 16); - if (!longitudinal_allowed) { - if (gas != HONDA_BOSCH_NO_GAS_VALUE) { - tx = 0; - } - } - if (gas > HONDA_BOSCH_GAS_MAX) { + + bool violation = false; + violation |= longitudinal_accel_checks(accel, HONDA_BOSCH_LONG_LIMITS); + violation |= longitudinal_gas_checks(gas, HONDA_BOSCH_LONG_LIMITS); + if (violation) { tx = 0; } } @@ -337,10 +338,8 @@ static int honda_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { // GAS: safety check (interceptor) if (addr == 0x200) { - if (!longitudinal_allowed) { - if (GET_BYTE(to_send, 0) || GET_BYTE(to_send, 1)) { - tx = 0; - } + if (longitudinal_interceptor_checks(to_send)) { + tx = 0; } } diff --git a/panda/board/safety/safety_hyundai.h b/panda/board/safety/safety_hyundai.h index 0620e9197..6202c1d73 100644 --- a/panda/board/safety/safety_hyundai.h +++ b/panda/board/safety/safety_hyundai.h @@ -1,26 +1,30 @@ #include "safety_hyundai_common.h" -const SteeringLimits HYUNDAI_STEERING_LIMITS = { - .max_steer = 384, - .max_rt_delta = 112, - .max_rt_interval = 250000, - .max_rate_up = 3, - .max_rate_down = 7, - .driver_torque_allowance = 50, - .driver_torque_factor = 2, - .type = TorqueDriverLimited, +#define HYUNDAI_LIMITS(steer, rate_up, rate_down) { \ + .max_steer = (steer), \ + .max_rate_up = (rate_up), \ + .max_rate_down = (rate_down), \ + .max_rt_delta = 112, \ + .max_rt_interval = 250000, \ + .driver_torque_allowance = 50, \ + .driver_torque_factor = 2, \ + .type = TorqueDriverLimited, \ + /* the EPS faults when the steering angle is above a certain threshold for too long. to prevent this, */ \ + /* we allow setting CF_Lkas_ActToi bit to 0 while maintaining the requested torque value for two consecutive frames */ \ + .min_valid_request_frames = 89, \ + .max_invalid_request_frames = 2, \ + .min_valid_request_rt_interval = 810000, /* 810ms; a ~10% buffer on cutting every 90 frames */ \ + .has_steer_req_tolerance = true, \ +} - // the EPS faults when the steering angle is above a certain threshold for too long. to prevent this, - // we allow setting CF_Lkas_ActToi bit to 0 while maintaining the requested torque value for two consecutive frames - .min_valid_request_frames = 89, - .max_invalid_request_frames = 2, - .min_valid_request_rt_interval = 810000, // 810ms; a ~10% buffer on cutting every 90 frames - .has_steer_req_tolerance = true, +const SteeringLimits HYUNDAI_STEERING_LIMITS = HYUNDAI_LIMITS(384, 3, 7); +const SteeringLimits HYUNDAI_STEERING_LIMITS_ALT = HYUNDAI_LIMITS(270, 2, 3); + +const LongitudinalLimits HYUNDAI_LONG_LIMITS = { + .max_accel = 200, // 1/100 m/s2 + .min_accel = -350, // 1/100 m/s2 }; -const int HYUNDAI_MAX_ACCEL = 200; // 1/100 m/s2 -const int HYUNDAI_MIN_ACCEL = -350; // 1/100 m/s2 - const CanMsg HYUNDAI_TX_MSGS[] = { {832, 0, 8}, // LKAS11 Bus 0 {1265, 0, 4}, // CLU11 Bus 0 @@ -225,7 +229,7 @@ static int hyundai_rx_hook(CANPacket_t *to_push) { return valid; } -static int hyundai_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { +static int hyundai_tx_hook(CANPacket_t *to_send) { int tx = 1; int addr = GET_ADDR(to_send); @@ -257,16 +261,10 @@ static int hyundai_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { int aeb_decel_cmd = GET_BYTE(to_send, 2); int aeb_req = GET_BIT(to_send, 54U); - bool violation = 0; - - if (!longitudinal_allowed) { - if ((desired_accel_raw != 0) || (desired_accel_val != 0)) { - violation = 1; - } - } - violation |= max_limit_check(desired_accel_raw, HYUNDAI_MAX_ACCEL, HYUNDAI_MIN_ACCEL); - violation |= max_limit_check(desired_accel_val, HYUNDAI_MAX_ACCEL, HYUNDAI_MIN_ACCEL); + bool violation = false; + violation |= longitudinal_accel_checks(desired_accel_raw, HYUNDAI_LONG_LIMITS); + violation |= longitudinal_accel_checks(desired_accel_val, HYUNDAI_LONG_LIMITS); violation |= (aeb_decel_cmd != 0); violation |= (aeb_req != 0); @@ -280,7 +278,8 @@ static int hyundai_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { int desired_torque = ((GET_BYTES_04(to_send) >> 16) & 0x7ffU) - 1024U; bool steer_req = GET_BIT(to_send, 27U) != 0U; - if (steer_torque_cmd_checks(desired_torque, steer_req, HYUNDAI_STEERING_LIMITS)) { + const SteeringLimits limits = hyundai_alt_limits ? HYUNDAI_STEERING_LIMITS_ALT : HYUNDAI_STEERING_LIMITS; + if (steer_torque_cmd_checks(desired_torque, steer_req, limits)) { tx = 0; } } diff --git a/panda/board/safety/safety_hyundai_canfd.h b/panda/board/safety/safety_hyundai_canfd.h index 5086a39bc..3e002941b 100644 --- a/panda/board/safety/safety_hyundai_canfd.h +++ b/panda/board/safety/safety_hyundai_canfd.h @@ -65,6 +65,24 @@ AddrCheckStruct hyundai_canfd_addr_checks[] = { }; #define HYUNDAI_CANFD_ADDR_CHECK_LEN (sizeof(hyundai_canfd_addr_checks) / sizeof(hyundai_canfd_addr_checks[0])) +// 0x1a0 is on bus 0 +AddrCheckStruct hyundai_canfd_radar_scc_addr_checks[] = { + {.msg = {{0x35, 1, 32, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}, + {0x35, 0, 32, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}, + {0x105, 0, 32, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}}}, + {.msg = {{0x175, 1, 24, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 20000U}, + {0x175, 0, 24, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 20000U}, { 0 }}}, + {.msg = {{0xa0, 1, 24, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}, + {0xa0, 0, 24, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}, { 0 }}}, + {.msg = {{0xea, 1, 24, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}, + {0xea, 0, 24, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}, { 0 }}}, + {.msg = {{0x1a0, 0, 32, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 20000U}, { 0 }, { 0 }}}, + {.msg = {{0x1cf, 1, 8, .check_checksum = false, .max_counter = 0xfU, .expected_timestep = 20000U}, + {0x1cf, 0, 8, .check_checksum = false, .max_counter = 0xfU, .expected_timestep = 20000U}, + {0x1aa, 0, 16, .check_checksum = false, .max_counter = 0xffU, .expected_timestep = 20000U}}}, +}; +#define HYUNDAI_CANFD_RADAR_SCC_ADDR_CHECK_LEN (sizeof(hyundai_canfd_radar_scc_addr_checks) / sizeof(hyundai_canfd_radar_scc_addr_checks[0])) + AddrCheckStruct hyundai_canfd_long_addr_checks[] = { {.msg = {{0x35, 1, 32, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}, {0x35, 0, 32, .check_checksum = true, .max_counter = 0xffU, .expected_timestep = 10000U}, @@ -226,8 +244,7 @@ static int hyundai_canfd_rx_hook(CANPacket_t *to_push) { return valid; } -static int hyundai_canfd_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { - UNUSED(longitudinal_allowed); +static int hyundai_canfd_tx_hook(CANPacket_t *to_send) { int tx = 0; int addr = GET_ADDR(to_send); @@ -278,13 +295,8 @@ static int hyundai_canfd_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed bool violation = false; if (hyundai_longitudinal) { - if (!longitudinal_allowed) { - if ((desired_accel_raw != 0) || (desired_accel_val != 0)) { - violation = true; - } - } - violation |= max_limit_check(desired_accel_raw, HYUNDAI_MAX_ACCEL, HYUNDAI_MIN_ACCEL); - violation |= max_limit_check(desired_accel_val, HYUNDAI_MAX_ACCEL, HYUNDAI_MIN_ACCEL); + violation |= longitudinal_accel_checks(desired_accel_raw, HYUNDAI_LONG_LIMITS); + violation |= longitudinal_accel_checks(desired_accel_val, HYUNDAI_LONG_LIMITS); } else { // only used to cancel on here if ((desired_accel_raw != 0) || (desired_accel_val != 0)) { @@ -344,6 +356,8 @@ static const addr_checks* hyundai_canfd_init(uint16_t param) { } else { if (!hyundai_ev_gas_signal && !hyundai_hybrid_gas_signal) { hyundai_canfd_rx_checks = (addr_checks){hyundai_canfd_ice_addr_checks, HYUNDAI_CANFD_ICE_ADDR_CHECK_LEN}; + } else if (!hyundai_camera_scc && !hyundai_canfd_hda2) { + hyundai_canfd_rx_checks = (addr_checks){hyundai_canfd_radar_scc_addr_checks, HYUNDAI_CANFD_RADAR_SCC_ADDR_CHECK_LEN}; } else { hyundai_canfd_rx_checks = (addr_checks){hyundai_canfd_addr_checks, HYUNDAI_CANFD_ADDR_CHECK_LEN}; } diff --git a/panda/board/safety/safety_hyundai_common.h b/panda/board/safety/safety_hyundai_common.h index f1f3b3412..8bd84feac 100644 --- a/panda/board/safety/safety_hyundai_common.h +++ b/panda/board/safety/safety_hyundai_common.h @@ -5,6 +5,7 @@ const int HYUNDAI_PARAM_EV_GAS = 1; const int HYUNDAI_PARAM_HYBRID_GAS = 2; const int HYUNDAI_PARAM_LONGITUDINAL = 4; const int HYUNDAI_PARAM_CAMERA_SCC = 8; +const int HYUNDAI_PARAM_ALT_LIMITS = 64; // TODO: shift this down with the rest of the common flags const uint8_t HYUNDAI_PREV_BUTTON_SAMPLES = 8; // roughly 160 ms const uint32_t HYUNDAI_STANDSTILL_THRSLD = 30; // ~1kph @@ -21,12 +22,14 @@ bool hyundai_ev_gas_signal = false; bool hyundai_hybrid_gas_signal = false; bool hyundai_longitudinal = false; bool hyundai_camera_scc = false; +bool hyundai_alt_limits = false; uint8_t hyundai_last_button_interaction; // button messages since the user pressed an enable button void hyundai_common_init(uint16_t param) { hyundai_ev_gas_signal = GET_FLAG(param, HYUNDAI_PARAM_EV_GAS); hyundai_hybrid_gas_signal = !hyundai_ev_gas_signal && GET_FLAG(param, HYUNDAI_PARAM_HYBRID_GAS); hyundai_camera_scc = GET_FLAG(param, HYUNDAI_PARAM_CAMERA_SCC); + hyundai_alt_limits = GET_FLAG(param, HYUNDAI_PARAM_ALT_LIMITS); hyundai_last_button_interaction = HYUNDAI_PREV_BUTTON_SAMPLES; diff --git a/panda/board/safety/safety_mazda.h b/panda/board/safety/safety_mazda.h index 423c096dd..dae484ce3 100644 --- a/panda/board/safety/safety_mazda.h +++ b/panda/board/safety/safety_mazda.h @@ -72,8 +72,7 @@ static int mazda_rx_hook(CANPacket_t *to_push) { return valid; } -static int mazda_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { - UNUSED(longitudinal_allowed); +static int mazda_tx_hook(CANPacket_t *to_send) { int tx = 1; int addr = GET_ADDR(to_send); diff --git a/panda/board/safety/safety_nissan.h b/panda/board/safety/safety_nissan.h index 189a19af8..c977cf66f 100644 --- a/panda/board/safety/safety_nissan.h +++ b/panda/board/safety/safety_nissan.h @@ -1,15 +1,14 @@ - -const uint32_t NISSAN_RT_INTERVAL = 250000; // 250ms between real time checks - -const struct lookup_t NISSAN_LOOKUP_ANGLE_RATE_UP = { - {2., 7., 17.}, - {5., .8, .15}}; - -const struct lookup_t NISSAN_LOOKUP_ANGLE_RATE_DOWN = { - {2., 7., 17.}, - {5., 3.5, .5}}; - -const int NISSAN_DEG_TO_CAN = 100; +const SteeringLimits NISSAN_STEERING_LIMITS = { + .angle_deg_to_can = 100, + .angle_rate_up_lookup = { + {2., 7., 17.}, + {5., .8, .15} + }, + .angle_rate_down_lookup = { + {2., 7., 17.}, + {5., 3.5, .5} + }, +}; const CanMsg NISSAN_TX_MSGS[] = { {0x169, 0, 8}, // LKAS @@ -100,12 +99,11 @@ static int nissan_rx_hook(CANPacket_t *to_push) { } -static int nissan_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { - UNUSED(longitudinal_allowed); +static int nissan_tx_hook(CANPacket_t *to_send) { int tx = 1; int addr = GET_ADDR(to_send); - bool violation = 0; + bool violation = false; if (!msg_allowed(to_send, NISSAN_TX_MSGS, sizeof(NISSAN_TX_MSGS) / sizeof(NISSAN_TX_MSGS[0]))) { tx = 0; @@ -116,34 +114,11 @@ static int nissan_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { int desired_angle = ((GET_BYTE(to_send, 0) << 10) | (GET_BYTE(to_send, 1) << 2) | ((GET_BYTE(to_send, 2) >> 6) & 0x3U)); bool lka_active = (GET_BYTE(to_send, 6) >> 4) & 1U; - // offeset 1310 * NISSAN_DEG_TO_CAN + // offeset 1310 * NISSAN_STEERING_LIMITS.angle_deg_to_can desired_angle = desired_angle - 131000; - if (controls_allowed && lka_active) { - // add 1 to not false trigger the violation - float delta_angle_float; - delta_angle_float = (interpolate(NISSAN_LOOKUP_ANGLE_RATE_UP, vehicle_speed) * NISSAN_DEG_TO_CAN) + 1.; - int delta_angle_up = (int)(delta_angle_float); - delta_angle_float = (interpolate(NISSAN_LOOKUP_ANGLE_RATE_DOWN, vehicle_speed) * NISSAN_DEG_TO_CAN) + 1.; - int delta_angle_down = (int)(delta_angle_float); - int highest_desired_angle = desired_angle_last + ((desired_angle_last > 0) ? delta_angle_up : delta_angle_down); - int lowest_desired_angle = desired_angle_last - ((desired_angle_last >= 0) ? delta_angle_down : delta_angle_up); - - // check for violation; - violation |= max_limit_check(desired_angle, highest_desired_angle, lowest_desired_angle); - } - desired_angle_last = desired_angle; - - // desired steer angle should be the same as steer angle measured when controls are off - if ((!controls_allowed) && - ((desired_angle < (angle_meas.min - 1)) || - (desired_angle > (angle_meas.max + 1)))) { - violation = 1; - } - - // no lka_enabled bit if controls not allowed - if (!controls_allowed && lka_active) { - violation = 1; + if (steer_angle_cmd_checks(desired_angle, lka_active, NISSAN_STEERING_LIMITS)) { + violation = true; } } diff --git a/panda/board/safety/safety_subaru.h b/panda/board/safety/safety_subaru.h index a1a98a15f..3c782e5f5 100644 --- a/panda/board/safety/safety_subaru.h +++ b/panda/board/safety/safety_subaru.h @@ -120,8 +120,7 @@ static int subaru_rx_hook(CANPacket_t *to_push) { return valid; } -static int subaru_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { - UNUSED(longitudinal_allowed); +static int subaru_tx_hook(CANPacket_t *to_send) { int tx = 1; int addr = GET_ADDR(to_send); diff --git a/panda/board/safety/safety_subaru_legacy.h b/panda/board/safety/safety_subaru_legacy.h index 7e58e20fa..a0b6b120d 100644 --- a/panda/board/safety/safety_subaru_legacy.h +++ b/panda/board/safety/safety_subaru_legacy.h @@ -61,8 +61,7 @@ static int subaru_legacy_rx_hook(CANPacket_t *to_push) { return valid; } -static int subaru_legacy_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { - UNUSED(longitudinal_allowed); +static int subaru_legacy_tx_hook(CANPacket_t *to_send) { int tx = 1; int addr = GET_ADDR(to_send); diff --git a/panda/board/safety/safety_tesla.h b/panda/board/safety/safety_tesla.h index 312074dec..a0a82f4bc 100644 --- a/panda/board/safety/safety_tesla.h +++ b/panda/board/safety/safety_tesla.h @@ -1,14 +1,21 @@ -const struct lookup_t TESLA_LOOKUP_ANGLE_RATE_UP = { +const SteeringLimits TESLA_STEERING_LIMITS = { + .angle_deg_to_can = 10, + .angle_rate_up_lookup = { {2., 7., 17.}, - {5., .8, .25}}; - -const struct lookup_t TESLA_LOOKUP_ANGLE_RATE_DOWN = { + {5., .8, .25} + }, + .angle_rate_down_lookup = { {2., 7., 17.}, - {5., 3.5, .8}}; + {5., 3.5, .8} + }, +}; + +const LongitudinalLimits TESLA_LONG_LIMITS = { + .max_accel = 425, // 2. m/s^2 + .min_accel = 287, // -3.52 m/s^2 // TODO: limit to -3.48 + .inactive_accel = 375, // 0. m/s^2 +}; -const int TESLA_DEG_TO_CAN = 10; -const float TESLA_MAX_ACCEL = 2.0; // m/s^2 -const float TESLA_MIN_ACCEL = -3.5; // m/s^2 const int TESLA_FLAG_POWERTRAIN = 1; const int TESLA_FLAG_LONGITUDINAL_CONTROL = 2; @@ -26,8 +33,6 @@ const CanMsg TESLA_PT_TX_MSGS[] = { }; #define TESLA_PT_TX_LEN (sizeof(TESLA_PT_TX_MSGS) / sizeof(TESLA_PT_TX_MSGS[0])) -const int TESLA_NO_ACCEL_VALUE = 375; // value sent when not requesting acceleration - AddrCheckStruct tesla_addr_checks[] = { {.msg = {{0x370, 0, 8, .expected_timestep = 40000U}, { 0 }, { 0 }}}, // EPAS_sysStatus (25Hz) {.msg = {{0x108, 0, 8, .expected_timestep = 10000U}, { 0 }, { 0 }}}, // DI_torque1 (100Hz) @@ -112,7 +117,7 @@ static int tesla_rx_hook(CANPacket_t *to_push) { } -static int tesla_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { +static int tesla_tx_hook(CANPacket_t *to_send) { int tx = 1; int addr = GET_ADDR(to_send); @@ -133,29 +138,7 @@ static int tesla_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { bool steer_control_enabled = (steer_control_type != 0) && // NONE (steer_control_type != 3); // DISABLED - // Rate limit while steering - if(controls_allowed && steer_control_enabled) { - // Add 1 to not false trigger the violation - float delta_angle_float; - delta_angle_float = (interpolate(TESLA_LOOKUP_ANGLE_RATE_UP, vehicle_speed) * TESLA_DEG_TO_CAN); - int delta_angle_up = (int)(delta_angle_float) + 1; - delta_angle_float = (interpolate(TESLA_LOOKUP_ANGLE_RATE_DOWN, vehicle_speed) * TESLA_DEG_TO_CAN); - int delta_angle_down = (int)(delta_angle_float) + 1; - int highest_desired_angle = desired_angle_last + ((desired_angle_last > 0) ? delta_angle_up : delta_angle_down); - int lowest_desired_angle = desired_angle_last - ((desired_angle_last >= 0) ? delta_angle_down : delta_angle_up); - - // Check for violation; - violation |= max_limit_check(desired_angle, highest_desired_angle, lowest_desired_angle); - } - desired_angle_last = desired_angle; - - // Angle should be the same as current angle while not steering - if(!controls_allowed && ((desired_angle < (angle_meas.min - 1)) || (desired_angle > (angle_meas.max + 1)))) { - violation = true; - } - - // No angle control allowed when controls are not allowed - if(!controls_allowed && steer_control_enabled) { + if (steer_angle_cmd_checks(desired_angle, steer_control_enabled, TESLA_STEERING_LIMITS)) { violation = true; } } @@ -185,23 +168,8 @@ static int tesla_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { // Don't allow any acceleration limits above the safety limits int raw_accel_max = ((GET_BYTE(to_send, 6) & 0x1FU) << 4) | (GET_BYTE(to_send, 5) >> 4); int raw_accel_min = ((GET_BYTE(to_send, 5) & 0x0FU) << 5) | (GET_BYTE(to_send, 4) >> 3); - float accel_max = (0.04 * raw_accel_max) - 15; - float accel_min = (0.04 * raw_accel_min) - 15; - - if ((accel_max > TESLA_MAX_ACCEL) || (accel_min > TESLA_MAX_ACCEL)){ - violation = true; - } - - if ((accel_max < TESLA_MIN_ACCEL) || (accel_min < TESLA_MIN_ACCEL)){ - violation = true; - } - - // Don't allow longitudinal actuation if controls aren't allowed - if (!longitudinal_allowed) { - if ((raw_accel_max != TESLA_NO_ACCEL_VALUE) || (raw_accel_min != TESLA_NO_ACCEL_VALUE)) { - violation = true; - } - } + violation |= longitudinal_accel_checks(raw_accel_max, TESLA_LONG_LIMITS); + violation |= longitudinal_accel_checks(raw_accel_min, TESLA_LONG_LIMITS); } else { violation = true; } diff --git a/panda/board/safety/safety_toyota.h b/panda/board/safety/safety_toyota.h index 510bc10e3..5aef744a8 100644 --- a/panda/board/safety/safety_toyota.h +++ b/panda/board/safety/safety_toyota.h @@ -16,8 +16,10 @@ const SteeringLimits TOYOTA_STEERING_LIMITS = { }; // longitudinal limits -const int TOYOTA_MAX_ACCEL = 2000; // 2.0 m/s2 -const int TOYOTA_MIN_ACCEL = -3500; // -3.5 m/s2 +const LongitudinalLimits TOYOTA_LONG_LIMITS = { + .max_accel = 2000, // 2.0 m/s2 + .min_accel = -3500, // -3.5 m/s2 +}; // panda interceptor threshold needs to be equivalent to openpilot threshold to avoid controls mismatches // If thresholds are mismatched then it is possible for panda to see the gas fall and rise while openpilot is in the pre-enabled state @@ -130,7 +132,7 @@ static int toyota_rx_hook(CANPacket_t *to_push) { return valid; } -static int toyota_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { +static int toyota_tx_hook(CANPacket_t *to_send) { int tx = 1; int addr = GET_ADDR(to_send); @@ -145,10 +147,8 @@ static int toyota_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { // GAS PEDAL: safety check if (addr == 0x200) { - if (!longitudinal_allowed) { - if (GET_BYTE(to_send, 0) || GET_BYTE(to_send, 1)) { - tx = 0; - } + if (longitudinal_interceptor_checks(to_send)) { + tx = 0; } } @@ -156,22 +156,21 @@ static int toyota_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { if (addr == 0x343) { int desired_accel = (GET_BYTE(to_send, 0) << 8) | GET_BYTE(to_send, 1); desired_accel = to_signed(desired_accel, 16); - if (!longitudinal_allowed || toyota_stock_longitudinal) { - if (desired_accel != 0) { - tx = 0; - } - } + + bool violation = false; + violation |= longitudinal_accel_checks(desired_accel, TOYOTA_LONG_LIMITS); // only ACC messages that cancel are allowed when openpilot is not controlling longitudinal if (toyota_stock_longitudinal) { bool cancel_req = GET_BIT(to_send, 24U) != 0U; if (!cancel_req) { - tx = 0; + violation = true; + } + if (desired_accel != TOYOTA_LONG_LIMITS.inactive_accel) { + violation = true; } } - bool violation = max_limit_check(desired_accel, TOYOTA_MAX_ACCEL, TOYOTA_MIN_ACCEL); - if (violation) { tx = 0; } diff --git a/panda/board/safety/safety_volkswagen_mqb.h b/panda/board/safety/safety_volkswagen_mqb.h index b0ecf9856..7c22e9f0f 100644 --- a/panda/board/safety/safety_volkswagen_mqb.h +++ b/panda/board/safety/safety_volkswagen_mqb.h @@ -14,8 +14,11 @@ const SteeringLimits VOLKSWAGEN_MQB_STEERING_LIMITS = { // longitudinal limits // acceleration in m/s2 * 1000 to avoid floating point math -const int VOLKSWAGEN_MQB_MAX_ACCEL = 2000; -const int VOLKSWAGEN_MQB_MIN_ACCEL = -3500; +const LongitudinalLimits VOLKSWAGEN_MQB_LONG_LIMITS = { + .max_accel = 2000, + .min_accel = -3500, + .inactive_accel = 3010, // VW sends one increment above the max range when inactive +}; #define MSG_ESP_19 0x0B2 // RX from ABS, for wheel speeds #define MSG_LH_EPS_03 0x09F // RX from EPS, for driver steering torque @@ -27,6 +30,7 @@ const int VOLKSWAGEN_MQB_MIN_ACCEL = -3500; #define MSG_GRA_ACC_01 0x12B // TX by OP, ACC control buttons for cancel/resume #define MSG_ACC_07 0x12E // TX by OP, ACC control instructions to the drivetrain coordinator #define MSG_ACC_02 0x30C // TX by OP, ACC HUD data to the instrument cluster +#define MSG_MOTOR_14 0x3BE // RX from ECU, for brake switch status #define MSG_LDW_02 0x397 // TX by OP, Lane line recognition and text alerts // Transmit of GRA_ACC_01 is allowed on bus 0 and 2 to keep compatibility with gateway and camera integration @@ -35,16 +39,19 @@ const CanMsg VOLKSWAGEN_MQB_LONG_TX_MSGS[] = {{MSG_HCA_01, 0, 8}, {MSG_LDW_02, 0 {MSG_ACC_02, 0, 8}, {MSG_ACC_06, 0, 8}, {MSG_ACC_07, 0, 8}}; AddrCheckStruct volkswagen_mqb_addr_checks[] = { - {.msg = {{MSG_ESP_19, 0, 8, .check_checksum = false, .max_counter = 0U, .expected_timestep = 10000U}, { 0 }, { 0 }}}, - {.msg = {{MSG_LH_EPS_03, 0, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 10000U}, { 0 }, { 0 }}}, - {.msg = {{MSG_ESP_05, 0, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, - {.msg = {{MSG_TSK_06, 0, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, - {.msg = {{MSG_MOTOR_20, 0, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, + {.msg = {{MSG_ESP_19, 0, 8, .check_checksum = false, .max_counter = 0U, .expected_timestep = 10000U}, { 0 }, { 0 }}}, + {.msg = {{MSG_LH_EPS_03, 0, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 10000U}, { 0 }, { 0 }}}, + {.msg = {{MSG_ESP_05, 0, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, + {.msg = {{MSG_TSK_06, 0, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, + {.msg = {{MSG_MOTOR_20, 0, 8, .check_checksum = true, .max_counter = 15U, .expected_timestep = 20000U}, { 0 }, { 0 }}}, + {.msg = {{MSG_MOTOR_14, 0, 8, .check_checksum = false, .max_counter = 0U, .expected_timestep = 100000U}, { 0 }, { 0 }}}, }; #define VOLKSWAGEN_MQB_ADDR_CHECKS_LEN (sizeof(volkswagen_mqb_addr_checks) / sizeof(volkswagen_mqb_addr_checks[0])) addr_checks volkswagen_mqb_rx_checks = {volkswagen_mqb_addr_checks, VOLKSWAGEN_MQB_ADDR_CHECKS_LEN}; uint8_t volkswagen_crc8_lut_8h2f[256]; // Static lookup table for CRC8 poly 0x2F, aka 8H2F/AUTOSAR +bool volkswagen_mqb_brake_pedal_switch = false; +bool volkswagen_mqb_brake_pressure_detected = false; static uint32_t volkswagen_mqb_get_checksum(CANPacket_t *to_push) { return (uint8_t)GET_BYTE(to_push, 0); @@ -95,6 +102,8 @@ static const addr_checks* volkswagen_mqb_init(uint16_t param) { volkswagen_set_button_prev = false; volkswagen_resume_button_prev = false; + volkswagen_mqb_brake_pedal_switch = false; + volkswagen_mqb_brake_pressure_detected = false; #ifdef ALLOW_DEBUG volkswagen_longitudinal = GET_FLAG(param, FLAG_VOLKSWAGEN_LONG_CONTROL); @@ -111,15 +120,16 @@ static int volkswagen_mqb_rx_hook(CANPacket_t *to_push) { if (valid && (GET_BUS(to_push) == 0U)) { int addr = GET_ADDR(to_push); - // Update in-motion state by sampling front wheel speeds - // Signal: ESP_19.ESP_VL_Radgeschw_02 (front left) in scaled km/h - // Signal: ESP_19.ESP_VR_Radgeschw_02 (front right) in scaled km/h + // Update in-motion state by sampling wheel speeds if (addr == MSG_ESP_19) { - int wheel_speed_fl = GET_BYTE(to_push, 4) | (GET_BYTE(to_push, 5) << 8); - int wheel_speed_fr = GET_BYTE(to_push, 6) | (GET_BYTE(to_push, 7) << 8); - // Check for average front speed in excess of 0.3m/s, 1.08km/h - // DBC speed scale 0.0075: 0.3m/s = 144, sum both wheels to compare - vehicle_moving = (wheel_speed_fl + wheel_speed_fr) > 288; + // sum 4 wheel speeds + int speed = 0; + for (uint8_t i = 0U; i < 8U; i += 2U) { + int wheel_speed = GET_BYTE(to_push, i) | (GET_BYTE(to_push, i + 1U) << 8); + speed += wheel_speed; + } + // Check all wheel speeds for any movement + vehicle_moving = speed > 0; } // Update driver input torque samples @@ -176,19 +186,24 @@ static int volkswagen_mqb_rx_hook(CANPacket_t *to_push) { gas_pressed = ((GET_BYTES_04(to_push) >> 12) & 0xFFU) != 0U; } - // Signal: ESP_05.ESP_Fahrer_bremst - if (addr == MSG_ESP_05) { - brake_pressed = (GET_BYTE(to_push, 3) & 0x4U) >> 2; + // Signal: Motor_14.MO_Fahrer_bremst (ECU detected brake pedal switch F63) + if (addr == MSG_MOTOR_14) { + volkswagen_mqb_brake_pedal_switch = (GET_BYTE(to_push, 3) & 0x10U) >> 4; } + // Signal: ESP_05.ESP_Fahrer_bremst (ESP detected driver brake pressure above platform specified threshold) + if (addr == MSG_ESP_05) { + volkswagen_mqb_brake_pressure_detected = (GET_BYTE(to_push, 3) & 0x4U) >> 2; + } + + brake_pressed = volkswagen_mqb_brake_pedal_switch || volkswagen_mqb_brake_pressure_detected; + generic_rx_checks((addr == MSG_HCA_01)); } return valid; } -static int volkswagen_mqb_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { - UNUSED(longitudinal_allowed); - +static int volkswagen_mqb_tx_hook(CANPacket_t *to_send) { int addr = GET_ADDR(to_send); int tx = 1; @@ -230,15 +245,7 @@ static int volkswagen_mqb_tx_hook(CANPacket_t *to_send, bool longitudinal_allowe desired_accel = (((GET_BYTE(to_send, 7) << 3) | ((GET_BYTE(to_send, 6) & 0xE0U) >> 5)) * 5U) - 7220U; } - // VW send one increment above the max range when inactive - if (desired_accel == 3010) { - desired_accel = 0; - } - - if (!controls_allowed && (desired_accel != 0)) { - violation = 1; - } - violation |= max_limit_check(desired_accel, VOLKSWAGEN_MQB_MAX_ACCEL, VOLKSWAGEN_MQB_MIN_ACCEL); + violation |= longitudinal_accel_checks(desired_accel, VOLKSWAGEN_MQB_LONG_LIMITS); if (violation) { tx = 0; diff --git a/panda/board/safety/safety_volkswagen_pq.h b/panda/board/safety/safety_volkswagen_pq.h index dce0a3399..20b9da63d 100644 --- a/panda/board/safety/safety_volkswagen_pq.h +++ b/panda/board/safety/safety_volkswagen_pq.h @@ -14,8 +14,11 @@ const SteeringLimits VOLKSWAGEN_PQ_STEERING_LIMITS = { // longitudinal limits // acceleration in m/s2 * 1000 to avoid floating point math -const int VOLKSWAGEN_PQ_MAX_ACCEL = 2000; -const int VOLKSWAGEN_PQ_MIN_ACCEL = -3500; +const LongitudinalLimits VOLKSWAGEN_PQ_LONG_LIMITS = { + .max_accel = 2000, + .min_accel = -3500, + .inactive_accel = 3010, // VW sends one increment above the max range when inactive +}; #define MSG_LENKHILFE_3 0x0D0 // RX from EPS, for steering angle and driver steering torque #define MSG_HCA_1 0x0D2 // TX by OP, Heading Control Assist steering torque @@ -106,8 +109,7 @@ static int volkswagen_pq_rx_hook(CANPacket_t *to_push) { // Signal: Bremse_1.Geschwindigkeit_neu__Bremse_1_ if (addr == MSG_BREMSE_1) { int speed = ((GET_BYTE(to_push, 2) & 0xFEU) >> 1) | (GET_BYTE(to_push, 3) << 7); - // DBC speed scale 0.01: 0.3m/s = 108. - vehicle_moving = speed > 108; + vehicle_moving = speed > 0; } // Update driver input torque samples @@ -174,7 +176,7 @@ static int volkswagen_pq_rx_hook(CANPacket_t *to_push) { return valid; } -static int volkswagen_pq_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { +static int volkswagen_pq_tx_hook(CANPacket_t *to_send) { int addr = GET_ADDR(to_send); int tx = 1; @@ -203,24 +205,10 @@ static int volkswagen_pq_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed // Safety check for acceleration commands // To avoid floating point math, scale upward and compare to pre-scaled safety m/s2 boundaries if (addr == MSG_ACC_SYSTEM) { - bool violation = 0; - int desired_accel = 0; - // Signal: ACC_System.ACS_Sollbeschl (acceleration in m/s2, scale 0.005, offset -7.22) - desired_accel = ((((GET_BYTE(to_send, 4) & 0x7U) << 8) | GET_BYTE(to_send, 3)) * 5U) - 7220U; + int desired_accel = ((((GET_BYTE(to_send, 4) & 0x7U) << 8) | GET_BYTE(to_send, 3)) * 5U) - 7220U; - // VW send one increment above the max range when inactive - if (desired_accel == 3010) { - desired_accel = 0; - } - - if (!longitudinal_allowed && (desired_accel != 0)) { - violation = 1; - } - - violation |= max_limit_check(desired_accel, VOLKSWAGEN_PQ_MAX_ACCEL, VOLKSWAGEN_PQ_MIN_ACCEL); - - if (violation) { + if (longitudinal_accel_checks(desired_accel, VOLKSWAGEN_PQ_LONG_LIMITS)) { tx = 0; } } diff --git a/panda/board/safety_declarations.h b/panda/board/safety_declarations.h index d50d4d1fe..7bc117515 100644 --- a/panda/board/safety_declarations.h +++ b/panda/board/safety_declarations.h @@ -1,3 +1,5 @@ +#pragma once + #define GET_BIT(msg, b) (((msg)->data[((b) / 8U)] >> ((b) % 8U)) & 0x1U) #define GET_BYTE(msg, b) ((msg)->data[(b)]) #define GET_BYTES_04(msg) ((msg)->data[0] | ((msg)->data[1] << 8U) | ((msg)->data[2] << 16U) | ((msg)->data[3] << 24U)) @@ -32,6 +34,7 @@ typedef enum { } SteeringControlType; typedef struct { + // torque cmd limits const int max_steer; const int max_rate_up; const int max_rate_down; @@ -52,8 +55,30 @@ typedef struct { const int max_invalid_request_frames; const uint32_t min_valid_request_rt_interval; const bool has_steer_req_tolerance; + + // angle cmd limits + const int angle_deg_to_can; + const struct lookup_t angle_rate_up_lookup; + const struct lookup_t angle_rate_down_lookup; } SteeringLimits; +typedef struct { + // acceleration cmd limits + const int max_accel; + const int min_accel; + const int inactive_accel; + + // gas & brake cmd limits + // inactive and min gas are 0 on most safety modes + const int max_gas; + const int min_gas; + const int inactive_gas; + const int max_brake; + + // speed cmd limits + const int inactive_speed; +} LongitudinalLimits; + typedef struct { const int addr; const int bus; @@ -113,11 +138,17 @@ void generic_rx_checks(bool stock_ecu_detected); void relay_malfunction_set(void); void relay_malfunction_reset(void); bool steer_torque_cmd_checks(int desired_torque, int steer_req, const SteeringLimits limits); +bool steer_angle_cmd_checks(int desired_angle, bool steer_control_enabled, const SteeringLimits limits); +bool longitudinal_accel_checks(int desired_accel, const LongitudinalLimits limits); +bool longitudinal_speed_checks(int desired_speed, const LongitudinalLimits limits); +bool longitudinal_gas_checks(int desired_gas, const LongitudinalLimits limits); +bool longitudinal_brake_checks(int desired_brake, const LongitudinalLimits limits); +bool longitudinal_interceptor_checks(CANPacket_t *to_send); void pcm_cruise_check(bool cruise_engaged); typedef const addr_checks* (*safety_hook_init)(uint16_t param); typedef int (*rx_hook)(CANPacket_t *to_push); -typedef int (*tx_hook)(CANPacket_t *to_send, bool longitudinal_allowed); +typedef int (*tx_hook)(CANPacket_t *to_send); typedef int (*tx_lin_hook)(int lin_num, uint8_t *data, int len); typedef int (*fwd_hook)(int bus_num, CANPacket_t *to_fwd); diff --git a/panda/board/stm32fx/board.h b/panda/board/stm32fx/board.h index a9093cb1a..878c1be9e 100644 --- a/panda/board/stm32fx/board.h +++ b/panda/board/stm32fx/board.h @@ -11,7 +11,7 @@ #include "stm32fx/llfan.h" #include "stm32fx/llrtc.h" #include "drivers/rtc.h" - #include "stm32fx/clock_source.h" + #include "drivers/clock_source.h" #include "boards/white.h" #include "boards/grey.h" #include "boards/black.h" @@ -48,7 +48,7 @@ void detect_board_type(void) { current_board = &board_pedal; #else hw_type = HW_TYPE_UNKNOWN; - puts("Hardware type is UNKNOWN!\n"); + print("Hardware type is UNKNOWN!\n"); #endif #endif } diff --git a/panda/board/stm32fx/clock.h b/panda/board/stm32fx/clock.h index f2386328d..fb918d312 100644 --- a/panda/board/stm32fx/clock.h +++ b/panda/board/stm32fx/clock.h @@ -4,9 +4,18 @@ void clock_init(void) { while ((RCC->CR & RCC_CR_HSERDY) == 0); // divide things + // AHB = 96MHz + // APB1 = 48MHz + // APB2 = 48MHz register_set(&(RCC->CFGR), RCC_CFGR_HPRE_DIV1 | RCC_CFGR_PPRE2_DIV2 | RCC_CFGR_PPRE1_DIV2, 0xFF7FFCF3U); - // 16mhz crystal + // 16MHz crystal + // PLLM: 8 + // PLLN: 96 + // PLLP: 2 + // PLLQ: 4 + // P output: 96MHz + // Q output: 48MHz register_set(&(RCC->PLLCFGR), RCC_PLLCFGR_PLLQ_2 | RCC_PLLCFGR_PLLM_3 | RCC_PLLCFGR_PLLN_6 | RCC_PLLCFGR_PLLN_5 | RCC_PLLCFGR_PLLSRC_HSE, 0x7F437FFFU); // start PLL diff --git a/panda/board/stm32fx/clock_source.h b/panda/board/stm32fx/clock_source.h deleted file mode 100644 index f8fea5fca..000000000 --- a/panda/board/stm32fx/clock_source.h +++ /dev/null @@ -1,98 +0,0 @@ - -#define CLOCK_SOURCE_MODE_DISABLED 0U -#define CLOCK_SOURCE_MODE_FREE_RUNNING 1U -#define CLOCK_SOURCE_MODE_PWM 2U - -#define CLOCK_SOURCE_PERIOD_MS 50U -#define CLOCK_SOURCE_PULSE_LEN_MS 2U - -uint8_t clock_source_mode = CLOCK_SOURCE_MODE_DISABLED; - -void TIM1_UP_TIM10_IRQ_Handler(void) { - if((TIM1->SR & TIM_SR_UIF) != 0) { - if(clock_source_mode == CLOCK_SOURCE_MODE_FREE_RUNNING) { - // Start clock pulse - set_gpio_output(GPIOB, 14, true); - set_gpio_output(GPIOB, 15, true); - set_gpio_output(GPIOC, 5, true); - } - - // Reset interrupt - TIM1->SR &= ~(TIM_SR_UIF); - } -} - -void TIM1_CC_IRQ_Handler(void) { - if((TIM1->SR & TIM_SR_CC1IF) != 0) { - if(clock_source_mode == CLOCK_SOURCE_MODE_FREE_RUNNING) { - // End clock pulse - set_gpio_output(GPIOB, 14, false); - set_gpio_output(GPIOB, 15, false); - set_gpio_output(GPIOC, 5, false); - } - - // Reset interrupt - TIM1->SR &= ~(TIM_SR_CC1IF); - } -} - -void clock_source_init(uint8_t mode){ - // Setup timer - REGISTER_INTERRUPT(TIM1_UP_TIM10_IRQn, TIM1_UP_TIM10_IRQ_Handler, (1200U / CLOCK_SOURCE_PERIOD_MS) , FAULT_INTERRUPT_RATE_TIM1) - REGISTER_INTERRUPT(TIM1_CC_IRQn, TIM1_CC_IRQ_Handler, (1200U / CLOCK_SOURCE_PERIOD_MS) , FAULT_INTERRUPT_RATE_TIM1) - register_set(&(TIM1->PSC), ((APB2_FREQ*100U)-1U), 0xFFFFU); // Tick on 0.1 ms - register_set(&(TIM1->ARR), ((CLOCK_SOURCE_PERIOD_MS*10U) - 1U), 0xFFFFU); // Period - register_set(&(TIM1->CCMR1), 0U, 0xFFFFU); // No output on compare - register_set(&(TIM1->CCER), TIM_CCER_CC1E, 0xFFFFU); // Enable compare 1 - register_set(&(TIM1->CCR1), (CLOCK_SOURCE_PULSE_LEN_MS*10U), 0xFFFFU); // Compare 1 value - register_set(&(TIM1->CCR2), (CLOCK_SOURCE_PULSE_LEN_MS*10U), 0xFFFFU); // Compare 1 value - register_set(&(TIM1->CCR3), (CLOCK_SOURCE_PULSE_LEN_MS*10U), 0xFFFFU); // Compare 1 value - register_set_bits(&(TIM1->DIER), TIM_DIER_UIE | TIM_DIER_CC1IE); // Enable interrupts - register_set(&(TIM1->CR1), TIM_CR1_CEN, 0x3FU); // Enable timer - - // Set mode - switch(mode) { - case CLOCK_SOURCE_MODE_DISABLED: - // No clock signal - NVIC_DisableIRQ(TIM1_UP_TIM10_IRQn); - NVIC_DisableIRQ(TIM1_CC_IRQn); - - // Disable pulse if we were in the middle of it - set_gpio_output(GPIOB, 14, false); - set_gpio_output(GPIOB, 15, false); - - clock_source_mode = CLOCK_SOURCE_MODE_DISABLED; - break; - case CLOCK_SOURCE_MODE_FREE_RUNNING: - // Clock signal is based on internal timer - NVIC_EnableIRQ(TIM1_UP_TIM10_IRQn); - NVIC_EnableIRQ(TIM1_CC_IRQn); - - clock_source_mode = CLOCK_SOURCE_MODE_FREE_RUNNING; - break; - case CLOCK_SOURCE_MODE_PWM: - // No interrupts - NVIC_DisableIRQ(TIM1_UP_TIM10_IRQn); - NVIC_DisableIRQ(TIM1_CC_IRQn); - - // Set GPIO as timer channels - set_gpio_alternate(GPIOB, 14, GPIO_AF1_TIM1); - set_gpio_alternate(GPIOB, 15, GPIO_AF1_TIM1); - - // Set PWM mode - register_set(&(TIM1->CCMR1), (0b110 << TIM_CCMR1_OC2M_Pos), 0xFFFFU); - register_set(&(TIM1->CCMR2), (0b110 << TIM_CCMR2_OC3M_Pos), 0xFFFFU); - - // Enable output - register_set(&(TIM1->BDTR), TIM_BDTR_MOE, 0xFFFFU); - - // Enable complementary compares - register_set_bits(&(TIM1->CCER), TIM_CCER_CC2NE | TIM_CCER_CC3NE); - - clock_source_mode = CLOCK_SOURCE_MODE_PWM; - break; - default: - puts("Unknown clock source mode: "); puth(mode); puts("\n"); - break; - } -} diff --git a/panda/board/stm32fx/llbxcan.h b/panda/board/stm32fx/llbxcan.h index a1f0182be..0fdfd398a 100644 --- a/panda/board/stm32fx/llbxcan.h +++ b/panda/board/stm32fx/llbxcan.h @@ -1,3 +1,8 @@ +// Flasher and pedal use raw mailbox access +#define GET_MAILBOX_BYTE(msg, b) (((int)(b) > 3) ? (((msg)->RDHR >> (8U * ((unsigned int)(b) % 4U))) & 0xFFU) : (((msg)->RDLR >> (8U * (unsigned int)(b))) & 0xFFU)) +#define GET_MAILBOX_BYTES_04(msg) ((msg)->RDLR) +#define GET_MAILBOX_BYTES_48(msg) ((msg)->RDHR) + // SAE 2284-3 : minimum 16 tq, SJW 3, sample point at 81.3% #define CAN_QUANTA 16U #define CAN_SEQ1 12U @@ -11,7 +16,7 @@ #define CAN_NAME_FROM_CANIF(CAN_DEV) (((CAN_DEV)==CAN1) ? "CAN1" : (((CAN_DEV) == CAN2) ? "CAN2" : "CAN3")) -void puts(const char *a); +void print(const char *a); // kbps multiplied by 10 const uint32_t speeds[] = {100U, 200U, 500U, 1000U, 1250U, 2500U, 5000U, 10000U}; @@ -29,7 +34,7 @@ bool llcan_set_speed(CAN_TypeDef *CAN_obj, uint32_t speed, bool loopback, bool s timeout_counter++; if(timeout_counter >= CAN_INIT_TIMEOUT_MS){ - puts(CAN_NAME_FROM_CANIF(CAN_obj)); puts(" set_speed timed out (1)!\n"); + print(CAN_NAME_FROM_CANIF(CAN_obj)); print(" set_speed timed out (1)!\n"); ret = false; break; } @@ -60,7 +65,7 @@ bool llcan_set_speed(CAN_TypeDef *CAN_obj, uint32_t speed, bool loopback, bool s timeout_counter++; if(timeout_counter >= CAN_INIT_TIMEOUT_MS){ - puts(CAN_NAME_FROM_CANIF(CAN_obj)); puts(" set_speed timed out (2)!\n"); + print(CAN_NAME_FROM_CANIF(CAN_obj)); print(" set_speed timed out (2)!\n"); ret = false; break; } @@ -84,7 +89,7 @@ bool llcan_init(CAN_TypeDef *CAN_obj) { timeout_counter++; if(timeout_counter >= CAN_INIT_TIMEOUT_MS){ - puts(CAN_NAME_FROM_CANIF(CAN_obj)); puts(" initialization timed out!\n"); + print(CAN_NAME_FROM_CANIF(CAN_obj)); print(" initialization timed out!\n"); ret = false; break; } @@ -120,7 +125,7 @@ bool llcan_init(CAN_TypeDef *CAN_obj) { NVIC_EnableIRQ(CAN3_SCE_IRQn); #endif } else { - puts("Invalid CAN: initialization failed\n"); + print("Invalid CAN: initialization failed\n"); } } return ret; diff --git a/panda/board/stm32fx/lldac.h b/panda/board/stm32fx/lldac.h index 7b2e112be..6cd2f8ca2 100644 --- a/panda/board/stm32fx/lldac.h +++ b/panda/board/stm32fx/lldac.h @@ -11,6 +11,6 @@ void dac_set(int channel, uint32_t value) { } else if (channel == 1) { register_set(&(DAC->DHR12R2), value, 0xFFFU); } else { - puts("Failed to set DAC: invalid channel value: 0x"); puth(value); puts("\n"); + print("Failed to set DAC: invalid channel value: 0x"); puth(value); print("\n"); } } diff --git a/panda/board/stm32fx/llfan.h b/panda/board/stm32fx/llfan.h index 403314153..3e4b0fa3f 100644 --- a/panda/board/stm32fx/llfan.h +++ b/panda/board/stm32fx/llfan.h @@ -1,23 +1,23 @@ // TACH interrupt handler void EXTI2_IRQ_Handler(void) { - volatile unsigned int pr = EXTI->PR & (1U << 2); - if ((pr & (1U << 2)) != 0U) { - fan_state.tach_counter++; - } - EXTI->PR = (1U << 2); + volatile unsigned int pr = EXTI->PR & (1U << 2); + if ((pr & (1U << 2)) != 0U) { + fan_state.tach_counter++; + } + EXTI->PR = (1U << 2); } -void fan_init(void){ - // 5000RPM * 4 tach edges / 60 seconds - REGISTER_INTERRUPT(EXTI2_IRQn, EXTI2_IRQ_Handler, 700U, FAULT_INTERRUPT_RATE_TACH) +void llfan_init(void) { + // 5000RPM * 4 tach edges / 60 seconds + REGISTER_INTERRUPT(EXTI2_IRQn, EXTI2_IRQ_Handler, 700U, FAULT_INTERRUPT_RATE_TACH) - // Init PWM speed control - pwm_init(TIM3, 3); + // Init PWM speed control + pwm_init(TIM3, 3); - // Init TACH interrupt - register_set(&(SYSCFG->EXTICR[0]), SYSCFG_EXTICR1_EXTI2_PD, 0xF00U); - register_set_bits(&(EXTI->IMR), (1U << 2)); - register_set_bits(&(EXTI->RTSR), (1U << 2)); - register_set_bits(&(EXTI->FTSR), (1U << 2)); - NVIC_EnableIRQ(EXTI2_IRQn); + // Init TACH interrupt + register_set(&(SYSCFG->EXTICR[0]), SYSCFG_EXTICR1_EXTI2_PD, 0xF00U); + register_set_bits(&(EXTI->IMR), (1U << 2)); + register_set_bits(&(EXTI->RTSR), (1U << 2)); + register_set_bits(&(EXTI->FTSR), (1U << 2)); + NVIC_EnableIRQ(EXTI2_IRQn); } diff --git a/panda/board/stm32fx/llspi.h b/panda/board/stm32fx/llspi.h index 020ead505..3aa7236bd 100644 --- a/panda/board/stm32fx/llspi.h +++ b/panda/board/stm32fx/llspi.h @@ -47,13 +47,20 @@ void DMA2_Stream3_IRQ_Handler(void) { DMA2->LIFCR = DMA_LIFCR_CTCIF3; // Wait until the transaction is actually finished and clear the DR - // TODO: needs a timeout here, otherwise it gets stuck with no master clock! - while (!(SPI1->SR & SPI_SR_TXE)); + // Timeout to prevent hang when the master clock stops. + bool timed_out = false; + uint32_t start_time = microsecond_timer_get(); + while (!(SPI1->SR & SPI_SR_TXE)) { + if (get_ts_elapsed(microsecond_timer_get(), start_time) > SPI_TIMEOUT_US) { + timed_out = true; + break; + } + } volatile uint8_t dat = SPI1->DR; (void)dat; SPI1->DR = 0U; - spi_handle_tx(); + spi_handle_tx(timed_out); } // ***************************** SPI init ***************************** diff --git a/panda/board/stm32fx/lluart.h b/panda/board/stm32fx/lluart.h index 69959a76f..cc5b73134 100644 --- a/panda/board/stm32fx/lluart.h +++ b/panda/board/stm32fx/lluart.h @@ -93,7 +93,7 @@ void uart_interrupt_handler(uart_ring *q) { uint32_t err = (status & USART_SR_ORE) | (status & USART_SR_NE) | (status & USART_SR_FE) | (status & USART_SR_PE); if(err != 0U){ #ifdef DEBUG_UART - puts("Encountered UART error: "); puth(err); puts("\n"); + print("Encountered UART error: "); puth(err); print("\n"); #endif UART_READ_DR(q->uart) } @@ -109,7 +109,7 @@ void uart_interrupt_handler(uart_ring *q) { dma_pointer_handler(&uart_ring_gps, DMA2_Stream5->NDTR); } else { #ifdef DEBUG_UART - puts("No IDLE dma_pointer_handler implemented for this UART."); + print("No IDLE dma_pointer_handler implemented for this UART."); #endif } } @@ -128,7 +128,7 @@ void DMA2_Stream5_IRQ_Handler(void) { // Handle errors if((DMA2->HISR & DMA_HISR_TEIF5) || (DMA2->HISR & DMA_HISR_DMEIF5) || (DMA2->HISR & DMA_HISR_FEIF5)){ #ifdef DEBUG_UART - puts("Encountered UART DMA error. Clearing and restarting DMA...\n"); + print("Encountered UART DMA error. Clearing and restarting DMA...\n"); #endif // Clear flags @@ -173,7 +173,7 @@ void dma_rx_init(uart_ring *q) { // Enable interrupt NVIC_EnableIRQ(DMA2_Stream5_IRQn); } else { - puts("Tried to initialize RX DMA for an unsupported UART\n"); + print("Tried to initialize RX DMA for an unsupported UART\n"); } } @@ -192,44 +192,46 @@ void uart_set_baud(USART_TypeDef *u, unsigned int baud) { } void uart_init(uart_ring *q, int baud) { - // Register interrupts (max data rate: 115200 baud) - if(q->uart == USART1){ - REGISTER_INTERRUPT(USART1_IRQn, USART1_IRQ_Handler, 150000U, FAULT_INTERRUPT_RATE_UART_1) - } else if (q->uart == USART2){ - REGISTER_INTERRUPT(USART2_IRQn, USART2_IRQ_Handler, 150000U, FAULT_INTERRUPT_RATE_UART_2) - } else if (q->uart == USART3){ - REGISTER_INTERRUPT(USART3_IRQn, USART3_IRQ_Handler, 150000U, FAULT_INTERRUPT_RATE_UART_3) - } else if (q->uart == UART5){ - REGISTER_INTERRUPT(UART5_IRQn, UART5_IRQ_Handler, 150000U, FAULT_INTERRUPT_RATE_UART_5) - } else { - // UART not used. Skip registering interrupts - } - if(q->dma_rx){ - REGISTER_INTERRUPT(DMA2_Stream5_IRQn, DMA2_Stream5_IRQ_Handler, 100U, FAULT_INTERRUPT_RATE_UART_DMA) // Called twice per buffer - } + if(q->uart != NULL){ + // Register interrupts (max data rate: 115200 baud) + if(q->uart == USART1){ + REGISTER_INTERRUPT(USART1_IRQn, USART1_IRQ_Handler, 150000U, FAULT_INTERRUPT_RATE_UART_1) + } else if (q->uart == USART2){ + REGISTER_INTERRUPT(USART2_IRQn, USART2_IRQ_Handler, 150000U, FAULT_INTERRUPT_RATE_UART_2) + } else if (q->uart == USART3){ + REGISTER_INTERRUPT(USART3_IRQn, USART3_IRQ_Handler, 150000U, FAULT_INTERRUPT_RATE_UART_3) + } else if (q->uart == UART5){ + REGISTER_INTERRUPT(UART5_IRQn, UART5_IRQ_Handler, 150000U, FAULT_INTERRUPT_RATE_UART_5) + } else { + // UART not used. Skip registering interrupts + } + if(q->dma_rx){ + REGISTER_INTERRUPT(DMA2_Stream5_IRQn, DMA2_Stream5_IRQ_Handler, 100U, FAULT_INTERRUPT_RATE_UART_DMA) // Called twice per buffer + } - // Set baud and enable peripheral with TX and RX mode - uart_set_baud(q->uart, baud); - q->uart->CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; - if ((q->uart == USART2) || (q->uart == USART3) || (q->uart == UART5)) { - q->uart->CR1 |= USART_CR1_RXNEIE; - } + // Set baud and enable peripheral with TX and RX mode + uart_set_baud(q->uart, baud); + q->uart->CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; + if ((q->uart == USART2) || (q->uart == USART3) || (q->uart == UART5)) { + q->uart->CR1 |= USART_CR1_RXNEIE; + } - // Enable UART interrupts - if(q->uart == USART1){ - NVIC_EnableIRQ(USART1_IRQn); - } else if (q->uart == USART2){ - NVIC_EnableIRQ(USART2_IRQn); - } else if (q->uart == USART3){ - NVIC_EnableIRQ(USART3_IRQn); - } else if (q->uart == UART5){ - NVIC_EnableIRQ(UART5_IRQn); - } else { - // UART not used. Skip enabling interrupts - } + // Enable UART interrupts + if(q->uart == USART1){ + NVIC_EnableIRQ(USART1_IRQn); + } else if (q->uart == USART2){ + NVIC_EnableIRQ(USART2_IRQn); + } else if (q->uart == USART3){ + NVIC_EnableIRQ(USART3_IRQn); + } else if (q->uart == UART5){ + NVIC_EnableIRQ(UART5_IRQn); + } else { + // UART not used. Skip enabling interrupts + } - // Initialise RX DMA if used - if(q->dma_rx){ - dma_rx_init(q); + // Initialise RX DMA if used + if(q->dma_rx){ + dma_rx_init(q); + } } } diff --git a/panda/board/stm32fx/llusb.h b/panda/board/stm32fx/llusb.h index df4747312..ab34672aa 100644 --- a/panda/board/stm32fx/llusb.h +++ b/panda/board/stm32fx/llusb.h @@ -33,14 +33,14 @@ void usb_init(void) { // full speed PHY, do reset and remove power down /*puth(USBx->GRSTCTL); - puts(" resetting PHY\n");*/ + print(" resetting PHY\n");*/ while ((USBx->GRSTCTL & USB_OTG_GRSTCTL_AHBIDL) == 0); - //puts("AHB idle\n"); + //print("AHB idle\n"); // reset PHY here USBx->GRSTCTL |= USB_OTG_GRSTCTL_CSRST; while ((USBx->GRSTCTL & USB_OTG_GRSTCTL_CSRST) == USB_OTG_GRSTCTL_CSRST); - //puts("reset done\n"); + //print("reset done\n"); // internal PHY, force device mode USBx->GUSBCFG = USB_OTG_GUSBCFG_PHYSEL | USB_OTG_GUSBCFG_FDMOD; diff --git a/panda/board/stm32fx/stm32fx_config.h b/panda/board/stm32fx/stm32fx_config.h index 9361f79ac..066160693 100644 --- a/panda/board/stm32fx/stm32fx_config.h +++ b/panda/board/stm32fx/stm32fx_config.h @@ -10,10 +10,11 @@ // from the linker script #define APP_START_ADDRESS 0x8004000U -#define CORE_FREQ 96U // in Mhz -//APB1 - 48Mhz, APB2 - 96Mhz -#define APB1_FREQ CORE_FREQ/2U -#define APB2_FREQ CORE_FREQ/1U +#define CORE_FREQ 96U // in MHz +#define APB1_FREQ (CORE_FREQ/2U) +#define APB1_TIMER_FREQ (APB1_FREQ*2U) // APB1 is multiplied by 2 for the timer peripherals +#define APB2_FREQ (CORE_FREQ/2U) +#define APB2_TIMER_FREQ (APB2_FREQ*2U) // APB2 is multiplied by 2 for the timer peripherals #define BOOTLOADER_ADDRESS 0x1FFF0004U diff --git a/panda/board/stm32h7/board.h b/panda/board/stm32h7/board.h index f8c3ea3c2..086db86b3 100644 --- a/panda/board/stm32h7/board.h +++ b/panda/board/stm32h7/board.h @@ -9,7 +9,10 @@ #include "drivers/fan.h" #include "stm32h7/llfan.h" #include "stm32h7/llrtc.h" +#include "stm32h7/lldac.h" +#include "drivers/fake_siren.h" #include "drivers/rtc.h" +#include "drivers/clock_source.h" #include "boards/red.h" #include "boards/red_chiplet.h" #include "boards/red_v2.h" @@ -37,6 +40,6 @@ void detect_board_type(void) { current_board = &board_tres; } else { hw_type = HW_TYPE_UNKNOWN; - puts("Hardware type is UNKNOWN!\n"); + print("Hardware type is UNKNOWN!\n"); } } diff --git a/panda/board/stm32h7/inc/stm32h725xx.h b/panda/board/stm32h7/inc/stm32h725xx.h index 0631daac0..0e0d4a9d6 100644 --- a/panda/board/stm32h7/inc/stm32h725xx.h +++ b/panda/board/stm32h7/inc/stm32h725xx.h @@ -24478,7 +24478,7 @@ typedef struct ((INSTANCE) == TIM23) || \ ((INSTANCE) == TIM24)) -/****************** TIM Instances : supporting internal trigger inputs(ITRX) *******/ +/****************** TIM Instances : supporting internal trigger inputstr(ITRX) *******/ #define IS_TIM_CLOCKSOURCE_ITRX_INSTANCE(INSTANCE)\ (((INSTANCE) == TIM1) || \ ((INSTANCE) == TIM2) || \ diff --git a/panda/board/stm32h7/inc/stm32h735xx.h b/panda/board/stm32h7/inc/stm32h735xx.h index 0631daac0..0e0d4a9d6 100644 --- a/panda/board/stm32h7/inc/stm32h735xx.h +++ b/panda/board/stm32h7/inc/stm32h735xx.h @@ -24478,7 +24478,7 @@ typedef struct ((INSTANCE) == TIM23) || \ ((INSTANCE) == TIM24)) -/****************** TIM Instances : supporting internal trigger inputs(ITRX) *******/ +/****************** TIM Instances : supporting internal trigger inputstr(ITRX) *******/ #define IS_TIM_CLOCKSOURCE_ITRX_INSTANCE(INSTANCE)\ (((INSTANCE) == TIM1) || \ ((INSTANCE) == TIM2) || \ diff --git a/panda/board/stm32h7/interrupt_handlers.h b/panda/board/stm32h7/interrupt_handlers.h index 2b0a2b428..0811ffe52 100644 --- a/panda/board/stm32h7/interrupt_handlers.h +++ b/panda/board/stm32h7/interrupt_handlers.h @@ -72,3 +72,4 @@ void OTG_HS_EP1_OUT_IRQHandler(void) {handle_interrupt(OTG_HS_EP1_OUT_IRQn);} void OTG_HS_EP1_IN_IRQHandler(void) {handle_interrupt(OTG_HS_EP1_IN_IRQn);} void OTG_HS_WKUP_IRQHandler(void) {handle_interrupt(OTG_HS_WKUP_IRQn);} void OTG_HS_IRQHandler(void) {handle_interrupt(OTG_HS_IRQn);} +void UART7_IRQHandler(void) {handle_interrupt(UART7_IRQn);} diff --git a/panda/board/stm32h7/lldac.h b/panda/board/stm32h7/lldac.h new file mode 100644 index 000000000..77e333e5c --- /dev/null +++ b/panda/board/stm32h7/lldac.h @@ -0,0 +1,42 @@ +void dac_init(DAC_TypeDef *dac, uint8_t channel, bool dma) { + register_set(&dac->CR, 0U, 0xFFFFU); + register_set(&dac->MCR, 0U, 0xFFFFU); + + switch(channel) { + case 1: + if (dma) { + register_set_bits(&dac->CR, DAC_CR_DMAEN1); + // register_set(&DAC->CR, (6U << DAC_CR_TSEL1_Pos), DAC_CR_TSEL1); + register_set_bits(&dac->CR, DAC_CR_TEN1); + } else { + register_clear_bits(&dac->CR, DAC_CR_DMAEN1); + } + register_set_bits(&dac->CR, DAC_CR_EN1); + break; + case 2: + if (dma) { + register_set_bits(&dac->CR, DAC_CR_DMAEN2); + } else { + register_clear_bits(&dac->CR, DAC_CR_DMAEN2); + } + register_set_bits(&dac->CR, DAC_CR_EN2); + break; + default: + break; + } +} + +// Set channel 1 value, in mV +void dac_set(DAC_TypeDef *dac, uint8_t channel, uint32_t value) { + uint32_t raw_val = MAX(MIN(value * (1U << 8U) / 3300U, (1U << 8U)), 0U); + switch(channel) { + case 1: + register_set(&dac->DHR8R1, raw_val, 0xFFU); + break; + case 2: + register_set(&dac->DHR8R2, raw_val, 0xFFU); + break; + default: + break; + } +} diff --git a/panda/board/stm32h7/llfan.h b/panda/board/stm32h7/llfan.h index fc529dcbe..dce622503 100644 --- a/panda/board/stm32h7/llfan.h +++ b/panda/board/stm32h7/llfan.h @@ -1,2 +1,23 @@ -void EXTI2_IRQ_Handler(void) { } -void fan_init(void){ } +// TACH interrupt handler +void EXTI2_IRQ_Handler(void) { + volatile unsigned int pr = EXTI->PR1 & (1U << 2); + if ((pr & (1U << 2)) != 0U) { + fan_state.tach_counter++; + } + EXTI->PR1 = (1U << 2); +} + +void llfan_init(void) { + // 5000RPM * 4 tach edges / 60 seconds + REGISTER_INTERRUPT(EXTI2_IRQn, EXTI2_IRQ_Handler, 700U, FAULT_INTERRUPT_RATE_TACH) + + // Init PWM speed control + pwm_init(TIM3, 3); + + // Init TACH interrupt + register_set(&(SYSCFG->EXTICR[0]), SYSCFG_EXTICR1_EXTI2_PD, 0xF00U); + register_set_bits(&(EXTI->IMR1), (1U << 2)); + register_set_bits(&(EXTI->RTSR1), (1U << 2)); + register_set_bits(&(EXTI->FTSR1), (1U << 2)); + NVIC_EnableIRQ(EXTI2_IRQn); +} diff --git a/panda/board/stm32h7/llfdcan.h b/panda/board/stm32h7/llfdcan.h index d21f415ce..b2bb885b4 100644 --- a/panda/board/stm32h7/llfdcan.h +++ b/panda/board/stm32h7/llfdcan.h @@ -37,7 +37,7 @@ #define CAN_NUM_FROM_CANIF(CAN_DEV) (((CAN_DEV)==FDCAN1) ? 0UL : (((CAN_DEV) == FDCAN2) ? 1UL : 2UL)) -void puts(const char *a); +void print(const char *a); // kbps multiplied by 10 const uint32_t speeds[] = {100U, 200U, 500U, 1000U, 1250U, 2500U, 5000U, 10000U}; @@ -148,10 +148,10 @@ bool llcan_set_speed(FDCAN_GlobalTypeDef *CANx, uint32_t speed, uint32_t data_sp } ret = fdcan_exit_init(CANx); if (!ret) { - puts(CAN_NAME_FROM_CANIF(CANx)); puts(" set_speed timed out! (2)\n"); + print(CAN_NAME_FROM_CANIF(CANx)); print(" set_speed timed out! (2)\n"); } } else { - puts(CAN_NAME_FROM_CANIF(CANx)); puts(" set_speed timed out! (1)\n"); + print(CAN_NAME_FROM_CANIF(CANx)); print(" set_speed timed out! (1)\n"); } return ret; } @@ -219,7 +219,7 @@ bool llcan_init(FDCAN_GlobalTypeDef *CANx) { ret = fdcan_exit_init(CANx); if(!ret) { - puts(CAN_NAME_FROM_CANIF(CANx)); puts(" llcan_init timed out (2)!\n"); + print(CAN_NAME_FROM_CANIF(CANx)); print(" llcan_init timed out (2)!\n"); } if (CANx == FDCAN1) { @@ -232,11 +232,11 @@ bool llcan_init(FDCAN_GlobalTypeDef *CANx) { NVIC_EnableIRQ(FDCAN3_IT0_IRQn); NVIC_EnableIRQ(FDCAN3_IT1_IRQn); } else { - puts("Invalid CAN: initialization failed\n"); + print("Invalid CAN: initialization failed\n"); } } else { - puts(CAN_NAME_FROM_CANIF(CANx)); puts(" llcan_init timed out (1)!\n"); + print(CAN_NAME_FROM_CANIF(CANx)); print(" llcan_init timed out (1)!\n"); } return ret; } diff --git a/panda/board/stm32h7/lli2c.h b/panda/board/stm32h7/lli2c.h new file mode 100644 index 000000000..74e8ac830 --- /dev/null +++ b/panda/board/stm32h7/lli2c.h @@ -0,0 +1,153 @@ + +// TODO: this driver relies heavily on polling, +// if we want it to be more async, we should use interrupts + +#define I2C_TIMEOUT_US 100000U + +// cppcheck-suppress misra-c2012-2.7; not sure why it triggers here? +bool i2c_status_wait(volatile uint32_t *reg, uint32_t mask, uint32_t val) { + uint32_t start_time = microsecond_timer_get(); + while(((*reg & mask) != val) && (get_ts_elapsed(microsecond_timer_get(), start_time) < I2C_TIMEOUT_US)); + return ((*reg & mask) == val); +} + +bool i2c_write_reg(I2C_TypeDef *I2C, uint8_t addr, uint8_t reg, uint8_t value) { + // Setup transfer and send START + addr + bool ret = false; + for(uint32_t i=0U; i<10U; i++) { + register_clear_bits(&I2C->CR2, I2C_CR2_ADD10); + I2C->CR2 = ((addr << 1U) & I2C_CR2_SADD_Msk); + register_clear_bits(&I2C->CR2, I2C_CR2_RD_WRN); + register_set_bits(&I2C->CR2, I2C_CR2_AUTOEND); + I2C->CR2 |= (2 << I2C_CR2_NBYTES_Pos); + + I2C->CR2 |= I2C_CR2_START; + if(!i2c_status_wait(&I2C->CR2, I2C_CR2_START, 0U)) { + continue; + } + + // check if we lost arbitration + if ((I2C->ISR & I2C_ISR_ARLO) != 0U) { + register_set_bits(&I2C->ICR, I2C_ICR_ARLOCF); + } else { + ret = true; + break; + } + } + + if (!ret) { + goto end; + } + + // Send data + ret = i2c_status_wait(&I2C->ISR, I2C_ISR_TXIS, I2C_ISR_TXIS); + if(!ret) { + goto end; + } + I2C->TXDR = reg; + + ret = i2c_status_wait(&I2C->ISR, I2C_ISR_TXIS, I2C_ISR_TXIS); + if(!ret) { + goto end; + } + I2C->TXDR = value; + +end: + return ret; +} + +bool i2c_read_reg(I2C_TypeDef *I2C, uint8_t addr, uint8_t reg, uint8_t *value) { + // Setup transfer and send START + addr + bool ret = false; + for(uint32_t i=0U; i<10U; i++) { + register_clear_bits(&I2C->CR2, I2C_CR2_ADD10); + I2C->CR2 = ((addr << 1U) & I2C_CR2_SADD_Msk); + register_clear_bits(&I2C->CR2, I2C_CR2_RD_WRN); + register_clear_bits(&I2C->CR2, I2C_CR2_AUTOEND); + I2C->CR2 |= (1 << I2C_CR2_NBYTES_Pos); + + I2C->CR2 |= I2C_CR2_START; + if(!i2c_status_wait(&I2C->CR2, I2C_CR2_START, 0U)) { + continue; + } + + // check if we lost arbitration + if ((I2C->ISR & I2C_ISR_ARLO) != 0U) { + register_set_bits(&I2C->ICR, I2C_ICR_ARLOCF); + } else { + ret = true; + break; + } + } + + if (!ret) { + goto end; + } + + // Send data + ret = i2c_status_wait(&I2C->ISR, I2C_ISR_TXIS, I2C_ISR_TXIS); + if(!ret) { + goto end; + } + I2C->TXDR = reg; + + // Restart + I2C->CR2 = (((addr << 1) | 0x1U) & I2C_CR2_SADD_Msk) | (1U << I2C_CR2_NBYTES_Pos) | I2C_CR2_RD_WRN | I2C_CR2_START; + ret = i2c_status_wait(&I2C->CR2, I2C_CR2_START, 0U); + if(!ret) { + goto end; + } + + // check if we lost arbitration + if ((I2C->ISR & I2C_ISR_ARLO) != 0U) { + register_set_bits(&I2C->ICR, I2C_ICR_ARLOCF); + ret = false; + goto end; + } + + // Read data + ret = i2c_status_wait(&I2C->ISR, I2C_ISR_RXNE, I2C_ISR_RXNE); + if(!ret) { + goto end; + } + *value = I2C->RXDR; + + // Stop + I2C->CR2 |= I2C_CR2_STOP; + +end: + return ret; +} + +bool i2c_set_reg_bits(I2C_TypeDef *I2C, uint8_t addr, uint8_t reg, uint8_t bits) { + uint8_t value; + bool ret = i2c_read_reg(I2C, addr, reg, &value); + if(ret) { + ret = i2c_write_reg(I2C, addr, reg, value | bits); + } + return ret; +} + +bool i2c_clear_reg_bits(I2C_TypeDef *I2C, uint8_t addr, uint8_t reg, uint8_t bits) { + uint8_t value; + bool ret = i2c_read_reg(I2C, addr, reg, &value); + if(ret) { + ret = i2c_write_reg(I2C, addr, reg, value & (uint8_t) (~bits)); + } + return ret; +} + +bool i2c_set_reg_mask(I2C_TypeDef *I2C, uint8_t addr, uint8_t reg, uint8_t value, uint8_t mask) { + uint8_t old_value; + bool ret = i2c_read_reg(I2C, addr, reg, &old_value); + if(ret) { + ret = i2c_write_reg(I2C, addr, reg, (old_value & (uint8_t) (~mask)) | (value & mask)); + } + return ret; +} + +void i2c_init(I2C_TypeDef *I2C) { + // 100kHz clock speed + I2C->TIMINGR = 0x107075B0; + I2C->CR1 = I2C_CR1_PE; +} \ No newline at end of file diff --git a/panda/board/stm32h7/llspi.h b/panda/board/stm32h7/llspi.h index 0215c6fbb..9e21659b8 100644 --- a/panda/board/stm32h7/llspi.h +++ b/panda/board/stm32h7/llspi.h @@ -55,13 +55,20 @@ void DMA2_Stream3_IRQ_Handler(void) { // Clear interrupt flag DMA2->LIFCR = DMA_LIFCR_CTCIF3; - // Wait until the transaction is actually finished and clear the DR - // TODO: needs a timeout here, otherwise it gets stuck with no master clock! - while (!(SPI4->SR & SPI_SR_TXC)); + // Wait until the transaction is actually finished and clear the DR. + // Timeout to prevent hang when the master clock stops. + bool timed_out = false; + uint32_t start_time = microsecond_timer_get(); + while (!(SPI4->SR & SPI_SR_TXC)) { + if (get_ts_elapsed(microsecond_timer_get(), start_time) > SPI_TIMEOUT_US) { + timed_out = true; + break; + } + } volatile uint8_t dat = SPI4->TXDR; (void)dat; - spi_handle_tx(); + spi_handle_tx(timed_out); } diff --git a/panda/board/stm32h7/lluart.h b/panda/board/stm32h7/lluart.h index aa44f7bb8..89e47606c 100644 --- a/panda/board/stm32h7/lluart.h +++ b/panda/board/stm32h7/lluart.h @@ -1,5 +1,130 @@ -void uart_init(uart_ring *q, int baud) { UNUSED(q); UNUSED(baud); } -void uart_set_baud(USART_TypeDef *u, unsigned int baud) { UNUSED(u); UNUSED(baud); } + void dma_pointer_handler(uart_ring *q, uint32_t dma_ndtr) { UNUSED(q); UNUSED(dma_ndtr); } -void uart_rx_ring(uart_ring *q) { UNUSED(q); } -void uart_tx_ring(uart_ring *q) { UNUSED(q); } +void dma_rx_init(uart_ring *q) { UNUSED(q); } + +#define __DIV(_PCLK_, _BAUD_) (((_PCLK_) * 25U) / (4U * (_BAUD_))) +#define __DIVMANT(_PCLK_, _BAUD_) (__DIV((_PCLK_), (_BAUD_)) / 100U) +#define __DIVFRAQ(_PCLK_, _BAUD_) ((((__DIV((_PCLK_), (_BAUD_)) - (__DIVMANT((_PCLK_), (_BAUD_)) * 100U)) * 16U) + 50U) / 100U) +#define __USART_BRR(_PCLK_, _BAUD_) ((__DIVMANT((_PCLK_), (_BAUD_)) << 4) | (__DIVFRAQ((_PCLK_), (_BAUD_)) & 0x0FU)) + +void uart_rx_ring(uart_ring *q){ + // Do not read out directly if DMA enabled + if (q->dma_rx == false) { + ENTER_CRITICAL(); + + // Read out RX buffer + uint8_t c = q->uart->RDR; // This read after reading SR clears a bunch of interrupts + + uint16_t next_w_ptr = (q->w_ptr_rx + 1U) % q->rx_fifo_size; + // Do not overwrite buffer data + if (next_w_ptr != q->r_ptr_rx) { + q->elems_rx[q->w_ptr_rx] = c; + q->w_ptr_rx = next_w_ptr; + if (q->callback != NULL) { + q->callback(q); + } + } + + EXIT_CRITICAL(); + } +} + +void uart_tx_ring(uart_ring *q){ + ENTER_CRITICAL(); + // Send out next byte of TX buffer + if (q->w_ptr_tx != q->r_ptr_tx) { + // Only send if transmit register is empty (aka last byte has been sent) + if ((q->uart->ISR & USART_ISR_TXE_TXFNF) != 0) { + q->uart->TDR = q->elems_tx[q->r_ptr_tx]; // This clears TXE + q->r_ptr_tx = (q->r_ptr_tx + 1U) % q->tx_fifo_size; + } + + // Enable TXE interrupt if there is still data to be sent + if(q->r_ptr_tx != q->w_ptr_tx){ + q->uart->CR1 |= USART_CR1_TXEIE; + } else { + q->uart->CR1 &= ~USART_CR1_TXEIE; + } + } + EXIT_CRITICAL(); +} + +void uart_set_baud(USART_TypeDef *u, unsigned int baud) { + // UART7 is connected to APB1 at 60MHz + u->BRR = 60000000U / baud; +} + +// This read after reading ISR clears all error interrupts. We don't want compiler warnings, nor optimizations +#define UART_READ_RDR(uart) volatile uint8_t t = (uart)->RDR; UNUSED(t); + +void uart_interrupt_handler(uart_ring *q) { + ENTER_CRITICAL(); + + // Read UART status. This is also the first step necessary in clearing most interrupts + uint32_t status = q->uart->ISR; + + // If RXFNE is set, perform a read. This clears RXFNE, ORE, IDLE, NF and FE + if((status & USART_ISR_RXNE_RXFNE) != 0U){ + uart_rx_ring(q); + } + + // Detect errors and clear them + uint32_t err = (status & USART_ISR_ORE) | (status & USART_ISR_NE) | (status & USART_ISR_FE) | (status & USART_ISR_PE); + if(err != 0U){ + #ifdef DEBUG_UART + print("Encountered UART error: "); puth(err); print("\n"); + #endif + UART_READ_RDR(q->uart) + } + + if ((err & USART_ISR_ORE) != 0U) { + q->uart->ICR |= USART_ICR_ORECF; + } else if ((err & USART_ISR_NE) != 0U) { + q->uart->ICR |= USART_ICR_NECF; + } else if ((err & USART_ISR_FE) != 0U) { + q->uart->ICR |= USART_ICR_FECF; + } else if ((err & USART_ISR_PE) != 0U) { + q->uart->ICR |= USART_ICR_PECF; + } else {} + + // Send if necessary + uart_tx_ring(q); + + // Run DMA pointer handler if the line is idle + if(q->dma_rx && (status & USART_ISR_IDLE)){ + // Reset IDLE flag + UART_READ_RDR(q->uart) + + #ifdef DEBUG_UART + print("No IDLE dma_pointer_handler implemented for this UART."); + #endif + } + + EXIT_CRITICAL(); +} + +void UART7_IRQ_Handler(void) { uart_interrupt_handler(&uart_ring_som_debug); } + +void uart_init(uart_ring *q, int baud) { + if (q->uart == UART7) { + REGISTER_INTERRUPT(UART7_IRQn, UART7_IRQ_Handler, 150000U, FAULT_INTERRUPT_RATE_UART_7) + + if (q->dma_rx) { + // TODO + } + + uart_set_baud(q->uart, baud); + q->uart->CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE; + + // Enable interrupt on RX not empty + q->uart->CR1 |= USART_CR1_RXNEIE; + + // Enable UART interrupts + NVIC_EnableIRQ(UART7_IRQn); + + // Initialise RX DMA if used + if (q->dma_rx) { + dma_rx_init(q); + } + } +} diff --git a/panda/board/stm32h7/peripherals.h b/panda/board/stm32h7/peripherals.h index cc52e6084..754f157ae 100644 --- a/panda/board/stm32h7/peripherals.h +++ b/panda/board/stm32h7/peripherals.h @@ -100,11 +100,18 @@ void peripherals_init(void) { RCC->AHB4ENR |= RCC_AHB4ENR_GPIOGEN; RCC->APB2ENR |= RCC_APB2ENR_SPI4EN; // SPI + RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN; // DAC DMA RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN; // SPI DMA RCC->APB1LENR |= RCC_APB1LENR_TIM2EN; // main counter + RCC->APB1LENR |= RCC_APB1LENR_TIM3EN; // fan pwm RCC->APB1LENR |= RCC_APB1LENR_TIM6EN; // interrupt timer - RCC->APB2ENR |= RCC_APB2ENR_TIM8EN; // clock source timer + RCC->APB1LENR |= RCC_APB1LENR_TIM7EN; // DMA trigger timer + RCC->APB1LENR |= RCC_APB1LENR_UART7EN; // SOM uart + RCC->APB1LENR |= RCC_APB1LENR_DAC12EN; // DAC + RCC->APB2ENR |= RCC_APB2ENR_TIM8EN; // tick timer RCC->APB1LENR |= RCC_APB1LENR_TIM12EN; // slow loop + RCC->APB1LENR |= RCC_APB1LENR_I2C5EN; // codec I2C + RCC->APB2ENR |= RCC_APB2ENR_TIM1EN; // clock source timer RCC->APB1HENR |= RCC_APB1HENR_FDCANEN; // FDCAN core enable RCC->AHB1ENR |= RCC_AHB1ENR_ADC12EN; // Enable ADC clocks diff --git a/panda/board/stm32h7/stm32h7_config.h b/panda/board/stm32h7/stm32h7_config.h index 393d7ae30..d1b19fa34 100644 --- a/panda/board/stm32h7/stm32h7_config.h +++ b/panda/board/stm32h7/stm32h7_config.h @@ -7,8 +7,10 @@ #define CORE_FREQ 240U // in Mhz //APB1 - 120Mhz, APB2 - 120Mhz -#define APB1_FREQ CORE_FREQ/2U -#define APB2_FREQ CORE_FREQ/2U +#define APB1_FREQ (CORE_FREQ/4U) +#define APB1_TIMER_FREQ (APB1_FREQ*2U) // APB1 is multiplied by 2 for the timer peripherals +#define APB2_FREQ (CORE_FREQ/4U) +#define APB2_TIMER_FREQ (APB2_FREQ*2U) // APB2 is multiplied by 2 for the timer peripherals #define BOOTLOADER_ADDRESS 0x1FF09804U @@ -55,12 +57,16 @@ #include "stm32h7/interrupt_handlers.h" #include "drivers/timers.h" #include "stm32h7/lladc.h" -#include "stm32h7/board.h" -#include "stm32h7/clock.h" #if !defined(BOOTSTUB) && defined(PANDA) #include "drivers/uart.h" #include "stm32h7/lluart.h" +#endif + +#include "stm32h7/board.h" +#include "stm32h7/clock.h" + +#if !defined(BOOTSTUB) && defined(PANDA) #include "stm32h7/llexti.h" #endif diff --git a/panda/board/tests/test_rsa.c b/panda/board/tests/test_rsa.c deleted file mode 100644 index 5c784e23a..000000000 --- a/panda/board/tests/test_rsa.c +++ /dev/null @@ -1,34 +0,0 @@ -/* -gcc -DTEST_RSA test_rsa.c ../crypto/rsa.c ../crypto/sha.c && ./a.out -*/ - -#include -#include - -#define MAX_LEN 0x40000 -char buf[MAX_LEN]; - -#include "../crypto/sha.h" -#include "../crypto/rsa.h" -#include "../obj/cert.h" - -int main() { - FILE *f = fopen("../obj/panda.bin", "rb"); - int tlen = fread(buf, 1, MAX_LEN, f); - fclose(f); - printf("read %d\n", tlen); - uint32_t *_app_start = (uint32_t *)buf; - - int len = _app_start[0]; - char digest[SHA_DIGEST_SIZE]; - SHA_hash(&_app_start[1], len-4, digest); - printf("SHA hash done\n"); - - if (!RSA_verify(&rsa_key, ((void*)&_app_start[0]) + len, RSANUMBYTES, digest, SHA_DIGEST_SIZE)) { - printf("RSA fail\n"); - } else { - printf("RSA match!!!\n"); - } - - return 0; -} diff --git a/panda/board/utils.h b/panda/board/utils.h index d767738b1..4232e0a97 100644 --- a/panda/board/utils.h +++ b/panda/board/utils.h @@ -1,3 +1,28 @@ +#define MIN(a,b) \ + ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + (_a < _b) ? _a : _b; }) + +#define MAX(a,b) \ + ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + (_a > _b) ? _a : _b; }) + +#define ABS(a) \ + ({ __typeof__ (a) _a = (a); \ + (_a > 0) ? _a : (-_a); }) + +#ifndef NULL +#define NULL ((void*)0) +#endif + +// STM32 HAL defines this +#ifndef UNUSED +#define UNUSED(x) ((void)(x)) +#endif + +#define COMPILE_TIME_ASSERT(pred) ((void)sizeof(char[1 - (2 * ((int)(!(pred))))])) + // compute the time elapsed (in microseconds) from 2 counter samples // case where ts < ts_last is ok: overflow is properly re-casted into uint32_t uint32_t get_ts_elapsed(uint32_t ts, uint32_t ts_last) { diff --git a/panda/examples/query_fw_versions.py b/panda/examples/query_fw_versions.py index 2d4509745..82a204185 100755 --- a/panda/examples/query_fw_versions.py +++ b/panda/examples/query_fw_versions.py @@ -4,14 +4,15 @@ from tqdm import tqdm from panda import Panda from panda.python.uds import UdsClient, MessageTimeoutError, NegativeResponseError, SESSION_TYPE, DATA_IDENTIFIER_TYPE - if __name__ == "__main__": parser = argparse.ArgumentParser() - parser.add_argument('--rxoffset', default="") - parser.add_argument('--nonstandard', action='store_true') - parser.add_argument('--debug', action='store_true') - parser.add_argument('--addr') - parser.add_argument('--bus') + parser.add_argument("--rxoffset", default="") + parser.add_argument("--nonstandard", action="store_true") + parser.add_argument("--no-obd", action="store_true", help="Bus 1 will not be multiplexed to the OBD-II port") + parser.add_argument("--debug", action="store_true") + parser.add_argument("--addr") + parser.add_argument("--bus") + parser.add_argument('-s', '--serial', help="Serial number of panda to use") args = parser.parse_args() if args.addr: @@ -25,15 +26,25 @@ if __name__ == "__main__": for std_id in DATA_IDENTIFIER_TYPE: uds_data_ids[std_id.value] = std_id.name if args.nonstandard: - for uds_id in range(0xf100,0xf180): + for uds_id in range(0xf100, 0xf180): uds_data_ids[uds_id] = "IDENTIFICATION_OPTION_VEHICLE_MANUFACTURER_SPECIFIC_DATA_IDENTIFIER" - for uds_id in range(0xf1a0,0xf1f0): + for uds_id in range(0xf1a0, 0xf1f0): uds_data_ids[uds_id] = "IDENTIFICATION_OPTION_VEHICLE_MANUFACTURER_SPECIFIC" - for uds_id in range(0xf1f0,0xf200): + for uds_id in range(0xf1f0, 0xf200): uds_data_ids[uds_id] = "IDENTIFICATION_OPTION_SYSTEM_SUPPLIER_SPECIFIC" - panda = Panda() - panda.set_safety_mode(Panda.SAFETY_ELM327) + panda_serials = Panda.list() + if args.serial is None and len(panda_serials) > 1: + print("\nMultiple pandas found, choose one:") + for serial in panda_serials: + with Panda(serial) as panda: + print(f" {serial}: internal={panda.is_internal()}") + print() + parser.print_help() + exit() + + panda = Panda(serial=args.serial) + panda.set_safety_mode(Panda.SAFETY_ELM327, 1 if args.no_obd else 0) print("querying addresses ...") with tqdm(addrs) as t: for addr in t: diff --git a/panda/python/__init__.py b/panda/python/__init__.py index 98b2c4a24..2e44b3db7 100644 --- a/panda/python/__init__.py +++ b/panda/python/__init__.py @@ -5,20 +5,19 @@ import time import usb1 import struct import hashlib +import binascii import datetime -import traceback import warnings import logging from functools import wraps from typing import Optional from itertools import accumulate -from .dfu import PandaDFU, MCU_TYPE_F2, MCU_TYPE_F4, MCU_TYPE_H7 # pylint: disable=import-error -from .flash_release import flash_release # noqa pylint: disable=import-error -from .update import ensure_st_up_to_date # noqa pylint: disable=import-error -from .serial import PandaSerial # noqa pylint: disable=import-error -from .isotp import isotp_send, isotp_recv # pylint: disable=import-error -from .config import DEFAULT_FW_FN, DEFAULT_H7_FW_FN, SECTOR_SIZES_FX, SECTOR_SIZES_H7 # noqa pylint: disable=import-error +from .constants import McuType +from .dfu import PandaDFU +from .isotp import isotp_send, isotp_recv +from .spi import PandaSpiHandle, PandaSpiException +from .usb import PandaUsbHandle __version__ = '0.0.10' @@ -27,82 +26,70 @@ LOGLEVEL = os.environ.get('LOGLEVEL', 'INFO').upper() logging.basicConfig(level=LOGLEVEL, format='%(message)s') -BASEDIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../") - -DEBUG = os.getenv("PANDADEBUG") is not None - -CANPACKET_HEAD_SIZE = 0x5 +USBPACKET_MAX_SIZE = 0x40 +CANPACKET_HEAD_SIZE = 0x6 DLC_TO_LEN = [0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64] LEN_TO_DLC = {length: dlc for (dlc, length) in enumerate(DLC_TO_LEN)} +def calculate_checksum(data): + res = 0 + for b in data: + res ^= b + return res + def pack_can_buffer(arr): snds = [b''] - idx = 0 for address, _, dat, bus in arr: assert len(dat) in LEN_TO_DLC - if DEBUG: - print(f" W 0x{address:x}: 0x{dat.hex()}") + #logging.debug(" W 0x%x: 0x%s", address, dat.hex()) + extended = 1 if address >= 0x800 else 0 data_len_code = LEN_TO_DLC[len(dat)] - header = bytearray(5) + header = bytearray(CANPACKET_HEAD_SIZE) word_4b = address << 3 | extended << 2 header[0] = (data_len_code << 4) | (bus << 1) header[1] = word_4b & 0xFF header[2] = (word_4b >> 8) & 0xFF header[3] = (word_4b >> 16) & 0xFF header[4] = (word_4b >> 24) & 0xFF - snds[idx] += header + dat - if len(snds[idx]) > 256: # Limit chunks to 256 bytes - snds.append(b'') - idx += 1 + header[5] = calculate_checksum(header[:5] + dat) + + snds[-1] += header + dat + if len(snds[-1]) > 256: # Limit chunks to 256 bytes + snds.append(b'') - # Apply counter to each 64 byte packet - for idx in range(len(snds)): - tx = b'' - counter = 0 - for i in range (0, len(snds[idx]), 63): - tx += bytes([counter]) + snds[idx][i:i+63] - counter += 1 - snds[idx] = tx return snds def unpack_can_buffer(dat): ret = [] - counter = 0 - tail = bytearray() - for i in range(0, len(dat), 64): - if counter != dat[i]: - print("CAN: LOST RECV PACKET COUNTER") + + while len(dat) >= CANPACKET_HEAD_SIZE: + data_len = DLC_TO_LEN[(dat[0]>>4)] + + header = dat[:CANPACKET_HEAD_SIZE] + + bus = (header[0] >> 1) & 0x7 + address = (header[4] << 24 | header[3] << 16 | header[2] << 8 | header[1]) >> 3 + + if (header[1] >> 1) & 0x1: + # returned + bus += 128 + if header[1] & 0x1: + # rejected + bus += 192 + + # we need more from the next transfer + if data_len > len(dat) - CANPACKET_HEAD_SIZE: break - counter+=1 - chunk = tail + dat[i+1:i+64] - tail = bytearray() - pos = 0 - while pos>4)] - pckt_len = CANPACKET_HEAD_SIZE + data_len - if pckt_len <= len(chunk[pos:]): - header = chunk[pos:pos+CANPACKET_HEAD_SIZE] - if len(header) < 5: - print("CAN: MALFORMED USB RECV PACKET") - break - bus = (header[0] >> 1) & 0x7 - address = (header[4] << 24 | header[3] << 16 | header[2] << 8 | header[1]) >> 3 - returned = (header[1] >> 1) & 0x1 - rejected = header[1] & 0x1 - data = chunk[pos + CANPACKET_HEAD_SIZE:pos + CANPACKET_HEAD_SIZE + data_len] - if returned: - bus += 128 - if rejected: - bus += 192 - if DEBUG: - print(f" R 0x{address:x}: 0x{data.hex()}") - ret.append((address, 0, data, bus)) - pos += pckt_len - else: - tail = chunk[pos:] - break - return ret + + assert calculate_checksum(dat[:(CANPACKET_HEAD_SIZE+data_len)]) == 0, "CAN packet checksum incorrect" + + data = dat[CANPACKET_HEAD_SIZE:(CANPACKET_HEAD_SIZE+data_len)] + dat = dat[(CANPACKET_HEAD_SIZE+data_len):] + + ret.append((address, 0, data, bus)) + + return (ret, dat) def ensure_health_packet_version(fn): @wraps(fn) @@ -174,6 +161,7 @@ class Panda: SERIAL_ESP = 1 SERIAL_LIN1 = 2 SERIAL_LIN2 = 3 + SERIAL_SOM_DEBUG = 4 GMLAN_CAN2 = 1 GMLAN_CAN3 = 2 @@ -192,11 +180,11 @@ class Panda: HW_TYPE_RED_PANDA_V2 = b'\x08' HW_TYPE_TRES = b'\x09' - CAN_PACKET_VERSION = 2 + CAN_PACKET_VERSION = 4 HEALTH_PACKET_VERSION = 11 - CAN_HEALTH_PACKET_VERSION = 3 + CAN_HEALTH_PACKET_VERSION = 4 HEALTH_STRUCT = struct.Struct("> 8, ]) + this_bcd = device.getbcdDevice() + if this_bcd is not None and this_bcd != 0x2300: + bcd = bytearray([this_bcd >> 8, ]) break - except Exception as e: - print("exception", e) - traceback.print_exc() - if not wait or self._handle is not None: + except Exception: + logging.exception("USB connect error") + if not wait or handle is not None: break context = usb1.USBContext() # New context needed so new devices show up + usb_handle = None + if handle is not None: + usb_handle = PandaUsbHandle(handle) + + return usb_handle, usb_serial, bootstub, bcd + + @staticmethod + def list(): + ret = Panda.usb_list() + ret += Panda.spi_list() + return list(set(ret)) + + @staticmethod + def usb_list(): + context = usb1.USBContext() + ret = [] + try: + for device in context.getDeviceList(skip_on_error=True): + if device.getVendorID() == 0xbbaa and device.getProductID() in (0xddcc, 0xddee): + try: + serial = device.getSerialNumber() + if len(serial) == 24: + ret.append(serial) + else: + warnings.warn(f"found device with panda descriptors but invalid serial: {serial}", RuntimeWarning) + except Exception: + continue + except Exception: + pass + return ret + + @staticmethod + def spi_list(): + _, serial, _, _ = Panda.spi_connect(None) + if serial is not None: + return [serial, ] + return [] + def reset(self, enter_bootstub=False, enter_bootloader=False, reconnect=True): try: if enter_bootloader: @@ -340,7 +408,7 @@ class Panda: success = True break except Exception: - print("reconnecting is taking %d seconds..." % (i + 1)) + logging.debug("reconnecting is taking %d seconds...", i + 1) try: dfu = PandaDFU(PandaDFU.st_serial_to_dfu_serial(self._serial, self._mcu_type)) dfu.recover() @@ -350,8 +418,6 @@ class Panda: if not success: raise Exception("reconnect failed") - - @staticmethod def flash_static(handle, code, mcu_type): assert mcu_type is not None, "must set valid mcu_type to flash" @@ -361,28 +427,28 @@ class Panda: assert fr[4:8] == b"\xde\xad\xd0\x0d" # determine sectors to erase - apps_sectors_cumsum = accumulate(SECTOR_SIZES_H7[1:] if mcu_type == MCU_TYPE_H7 else SECTOR_SIZES_FX[1:]) + apps_sectors_cumsum = accumulate(mcu_type.config.sector_sizes[1:]) last_sector = next((i + 1 for i, v in enumerate(apps_sectors_cumsum) if v > len(code)), -1) assert last_sector >= 1, "Binary too small? No sector to erase." assert last_sector < 7, "Binary too large! Risk of overwriting provisioning chunk." # unlock flash - print("flash: unlocking") + logging.warning("flash: unlocking") handle.controlWrite(Panda.REQUEST_IN, 0xb1, 0, 0, b'') # erase sectors - print(f"flash: erasing sectors 1 - {last_sector}") + logging.warning(f"flash: erasing sectors 1 - {last_sector}") for i in range(1, last_sector + 1): handle.controlWrite(Panda.REQUEST_IN, 0xb2, i, 0, b'') # flash over EP2 STEP = 0x10 - print("flash: flashing") + logging.warning("flash: flashing") for i in range(0, len(code), STEP): handle.bulkWrite(2, code[i:i + STEP]) # reset - print("flash: resetting") + logging.warning("flash: resetting") try: handle.controlWrite(Panda.REQUEST_IN, 0xd8, 0, 0, b'') except Exception: @@ -390,9 +456,9 @@ class Panda: def flash(self, fn=None, code=None, reconnect=True): if not fn: - fn = DEFAULT_H7_FW_FN if self._mcu_type == MCU_TYPE_H7 else DEFAULT_FW_FN + fn = self._mcu_type.config.app_path assert os.path.isfile(fn) - print("flash: main version is " + self.get_version()) + logging.debug("flash: main version is %s", self.get_version()) if not self.bootstub: self.reset(enter_bootstub=True) assert(self.bootstub) @@ -402,7 +468,7 @@ class Panda: code = f.read() # get version - print("flash: bootstub version is " + self.get_version()) + logging.debug("flash: bootstub version is %s", self.get_version()) # do flash Panda.flash_static(self._handle, code, mcu_type=self._mcu_type) @@ -433,31 +499,12 @@ class Panda: def wait_for_dfu(dfu_serial: str, timeout: Optional[int] = None) -> bool: t_start = time.monotonic() while dfu_serial not in PandaDFU.list(): - print("waiting for DFU...") + logging.debug("waiting for DFU...") time.sleep(0.1) if timeout is not None and (time.monotonic() - t_start) > timeout: return False return True - @staticmethod - def list(): - context = usb1.USBContext() - ret = [] - try: - for device in context.getDeviceList(skip_on_error=True): - if device.getVendorID() == 0xbbaa and device.getProductID() in (0xddcc, 0xddee): - try: - serial = device.getSerialNumber() - if len(serial) == 24: - ret.append(serial) - else: - warnings.warn(f"found device with panda descriptors but invalid serial: {serial}", RuntimeWarning) - except Exception: - continue - except Exception: - pass - return ret - def call_control_api(self, msg): self._handle.controlWrite(Panda.REQUEST_OUT, msg, 0, 0, b'') @@ -524,11 +571,12 @@ class Panda: "total_tx_cnt": a[13], "total_rx_cnt": a[14], "total_fwd_cnt": a[15], - "can_speed": a[16], - "can_data_speed": a[17], - "canfd_enabled": a[18], - "brs_enabled": a[19], - "canfd_non_iso": a[20], + "total_tx_checksum_error_cnt": a[16], + "can_speed": a[17], + "can_data_speed": a[18], + "canfd_enabled": a[19], + "brs_enabled": a[20], + "canfd_non_iso": a[21], } # ******************* control ******************* @@ -536,8 +584,8 @@ class Panda: def enter_bootloader(self): try: self._handle.controlWrite(Panda.REQUEST_OUT, 0xd1, 0, 0, b'') - except Exception as e: - print(e) + except Exception: + logging.exception("exception while entering bootloader") def get_version(self): return self._handle.controlRead(Panda.REQUEST_IN, 0xd6, 0, 0, 0x40).decode('utf8') @@ -556,10 +604,9 @@ class Panda: def get_type(self): ret = self._handle.controlRead(Panda.REQUEST_IN, 0xc1, 0, 0, 0x40) - # bootstub doesn't implement this call, so fallback to bcdDevice - invalid_type = self.bootstub and (ret is None or len(ret) != 1) - if invalid_type and self._bcd_device is not None: - ret = self._bcd_device + # old bootstubs don't implement this endpoint, see comment in Panda.device + if self._bcd_hw_type is not None and (ret is None or len(ret) != 1): + ret = self._bcd_hw_type return ret @@ -572,15 +619,20 @@ class Panda: else: return (0, 0, 0) - def get_mcu_type(self): + def get_mcu_type(self) -> McuType: hw_type = self.get_type() if hw_type in Panda.F2_DEVICES: - return MCU_TYPE_F2 + return McuType.F2 elif hw_type in Panda.F4_DEVICES: - return MCU_TYPE_F4 + return McuType.F4 elif hw_type in Panda.H7_DEVICES: - return MCU_TYPE_H7 - return None + return McuType.H7 + else: + # have to assume F4, see comment in Panda.connect + if self._assume_f4_mcu: + return McuType.F4 + + raise ValueError(f"unknown HW type: {hw_type}") def has_obd(self): return self.get_type() in Panda.HAS_OBD @@ -589,22 +641,33 @@ class Panda: return self.get_type() in Panda.INTERNAL_DEVICES def get_serial(self): + """ + Returns the comma-issued dongle ID from our provisioning + """ dat = self._handle.controlRead(Panda.REQUEST_IN, 0xd0, 0, 0, 0x20) hashsig, calc_hash = dat[0x1c:], hashlib.sha1(dat[0:0x1c]).digest()[0:4] assert(hashsig == calc_hash) return [dat[0:0x10].decode("utf8"), dat[0x10:0x10 + 10].decode("utf8")] def get_usb_serial(self): + """ + Returns the serial number reported from the USB descriptor; + matches the MCU UID + """ return self._serial + def get_uid(self): + """ + Returns the UID from the MCU + """ + dat = self._handle.controlRead(Panda.REQUEST_IN, 0xc3, 0, 0, 12) + return binascii.hexlify(dat).decode() + def get_secret(self): return self._handle.controlRead(Panda.REQUEST_IN, 0xd0, 1, 0, 0x10) # ******************* configuration ******************* - def set_usb_power(self, on): - self._handle.controlWrite(Panda.REQUEST_OUT, 0xe6, int(on), 0, b'') - def set_power_save(self, power_save_enabled=0): self._handle.controlWrite(Panda.REQUEST_OUT, 0xe7, int(power_save_enabled), 0, b'') @@ -666,6 +729,9 @@ class Panda: # Timeout is in ms. If set to 0, the timeout is infinite. CAN_SEND_TIMEOUT_MS = 10 + def can_reset_communications(self): + self._handle.controlWrite(Panda.REQUEST_OUT, 0xc0, 0, 0, b'') + @ensure_can_packet_version def can_send_many(self, arr, timeout=CAN_SEND_TIMEOUT_MS): snds = pack_can_buffer(arr) @@ -677,10 +743,10 @@ class Panda: tx = tx[bs:] if len(tx) == 0: break - print("CAN: PARTIAL SEND MANY, RETRYING") + logging.error("CAN: PARTIAL SEND MANY, RETRYING") break except (usb1.USBErrorIO, usb1.USBErrorOverflow): - print("CAN: BAD SEND MANY, RETRYING") + logging.error("CAN: BAD SEND MANY, RETRYING") def can_send(self, addr, dat, bus, timeout=CAN_SEND_TIMEOUT_MS): self.can_send_many([[addr, None, dat, bus]], timeout=timeout) @@ -693,9 +759,10 @@ class Panda: dat = self._handle.bulkRead(1, 16384) # Max receive batch size + 2 extra reserve frames break except (usb1.USBErrorIO, usb1.USBErrorOverflow): - print("CAN: BAD RECV, RETRYING") + logging.error("CAN: BAD RECV, RETRYING") time.sleep(0.1) - return unpack_can_buffer(dat) + msgs, self.can_rx_overflow_buffer = unpack_can_buffer(self.can_rx_overflow_buffer + dat) + return msgs def can_clear(self, bus): """Clears all messages from the specified internal CAN ringbuffer as @@ -729,6 +796,8 @@ class Panda: def serial_write(self, port_number, ln): ret = 0 + if type(ln) == str: + ln = bytes(ln, 'utf-8') for i in range(0, len(ln), 0x20): ret += self._handle.bulkWrite(2, struct.pack("B", port_number) + ln[i:i + 0x20]) return ret @@ -748,19 +817,15 @@ class Panda: # pulse low for wakeup def kline_wakeup(self, k=True, l=True): assert k or l, "must specify k-line, l-line, or both" - if DEBUG: - print("kline wakeup...") + logging.debug("kline wakeup...") self._handle.controlWrite(Panda.REQUEST_OUT, 0xf0, 2 if k and l else int(l), 0, b'') - if DEBUG: - print("kline wakeup done") + logging.debug("kline wakeup done") def kline_5baud(self, addr, k=True, l=True): assert k or l, "must specify k-line, l-line, or both" - if DEBUG: - print("kline 5 baud...") + logging.debug("kline 5 baud...") self._handle.controlWrite(Panda.REQUEST_OUT, 0xf4, 2 if k and l else int(l), addr, b'') - if DEBUG: - print("kline 5 baud done") + logging.debug("kline 5 baud done") def kline_drain(self, bus=2): # drain buffer @@ -769,8 +834,7 @@ class Panda: ret = self._handle.controlRead(Panda.REQUEST_IN, 0xe0, bus, 0, 0x40) if len(ret) == 0: break - elif DEBUG: - print(f"kline drain: 0x{ret.hex()}") + logging.debug(f"kline drain: 0x{ret.hex()}") bret += ret return bytes(bret) @@ -778,8 +842,8 @@ class Panda: echo = bytearray() while len(echo) != cnt: ret = self._handle.controlRead(Panda.REQUEST_OUT, 0xe0, bus, 0, cnt - len(echo)) - if DEBUG and len(ret) > 0: - print(f"kline recv: 0x{ret.hex()}") + if len(ret) > 0: + logging.debug(f"kline recv: 0x{ret.hex()}") echo += ret return bytes(echo) @@ -789,14 +853,13 @@ class Panda: x += bytes([sum(x) % 0x100]) for i in range(0, len(x), 0xf): ts = x[i:i + 0xf] - if DEBUG: - print(f"kline send: 0x{ts.hex()}") + logging.debug(f"kline send: 0x{ts.hex()}") self._handle.bulkWrite(2, bytes([bus]) + ts) echo = self.kline_ll_recv(len(ts), bus=bus) if echo != ts: - print(f"**** ECHO ERROR {i} ****") - print(f"0x{echo.hex()}") - print(f"0x{ts.hex()}") + logging.error(f"**** ECHO ERROR {i} ****") + logging.error(f"0x{echo.hex()}") + logging.error(f"0x{ts.hex()}") assert echo == ts def kline_recv(self, bus=2, header_len=4): @@ -829,6 +892,11 @@ class Panda: a = struct.unpack("HBBBBBB", dat) return datetime.datetime(a[0], a[1], a[2], a[4], a[5], a[6]) + # ****************** Timer ***************** + def get_microsecond_timer(self): + dat = self._handle.controlRead(Panda.REQUEST_IN, 0xa8, 0, 0, 4) + return struct.unpack("I", dat)[0] + # ******************* IR ******************* def set_ir_power(self, percentage): self._handle.controlWrite(Panda.REQUEST_OUT, 0xb0, int(percentage), 0, b'') @@ -846,10 +914,6 @@ class Panda: def set_phone_power(self, enabled): self._handle.controlWrite(Panda.REQUEST_OUT, 0xb3, int(enabled), 0, b'') - # ************** Clock Source ************** - def set_clock_source_mode(self, mode): - self._handle.controlWrite(Panda.REQUEST_OUT, 0xf5, int(mode), 0, b'') - # ****************** Siren ***************** def set_siren(self, enabled): self._handle.controlWrite(Panda.REQUEST_OUT, 0xf6, int(enabled), 0, b'') diff --git a/panda/python/base.py b/panda/python/base.py new file mode 100644 index 000000000..d051e8770 --- /dev/null +++ b/panda/python/base.py @@ -0,0 +1,25 @@ +from abc import ABC, abstractmethod +from typing import List + + +# This mimics the handle given by libusb1 for easy interoperability +class BaseHandle(ABC): + @abstractmethod + def close(self) -> None: + ... + + @abstractmethod + def controlWrite(self, request_type: int, request: int, value: int, index: int, data, timeout: int = 0) -> int: + ... + + @abstractmethod + def controlRead(self, request_type: int, request: int, value: int, index: int, length: int, timeout: int = 0) -> bytes: + ... + + @abstractmethod + def bulkWrite(self, endpoint: int, data: List[int], timeout: int = 0) -> int: + ... + + @abstractmethod + def bulkRead(self, endpoint: int, length: int, timeout: int = 0) -> bytes: + ... diff --git a/panda/python/ccp.py b/panda/python/ccp.py index a8c00169f..918312002 100644 --- a/panda/python/ccp.py +++ b/panda/python/ccp.py @@ -100,9 +100,9 @@ class CcpClient(): msgs = self._panda.can_recv() or [] if len(msgs) >= 256: print("CAN RX buffer overflow!!!", file=sys.stderr) - for rx_addr, _, rx_data, rx_bus in msgs: + for rx_addr, _, rx_data_bytearray, rx_bus in msgs: if rx_bus == self.can_bus and rx_addr == self.rx_addr: - rx_data = bytes(rx_data) # convert bytearray to bytes + rx_data = bytes(rx_data_bytearray) if self.debug: print(f"CAN-RX: {hex(rx_addr)} - 0x{bytes.hex(rx_data)}") assert len(rx_data) == 8, f"message length not 8: {len(rx_data)}" @@ -183,7 +183,7 @@ class CcpClient(): resp = self._recv_dto(0.025) # mta_addr_ext = resp[0] mta_addr = struct.unpack(f"{self.byte_order.value}I", resp[1:5])[0] - return mta_addr + return mta_addr # type: ignore def download_6_bytes(self, data: bytes) -> int: if len(data) != 6: @@ -192,7 +192,7 @@ class CcpClient(): resp = self._recv_dto(0.025) # mta_addr_ext = resp[0] mta_addr = struct.unpack(f"{self.byte_order.value}I", resp[1:5])[0] - return mta_addr + return mta_addr # type: ignore def upload(self, size: int) -> bytes: if size > 5: @@ -296,7 +296,7 @@ class CcpClient(): resp = self._recv_dto(0.1) # mta_addr_ext = resp[0] mta_addr = struct.unpack(f"{self.byte_order.value}I", resp[1:5])[0] - return mta_addr + return mta_addr # type: ignore def program_6_bytes(self, data: bytes) -> int: if len(data) != 6: @@ -305,7 +305,7 @@ class CcpClient(): resp = self._recv_dto(0.1) # mta_addr_ext = resp[0] mta_addr = struct.unpack(f"{self.byte_order.value}I", resp[1:5])[0] - return mta_addr + return mta_addr # type: ignore def move_memory_block(self, size: int) -> None: self._send_cro(COMMAND_CODE.MOVE, struct.pack(f"{self.byte_order.value}I", size)) diff --git a/panda/python/config.py b/panda/python/config.py deleted file mode 100644 index 56940ed72..000000000 --- a/panda/python/config.py +++ /dev/null @@ -1,19 +0,0 @@ -import os - -BASEDIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../") - -BOOTSTUB_ADDRESS = 0x8000000 - -BLOCK_SIZE_FX = 0x800 -APP_ADDRESS_FX = 0x8004000 -SECTOR_SIZES_FX = [0x4000 for _ in range(4)] + [0x10000] + [0x20000 for _ in range(11)] -DEVICE_SERIAL_NUMBER_ADDR_FX = 0x1FFF79C0 -DEFAULT_FW_FN = os.path.join(BASEDIR, "board", "obj", "panda.bin.signed") -DEFAULT_BOOTSTUB_FN = os.path.join(BASEDIR, "board", "obj", "bootstub.panda.bin") - -BLOCK_SIZE_H7 = 0x400 -APP_ADDRESS_H7 = 0x8020000 -SECTOR_SIZES_H7 = [0x20000 for _ in range(7)] # there is an 8th sector, but we use that for the provisioning chunk, so don't program over that! -DEVICE_SERIAL_NUMBER_ADDR_H7 = 0x080FFFC0 -DEFAULT_H7_FW_FN = os.path.join(BASEDIR, "board", "obj", "panda_h7.bin.signed") -DEFAULT_H7_BOOTSTUB_FN = os.path.join(BASEDIR, "board", "obj", "bootstub.panda_h7.bin") diff --git a/panda/python/constants.py b/panda/python/constants.py new file mode 100644 index 000000000..d964192c8 --- /dev/null +++ b/panda/python/constants.py @@ -0,0 +1,52 @@ +import os +import enum +from typing import List, NamedTuple + +BASEDIR = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../") + + +class McuConfig(NamedTuple): + mcu: str + mcu_idcode: int + block_size: int + sector_sizes: List[int] + serial_number_address: int + app_address: int + app_path: str + bootstub_address: int + bootstub_path: str + +Fx = ( + 0x800, + [0x4000 for _ in range(4)] + [0x10000] + [0x20000 for _ in range(11)], + 0x1FFF79C0, + 0x8004000, + os.path.join(BASEDIR, "board", "obj", "panda.bin.signed"), + 0x8000000, + os.path.join(BASEDIR, "board", "obj", "bootstub.panda.bin"), +) +F2Config = McuConfig("STM32F2", 0x411, *Fx) +F4Config = McuConfig("STM32F4", 0x463, *Fx) + +H7Config = McuConfig( + "STM32H7", + 0x483, + 0x400, + # there is an 8th sector, but we use that for the provisioning chunk, so don't program over that! + [0x20000 for _ in range(7)], + 0x080FFFC0, + 0x8020000, + os.path.join(BASEDIR, "board", "obj", "panda_h7.bin.signed"), + 0x8000000, + os.path.join(BASEDIR, "board", "obj", "bootstub.panda_h7.bin"), +) + +@enum.unique +class McuType(enum.Enum): + F2 = F2Config + F4 = F4Config + H7 = H7Config + + @property + def config(self): + return self.value diff --git a/panda/python/dfu.py b/panda/python/dfu.py index 317780ca8..dfdec1fd3 100644 --- a/panda/python/dfu.py +++ b/panda/python/dfu.py @@ -1,12 +1,9 @@ import usb1 import struct import binascii -from .config import BOOTSTUB_ADDRESS, APP_ADDRESS_H7, APP_ADDRESS_FX, BLOCK_SIZE_H7, BLOCK_SIZE_FX, DEFAULT_H7_BOOTSTUB_FN, DEFAULT_BOOTSTUB_FN +from .constants import McuType -MCU_TYPE_F2 = 0 -MCU_TYPE_F4 = 1 -MCU_TYPE_H7 = 2 # *** DFU mode *** DFU_DNLOAD = 1 @@ -15,8 +12,9 @@ DFU_GETSTATUS = 3 DFU_CLRSTATUS = 4 DFU_ABORT = 6 -class PandaDFU(object): +class PandaDFU: def __init__(self, dfu_serial): + self._handle = None context = usb1.USBContext() for device in context.getDeviceList(skip_on_error=True): if device.getVendorID() == 0x0483 and device.getProductID() == 0xdf11: @@ -25,10 +23,12 @@ class PandaDFU(object): except Exception: continue if this_dfu_serial == dfu_serial or dfu_serial is None: - self._mcu_type = self.get_mcu_type(device) self._handle = device.open() - return - raise Exception("failed to open " + dfu_serial if dfu_serial is not None else "DFU device") + self._mcu_type = self.get_mcu_type(device) + break + + if self._handle is None: + raise Exception(f"failed to open DFU device {dfu_serial}") @staticmethod def list(): @@ -46,18 +46,19 @@ class PandaDFU(object): return dfu_serials @staticmethod - def st_serial_to_dfu_serial(st, mcu_type=MCU_TYPE_F4): + def st_serial_to_dfu_serial(st, mcu_type=McuType.F4): if st is None or st == "none": return None uid_base = struct.unpack("H" * 6, bytes.fromhex(st)) - if mcu_type == MCU_TYPE_H7: + if mcu_type == McuType.H7: return binascii.hexlify(struct.pack("!HHH", uid_base[1] + uid_base[5], uid_base[0] + uid_base[4], uid_base[3])).upper().decode("utf-8") else: return binascii.hexlify(struct.pack("!HHH", uid_base[1] + uid_base[5], uid_base[0] + uid_base[4] + 0xA, uid_base[3])).upper().decode("utf-8") - # TODO: Find a way to detect F4 vs F2 - def get_mcu_type(self, dev): - return MCU_TYPE_H7 if dev.getbcdDevice() == 512 else MCU_TYPE_F4 + def get_mcu_type(self, dev) -> McuType: + # TODO: Find a way to detect F4 vs F2 + # TODO: also check F4 BCD, don't assume in else + return McuType.H7 if dev.getbcdDevice() == 512 else McuType.F4 def status(self): while 1: @@ -97,26 +98,18 @@ class PandaDFU(object): def program_bootstub(self, code_bootstub): self.clear_status() - self.erase(BOOTSTUB_ADDRESS) - if self._mcu_type == MCU_TYPE_H7: - self.erase(APP_ADDRESS_H7) - self.program(BOOTSTUB_ADDRESS, code_bootstub, BLOCK_SIZE_H7) - else: - self.erase(APP_ADDRESS_FX) - self.program(BOOTSTUB_ADDRESS, code_bootstub, BLOCK_SIZE_FX) + self.erase(self._mcu_type.config.bootstub_address) + self.erase(self._mcu_type.config.app_address) + self.program(self._mcu_type.config.bootstub_address, code_bootstub, self._mcu_type.config.block_size) self.reset() def recover(self): - fn = DEFAULT_H7_BOOTSTUB_FN if self._mcu_type == MCU_TYPE_H7 else DEFAULT_BOOTSTUB_FN - - with open(fn, "rb") as f: + with open(self._mcu_type.config.bootstub_path, "rb") as f: code = f.read() - self.program_bootstub(code) def reset(self): - # **** Reset **** - self._handle.controlWrite(0x21, DFU_DNLOAD, 0, 0, b"\x21" + struct.pack("I", BOOTSTUB_ADDRESS)) + self._handle.controlWrite(0x21, DFU_DNLOAD, 0, 0, b"\x21" + struct.pack("I", self._mcu_type.config.bootstub_address)) self.status() try: self._handle.controlWrite(0x21, DFU_DNLOAD, 2, 0, b"") diff --git a/panda/python/flash_release.py b/panda/python/flash_release.py deleted file mode 100755 index ae50e6dc0..000000000 --- a/panda/python/flash_release.py +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env python3 - -import sys -import time -import requests -import json -import io - -def flash_release(path=None, st_serial=None): - from panda import Panda, PandaDFU - from zipfile import ZipFile - - def status(x): - print("\033[1;32;40m" + x + "\033[00m") - - if st_serial is not None: - # look for Panda - panda_list = Panda.list() - if len(panda_list) == 0: - raise Exception("panda not found, make sure it's connected and your user can access it") - elif len(panda_list) > 1: - raise Exception("Please only connect one panda") - st_serial = panda_list[0] - print("Using panda with serial %s" % st_serial) - - if path is None: - print("Fetching latest firmware from github.com/commaai/panda-artifacts") - r = requests.get("https://raw.githubusercontent.com/commaai/panda-artifacts/master/latest.json") - url = json.loads(r.text)['url'] - r = requests.get(url) - print("Fetching firmware from %s" % url) - path = io.BytesIO(r.content) - - zf = ZipFile(path) - zf.printdir() - - version = zf.read("version").decode().strip() - status("0. Preparing to flash " + str(version)) - - code_bootstub = zf.read("bootstub.panda.bin") - code_panda = zf.read("panda.bin") - - # enter DFU mode - status("1. Entering DFU mode") - panda = Panda(st_serial) - panda.reset(enter_bootstub=True) - panda.reset(enter_bootloader=True) - time.sleep(1) - - # program bootstub - status("2. Programming bootstub") - dfu = PandaDFU(PandaDFU.st_serial_to_dfu_serial(st_serial)) - dfu.program_bootstub(code_bootstub) - time.sleep(1) - - # flash main code - status("3. Flashing main code") - panda = Panda(st_serial) - panda.flash(code=code_panda) - panda.close() - - # check for connection - status("4. Verifying version") - panda = Panda(st_serial) - my_version = panda.get_version() - print("dongle id: %s" % panda.get_serial()[0]) - print(my_version, "should be", version) - assert(str(version) == str(my_version)) - - # done! - status("6. Success!") - -if __name__ == "__main__": - flash_release(*sys.argv[1:]) diff --git a/panda/python/spi.py b/panda/python/spi.py index 6a6569fed..8b3cab105 100644 --- a/panda/python/spi.py +++ b/panda/python/spi.py @@ -1,10 +1,21 @@ +import os +import fcntl import math +import time import struct -import spidev import logging +import threading +from contextlib import contextmanager from functools import reduce from typing import List +from .base import BaseHandle + +try: + import spidev +except ImportError: + spidev = None + # Constants SYNC = 0x5A HACK = 0x79 @@ -12,17 +23,69 @@ DACK = 0x85 NACK = 0x1F CHECKSUM_START = 0xAB -MAX_RETRY_COUNT = 5 +ACK_TIMEOUT_SECONDS = 0.1 +MAX_XFER_RETRY_COUNT = 5 USB_MAX_SIZE = 0x40 -# This mimics the handle given by libusb1 for easy interoperability -class SpiHandle: - def __init__(self): - self.spi = spidev.SpiDev() # pylint: disable=c-extension-no-member - self.spi.open(0, 0) +DEV_PATH = "/dev/spidev0.0" - self.spi.max_speed_hz = 30000000 + +class PandaSpiException(Exception): + pass + +class PandaSpiUnavailable(PandaSpiException): + pass + +class PandaSpiNackResponse(PandaSpiException): + pass + +class PandaSpiMissingAck(PandaSpiException): + pass + +class PandaSpiBadChecksum(PandaSpiException): + pass + +class PandaSpiTransferFailed(PandaSpiException): + pass + + +SPI_LOCK = threading.Lock() + +class SpiDevice: + """ + Provides locked, thread-safe access to a panda's SPI interface. + """ + def __init__(self, speed=30000000): + if not os.path.exists(DEV_PATH): + raise PandaSpiUnavailable(f"SPI device not found: {DEV_PATH}") + if spidev is None: + raise PandaSpiUnavailable("spidev is not installed") + + self._spidev = spidev.SpiDev() # pylint: disable=c-extension-no-member + self._spidev.open(0, 0) + self._spidev.max_speed_hz = speed + + @contextmanager + def acquire(self): + try: + SPI_LOCK.acquire() + fcntl.flock(self._spidev, fcntl.LOCK_EX) + yield self._spidev + finally: + fcntl.flock(self._spidev, fcntl.LOCK_UN) + SPI_LOCK.release() + + def close(self): + self._spidev.close() + + +class PandaSpiHandle(BaseHandle): + """ + A class that mimics a libusb1 handle for panda SPI communications. + """ + def __init__(self): + self.dev = SpiDevice() # helpers def _calc_checksum(self, data: List[int]) -> int: @@ -31,75 +94,81 @@ class SpiHandle: cksum ^= b return cksum - def _transfer(self, endpoint: int, data, max_rx_len: int = 1000) -> bytes: + def _wait_for_ack(self, spi, ack_val: int) -> None: + start = time.monotonic() + while (time.monotonic() - start) < ACK_TIMEOUT_SECONDS: + dat = spi.xfer2(b"\x12")[0] + if dat == NACK: + raise PandaSpiNackResponse + elif dat == ack_val: + return + + raise PandaSpiMissingAck + + def _transfer(self, spi, endpoint: int, data, max_rx_len: int = 1000) -> bytes: logging.debug("starting transfer: endpoint=%d, max_rx_len=%d", endpoint, max_rx_len) logging.debug("==============================================") - for n in range(MAX_RETRY_COUNT): + exc = PandaSpiException() + for n in range(MAX_XFER_RETRY_COUNT): logging.debug("\ntry #%d", n+1) try: logging.debug("- send header") packet = struct.pack(" int: - for x in range(math.ceil(len(data) / USB_MAX_SIZE)): - self._transfer(endpoint, data[USB_MAX_SIZE*x:USB_MAX_SIZE*(x+1)]) - return len(data) + with self.dev.acquire() as spi: + for x in range(math.ceil(len(data) / USB_MAX_SIZE)): + self._transfer(spi, endpoint, data[USB_MAX_SIZE*x:USB_MAX_SIZE*(x+1)]) + return len(data) def bulkRead(self, endpoint: int, length: int, timeout: int = 0) -> bytes: ret: List[int] = [] - for _ in range(math.ceil(length / USB_MAX_SIZE)): - d = self._transfer(endpoint, [], max_rx_len=USB_MAX_SIZE) - ret += d - if len(d) < USB_MAX_SIZE: - break + with self.dev.acquire() as spi: + for _ in range(math.ceil(length / USB_MAX_SIZE)): + d = self._transfer(spi, endpoint, [], max_rx_len=USB_MAX_SIZE) + ret += d + if len(d) < USB_MAX_SIZE: + break return bytes(ret) diff --git a/panda/python/spi_dfu.py b/panda/python/spi_dfu.py new file mode 100644 index 000000000..73d930528 --- /dev/null +++ b/panda/python/spi_dfu.py @@ -0,0 +1,118 @@ +import time +import struct +from functools import reduce + +from .constants import McuType +from .spi import SpiDevice + +SYNC = 0x5A +ACK = 0x79 +NACK = 0x1F + +# https://www.st.com/resource/en/application_note/an4286-spi-protocol-used-in-the-stm32-bootloader-stmicroelectronics.pdf +class PandaSpiDFU: + def __init__(self, dfu_serial): + self.dev = SpiDevice(speed=1000000) + + # say hello + with self.dev.acquire() as spi: + try: + spi.xfer([SYNC, ]) + self._get_ack(spi) + except Exception: + raise Exception("failed to connect to panda") # pylint: disable=W0707 + + self._mcu_type = self.get_mcu_type() + + def _get_ack(self, spi, timeout=1.0): + data = 0x00 + start_time = time.monotonic() + while data not in (ACK, NACK) and (time.monotonic() - start_time < timeout): + data = spi.xfer([0x00, ])[0] + time.sleep(0.001) + spi.xfer([ACK, ]) + + if data == NACK: + raise Exception("Got NACK response") + elif data != ACK: + raise Exception("Missing ACK") + + def _cmd(self, cmd, data=None, read_bytes=0) -> bytes: + ret = b"" + with self.dev.acquire() as spi: + # sync + spi.xfer([SYNC, ]) + + # send command + spi.xfer([cmd, cmd ^ 0xFF]) + self._get_ack(spi) + + # send data + if data is not None: + for d in data: + spi.xfer(self.add_checksum(d)) + self._get_ack(spi, timeout=20) + + # receive + if read_bytes > 0: + # send busy byte + ret = spi.xfer([0x00, ]*(read_bytes + 1))[1:] + self._get_ack(spi) + + return ret + + def add_checksum(self, data): + return data + bytes([reduce(lambda a, b: a ^ b, data)]) + + # ***** ST Bootloader functions ***** + + def get_bootloader_version(self) -> int: + return self._cmd(0x01, read_bytes=1)[0] + + def get_id(self) -> int: + ret = self._cmd(0x02, read_bytes=3) + assert ret[0] == 1 + return ((ret[1] << 8) + ret[2]) + + def go_cmd(self, address: int) -> None: + self._cmd(0x21, data=[struct.pack('>I', address), ]) + + def erase(self, address: int) -> None: + d = struct.pack('>H', address) + self._cmd(0x44, data=[d, ]) + + # ***** panda api ***** + + def get_mcu_type(self) -> McuType: + mcu_by_id = {mcu.config.mcu_idcode: mcu for mcu in McuType} + return mcu_by_id[self.get_id()] + + def global_erase(self): + self.erase(0xFFFF) + + def program_file(self, address, fn): + with open(fn, 'rb') as f: + code = f.read() + + i = 0 + while i < len(code): + #print(i, len(code)) + block = code[i:i+256] + if len(block) < 256: + block += b'\xFF' * (256 - len(block)) + + self._cmd(0x31, data=[ + struct.pack('>I', address + i), + bytes([len(block) - 1]) + block, + ]) + #print(f"Written {len(block)} bytes to {hex(address + i)}") + i += 256 + + def program_bootstub(self): + self.program_file(self._mcu_type.config.bootstub_address, self._mcu_type.config.bootstub_path) + + def program_app(self): + self.program_file(self._mcu_type.config.app_address, self._mcu_type.config.app_path) + + def reset(self): + self.go_cmd(self._mcu_type.config.bootstub_address) diff --git a/panda/python/update.py b/panda/python/update.py deleted file mode 100755 index f8e328044..000000000 --- a/panda/python/update.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python3 -import os -import time - -def ensure_st_up_to_date(): - from panda import Panda, PandaDFU, BASEDIR - - with open(os.path.join(BASEDIR, "VERSION")) as f: - repo_version = f.read() - - repo_version += "-EON" if os.path.isfile('/EON') else "-DEV" - - panda = None - panda_dfu = None - - while 1: - # break on normal mode Panda - panda_list = Panda.list() - if len(panda_list) > 0: - panda = Panda(panda_list[0]) - break - - # flash on DFU mode Panda - panda_dfu = PandaDFU.list() - if len(panda_dfu) > 0: - panda_dfu = PandaDFU(panda_dfu[0]) - panda_dfu.recover() - - print("waiting for board...") - time.sleep(1) - - if panda.bootstub or not panda.get_version().startswith(repo_version): - panda.flash() - - if panda.bootstub: - panda.recover() - - assert(not panda.bootstub) - version = str(panda.get_version()) - print("%s should be %s" % (version, repo_version)) - assert(version.startswith(repo_version)) - -if __name__ == "__main__": - ensure_st_up_to_date() diff --git a/panda/python/usb.py b/panda/python/usb.py new file mode 100644 index 000000000..179ebe53c --- /dev/null +++ b/panda/python/usb.py @@ -0,0 +1,23 @@ +from typing import List + +from .base import BaseHandle + +class PandaUsbHandle(BaseHandle): + def __init__(self, libusb_handle): + self._libusb_handle = libusb_handle + + def close(self): + self._libusb_handle.close() + + def controlWrite(self, request_type: int, request: int, value: int, index: int, data, timeout: int = 0): + return self._libusb_handle.controlWrite(request_type, request, value, index, data, timeout) + + def controlRead(self, request_type: int, request: int, value: int, index: int, length: int, timeout: int = 0): + return self._libusb_handle.controlRead(request_type, request, value, index, length, timeout) + + def bulkWrite(self, endpoint: int, data: List[int], timeout: int = 0) -> int: + return self._libusb_handle.bulkWrite(endpoint, data, timeout) # type: ignore + + def bulkRead(self, endpoint: int, length: int, timeout: int = 0) -> bytes: + return self._libusb_handle.bulkRead(endpoint, length, timeout) # type: ignore + diff --git a/release/build_release.sh b/release/build_release.sh index 80106eefb..60f81fce0 100755 --- a/release/build_release.sh +++ b/release/build_release.sh @@ -11,15 +11,19 @@ SOURCE_DIR="$(git rev-parse --show-toplevel)" if [ -f /TICI ]; then FILES_SRC="release/files_tici" - RELEASE_BRANCH=release3-staging - DASHCAM_BRANCH=dashcam3-staging else - exit 0 + echo "no release files set" + exit 1 fi +if [ -z "$RELEASE_BRANCH" ]; then + echo "RELEASE_BRANCH is not set" + exit 1 +fi + + # set git identity source $DIR/identity.sh -export GIT_SSH_COMMAND="ssh -i /data/gitkey" echo "[-] Setting up repo T=$SECONDS" rm -rf $BUILD_DIR @@ -27,7 +31,6 @@ mkdir -p $BUILD_DIR cd $BUILD_DIR git init git remote add origin git@github.com:commaai/openpilot.git -git fetch origin $RELEASE_BRANCH git checkout --orphan $RELEASE_BRANCH # do the files copy @@ -48,7 +51,6 @@ echo "#define COMMA_VERSION \"$VERSION-release\"" > common/version.h echo "[-] committing version $VERSION T=$SECONDS" git add -f . git commit -a -m "openpilot v$VERSION release" -git branch --set-upstream-to=origin/$RELEASE_BRANCH # Build panda firmware pushd panda/ @@ -105,10 +107,12 @@ RELEASE=1 selfdrive/test/test_onroad.py selfdrive/car/tests/test_car_interfaces.py rm -rf $TEST_FILES -if [ ! -z "$PUSH" ]; then - echo "[-] pushing T=$SECONDS" - git push -f origin $RELEASE_BRANCH +if [ ! -z "$RELEASE_BRANCH" ]; then + echo "[-] pushing release T=$SECONDS" + git push -f origin $RELEASE_BRANCH:$RELEASE_BRANCH +fi +if [ ! -z "$DASHCAM_BRANCH" ]; then # Create dashcam git rm selfdrive/car/*/carcontroller.py git commit -m "create dashcam release from release" diff --git a/release/check-submodules.sh b/release/check-submodules.sh index 182042e6b..bc85a43c5 100755 --- a/release/check-submodules.sh +++ b/release/check-submodules.sh @@ -1,7 +1,7 @@ #!/bin/bash while read hash submodule ref; do - git -C $submodule fetch --depth 100 origin master + git -C $submodule fetch --depth 200 origin master git -C $submodule branch -r --contains $hash | grep "origin/master" if [ "$?" -eq 0 ]; then echo "$submodule ok" diff --git a/release/files_common b/release/files_common index a294e1e5b..5b310d5c8 100644 --- a/release/files_common +++ b/release/files_common @@ -71,6 +71,7 @@ selfdrive/rtshield.py selfdrive/statsd.py system/logmessaged.py +system/micd.py system/swaglog.py system/version.py @@ -145,6 +146,7 @@ selfdrive/debug/vw_mqb_config.py common/SConscript common/version.h +common/prefix.h common/swaglog.h common/swaglog.cc common/statlog.h @@ -190,8 +192,6 @@ selfdrive/controls/lib/pid.py selfdrive/controls/lib/radar_helpers.py selfdrive/controls/lib/vehicle_model.py -selfdrive/controls/lib/cluster/* - selfdrive/controls/lib/lateral_mpc_lib/.gitignore selfdrive/controls/lib/longitudinal_mpc_lib/.gitignore selfdrive/controls/lib/lateral_mpc_lib/* @@ -216,6 +216,7 @@ system/hardware/tici/amplifier.py system/hardware/tici/updater system/hardware/tici/iwlist.py system/hardware/pc/__init__.py +system/hardware/pc/hardware.h system/hardware/pc/hardware.py selfdrive/locationd/__init__.py @@ -224,13 +225,9 @@ selfdrive/locationd/SConscript selfdrive/locationd/ubloxd.cc selfdrive/locationd/ublox_msg.cc selfdrive/locationd/ublox_msg.h -selfdrive/locationd/generated/ubx.cpp -selfdrive/locationd/generated/ubx.h -selfdrive/locationd/generated/gps.cpp -selfdrive/locationd/generated/gps.h +selfdrive/locationd/generated/* selfdrive/locationd/laikad.py -selfdrive/locationd/laikad_helpers.py selfdrive/locationd/locationd.h selfdrive/locationd/locationd.cc selfdrive/locationd/paramsd.py @@ -280,7 +277,6 @@ selfdrive/loggerd/deleter.py selfdrive/loggerd/xattr_cache.py selfdrive/sensord/SConscript -selfdrive/sensord/libdiag.h selfdrive/sensord/sensors_qcom2.cc selfdrive/sensord/sensors/*.cc selfdrive/sensord/sensors/*.h @@ -321,12 +317,17 @@ selfdrive/ui/qt/widgets/*.cc selfdrive/ui/qt/widgets/*.h selfdrive/ui/qt/maps/*.cc selfdrive/ui/qt/maps/*.h +selfdrive/ui/qt/setup/*.cc +selfdrive/ui/qt/setup/*.h + +selfdrive/ui/installer/*.cc +selfdrive/ui/installer/*.h +selfdrive/ui/installer/*.cc system/camerad/SConscript system/camerad/main.cc system/camerad/snapshot/* -system/camerad/include/* system/camerad/cameras/camera_common.h system/camerad/cameras/camera_common.cc system/camerad/cameras/sensor2_i2c.h @@ -348,20 +349,28 @@ selfdrive/manager/test/test_manager.py selfdrive/modeld/__init__.py selfdrive/modeld/SConscript selfdrive/modeld/modeld.cc +selfdrive/modeld/navmodeld.cc selfdrive/modeld/dmonitoringmodeld.cc selfdrive/modeld/constants.py selfdrive/modeld/modeld +selfdrive/modeld/navmodeld selfdrive/modeld/dmonitoringmodeld selfdrive/modeld/models/commonmodel.cc selfdrive/modeld/models/commonmodel.h + selfdrive/modeld/models/driving.cc selfdrive/modeld/models/driving.h +selfdrive/modeld/models/supercombo.onnx + selfdrive/modeld/models/dmonitoring.cc selfdrive/modeld/models/dmonitoring.h -selfdrive/modeld/models/supercombo.onnx selfdrive/modeld/models/dmonitoring_model_q.dlc +selfdrive/modeld/models/nav.cc +selfdrive/modeld/models/nav.h +selfdrive/modeld/models/navmodel_q.dlc + selfdrive/modeld/transforms/loadyuv.cc selfdrive/modeld/transforms/loadyuv.h selfdrive/modeld/transforms/loadyuv.cl @@ -374,7 +383,6 @@ selfdrive/modeld/thneed/thneed.h selfdrive/modeld/thneed/thneed_common.cc selfdrive/modeld/thneed/thneed_qcom2.cc selfdrive/modeld/thneed/serialize.cc -selfdrive/modeld/thneed/include/* selfdrive/modeld/runners/snpemodel.cc selfdrive/modeld/runners/snpemodel.h @@ -402,8 +410,11 @@ selfdrive/assets/offroad/* selfdrive/assets/sounds/* selfdrive/assets/training/* +third_party/.gitignore third_party/SConscript +third_party/cluster/* + third_party/linux/** third_party/opencl/** @@ -426,15 +437,14 @@ third_party/snpe/dsp** third_party/acados/x86_64/** third_party/acados/larch64/** third_party/acados/include/** +third_party/acados/acados_template/** +third_party/bootstrap/** third_party/qt5/larch64/bin/** scripts/update_now.sh scripts/stop_updater.sh -pyextra/.gitignore -pyextra/acados_template/** - rednose/.gitignore rednose/** laika/** @@ -484,6 +494,7 @@ cereal/visionipc/*.pxd panda/.gitignore panda/__init__.py +panda/SConscript panda/board/** panda/certs/** panda/crypto/** @@ -566,17 +577,13 @@ opendbc/tesla_can.dbc opendbc/tesla_radar.dbc opendbc/tesla_powertrain.dbc -tinygrad_repo/openpilot/compile.py tinygrad_repo/accel/opencl/* +tinygrad_repo/tinygrad/llops/ops_opencl.py +tinygrad_repo/openpilot/compile.py tinygrad_repo/extra/onnx.py tinygrad_repo/extra/thneed.py tinygrad_repo/extra/utils.py tinygrad_repo/tinygrad/llops/ops_gpu.py -tinygrad_repo/tinygrad/llops/ops_opencl.py -tinygrad_repo/tinygrad/helpers.py -tinygrad_repo/tinygrad/mlops.py -tinygrad_repo/tinygrad/ops.py -tinygrad_repo/tinygrad/shapetracker.py -tinygrad_repo/tinygrad/tensor.py -tinygrad_repo/tinygrad/nn/__init__.py -tinygrad_repo/tinygrad/nn/optim.py +tinygrad_repo/tinygrad/shape/* +tinygrad_repo/tinygrad/nn/* +tinygrad_repo/tinygrad/*.py diff --git a/selfdrive/assets/assets.qrc b/selfdrive/assets/assets.qrc index 39be41aa6..79a1a1e27 100644 --- a/selfdrive/assets/assets.qrc +++ b/selfdrive/assets/assets.qrc @@ -1,5 +1,6 @@ + ../../third_party/bootstrap/bootstrap-icons.svg img_continue_triangle.svg img_circled_check.svg img_circled_slash.svg diff --git a/selfdrive/assets/img_couch.svg b/selfdrive/assets/img_couch.svg index 5b3c04831..2e8601280 100644 --- a/selfdrive/assets/img_couch.svg +++ b/selfdrive/assets/img_couch.svg @@ -1,3 +1,4 @@ - - + + + diff --git a/selfdrive/assets/img_driver_face.png b/selfdrive/assets/img_driver_face.png index ddde478cd..03765a037 100644 Binary files a/selfdrive/assets/img_driver_face.png and b/selfdrive/assets/img_driver_face.png differ diff --git a/selfdrive/assets/navigation/default_marker.svg b/selfdrive/assets/navigation/default_marker.svg new file mode 100644 index 000000000..43d5290a9 --- /dev/null +++ b/selfdrive/assets/navigation/default_marker.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/selfdrive/athena/athenad.py b/selfdrive/athena/athenad.py index 5b351ca0f..d1cc4cea8 100755 --- a/selfdrive/athena/athenad.py +++ b/selfdrive/athena/athenad.py @@ -1,4 +1,6 @@ #!/usr/bin/env python3 +from __future__ import annotations + import base64 import bz2 import hashlib @@ -14,14 +16,15 @@ import sys import tempfile import threading import time -from collections import namedtuple +from dataclasses import asdict, dataclass, replace from datetime import datetime from functools import partial -from typing import Any, Dict +from queue import Queue +from typing import BinaryIO, Callable, Dict, List, Optional, Set, Union, cast import requests from jsonrpc import JSONRPCResponseManager, dispatcher -from websocket import (ABNF, WebSocketException, WebSocketTimeoutException, +from websocket import (ABNF, WebSocket, WebSocketException, WebSocketTimeoutException, create_connection) import cereal.messaging as messaging @@ -54,19 +57,54 @@ WS_FRAME_SIZE = 4096 NetworkType = log.DeviceState.NetworkType +UploadFileDict = Dict[str, Union[str, int, float, bool]] +UploadItemDict = Dict[str, Union[str, bool, int, float, Dict[str, str]]] + +UploadFilesToUrlResponse = Dict[str, Union[int, List[UploadItemDict], List[str]]] + + +@dataclass +class UploadFile: + fn: str + url: str + headers: Dict[str, str] + allow_cellular: bool + + @classmethod + def from_dict(cls, d: Dict) -> UploadFile: + return cls(d.get("fn", ""), d.get("url", ""), d.get("headers", {}), d.get("allow_cellular", False)) + + +@dataclass +class UploadItem: + path: str + url: str + headers: Dict[str, str] + created_at: int + id: Optional[str] + retry_count: int = 0 + current: bool = False + progress: float = 0 + allow_cellular: bool = False + + @classmethod + def from_dict(cls, d: Dict) -> UploadItem: + return cls(d["path"], d["url"], d["headers"], d["created_at"], d["id"], d["retry_count"], d["current"], + d["progress"], d["allow_cellular"]) + + dispatcher["echo"] = lambda s: s -recv_queue: Any = queue.Queue() -send_queue: Any = queue.Queue() -upload_queue: Any = queue.Queue() -low_priority_send_queue: Any = queue.Queue() -log_recv_queue: Any = queue.Queue() -cancelled_uploads: Any = set() -UploadItem = namedtuple('UploadItem', ['path', 'url', 'headers', 'created_at', 'id', 'retry_count', 'current', 'progress', 'allow_cellular'], defaults=(0, False, 0, False)) +recv_queue: Queue[str] = queue.Queue() +send_queue: Queue[str] = queue.Queue() +upload_queue: Queue[UploadItem] = queue.Queue() +low_priority_send_queue: Queue[str] = queue.Queue() +log_recv_queue: Queue[str] = queue.Queue() +cancelled_uploads: Set[str] = set() -cur_upload_items: Dict[int, Any] = {} +cur_upload_items: Dict[int, Optional[UploadItem]] = {} -def strip_bz2_extension(fn): +def strip_bz2_extension(fn: str) -> str: if fn.endswith('.bz2'): return fn[:-4] return fn @@ -76,29 +114,30 @@ class AbortTransferException(Exception): pass -class UploadQueueCache(): +class UploadQueueCache: params = Params() @staticmethod - def initialize(upload_queue): + def initialize(upload_queue: Queue[UploadItem]) -> None: try: upload_queue_json = UploadQueueCache.params.get("AthenadUploadQueue") if upload_queue_json is not None: for item in json.loads(upload_queue_json): - upload_queue.put(UploadItem(**item)) + upload_queue.put(UploadItem.from_dict(item)) except Exception: cloudlog.exception("athena.UploadQueueCache.initialize.exception") @staticmethod - def cache(upload_queue): + def cache(upload_queue: Queue[UploadItem]) -> None: try: - items = [i._asdict() for i in upload_queue.queue if i.id not in cancelled_uploads] + queue: List[Optional[UploadItem]] = list(upload_queue.queue) + items = [asdict(i) for i in queue if i is not None and (i.id not in cancelled_uploads)] UploadQueueCache.params.put("AthenadUploadQueue", json.dumps(items)) except Exception: cloudlog.exception("athena.UploadQueueCache.cache.exception") -def handle_long_poll(ws): +def handle_long_poll(ws: WebSocket) -> None: end_event = threading.Event() threads = [ @@ -126,7 +165,7 @@ def handle_long_poll(ws): thread.join() -def jsonrpc_handler(end_event): +def jsonrpc_handler(end_event: threading.Event) -> None: dispatcher["startLocalProxy"] = partial(startLocalProxy, end_event) while not end_event.is_set(): try: @@ -147,11 +186,12 @@ def jsonrpc_handler(end_event): def retry_upload(tid: int, end_event: threading.Event, increase_count: bool = True) -> None: - if cur_upload_items[tid].retry_count < MAX_RETRY_COUNT: - item = cur_upload_items[tid] + item = cur_upload_items[tid] + if item is not None and item.retry_count < MAX_RETRY_COUNT: new_retry_count = item.retry_count + 1 if increase_count else item.retry_count - item = item._replace( + item = replace( + item, retry_count=new_retry_count, progress=0, current=False @@ -175,44 +215,44 @@ def upload_handler(end_event: threading.Event) -> None: cur_upload_items[tid] = None try: - cur_upload_items[tid] = upload_queue.get(timeout=1)._replace(current=True) + cur_upload_items[tid] = item = replace(upload_queue.get(timeout=1), current=True) - if cur_upload_items[tid].id in cancelled_uploads: - cancelled_uploads.remove(cur_upload_items[tid].id) + if item.id in cancelled_uploads: + cancelled_uploads.remove(item.id) continue # Remove item if too old - age = datetime.now() - datetime.fromtimestamp(cur_upload_items[tid].created_at / 1000) + age = datetime.now() - datetime.fromtimestamp(item.created_at / 1000) if age.total_seconds() > MAX_AGE: - cloudlog.event("athena.upload_handler.expired", item=cur_upload_items[tid], error=True) + cloudlog.event("athena.upload_handler.expired", item=item, error=True) continue # Check if uploading over metered connection is allowed sm.update(0) metered = sm['deviceState'].networkMetered network_type = sm['deviceState'].networkType.raw - if metered and (not cur_upload_items[tid].allow_cellular): + if metered and (not item.allow_cellular): retry_upload(tid, end_event, False) continue try: - def cb(sz, cur): + def cb(sz: int, cur: int) -> None: # Abort transfer if connection changed to metered after starting upload sm.update(0) metered = sm['deviceState'].networkMetered - if metered and (not cur_upload_items[tid].allow_cellular): + if metered and (not item.allow_cellular): raise AbortTransferException - cur_upload_items[tid] = cur_upload_items[tid]._replace(progress=cur / sz if sz else 1) + cur_upload_items[tid] = replace(item, progress=cur / sz if sz else 1) - fn = cur_upload_items[tid].path + fn = item.path try: sz = os.path.getsize(fn) except OSError: sz = -1 - cloudlog.event("athena.upload_handler.upload_start", fn=fn, sz=sz, network_type=network_type, metered=metered, retry_count=cur_upload_items[tid].retry_count) - response = _do_upload(cur_upload_items[tid], cb) + cloudlog.event("athena.upload_handler.upload_start", fn=fn, sz=sz, network_type=network_type, metered=metered, retry_count=item.retry_count) + response = _do_upload(item, cb) if response.status_code not in (200, 201, 401, 403, 412): cloudlog.event("athena.upload_handler.retry", status_code=response.status_code, fn=fn, sz=sz, network_type=network_type, metered=metered) @@ -234,7 +274,7 @@ def upload_handler(end_event: threading.Event) -> None: cloudlog.exception("athena.upload_handler.exception") -def _do_upload(upload_item, callback=None): +def _do_upload(upload_item: UploadItem, callback: Optional[Callable] = None) -> requests.Response: path = upload_item.path compress = False @@ -244,27 +284,25 @@ def _do_upload(upload_item, callback=None): compress = True with open(path, "rb") as f: + data: BinaryIO if compress: cloudlog.event("athena.upload_handler.compress", fn=path, fn_orig=upload_item.path) - data = bz2.compress(f.read()) - size = len(data) - data = io.BytesIO(data) + compressed = bz2.compress(f.read()) + size = len(compressed) + data = io.BytesIO(compressed) else: size = os.fstat(f.fileno()).st_size data = f - if callback: - data = CallbackReader(data, callback, size) - return requests.put(upload_item.url, - data=data, + data=CallbackReader(data, callback, size) if callback else data, headers={**upload_item.headers, 'Content-Length': str(size)}, timeout=30) # security: user should be able to request any message from their car @dispatcher.add_method -def getMessage(service=None, timeout=1000): +def getMessage(service: str, timeout: int = 1000) -> Dict: if service is None or service not in service_list: raise Exception("invalid service") @@ -274,7 +312,8 @@ def getMessage(service=None, timeout=1000): if ret is None: raise TimeoutError - return ret.to_dict() + # this is because capnp._DynamicStructReader doesn't have typing information + return cast(Dict, ret.to_dict()) @dispatcher.add_method @@ -288,7 +327,7 @@ def getVersion() -> Dict[str, str]: @dispatcher.add_method -def setNavDestination(latitude=0, longitude=0, place_name=None, place_details=None): +def setNavDestination(latitude: int = 0, longitude: int = 0, place_name: Optional[str] = None, place_details: Optional[str] = None) -> Dict[str, int]: destination = { "latitude": latitude, "longitude": longitude, @@ -300,8 +339,8 @@ def setNavDestination(latitude=0, longitude=0, place_name=None, place_details=No return {"success": 1} -def scan_dir(path, prefix): - files = list() +def scan_dir(path: str, prefix: str) -> List[str]: + files = [] # only walk directories that match the prefix # (glob and friends traverse entire dir tree) with os.scandir(path) as i: @@ -320,18 +359,18 @@ def scan_dir(path, prefix): return files @dispatcher.add_method -def listDataDirectory(prefix=''): +def listDataDirectory(prefix='') -> List[str]: return scan_dir(ROOT, prefix) @dispatcher.add_method -def reboot(): +def reboot() -> Dict[str, int]: sock = messaging.sub_sock("deviceState", timeout=1000) ret = messaging.recv_one(sock) if ret is None or ret.deviceState.started: raise Exception("Reboot unavailable") - def do_reboot(): + def do_reboot() -> None: time.sleep(2) HARDWARE.reboot() @@ -341,50 +380,53 @@ def reboot(): @dispatcher.add_method -def uploadFileToUrl(fn, url, headers): - return uploadFilesToUrls([{ +def uploadFileToUrl(fn: str, url: str, headers: Dict[str, str]) -> UploadFilesToUrlResponse: + # this is because mypy doesn't understand that the decorator doesn't change the return type + response: UploadFilesToUrlResponse = uploadFilesToUrls([{ "fn": fn, "url": url, "headers": headers, }]) + return response @dispatcher.add_method -def uploadFilesToUrls(files_data): - items = [] - failed = [] - for file in files_data: - fn = file.get('fn', '') - if len(fn) == 0 or fn[0] == '/' or '..' in fn or 'url' not in file: - failed.append(fn) +def uploadFilesToUrls(files_data: List[UploadFileDict]) -> UploadFilesToUrlResponse: + files = map(UploadFile.from_dict, files_data) + + items: List[UploadItemDict] = [] + failed: List[str] = [] + for file in files: + if len(file.fn) == 0 or file.fn[0] == '/' or '..' in file.fn or len(file.url) == 0: + failed.append(file.fn) continue - path = os.path.join(ROOT, fn) + path = os.path.join(ROOT, file.fn) if not os.path.exists(path) and not os.path.exists(strip_bz2_extension(path)): - failed.append(fn) + failed.append(file.fn) continue # Skip item if already in queue - url = file['url'].split('?')[0] + url = file.url.split('?')[0] if any(url == item['url'].split('?')[0] for item in listUploadQueue()): continue item = UploadItem( path=path, - url=file['url'], - headers=file.get('headers', {}), + url=file.url, + headers=file.headers, created_at=int(time.time() * 1000), id=None, - allow_cellular=file.get('allow_cellular', False), + allow_cellular=file.allow_cellular, ) upload_id = hashlib.sha1(str(item).encode()).hexdigest() - item = item._replace(id=upload_id) + item = replace(item, id=upload_id) upload_queue.put_nowait(item) - items.append(item._asdict()) + items.append(asdict(item)) UploadQueueCache.cache(upload_queue) - resp = {"enqueued": len(items), "items": items} + resp: UploadFilesToUrlResponse = {"enqueued": len(items), "items": items} if failed: resp["failed"] = failed @@ -392,32 +434,32 @@ def uploadFilesToUrls(files_data): @dispatcher.add_method -def listUploadQueue(): +def listUploadQueue() -> List[UploadItemDict]: items = list(upload_queue.queue) + list(cur_upload_items.values()) - return [i._asdict() for i in items if (i is not None) and (i.id not in cancelled_uploads)] + return [asdict(i) for i in items if (i is not None) and (i.id not in cancelled_uploads)] @dispatcher.add_method -def cancelUpload(upload_id): +def cancelUpload(upload_id: Union[str, List[str]]) -> Dict[str, Union[int, str]]: if not isinstance(upload_id, list): upload_id = [upload_id] uploading_ids = {item.id for item in list(upload_queue.queue)} cancelled_ids = uploading_ids.intersection(upload_id) if len(cancelled_ids) == 0: - return 404 + return {"success": 0, "error": "not found"} cancelled_uploads.update(cancelled_ids) return {"success": 1} @dispatcher.add_method -def primeActivated(activated): +def primeActivated(activated: bool) -> Dict[str, int]: return {"success": 1} @dispatcher.add_method -def setBandwithLimit(upload_speed_kbps, download_speed_kbps): +def setBandwithLimit(upload_speed_kbps: int, download_speed_kbps: int) -> Dict[str, Union[int, str]]: if not AGNOS: return {"success": 0, "error": "only supported on AGNOS"} @@ -428,7 +470,7 @@ def setBandwithLimit(upload_speed_kbps, download_speed_kbps): return {"success": 0, "error": "failed to set limit", "stdout": e.stdout, "stderr": e.stderr} -def startLocalProxy(global_end_event, remote_ws_uri, local_port): +def startLocalProxy(global_end_event: threading.Event, remote_ws_uri: str, local_port: int) -> Dict[str, int]: try: if local_port not in LOCAL_PORT_WHITELIST: raise Exception("Requested local port not whitelisted") @@ -462,7 +504,7 @@ def startLocalProxy(global_end_event, remote_ws_uri, local_port): @dispatcher.add_method -def getPublicKey(): +def getPublicKey() -> Optional[str]: if not os.path.isfile(PERSIST + '/comma/id_rsa.pub'): return None @@ -471,7 +513,7 @@ def getPublicKey(): @dispatcher.add_method -def getSshAuthorizedKeys(): +def getSshAuthorizedKeys() -> str: return Params().get("GithubSshKeys", encoding='utf8') or '' @@ -486,7 +528,7 @@ def getNetworkType(): @dispatcher.add_method -def getNetworkMetered(): +def getNetworkMetered() -> bool: network_type = HARDWARE.get_network_type() return HARDWARE.get_network_metered(network_type) @@ -497,7 +539,7 @@ def getNetworks(): @dispatcher.add_method -def takeSnapshot(): +def takeSnapshot() -> Optional[Union[str, Dict[str, str]]]: from system.camerad.snapshot.snapshot import jpeg_write, snapshot ret = snapshot() if ret is not None: @@ -514,16 +556,19 @@ def takeSnapshot(): raise Exception("not available while camerad is started") -def get_logs_to_send_sorted(): +def get_logs_to_send_sorted() -> List[str]: # TODO: scan once then use inotify to detect file creation/deletion curr_time = int(time.time()) logs = [] for log_entry in os.listdir(SWAGLOG_DIR): log_path = os.path.join(SWAGLOG_DIR, log_entry) + time_sent = 0 try: - time_sent = int.from_bytes(getxattr(log_path, LOG_ATTR_NAME), sys.byteorder) + value = getxattr(log_path, LOG_ATTR_NAME) + if value is not None: + time_sent = int.from_bytes(value, sys.byteorder) except (ValueError, TypeError): - time_sent = 0 + pass # assume send failed and we lost the response if sent more than one hour ago if not time_sent or curr_time - time_sent > 3600: logs.append(log_entry) @@ -531,7 +576,7 @@ def get_logs_to_send_sorted(): return sorted(logs)[:-1] -def log_handler(end_event): +def log_handler(end_event: threading.Event) -> None: if PC: return @@ -593,7 +638,7 @@ def log_handler(end_event): cloudlog.exception("athena.log_handler.exception") -def stat_handler(end_event): +def stat_handler(end_event: threading.Event) -> None: while not end_event.is_set(): last_scan = 0 curr_scan = sec_since_boot() @@ -619,7 +664,7 @@ def stat_handler(end_event): time.sleep(0.1) -def ws_proxy_recv(ws, local_sock, ssock, end_event, global_end_event): +def ws_proxy_recv(ws: WebSocket, local_sock: socket.socket, ssock: socket.socket, end_event: threading.Event, global_end_event: threading.Event) -> None: while not (end_event.is_set() or global_end_event.is_set()): try: data = ws.recv() @@ -638,7 +683,7 @@ def ws_proxy_recv(ws, local_sock, ssock, end_event, global_end_event): end_event.set() -def ws_proxy_send(ws, local_sock, signal_sock, end_event): +def ws_proxy_send(ws: WebSocket, local_sock: socket.socket, signal_sock: socket.socket, end_event: threading.Event) -> None: while not end_event.is_set(): try: r, _, _ = select.select((local_sock, signal_sock), (), ()) @@ -663,7 +708,7 @@ def ws_proxy_send(ws, local_sock, signal_sock, end_event): cloudlog.debug("athena.ws_proxy_send done closing sockets") -def ws_recv(ws, end_event): +def ws_recv(ws: WebSocket, end_event: threading.Event) -> None: last_ping = int(sec_since_boot() * 1e9) while not end_event.is_set(): try: @@ -685,7 +730,7 @@ def ws_recv(ws, end_event): end_event.set() -def ws_send(ws, end_event): +def ws_send(ws: WebSocket, end_event: threading.Event) -> None: while not end_event.is_set(): try: try: @@ -704,7 +749,7 @@ def ws_send(ws, end_event): end_event.set() -def backoff(retries): +def backoff(retries: int) -> int: return random.randrange(0, min(128, int(2 ** retries))) diff --git a/selfdrive/boardd/boardd.cc b/selfdrive/boardd/boardd.cc index 549690225..5d885c2c7 100644 --- a/selfdrive/boardd/boardd.cc +++ b/selfdrive/boardd/boardd.cc @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -48,8 +49,8 @@ #define MAX_IR_POWER 0.5f #define MIN_IR_POWER 0.0f -#define CUTOFF_IL 200 -#define SATURATE_IL 1600 +#define CUTOFF_IL 400 +#define SATURATE_IL 1000 #define NIBBLE_TO_HEX(n) ((n) < 10 ? (n) + '0' : ((n) - 10) + 'a') using namespace std::chrono_literals; @@ -105,6 +106,8 @@ void sync_time(Panda *panda, SyncTimeDir dir) { bool safety_setter_thread(std::vector pandas) { LOGD("Starting safety setter thread"); + Params p; + // there should be at least one panda connected if (pandas.size() == 0) { return false; @@ -116,29 +119,26 @@ bool safety_setter_thread(std::vector pandas) { pandas[i]->set_safety_model(cereal::CarParams::SafetyModel::ELM327, safety_param); } - Params p = Params(); - - // wait for VIN to be read + // wait for FW query at OBD port to finish while (true) { if (do_exit || !check_all_connected(pandas) || !ignition) { return false; } - std::string value_vin = p.get("CarVin"); - if (value_vin.size() > 0) { - // sanity check VIN format - assert(value_vin.size() == 17); - LOGW("got CarVin %s", value_vin.c_str()); + if (p.getBool("FirmwareObdQueryDone")) { + LOGW("finished FW query at OBD port"); break; } util::sleep_for(20); } - // set to ELM327 for ECU knockouts + // set to ELM327 to finish fingerprinting and for potential ECU knockouts for (Panda *panda : pandas) { panda->set_safety_model(cereal::CarParams::SafetyModel::ELM327, 1U); } + p.putBool("ObdMultiplexingDisabled", true); + std::string params; LOGW("waiting for params to set safety model"); while (true) { @@ -225,9 +225,9 @@ void can_send_thread(std::vector pandas, bool fake_send) { //Dont send if older than 1 second if ((nanos_since_boot() - event.getLogMonoTime() < 1e9) && !fake_send) { for (const auto& panda : pandas) { - LOGT("sending sendcan to panda: %s", (panda->hw_serial).c_str()); + LOGT("sending sendcan to panda: %s", (panda->hw_serial()).c_str()); panda->can_send(event.getSendcan()); - LOGT("sendcan sent to panda: %s", (panda->hw_serial).c_str()); + LOGT("sendcan sent to panda: %s", (panda->hw_serial()).c_str()); } } } @@ -528,9 +528,6 @@ void peripheral_control_thread(Panda *panda, bool no_fan_control) { cnt++; sm.update(1000); // TODO: what happens if EINTR is sent while in sm.update? - // Other pandas don't have fan/IR to control - if (panda->hw_type != cereal::PandaState::PandaType::UNO && panda->hw_type != cereal::PandaState::PandaType::DOS) continue; - if (sm.updated("deviceState") && !no_fan_control) { // Fan speed uint16_t fan_speed = sm["deviceState"].getDeviceState().getFanSpeedPercentDesired(); @@ -543,9 +540,8 @@ void peripheral_control_thread(Panda *panda, bool no_fan_control) { if (sm.updated("driverCameraState")) { auto event = sm["driverCameraState"]; int cur_integ_lines = event.getDriverCameraState().getIntegLines(); - float cur_gain = event.getDriverCameraState().getGain(); - cur_integ_lines = integ_lines_filter.update(cur_integ_lines * cur_gain); + cur_integ_lines = integ_lines_filter.update(cur_integ_lines); last_front_frame_t = event.getLogMonoTime(); if (cur_integ_lines <= CUTOFF_IL) { diff --git a/selfdrive/boardd/panda.cc b/selfdrive/boardd/panda.cc index deccee3e7..647a0d9c7 100644 --- a/selfdrive/boardd/panda.cc +++ b/selfdrive/boardd/panda.cc @@ -6,16 +6,17 @@ #include #include "cereal/messaging/messaging.h" -#include "panda/board/dlc_to_len.h" #include "common/swaglog.h" #include "common/util.h" Panda::Panda(std::string serial, uint32_t bus_offset) : bus_offset(bus_offset) { - // TODO: support SPI here one day... - if (serial.find("spi") != std::string::npos) { - handle = std::make_unique(serial); - } else { + // try USB first, then SPI + try { handle = std::make_unique(serial); + } catch (std::exception &e) { +#ifndef __APPLE__ + handle = std::make_unique(serial); +#endif } hw_type = get_hw_type(); @@ -24,7 +25,10 @@ Panda::Panda(std::string serial, uint32_t bus_offset) : bus_offset(bus_offset) { (hw_type != cereal::PandaState::PandaType::GREY_PANDA)); has_rtc = (hw_type == cereal::PandaState::PandaType::UNO) || - (hw_type == cereal::PandaState::PandaType::DOS); + (hw_type == cereal::PandaState::PandaType::DOS) || + (hw_type == cereal::PandaState::PandaType::TRES); + + can_reset_communications(); return; } @@ -37,8 +41,22 @@ bool Panda::comms_healthy() { return handle->comms_healthy; } +std::string Panda::hw_serial() { + return handle->hw_serial; +} + std::vector Panda::list() { - return PandaUsbHandle::list(); + std::vector serials = PandaUsbHandle::list(); + +#ifndef __APPLE__ + for (auto s : PandaSpiHandle::list()) { + if (std::find(serials.begin(), serials.end(), s) == serials.end()) { + serials.push_back(s); + } + } +#endif + + return serials; } void Panda::set_safety_model(cereal::CarParams::SafetyModel safety_model, uint16_t safety_param) { @@ -169,17 +187,6 @@ static uint8_t len_to_dlc(uint8_t len) { } } -static void write_packet(uint8_t *dest, int *write_pos, const uint8_t *src, size_t size) { - for (int i = 0, &pos = *write_pos; i < size; ++i, ++pos) { - // Insert counter every 64 bytes (first byte of 64 bytes USB packet) - if (pos % USBPACKET_MAX_SIZE == 0) { - dest[pos] = pos / USBPACKET_MAX_SIZE; - pos++; - } - dest[pos] = src[i]; - } -} - void Panda::pack_can_buffer(const capnp::List::Reader &can_data_list, std::function write_func) { int32_t pos = 0; @@ -201,9 +208,17 @@ void Panda::pack_can_buffer(const capnp::List::Reader &can_data header.extended = (cmsg.getAddress() >= 0x800) ? 1 : 0; header.data_len_code = data_len_code; header.bus = bus - bus_offset; + header.checksum = 0; + + memcpy(&send_buf[pos], (uint8_t *)&header, sizeof(can_header)); + memcpy(&send_buf[pos + sizeof(can_header)], (uint8_t *)can_data.begin(), can_data.size()); + uint32_t msg_size = sizeof(can_header) + can_data.size(); + + // set checksum + ((can_header *) &send_buf[pos])->checksum = calculate_checksum(&send_buf[pos], msg_size); + + pos += msg_size; - write_packet(send_buf, &pos, (uint8_t *)&header, sizeof(can_header)); - write_packet(send_buf, &pos, (uint8_t *)can_data.begin(), can_data.size()); if (pos >= USB_TX_SOFT_LIMIT) { write_func(send_buf, pos); pos = 0; @@ -221,46 +236,71 @@ void Panda::can_send(capnp::List::Reader can_data_list) { } bool Panda::can_receive(std::vector& out_vec) { - uint8_t data[RECV_SIZE]; - int recv = handle->bulk_read(0x81, (uint8_t*)data, RECV_SIZE); + // Check if enough space left in buffer to store RECV_SIZE data + assert(receive_buffer_size + RECV_SIZE <= sizeof(receive_buffer)); + + int recv = handle->bulk_read(0x81, &receive_buffer[receive_buffer_size], RECV_SIZE); if (!comms_healthy()) { return false; } if (recv == RECV_SIZE) { LOGW("Panda receive buffer full"); } + receive_buffer_size += recv; - return (recv <= 0) ? true : unpack_can_buffer(data, recv, out_vec); + return (recv <= 0) ? true : unpack_can_buffer(receive_buffer, receive_buffer_size, out_vec); } -bool Panda::unpack_can_buffer(uint8_t *data, int size, std::vector &out_vec) { - recv_buf.clear(); - for (int i = 0; i < size; i += USBPACKET_MAX_SIZE) { - if (data[i] != i / USBPACKET_MAX_SIZE) { - LOGE("CAN: MALFORMED USB RECV PACKET"); - handle->comms_healthy = false; - return false; - } - int chunk_len = std::min(USBPACKET_MAX_SIZE, (size - i)); - recv_buf.insert(recv_buf.end(), &data[i + 1], &data[i + chunk_len]); - } +void Panda::can_reset_communications() { + handle->control_write(0xc0, 0, 0); +} +bool Panda::unpack_can_buffer(uint8_t *data, uint32_t &size, std::vector &out_vec) { int pos = 0; - while (pos < recv_buf.size()) { + + while (pos <= size - sizeof(can_header)) { can_header header; - memcpy(&header, &recv_buf[pos], CANPACKET_HEAD_SIZE); + memcpy(&header, &data[pos], sizeof(can_header)); + + const uint8_t data_len = dlc_to_len[header.data_len_code]; + if (pos + sizeof(can_header) + data_len > size) { + // we don't have all the data for this message yet + break; + } can_frame &canData = out_vec.emplace_back(); canData.busTime = 0; canData.address = header.addr; canData.src = header.bus + bus_offset; - if (header.rejected) { canData.src += CANPACKET_REJECTED; } - if (header.returned) { canData.src += CANPACKET_RETURNED; } + if (header.rejected) { + canData.src += CAN_REJECTED_BUS_OFFSET; + } + if (header.returned) { + canData.src += CAN_RETURNED_BUS_OFFSET; + } - const uint8_t data_len = dlc_to_len[header.data_len_code]; - canData.dat.assign((char *)&recv_buf[pos + CANPACKET_HEAD_SIZE], data_len); + if (calculate_checksum(&data[pos], sizeof(can_header) + data_len) != 0) { + LOGE("Panda CAN checksum failed"); + size = 0; + return false; + } - pos += CANPACKET_HEAD_SIZE + data_len; + canData.dat.assign((char *)&data[pos + sizeof(can_header)], data_len); + + pos += sizeof(can_header) + data_len; } + + // move the overflowing data to the beginning of the buffer for the next round + memmove(data, &data[pos], size - pos); + size -= pos; + return true; } + +uint8_t Panda::calculate_checksum(uint8_t *data, uint32_t len) { + uint8_t checksum = 0U; + for (uint32_t i = 0U; i < len; i++) { + checksum ^= data[i]; + } + return checksum; +} diff --git a/selfdrive/boardd/panda.h b/selfdrive/boardd/panda.h index 5b3cbb9a3..69df2e2b6 100644 --- a/selfdrive/boardd/panda.h +++ b/selfdrive/boardd/panda.h @@ -11,20 +11,16 @@ #include "cereal/gen/cpp/car.capnp.h" #include "cereal/gen/cpp/log.capnp.h" #include "panda/board/health.h" +#include "panda/board/can_definitions.h" #include "selfdrive/boardd/panda_comms.h" - -#define PANDA_CAN_CNT 3 -#define PANDA_BUS_CNT 4 - #define USB_TX_SOFT_LIMIT (0x100U) #define USBPACKET_MAX_SIZE (0x40) #define RECV_SIZE (0x4000U) -#define CANPACKET_HEAD_SIZE 5U -#define CANPACKET_MAX_SIZE 72U -#define CANPACKET_REJECTED (0xC0U) -#define CANPACKET_RETURNED (0x80U) + +#define CAN_REJECTED_BUS_OFFSET 0xC0U +#define CAN_RETURNED_BUS_OFFSET 0x80U struct __attribute__((packed)) can_header { uint8_t reserved : 1; @@ -34,6 +30,7 @@ struct __attribute__((packed)) can_header { uint8_t returned : 1; uint8_t extended : 1; uint32_t addr : 29; + uint8_t checksum : 8; }; struct can_frame { @@ -47,18 +44,17 @@ struct can_frame { class Panda { private: std::unique_ptr handle; - std::vector recv_buf; public: Panda(std::string serial="", uint32_t bus_offset=0); - std::string hw_serial; cereal::PandaState::PandaType hw_type = cereal::PandaState::PandaType::UNKNOWN; bool has_rtc = false; const uint32_t bus_offset; bool connected(); bool comms_healthy(); + std::string hw_serial(); // Static functions static std::vector list(); @@ -85,11 +81,16 @@ public: void set_canfd_non_iso(uint16_t bus, bool non_iso); void can_send(capnp::List::Reader can_data_list); bool can_receive(std::vector& out_vec); + void can_reset_communications(); protected: // for unit tests + uint8_t receive_buffer[RECV_SIZE + sizeof(can_header) + 64]; + uint32_t receive_buffer_size = 0; + Panda(uint32_t bus_offset) : bus_offset(bus_offset) {} void pack_can_buffer(const capnp::List::Reader &can_data_list, std::function write_func); - bool unpack_can_buffer(uint8_t *data, int size, std::vector &out_vec); + bool unpack_can_buffer(uint8_t *data, uint32_t &size, std::vector &out_vec); + uint8_t calculate_checksum(uint8_t *data, uint32_t len); }; diff --git a/selfdrive/boardd/panda_comms.cc b/selfdrive/boardd/panda_comms.cc index e73cb6931..120d2f67d 100644 --- a/selfdrive/boardd/panda_comms.cc +++ b/selfdrive/boardd/panda_comms.cc @@ -44,7 +44,7 @@ PandaUsbHandle::PandaUsbHandle(std::string serial) : PandaCommsHandle(serial) { ret = libusb_get_string_descriptor_ascii(dev_handle, desc.iSerialNumber, desc_serial, std::size(desc_serial)); if (ret < 0) { goto fail; } - auto hw_serial = std::string((char *)desc_serial, ret); + hw_serial = std::string((char *)desc_serial, ret); if (serial.empty() || serial == hw_serial) { break; } diff --git a/selfdrive/boardd/panda_comms.h b/selfdrive/boardd/panda_comms.h index f42eadc5b..506b96b37 100644 --- a/selfdrive/boardd/panda_comms.h +++ b/selfdrive/boardd/panda_comms.h @@ -5,7 +5,9 @@ #include #include +#ifndef __APPLE__ #include +#endif #include @@ -13,8 +15,6 @@ #define TIMEOUT 0 #define SPI_BUF_SIZE 1024 -const bool PANDA_NO_RETRY = getenv("PANDA_NO_RETRY"); - // comms base class class PandaCommsHandle { @@ -23,6 +23,7 @@ public: virtual ~PandaCommsHandle() {}; virtual void cleanup() = 0; + std::string hw_serial; std::atomic connected = true; std::atomic comms_healthy = true; static std::vector list(); @@ -32,9 +33,6 @@ public: virtual int control_read(uint8_t request, uint16_t param1, uint16_t param2, unsigned char *data, uint16_t length, unsigned int timeout=TIMEOUT) = 0; virtual int bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT) = 0; virtual int bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT) = 0; - -protected: - std::recursive_mutex hw_lock; }; class PandaUsbHandle : public PandaCommsHandle { @@ -52,10 +50,11 @@ public: private: libusb_context *ctx = NULL; libusb_device_handle *dev_handle = NULL; - std::vector recv_buf; + std::recursive_mutex hw_lock; void handle_usb_issue(int err, const char func[]); }; +#ifndef __APPLE__ class PandaSpiHandle : public PandaCommsHandle { public: PandaSpiHandle(std::string serial); @@ -72,9 +71,11 @@ private: int spi_fd = -1; uint8_t tx_buf[SPI_BUF_SIZE]; uint8_t rx_buf[SPI_BUF_SIZE]; + inline static std::recursive_mutex hw_lock; int wait_for_ack(spi_ioc_transfer &transfer, uint8_t ack); int bulk_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t rx_len); int spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len); int spi_transfer_retry(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len); }; +#endif diff --git a/selfdrive/boardd/pandad.py b/selfdrive/boardd/pandad.py index 971756002..f61d9ee1a 100755 --- a/selfdrive/boardd/pandad.py +++ b/selfdrive/boardd/pandad.py @@ -7,7 +7,7 @@ import subprocess from typing import List, NoReturn from functools import cmp_to_key -from panda import DEFAULT_FW_FN, DEFAULT_H7_FW_FN, MCU_TYPE_H7, Panda, PandaDFU +from panda import Panda, PandaDFU from common.basedir import BASEDIR from common.params import Params from system.hardware import HARDWARE @@ -15,10 +15,8 @@ from system.swaglog import cloudlog def get_expected_signature(panda: Panda) -> bytes: - fn = DEFAULT_H7_FW_FN if (panda.get_mcu_type() == MCU_TYPE_H7) else DEFAULT_FW_FN - try: - return Panda.get_signature_from_firmware(fn) + return Panda.get_signature_from_firmware(panda.get_mcu_type().config.app_path) except Exception: cloudlog.exception("Error computing expected signature") return b"" diff --git a/selfdrive/boardd/spi.cc b/selfdrive/boardd/spi.cc index 2803f58db..9a10e30f9 100644 --- a/selfdrive/boardd/spi.cc +++ b/selfdrive/boardd/spi.cc @@ -1,11 +1,16 @@ +#ifndef __APPLE__ +#include #include #include #include #include #include +#include +#include #include "common/util.h" +#include "common/timing.h" #include "common/swaglog.h" #include "panda/board/comms_definitions.h" #include "selfdrive/boardd/panda_comms.h" @@ -24,40 +29,79 @@ struct __attribute__((packed)) spi_header { uint16_t max_rx_len; }; +const int SPI_MAX_RETRIES = 5; +const int SPI_ACK_TIMEOUT = 50; // milliseconds +const std::string SPI_DEVICE = "/dev/spidev0.0"; + +class LockEx { +public: + LockEx(int fd, std::recursive_mutex &m) : fd(fd), m(m) { + m.lock(); + flock(fd, LOCK_EX); + }; + + ~LockEx() { + m.unlock(); + flock(fd, LOCK_UN); + } + +private: + int fd; + std::recursive_mutex &m; +}; + PandaSpiHandle::PandaSpiHandle(std::string serial) : PandaCommsHandle(serial) { - LOGD("opening SPI panda: %s", serial.c_str()); + int ret; + const int uid_len = 12; + uint8_t uid[uid_len] = {0}; - int err; uint32_t spi_mode = SPI_MODE_0; uint32_t spi_speed = 30000000; uint8_t spi_bits_per_word = 8; - spi_fd = open(serial.c_str(), O_RDWR); + spi_fd = open(SPI_DEVICE.c_str(), O_RDWR); if (spi_fd < 0) { - LOGE("failed opening SPI device %d", err); + LOGE("failed opening SPI device %d", spi_fd); goto fail; } // SPI settings - err = util::safe_ioctl(spi_fd, SPI_IOC_WR_MODE, &spi_mode); - if (err < 0) { - LOGE("failed setting SPI mode %d", err); + ret = util::safe_ioctl(spi_fd, SPI_IOC_WR_MODE, &spi_mode); + if (ret < 0) { + LOGE("failed setting SPI mode %d", ret); goto fail; } - err = util::safe_ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &spi_speed); - if (err < 0) { + ret = util::safe_ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &spi_speed); + if (ret < 0) { LOGE("failed setting SPI speed"); goto fail; } - err = util::safe_ioctl(spi_fd, SPI_IOC_WR_BITS_PER_WORD, &spi_bits_per_word); - if (err < 0) { + ret = util::safe_ioctl(spi_fd, SPI_IOC_WR_BITS_PER_WORD, &spi_bits_per_word); + if (ret < 0) { LOGE("failed setting SPI bits per word"); goto fail; } + // get hw UID/serial + ret = control_read(0xc3, 0, 0, uid, uid_len); + if (ret == uid_len) { + std::stringstream stream; + for (int i = 0; i < uid_len; i++) { + stream << std::hex << std::setw(2) << std::setfill('0') << int(uid[i]); + } + hw_serial = stream.str(); + } else { + LOGD("failed to get serial %d", ret); + goto fail; + } + + if (!serial.empty() && (serial != hw_serial)) { + goto fail; + } + return; fail: @@ -80,6 +124,7 @@ void PandaSpiHandle::cleanup() { int PandaSpiHandle::control_write(uint8_t request, uint16_t param1, uint16_t param2, unsigned int timeout) { + LockEx lock(spi_fd, hw_lock); ControlPacket_t packet = { .request = request, .param1 = param1, @@ -90,6 +135,7 @@ int PandaSpiHandle::control_write(uint8_t request, uint16_t param1, uint16_t par } int PandaSpiHandle::control_read(uint8_t request, uint16_t param1, uint16_t param2, unsigned char *data, uint16_t length, unsigned int timeout) { + LockEx lock(spi_fd, hw_lock); ControlPacket_t packet = { .request = request, .param1 = param1, @@ -100,16 +146,16 @@ int PandaSpiHandle::control_read(uint8_t request, uint16_t param1, uint16_t para } int PandaSpiHandle::bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) { + LockEx lock(spi_fd, hw_lock); return bulk_transfer(endpoint, data, length, NULL, 0); } int PandaSpiHandle::bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) { + LockEx lock(spi_fd, hw_lock); return bulk_transfer(endpoint, NULL, 0, data, length); } int PandaSpiHandle::bulk_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t rx_len) { - std::lock_guard lk(hw_lock); - - const int xfer_size = 0x40; + const int xfer_size = 0x40 * 15; int ret = 0; uint16_t length = (tx_data != NULL) ? tx_len : rx_len; @@ -119,7 +165,8 @@ int PandaSpiHandle::bulk_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t t int len = std::min(xfer_size, tx_len - (xfer_size * i)); d = spi_transfer_retry(endpoint, tx_data + (xfer_size * i), len, NULL, 0); } else { - d = spi_transfer_retry(endpoint, NULL, 0, rx_data + (xfer_size * i), xfer_size); + uint16_t to_read = std::min(xfer_size, rx_len - ret); + d = spi_transfer_retry(endpoint, NULL, 0, rx_data + (xfer_size * i), to_read); } if (d < 0) { @@ -137,15 +184,16 @@ int PandaSpiHandle::bulk_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t t return ret; } - - std::vector PandaSpiHandle::list() { - // TODO: list all pandas available over SPI + try { + PandaSpiHandle sh(""); + return {sh.hw_serial}; + } catch (std::exception &e) { + // no panda on SPI + } return {}; } - - void add_checksum(uint8_t *data, int data_len) { data[data_len] = SPI_CHECKSUM_START; for (int i=0; i < data_len; i++) { @@ -165,17 +213,18 @@ bool check_checksum(uint8_t *data, int data_len) { int PandaSpiHandle::spi_transfer_retry(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len) { int ret; - std::lock_guard lk(hw_lock); + int count = SPI_MAX_RETRIES; do { // TODO: handle error ret = spi_transfer(endpoint, tx_data, tx_len, rx_data, max_rx_len); - } while (ret < 0 && connected && !PANDA_NO_RETRY); + count--; + } while (ret < 0 && connected && count > 0); return ret; } int PandaSpiHandle::wait_for_ack(spi_ioc_transfer &transfer, uint8_t ack) { - // TODO: add timeout? + double start_millis = millis_since_boot(); while (true) { int ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer); if (ret < 0) { @@ -189,6 +238,12 @@ int PandaSpiHandle::wait_for_ack(spi_ioc_transfer &transfer, uint8_t ack) { LOGW("SPI: got NACK"); return -1; } + + // handle timeout + if (millis_since_boot() - start_millis > SPI_ACK_TIMEOUT) { + LOGD("SPI: timed out waiting for ACK"); + return -1; + } } return 0; @@ -261,7 +316,10 @@ int PandaSpiHandle::spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx goto transfer_fail; } rx_data_len = *(uint16_t *)(rx_buf+1); - assert(rx_data_len < SPI_BUF_SIZE); + if (rx_data_len >= SPI_BUF_SIZE) { + LOGE("SPI: RX data len larger than buf size %d", rx_data_len); + goto transfer_fail; + } // Read data transfer.len = rx_data_len + 1; @@ -285,3 +343,4 @@ int PandaSpiHandle::spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx transfer_fail: return ret; } +#endif diff --git a/selfdrive/boardd/tests/test_boardd_loopback.py b/selfdrive/boardd/tests/test_boardd_loopback.py index e9bbcb458..b8ebbd88a 100755 --- a/selfdrive/boardd/tests/test_boardd_loopback.py +++ b/selfdrive/boardd/tests/test_boardd_loopback.py @@ -51,7 +51,7 @@ class TestBoardd(unittest.TestCase): cp.safetyConfigs = [safety_config]*num_pandas params = Params() - params.put("CarVin", b"0"*17) + params.put_bool("FirmwareObdQueryDone", True) params.put_bool("ControlsReady", True) params.put("CarParams", cp.to_bytes()) diff --git a/selfdrive/car/__init__.py b/selfdrive/car/__init__.py index 491c551b1..c056f7ca3 100644 --- a/selfdrive/car/__init__.py +++ b/selfdrive/car/__init__.py @@ -1,8 +1,9 @@ # functions common among cars import capnp +from collections import namedtuple from cereal import car -from common.numpy_fast import clip +from common.numpy_fast import clip, interp from typing import Dict # kg of standard extra cargo to count for drive, gas, etc... @@ -10,6 +11,7 @@ STD_CARGO_KG = 136. ButtonType = car.CarState.ButtonEvent.Type EventName = car.CarEvent.EventName +AngleRateLimit = namedtuple('AngleRateLimit', ['speed_bp', 'angle_v']) def apply_hysteresis(val: float, val_steady: float, hyst_gap: float) -> float: @@ -111,6 +113,15 @@ def apply_toyota_steer_torque_limits(apply_torque, apply_torque_last, motor_torq return int(round(float(apply_torque))) +def apply_std_steer_angle_limits(apply_angle, apply_angle_last, v_ego, LIMITS): + # pick angle rate limits based on wind up/down + steer_up = apply_angle_last * apply_angle > 0. and abs(apply_angle) > abs(apply_angle_last) + rate_limits = LIMITS.ANGLE_RATE_LIMIT_UP if steer_up else LIMITS.ANGLE_RATE_LIMIT_DOWN + + angle_rate_lim = interp(v_ego, rate_limits.speed_bp, rate_limits.angle_v) + return clip(apply_angle, apply_angle_last - angle_rate_lim, apply_angle_last + angle_rate_lim) + + def crc8_pedal(data): crc = 0xFF # standard init value poly = 0xD5 # standard crc8: x8+x7+x6+x4+x2+1 diff --git a/selfdrive/car/body/carcontroller.py b/selfdrive/car/body/carcontroller.py index 0d5d780bd..bcaf6f6f9 100644 --- a/selfdrive/car/body/carcontroller.py +++ b/selfdrive/car/body/carcontroller.py @@ -35,7 +35,7 @@ class CarController: torque -= deadband return torque - def update(self, CC, CS): + def update(self, CC, CS, now_nanos): torque_l = 0 torque_r = 0 @@ -85,6 +85,7 @@ class CarController: new_actuators = CC.actuators.copy() new_actuators.accel = torque_l new_actuators.steer = torque_r + new_actuators.steerOutputCan = torque_r self.frame += 1 return new_actuators, can_sends diff --git a/selfdrive/car/body/interface.py b/selfdrive/car/body/interface.py index ae7ab89aa..850a3538a 100644 --- a/selfdrive/car/body/interface.py +++ b/selfdrive/car/body/interface.py @@ -2,16 +2,13 @@ import math from cereal import car from common.realtime import DT_CTRL -from selfdrive.car import scale_rot_inertia, scale_tire_stiffness, get_safety_config +from selfdrive.car import get_safety_config from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.body.values import SPEED_FROM_RPM class CarInterface(CarInterfaceBase): @staticmethod - def get_params(candidate, fingerprint=None, car_fw=None, experimental_long=False): - - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) - + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.notCar = True ret.carName = "body" ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.body)] @@ -27,14 +24,10 @@ class CarInterface(CarInterfaceBase): ret.wheelSpeedFactor = SPEED_FROM_RPM ret.centerToFront = ret.wheelbase * 0.44 - ret.radarOffCan = True + ret.radarUnavailable = True ret.openpilotLongitudinalControl = True ret.steerControlType = car.CarParams.SteerControlType.angle - ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) - - ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront) - return ret def _update(self, c): @@ -50,5 +43,5 @@ class CarInterface(CarInterfaceBase): return ret - def apply(self, c): - return self.CC.update(c, self.CS) + def apply(self, c, now_nanos): + return self.CC.update(c, self.CS, now_nanos) diff --git a/selfdrive/car/body/values.py b/selfdrive/car/body/values.py index 66f1b947a..4fef96637 100644 --- a/selfdrive/car/body/values.py +++ b/selfdrive/car/body/values.py @@ -17,6 +17,9 @@ class CarControllerParams: LKAS_MAX_TORQUE = 1 # A value of 1 is easy to overpower STEER_THRESHOLD = 1.0 + def __init__(self, CP): + pass + class CAR: BODY = "COMMA BODY" @@ -26,6 +29,12 @@ CAR_INFO: Dict[str, CarInfo] = { CAR.BODY: CarInfo("comma body", package="All"), } +FINGERPRINTS = { + CAR.BODY: [{ + 513: 8, 516: 8, 514: 3, 515: 4, + }], +} + FW_QUERY_CONFIG = FwQueryConfig( requests=[ Request( @@ -40,10 +49,13 @@ FW_VERSIONS = { CAR.BODY: { (Ecu.engine, 0x720, None): [ b'0.0.01', - b'02/27/2022' + b'02/27/2022', + b'0.3.00a', ], + # git hash of the firmware used (Ecu.debug, 0x721, None): [ - b'166bd860' # git hash of the firmware used + b'166bd860', + b'dc780f85', ], }, } diff --git a/selfdrive/car/car_helpers.py b/selfdrive/car/car_helpers.py index 61e7a3d55..370772c90 100644 --- a/selfdrive/car/car_helpers.py +++ b/selfdrive/car/car_helpers.py @@ -8,7 +8,7 @@ from system.version import is_comma_remote, is_tested_branch from selfdrive.car.interfaces import get_interface_attr from selfdrive.car.fingerprints import eliminate_incompatible_cars, all_legacy_fingerprint_cars from selfdrive.car.vin import get_vin, is_valid_vin, VIN_UNKNOWN -from selfdrive.car.fw_versions import get_fw_versions_ordered, match_fw_to_car, get_present_ecus +from selfdrive.car.fw_versions import disable_obd_multiplexing, get_fw_versions_ordered, match_fw_to_car, get_present_ecus from system.swaglog import cloudlog import cereal.messaging as messaging from selfdrive.car import gen_empty_fingerprint @@ -99,7 +99,7 @@ def fingerprint(logcan, sendcan, num_pandas): else: cloudlog.warning("Getting VIN & FW versions") vin_rx_addr, vin = get_vin(logcan, sendcan, bus) - ecu_rx_addrs = get_present_ecus(logcan, sendcan) + ecu_rx_addrs = get_present_ecus(logcan, sendcan, num_pandas=num_pandas) car_fw = get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, num_pandas=num_pandas) cached = False @@ -113,7 +113,10 @@ def fingerprint(logcan, sendcan, num_pandas): cloudlog.event("Malformed VIN", vin=vin, error=True) vin = VIN_UNKNOWN cloudlog.warning("VIN %s", vin) - Params().put("CarVin", vin) + + params = Params() + params.put("CarVin", vin) + disable_obd_multiplexing(params) finger = gen_empty_fingerprint() candidate_cars = {i: all_legacy_fingerprint_cars() for i in [0, 1]} # attempt fingerprint on both bus 0 and 1 @@ -173,17 +176,15 @@ def fingerprint(logcan, sendcan, num_pandas): return car_fingerprint, finger, vin, car_fw, source, exact_match -def get_car(logcan, sendcan, num_pandas=1): +def get_car(logcan, sendcan, experimental_long_allowed, num_pandas=1): candidate, fingerprints, vin, car_fw, source, exact_match = fingerprint(logcan, sendcan, num_pandas) if candidate is None: cloudlog.warning("car doesn't match any fingerprints: %r", fingerprints) candidate = "mock" - experimental_long = Params().get_bool("ExperimentalLongitudinalEnabled") - CarInterface, CarController, CarState = interfaces[candidate] - CP = CarInterface.get_params(candidate, fingerprints, car_fw, experimental_long) + CP = CarInterface.get_params(candidate, fingerprints, car_fw, experimental_long_allowed) CP.carVin = vin CP.carFw = car_fw CP.fingerprintSource = source diff --git a/selfdrive/car/chrysler/carcontroller.py b/selfdrive/car/chrysler/carcontroller.py index 5a2d90c64..20a44bce2 100644 --- a/selfdrive/car/chrysler/carcontroller.py +++ b/selfdrive/car/chrysler/carcontroller.py @@ -2,7 +2,7 @@ from opendbc.can.packer import CANPacker from common.realtime import DT_CTRL from selfdrive.car import apply_toyota_steer_torque_limits from selfdrive.car.chrysler.chryslercan import create_lkas_hud, create_lkas_command, create_cruise_buttons -from selfdrive.car.chrysler.values import CAR, RAM_CARS, CarControllerParams +from selfdrive.car.chrysler.values import RAM_CARS, CarControllerParams, ChryslerFlags class CarController: @@ -19,7 +19,7 @@ class CarController: self.packer = CANPacker(dbc_name) self.params = CarControllerParams(CP) - def update(self, CC, CS): + def update(self, CC, CS, now_nanos): can_sends = [] lkas_active = CC.latActive and self.lkas_control_bit_prev @@ -45,13 +45,13 @@ class CarController: self.hud_count += 1 # steering - if self.frame % 2 == 0: + if self.frame % self.params.STEER_STEP == 0: # TODO: can we make this more sane? why is it different for all the cars? lkas_control_bit = self.lkas_control_bit_prev if CS.out.vEgo > self.CP.minSteerSpeed: lkas_control_bit = True - elif self.CP.carFingerprint in (CAR.PACIFICA_2019_HYBRID, CAR.PACIFICA_2020, CAR.JEEP_CHEROKEE_2019): + elif self.CP.flags & ChryslerFlags.HIGHER_MIN_STEERING_SPEED: if CS.out.vEgo < (self.CP.minSteerSpeed - 3.0): lkas_control_bit = False elif self.CP.carFingerprint in RAM_CARS: @@ -78,5 +78,6 @@ class CarController: new_actuators = CC.actuators.copy() new_actuators.steer = self.apply_steer_last / self.params.STEER_MAX + new_actuators.steerOutputCan = self.apply_steer_last return new_actuators, can_sends diff --git a/selfdrive/car/chrysler/carstate.py b/selfdrive/car/chrysler/carstate.py index 0f0d30782..fdc5aa338 100644 --- a/selfdrive/car/chrysler/carstate.py +++ b/selfdrive/car/chrysler/carstate.py @@ -81,8 +81,9 @@ class CarState(CarStateBase): if self.CP.carFingerprint in RAM_CARS: self.auto_high_beam = cp_cam.vl["DAS_6"]['AUTO_HIGH_BEAM_ON'] # Auto High Beam isn't Located in this message on chrysler or jeep currently located in 729 message - ret.steerFaultTemporary = cp.vl["EPS_3"]["DASM_FAULT"] == 1 + ret.steerFaultTemporary = cp.vl["EPS_3"]["DASM_FAULT"] == 1 else: + ret.steerFaultTemporary = cp.vl["EPS_2"]["LKAS_TEMPORARY_FAULT"] == 1 ret.steerFaultPermanent = cp.vl["EPS_2"]["LKAS_STATE"] == 4 # blindspot sensors @@ -135,6 +136,7 @@ class CarState(CarStateBase): ("COUNTER", "EPS_2",), ("COLUMN_TORQUE", "EPS_2"), ("EPS_TORQUE_MOTOR", "EPS_2"), + ("LKAS_TEMPORARY_FAULT", "EPS_2"), ("LKAS_STATE", "EPS_2"), ("COUNTER", "CRUISE_BUTTONS"), ] diff --git a/selfdrive/car/chrysler/interface.py b/selfdrive/car/chrysler/interface.py index 245e10650..961684f39 100755 --- a/selfdrive/car/chrysler/interface.py +++ b/selfdrive/car/chrysler/interface.py @@ -1,21 +1,18 @@ #!/usr/bin/env python3 from cereal import car from panda import Panda -from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config -from selfdrive.car.chrysler.values import CAR, DBC, RAM_HD, RAM_DT +from selfdrive.car import STD_CARGO_KG, get_safety_config +from selfdrive.car.chrysler.values import CAR, RAM_HD, RAM_DT, RAM_CARS, ChryslerFlags from selfdrive.car.interfaces import CarInterfaceBase class CarInterface(CarInterfaceBase): @staticmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None, experimental_long=False): - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "chrysler" - ret.dashcamOnly = candidate in RAM_HD - ret.radarOffCan = DBC[candidate]['radar'] is None - + ret.radarUnavailable = True # DBC[candidate]['radar'] is None ret.steerActuatorDelay = 0.1 ret.steerLimitTimer = 0.4 @@ -27,15 +24,21 @@ class CarInterface(CarInterfaceBase): ret.safetyConfigs[0].safetyParam |= Panda.FLAG_CHRYSLER_RAM_DT ret.minSteerSpeed = 3.8 # m/s - if candidate in (CAR.PACIFICA_2019_HYBRID, CAR.PACIFICA_2020, CAR.JEEP_CHEROKEE_2019): - # TODO: allow 2019 cars to steer down to 13 m/s if already engaged. - ret.minSteerSpeed = 17.5 # m/s 17 on the way up, 13 on the way down once engaged. + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) + if candidate not in RAM_CARS: + # Newer FW versions standard on the following platforms, or flashed by a dealer onto older platforms have a higher minimum steering speed. + new_eps_platform = candidate in (CAR.PACIFICA_2019_HYBRID, CAR.PACIFICA_2020, CAR.JEEP_CHEROKEE_2019) + new_eps_firmware = any(fw.ecu == 'eps' and fw.fwVersion[:4] >= b"6841" for fw in car_fw) + if new_eps_platform or new_eps_firmware: + ret.flags |= ChryslerFlags.HIGHER_MIN_STEERING_SPEED.value # Chrysler if candidate in (CAR.PACIFICA_2017_HYBRID, CAR.PACIFICA_2018, CAR.PACIFICA_2018_HYBRID, CAR.PACIFICA_2019_HYBRID, CAR.PACIFICA_2020): ret.mass = 2242. + STD_CARGO_KG ret.wheelbase = 3.089 ret.steerRatio = 16.2 # Pacifica Hybrid 2017 + + ret.lateralTuning.init('pid') ret.lateralTuning.pid.kpBP, ret.lateralTuning.pid.kiBP = [[9., 20.], [9., 20.]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.15, 0.30], [0.03, 0.05]] ret.lateralTuning.pid.kf = 0.00006 @@ -46,6 +49,8 @@ class CarInterface(CarInterfaceBase): ret.wheelbase = 2.71 ret.steerRatio = 16.7 ret.steerActuatorDelay = 0.2 + + ret.lateralTuning.init('pid') ret.lateralTuning.pid.kpBP, ret.lateralTuning.pid.kiBP = [[9., 20.], [9., 20.]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.15, 0.30], [0.03, 0.05]] ret.lateralTuning.pid.kf = 0.00006 @@ -56,12 +61,10 @@ class CarInterface(CarInterfaceBase): ret.wheelbase = 3.88 ret.steerRatio = 16.3 ret.mass = 2493. + STD_CARGO_KG - CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) ret.minSteerSpeed = 14.5 - if car_fw is not None: - for fw in car_fw: - if fw.ecu == 'eps' and fw.fwVersion[:8] in (b"68312176", b"68273275"): - ret.minSteerSpeed = 0. + # Older EPS FW allow steer to zero + if any(fw.ecu == 'eps' and fw.fwVersion[:4] <= b"6831" for fw in car_fw): + ret.minSteerSpeed = 0. elif candidate == CAR.RAM_HD: ret.steerActuatorDelay = 0.2 @@ -74,15 +77,11 @@ class CarInterface(CarInterfaceBase): else: raise ValueError(f"Unsupported car: {candidate}") + if ret.flags & ChryslerFlags.HIGHER_MIN_STEERING_SPEED: + # TODO: allow these cars to steer down to 13 m/s if already engaged. + ret.minSteerSpeed = 17.5 # m/s 17 on the way up, 13 on the way down once engaged. + ret.centerToFront = ret.wheelbase * 0.44 - - # starting with reasonable value for civic and scaling by mass and wheelbase - ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) - - # TODO: start from empirically derived lateral slip stiffness for the civic and scale by - # mass and CG position, so all cars will have approximately similar dyn behaviors - ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront) - ret.enableBsm = 720 in fingerprint[0] return ret @@ -105,5 +104,5 @@ class CarInterface(CarInterfaceBase): return ret - def apply(self, c): - return self.CC.update(c, self.CS) + def apply(self, c, now_nanos): + return self.CC.update(c, self.CS, now_nanos) diff --git a/selfdrive/car/chrysler/radar_interface.py b/selfdrive/car/chrysler/radar_interface.py index 348e3c363..0ab8c10b4 100755 --- a/selfdrive/car/chrysler/radar_interface.py +++ b/selfdrive/car/chrysler/radar_interface.py @@ -45,12 +45,13 @@ def _address_to_track(address): class RadarInterface(RadarInterfaceBase): def __init__(self, CP): super().__init__(CP) + self.CP = CP self.rcp = _create_radar_can_parser(CP.carFingerprint) self.updated_messages = set() self.trigger_msg = LAST_MSG def update(self, can_strings): - if self.rcp is None: + if self.rcp is None or self.CP.radarUnavailable: return super().update(None) vls = self.rcp.update_strings(can_strings) diff --git a/selfdrive/car/chrysler/values.py b/selfdrive/car/chrysler/values.py index 16530ed98..16ebb4fa1 100644 --- a/selfdrive/car/chrysler/values.py +++ b/selfdrive/car/chrysler/values.py @@ -1,3 +1,4 @@ +from enum import IntFlag from dataclasses import dataclass from enum import Enum from typing import Dict, List, Optional, Union @@ -11,6 +12,10 @@ from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, p16 Ecu = car.CarParams.Ecu +class ChryslerFlags(IntFlag): + HIGHER_MIN_STEERING_SPEED = 1 + + class CAR: # Chrysler PACIFICA_2017_HYBRID = "CHRYSLER PACIFICA HYBRID 2017" @@ -30,6 +35,7 @@ class CAR: class CarControllerParams: def __init__(self, CP): + self.STEER_STEP = 2 # 50 Hz self.STEER_ERROR_MAX = 80 if CP.carFingerprint in RAM_HD: self.STEER_DELTA_UP = 14 @@ -50,6 +56,7 @@ RAM_DT = {CAR.RAM_1500, } RAM_HD = {CAR.RAM_HD, } RAM_CARS = RAM_DT | RAM_HD + @dataclass class ChryslerCarInfo(CarInfo): package: str = "Adaptive Cruise Control (ACC)" @@ -128,7 +135,7 @@ FINGERPRINTS = { }], CAR.JEEP_CHEROKEE_2019: [{ # Jeep Grand Cherokee 2019, including most 2020 models - 55: 8, 168: 8, 179: 8, 181: 8, 256: 4, 257: 5, 258: 8, 264: 8, 268: 8, 272: 6, 273: 6, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 341: 8, 344: 8, 352: 8, 362: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 500: 8, 501: 8, 512: 8, 514: 8, 520: 8, 530: 8, 532: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 618: 8, 624: 8, 625: 8, 632: 8, 639: 8, 640: 1, 656: 4, 658: 6, 660: 8, 671: 8, 672: 8, 676: 8, 678: 8, 680: 8, 683: 8, 684: 8, 703: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 738: 8, 746: 5, 752: 2, 754: 8, 760: 8, 761: 8, 764: 8, 766: 8, 773: 8, 776: 8, 779: 8, 782: 8, 783: 8, 784: 8, 785: 8, 792: 8, 799: 8, 800: 8, 804: 8, 806: 2, 808: 8, 810: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 831: 6, 832: 8, 838: 2, 840: 8, 844: 5, 847: 1, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 882: 8, 897: 8, 906: 8, 924: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 960: 4, 968: 8, 969: 4, 970: 8, 973: 8, 974: 5, 976: 8, 977: 4, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1062: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1223: 8, 1225: 8, 1227: 8, 1235: 8, 1242: 8, 1250: 8, 1251: 8, 1252: 8, 1254: 8, 1264: 8, 1284: 8, 1536: 8, 1537: 8, 1543: 8, 1545: 8, 1562: 8, 1568: 8, 1570: 8, 1572: 8, 1593: 8, 1856: 8, 1858: 8, 1860: 8, 1863: 8, 1865: 8, 1867: 8, 1875: 8, 1882: 8, 1890: 8, 1891: 8, 1892: 8, 1894: 8, 1896: 8, 1904: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 + 55: 8, 168: 8, 179: 8, 181: 8, 256: 4, 257: 5, 258: 8, 264: 8, 268: 8, 272: 6, 273: 6, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 341: 8, 344: 8, 352: 8, 362: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 500: 8, 501: 8, 512: 8, 514: 8, 520: 8, 530: 8, 532: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 618: 8, 624: 8, 625: 8, 632: 8, 639: 8, 640: 1, 656: 4, 658: 6, 660: 8, 671: 8, 672: 8, 676: 8, 678: 8, 680: 8, 683: 8, 684: 8, 703: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 738: 8, 746: 5, 752: 2, 754: 8, 760: 8, 761: 8, 764: 8, 766: 8, 773: 8, 776: 8, 779: 8, 782: 8, 783: 8, 784: 8, 785: 8, 792: 8, 799: 8, 800: 8, 804: 8, 806: 2, 808: 8, 810: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 831: 6, 832: 8, 838: 2, 840: 8, 844: 5, 847: 1, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 874: 2, 882: 8, 897: 8, 906: 8, 924: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 960: 4, 968: 8, 969: 4, 970: 8, 973: 8, 974: 5, 976: 8, 977: 4, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1062: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1223: 8, 1225: 8, 1227: 8, 1235: 8, 1242: 8, 1250: 8, 1251: 8, 1252: 8, 1254: 8, 1264: 8, 1284: 8, 1536: 8, 1537: 8, 1543: 8, 1545: 8, 1562: 8, 1568: 8, 1570: 8, 1572: 8, 1593: 8, 1856: 8, 1858: 8, 1860: 8, 1863: 8, 1865: 8, 1867: 8, 1875: 8, 1882: 8, 1890: 8, 1891: 8, 1892: 8, 1894: 8, 1896: 8, 1904: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 }], } diff --git a/selfdrive/car/docs_definitions.py b/selfdrive/car/docs_definitions.py index 8e057a156..c0fb4420d 100644 --- a/selfdrive/car/docs_definitions.py +++ b/selfdrive/car/docs_definitions.py @@ -21,6 +21,7 @@ class Column(Enum): STEERING_TORQUE = "Steering Torque" AUTO_RESUME = "Resume from stop" HARNESS = "Harness" + VIDEO = "Video" class Star(Enum): @@ -68,17 +69,16 @@ class Harness(Enum): none = "None" -CarFootnote = namedtuple("CarFootnote", ["text", "column", "docs_only"], defaults=(None, False)) +CarFootnote = namedtuple("CarFootnote", ["text", "column", "docs_only", "shop_footnote"], defaults=(False, False)) class CommonFootnote(Enum): EXP_LONG_AVAIL = CarFootnote( - "Experimental openpilot longitudinal control is available behind a toggle; the toggle is only available in non-release branches such as `master-ci`. " + - "Using openpilot longitudinal may disable Automatic Emergency Braking (AEB).", + "Experimental openpilot longitudinal control is available behind a toggle; the toggle is only available in non-release branches such as `devel` or `master-ci`. ", Column.LONGITUDINAL, docs_only=True) EXP_LONG_DSU = CarFootnote( - "When the Driver Support Unit (DSU) is disconnected, openpilot Adaptive Cruise Control (ACC) will replace " + - "stock Adaptive Cruise Control (ACC). NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).", + "By default, this car will use the stock Adaptive Cruise Control (ACC) for longitudinal control. If the Driver Support Unit (DSU) is disconnected, openpilot ACC will replace " + + "stock ACC. NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).", Column.LONGITUDINAL) @@ -126,20 +126,11 @@ class CarInfo: harness: Enum = Harness.none def init(self, CP: car.CarParams, all_footnotes: Dict[Enum, int]): - # TODO: set all the min steer speeds in carParams and remove this - if self.min_steer_speed is not None: - assert CP.minSteerSpeed == 0, f"{CP.carFingerprint}: Minimum steer speed set in both CarInfo and CarParams" - else: - self.min_steer_speed = CP.minSteerSpeed - - # TODO: set all the min enable speeds in carParams correctly and remove this - if self.min_enable_speed is None: - self.min_enable_speed = CP.minEnableSpeed - self.car_name = CP.carName self.car_fingerprint = CP.carFingerprint self.make, self.model, self.years = split_name(self.name) + # longitudinal column op_long = "Stock" if CP.openpilotLongitudinalControl and not CP.enableDsu: op_long = "openpilot" @@ -150,6 +141,23 @@ class CarInfo: else: self.footnotes.append(CommonFootnote.EXP_LONG_AVAIL) + # min steer & enable speed columns + # TODO: set all the min steer speeds in carParams and remove this + if self.min_steer_speed is not None: + assert CP.minSteerSpeed == 0, f"{CP.carFingerprint}: Minimum steer speed set in both CarInfo and CarParams" + else: + self.min_steer_speed = CP.minSteerSpeed + + # TODO: set all the min enable speeds in carParams correctly and remove this + if self.min_enable_speed is None: + self.min_enable_speed = CP.minEnableSpeed + + # harness column + harness_col = self.harness.value + if self.harness is not Harness.none: + model_years = self.model + (' ' + self.years if self.years else '') + harness_col = f'{harness_col}' + self.row = { Column.MAKE: self.make, Column.MODEL: self.model, @@ -159,7 +167,8 @@ class CarInfo: Column.FSR_STEERING: f"{max(self.min_steer_speed * CV.MS_TO_MPH, 0):.0f} mph", Column.STEERING_TORQUE: Star.EMPTY, Column.AUTO_RESUME: Star.FULL if CP.autoResumeSng else Star.EMPTY, - Column.HARNESS: self.harness.value, + Column.HARNESS: harness_col, + Column.VIDEO: self.video_link if self.video_link is not None else "", # replaced with an image and link from template in get_column } # Set steering torque star from max lateral acceleration @@ -173,6 +182,9 @@ class CarInfo: return self + def init_make(self, CP: car.CarParams): + """CarInfo subclasses can add make-specific logic for harness selection, footnotes, etc.""" + def get_detail_sentence(self, CP): if not CP.notCar: sentence_builder = "openpilot upgrades your {car_model} with automated lane centering{alc} and adaptive cruise control{acc}." @@ -192,6 +204,13 @@ class CarInfo: if self.row[Column.STEERING_TORQUE] != Star.FULL: sentence_builder += " This car may not be able to take tight turns on its own." + # experimental mode + exp_link = "Experimental mode" + if CP.openpilotLongitudinalControl or CP.experimentalLongitudinalAvailable: + sentence_builder += f" Traffic light and stop sign handling is also available in {exp_link}." + else: + sentence_builder += f" {exp_link}, with traffic light and stop sign handling, is not currently available for this car, but may be added in a future software update." + return sentence_builder.format(car_model=f"{self.make} {self.model}", alc=alc, acc=acc) else: @@ -200,12 +219,14 @@ class CarInfo: else: raise Exception(f"This notCar does not have a detail sentence: {CP.carFingerprint}") - def get_column(self, column: Column, star_icon: str, footnote_tag: str) -> str: + def get_column(self, column: Column, star_icon: str, video_icon: str, footnote_tag: str) -> str: item: Union[str, Star] = self.row[column] if isinstance(item, Star): item = star_icon.format(item.value) elif column == Column.MODEL and len(self.years): item += f" {self.years}" + elif column == Column.VIDEO and len(item) > 0: + item = video_icon.format(item) footnotes = get_footnotes(self.footnotes, column) if len(footnotes): diff --git a/selfdrive/car/ecu_addrs.py b/selfdrive/car/ecu_addrs.py index 9f6ace2b5..e5d550fac 100755 --- a/selfdrive/car/ecu_addrs.py +++ b/selfdrive/car/ecu_addrs.py @@ -87,5 +87,5 @@ if __name__ == "__main__": for addr, subaddr, bus in ecu_addrs: msg = f" 0x{hex(addr)}" if subaddr is not None: - msg += f" (sub-address: 0x{hex(subaddr)})" + msg += f" (sub-address: {hex(subaddr)})" print(msg) diff --git a/selfdrive/car/ford/carcontroller.py b/selfdrive/car/ford/carcontroller.py index f18014601..99072ae97 100644 --- a/selfdrive/car/ford/carcontroller.py +++ b/selfdrive/car/ford/carcontroller.py @@ -1,28 +1,14 @@ -import math from cereal import car -from common.numpy_fast import clip, interp +from common.numpy_fast import clip from opendbc.can.packer import CANPacker -from selfdrive.car.ford import fordcan -from selfdrive.car.ford.values import CANBUS, CarControllerParams +from selfdrive.car import apply_std_steer_angle_limits +from selfdrive.car.ford.fordcan import create_acc_command, create_acc_ui_msg, create_button_msg, create_lat_ctl_msg, \ + create_lat_ctl2_msg, create_lka_msg, create_lkas_ui_msg +from selfdrive.car.ford.values import CANBUS, CANFD_CARS, CarControllerParams VisualAlert = car.CarControl.HUDControl.VisualAlert -def apply_ford_steer_angle_limits(apply_angle, apply_angle_last, vEgo): - # rate limit - steer_up = apply_angle_last * apply_angle > 0. and abs(apply_angle) > abs(apply_angle_last) - rate_limit = CarControllerParams.RATE_LIMIT_UP if steer_up else CarControllerParams.RATE_LIMIT_DOWN - max_angle_diff = interp(vEgo, rate_limit.speed_points, rate_limit.max_angle_diff_points) - apply_angle = clip(apply_angle, (apply_angle_last - max_angle_diff), (apply_angle_last + max_angle_diff)) - - # absolute limit (LatCtlPath_An_Actl) - apply_path_angle = math.radians(apply_angle) / CarControllerParams.LKAS_STEER_RATIO - apply_path_angle = clip(apply_path_angle, -0.5, 0.5235) - apply_angle = math.degrees(apply_path_angle) * CarControllerParams.LKAS_STEER_RATIO - - return apply_angle - - class CarController: def __init__(self, dbc_name, CP, VM): self.CP = CP @@ -30,12 +16,12 @@ class CarController: self.packer = CANPacker(dbc_name) self.frame = 0 - self.apply_angle_last = 0 + self.apply_curvature_last = 0 self.main_on_last = False self.lkas_enabled_last = False self.steer_alert_last = False - def update(self, CC, CS): + def update(self, CC, CS, now_nanos): can_sends = [] actuators = CC.actuators @@ -46,66 +32,69 @@ class CarController: ### acc buttons ### if CC.cruiseControl.cancel: - can_sends.append(fordcan.create_button_command(self.packer, CS.buttons_stock_values, cancel=True)) - can_sends.append(fordcan.create_button_command(self.packer, CS.buttons_stock_values, cancel=True, bus=CANBUS.main)) + can_sends.append(create_button_msg(self.packer, CS.buttons_stock_values, cancel=True)) + can_sends.append(create_button_msg(self.packer, CS.buttons_stock_values, cancel=True, bus=CANBUS.main)) elif CC.cruiseControl.resume and (self.frame % CarControllerParams.BUTTONS_STEP) == 0: - can_sends.append(fordcan.create_button_command(self.packer, CS.buttons_stock_values, resume=True)) - can_sends.append(fordcan.create_button_command(self.packer, CS.buttons_stock_values, resume=True, bus=CANBUS.main)) + can_sends.append(create_button_msg(self.packer, CS.buttons_stock_values, resume=True)) + can_sends.append(create_button_msg(self.packer, CS.buttons_stock_values, resume=True, bus=CANBUS.main)) # if stock lane centering isn't off, send a button press to toggle it off # the stock system checks for steering pressed, and eventually disengages cruise control elif CS.acc_tja_status_stock_values["Tja_D_Stat"] != 0 and (self.frame % CarControllerParams.ACC_UI_STEP) == 0: - can_sends.append(fordcan.create_button_command(self.packer, CS.buttons_stock_values, tja_toggle=True)) - + can_sends.append(create_button_msg(self.packer, CS.buttons_stock_values, tja_toggle=True)) ### lateral control ### - if CC.latActive: - lca_rq = 1 - apply_angle = apply_ford_steer_angle_limits(actuators.steeringAngleDeg, self.apply_angle_last, CS.out.vEgo) - else: - lca_rq = 0 - apply_angle = 0. - # send steering commands at 20Hz - if (self.frame % CarControllerParams.LKAS_STEER_STEP) == 0: - # use LatCtlPath_An_Actl to actuate steering - path_angle = math.radians(apply_angle) / CarControllerParams.LKAS_STEER_RATIO - - # set slower ramp type when small steering angle change - # 0=Slow, 1=Medium, 2=Fast, 3=Immediately - steer_change = abs(CS.out.steeringAngleDeg - actuators.steeringAngleDeg) - if steer_change < 2.0: - ramp_type = 0 - elif steer_change < 4.0: - ramp_type = 1 - elif steer_change < 6.0: - ramp_type = 2 + if (self.frame % CarControllerParams.STEER_STEP) == 0: + if CC.latActive: + # apply limits to curvature and clip to signal range + apply_curvature = apply_std_steer_angle_limits(actuators.curvature, self.apply_curvature_last, CS.out.vEgo, CarControllerParams) + apply_curvature = clip(apply_curvature, -CarControllerParams.CURVATURE_MAX, CarControllerParams.CURVATURE_MAX) else: - ramp_type = 3 - precision = 1 # 0=Comfortable, 1=Precise (the stock system always uses comfortable) + apply_curvature = 0. - self.apply_angle_last = apply_angle - can_sends.append(fordcan.create_lka_command(self.packer, 0, 0)) - can_sends.append(fordcan.create_tja_command(self.packer, lca_rq, ramp_type, precision, - 0, path_angle, 0, 0)) + self.apply_curvature_last = apply_curvature + can_sends.append(create_lka_msg(self.packer)) + if self.CP.carFingerprint in CANFD_CARS: + # TODO: extended mode + mode = 1 if CC.latActive else 0 + counter = self.frame // CarControllerParams.STEER_STEP + can_sends.append(create_lat_ctl2_msg(self.packer, mode, 0., 0., -apply_curvature, 0., counter)) + else: + can_sends.append(create_lat_ctl_msg(self.packer, CC.latActive, 0., 0., -apply_curvature, 0.)) + + ### longitudinal control ### + # send acc command at 50Hz + if self.CP.openpilotLongitudinalControl and (self.frame % CarControllerParams.ACC_CONTROL_STEP) == 0: + accel = clip(actuators.accel, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX) + + precharge_brake = accel < -0.1 + if accel > -0.5: + gas = accel + decel = False + else: + gas = -5.0 + decel = True + + can_sends.append(create_acc_command(self.packer, CC.longActive, gas, accel, precharge_brake, decel)) ### ui ### send_ui = (self.main_on_last != main_on) or (self.lkas_enabled_last != CC.latActive) or (self.steer_alert_last != steer_alert) # send lkas ui command at 1Hz or if ui state changes if (self.frame % CarControllerParams.LKAS_UI_STEP) == 0 or send_ui: - can_sends.append(fordcan.create_lkas_ui_command(self.packer, main_on, CC.latActive, steer_alert, hud_control, CS.lkas_status_stock_values)) + can_sends.append(create_lkas_ui_msg(self.packer, main_on, CC.latActive, steer_alert, hud_control, CS.lkas_status_stock_values)) # send acc ui command at 20Hz or if ui state changes if (self.frame % CarControllerParams.ACC_UI_STEP) == 0 or send_ui: - can_sends.append(fordcan.create_acc_ui_command(self.packer, main_on, CC.latActive, hud_control, CS.acc_tja_status_stock_values)) + can_sends.append(create_acc_ui_msg(self.packer, main_on, CC.latActive, hud_control, CS.acc_tja_status_stock_values)) self.main_on_last = main_on self.lkas_enabled_last = CC.latActive self.steer_alert_last = steer_alert new_actuators = actuators.copy() - new_actuators.steeringAngleDeg = self.apply_angle_last + new_actuators.curvature = self.apply_curvature_last self.frame += 1 return new_actuators, can_sends diff --git a/selfdrive/car/ford/carstate.py b/selfdrive/car/ford/carstate.py index 2276b1208..f97225e9f 100644 --- a/selfdrive/car/ford/carstate.py +++ b/selfdrive/car/ford/carstate.py @@ -14,7 +14,7 @@ class CarState(CarStateBase): super().__init__(CP) can_define = CANDefine(DBC[CP.carFingerprint]["pt"]) if CP.transmissionType == TransmissionType.automatic: - self.shifter_values = can_define.dv["Gear_Shift_by_Wire_FD1"]["TrnGear_D_RqDrv"] + self.shifter_values = can_define.dv["Gear_Shift_by_Wire_FD1"]["TrnRng_D_RqGsm"] def update(self, cp, cp_cam): ret = car.CarState.new_message() @@ -51,7 +51,7 @@ class CarState(CarStateBase): # gear if self.CP.transmissionType == TransmissionType.automatic: - gear = self.shifter_values.get(cp.vl["Gear_Shift_by_Wire_FD1"]["TrnGear_D_RqDrv"], None) + gear = self.shifter_values.get(cp.vl["Gear_Shift_by_Wire_FD1"]["TrnRng_D_RqGsm"]) ret.gearShifter = self.parse_gear_shifter(gear) elif self.CP.transmissionType == TransmissionType.manual: ret.clutchPressed = cp.vl["Engine_Clutch_Data"]["CluPdlPos_Pc_Meas"] > 0 @@ -118,7 +118,7 @@ class CarState(CarStateBase): ("DrStatRl_B_Actl", "BodyInfo_3_FD1"), # BCM Door open, rear left ("DrStatRr_B_Actl", "BodyInfo_3_FD1"), # BCM Door open, rear right ("FirstRowBuckleDriver", "RCMStatusMessage2_FD1"), # RCM Seatbelt status, driver - ("HeadLghtHiFlash_D_Stat", "Steering_Data_FD1"), # SCCM Passthru the remaining buttons + ("HeadLghtHiFlash_D_Stat", "Steering_Data_FD1"), # SCCM Passthrough the remaining buttons ("WiprFront_D_Stat", "Steering_Data_FD1"), ("LghtAmb_D_Sns", "Steering_Data_FD1"), ("AccButtnGapDecPress", "Steering_Data_FD1"), @@ -171,7 +171,7 @@ class CarState(CarStateBase): if CP.transmissionType == TransmissionType.automatic: signals += [ - ("TrnGear_D_RqDrv", "Gear_Shift_by_Wire_FD1"), # GWM transmission gear position + ("TrnRng_D_RqGsm", "Gear_Shift_by_Wire_FD1"), # GWM transmission gear position ] checks += [ ("Gear_Shift_by_Wire_FD1", 10), diff --git a/selfdrive/car/ford/fordcan.py b/selfdrive/car/ford/fordcan.py index 373ce096c..594d50f59 100644 --- a/selfdrive/car/ford/fordcan.py +++ b/selfdrive/car/ford/fordcan.py @@ -4,64 +4,120 @@ from selfdrive.car.ford.values import CANBUS HUDControl = car.CarControl.HUDControl -def create_lka_command(packer, angle_deg: float, curvature: float): +def calculate_lat_ctl2_checksum(mode: int, counter: int, dat: bytearray): + checksum = mode + counter + checksum += dat[2] + ((dat[3] & 0xE0) >> 5) # curvature + checksum += dat[6] + ((dat[7] & 0xE0) >> 5) # curvature rate + checksum += (dat[3] & 0x1F) + ((dat[4] & 0xFC) >> 2) # path angle + checksum += (dat[4] & 0x3) + dat[5] # path offset + return 0xFF - (checksum & 0xFF) + + +def create_lka_msg(packer): """ - Creates a CAN message for the Ford LKAS Command. + Creates an empty CAN message for the Ford LKA Command. This command can apply "Lane Keeping Aid" manoeuvres, which are subject to the PSCM lockout. Frequency is 20Hz. """ - values = { - "LkaDrvOvrrd_D_Rq": 0, # driver override level? [0|3] - "LkaActvStats_D2_Req": 0, # action [0|7] - "LaRefAng_No_Req": angle_deg, # angle [-102.4|102.3] degrees - "LaRampType_B_Req": 0, # Ramp speed: 0=Smooth, 1=Quick - "LaCurvature_No_Calc": curvature, # curvature [-0.01024|0.01023] 1/meter - "LdwActvStats_D_Req": 0, # LDW status [0|7] - "LdwActvIntns_D_Req": 0, # LDW intensity [0|3], shake alert strength - } - return packer.make_can_msg("Lane_Assist_Data1", CANBUS.main, values) + return packer.make_can_msg("Lane_Assist_Data1", CANBUS.main, {}) -def create_tja_command(packer, lca_rq: int, ramp_type: int, precision: int, path_offset: float, path_angle: float, curvature_rate: float, curvature: float): +def create_lat_ctl_msg(packer, lat_active: bool, path_offset: float, path_angle: float, curvature: float, + curvature_rate: float): """ Creates a CAN message for the Ford TJA/LCA Command. - This command can apply "Lane Centering" manoeuvres: continuous lane centering for traffic jam - assist and highway driving. It is not subject to the PSCM lockout. + This command can apply "Lane Centering" manoeuvres: continuous lane centering for traffic jam assist and highway + driving. It is not subject to the PSCM lockout. - Ford lane centering command uses a third order polynomial to describe the road centerline. The - polynomial is defined by the following coefficients: - c0: lateral offset between the vehicle and the centerline - c1: heading angle between the vehicle and the centerline - c2: curvature of the centerline + Ford lane centering command uses a third order polynomial to describe the road centerline. The polynomial is defined + by the following coefficients: + c0: lateral offset between the vehicle and the centerline (positive is right) + c1: heading angle between the vehicle and the centerline (positive is right) + c2: curvature of the centerline (positive is left) c3: rate of change of curvature of the centerline - As the PSCM combines this information with other sensor data, such as the vehicle's yaw rate and - speed, the steering angle cannot be easily controlled. + As the PSCM combines this information with other sensor data, such as the vehicle's yaw rate and speed, the steering + angle cannot be easily controlled. - The PSCM should be configured to accept TJA/LCA commands before these commands will be processed. - This can be done using tools such as Forscan. + The PSCM should be configured to accept TJA/LCA commands before these commands will be processed. This can be done + using tools such as Forscan. Frequency is 20Hz. """ values = { - "LatCtlRng_L_Max": 0, # Unknown [0|126] meter - "HandsOffCnfm_B_Rq": 0, # Unknown: 0=Inactive, 1=Active [0|1] - "LatCtl_D_Rq": lca_rq, # Mode: 0=None, 1=ContinuousPathFollowing, 2=InterventionLeft, 3=InterventionRight, 4-7=NotUsed [0|7] - "LatCtlRampType_D_Rq": ramp_type, # Ramp speed: 0=Slow, 1=Medium, 2=Fast, 3=Immediate [0|3] - "LatCtlPrecision_D_Rq": precision, # Precision: 0=Comfortable, 1=Precise, 2/3=NotUsed [0|3] - "LatCtlPathOffst_L_Actl": path_offset, # Path offset [-5.12|5.11] meter - "LatCtlPath_An_Actl": path_angle, # Path angle [-0.5|0.5235] radians - "LatCtlCurv_NoRate_Actl": curvature_rate, # Curvature rate [-0.001024|0.00102375] 1/meter^2 - "LatCtlCurv_No_Actl": curvature, # Curvature [-0.02|0.02094] 1/meter + "LatCtlRng_L_Max": 0, # Unknown [0|126] meter + "HandsOffCnfm_B_Rq": 0, # Unknown: 0=Inactive, 1=Active [0|1] + "LatCtl_D_Rq": 1 if lat_active else 0, # Mode: 0=None, 1=ContinuousPathFollowing, 2=InterventionLeft, + # 3=InterventionRight, 4-7=NotUsed [0|7] + "LatCtlRampType_D_Rq": 0, # Ramp speed: 0=Slow, 1=Medium, 2=Fast, 3=Immediate [0|3] + # Makes no difference with curvature control + "LatCtlPrecision_D_Rq": 1, # Precision: 0=Comfortable, 1=Precise, 2/3=NotUsed [0|3] + # The stock system always uses comfortable + "LatCtlPathOffst_L_Actl": path_offset, # Path offset [-5.12|5.11] meter + "LatCtlPath_An_Actl": path_angle, # Path angle [-0.5|0.5235] radians + "LatCtlCurv_NoRate_Actl": curvature_rate, # Curvature rate [-0.001024|0.00102375] 1/meter^2 + "LatCtlCurv_No_Actl": curvature, # Curvature [-0.02|0.02094] 1/meter } return packer.make_can_msg("LateralMotionControl", CANBUS.main, values) -def create_lkas_ui_command(packer, main_on: bool, enabled: bool, steer_alert: bool, hud_control, stock_values: dict): +def create_lat_ctl2_msg(packer, mode: int, path_offset: float, path_angle: float, curvature: float, + curvature_rate: float, counter: int): + """ + Create a CAN message for the new Ford Lane Centering command. + + This message is used on the CAN FD platform and replaces the old LateralMotionControl message. It is similar but has + additional signals for a counter and checksum. + + Frequency is 20Hz. + """ + + values = { + "LatCtl_D2_Rq": mode, # Mode: 0=None, 1=PathFollowingLimitedMode, 2=PathFollowingExtendedMode, + # 3=SafeRampOut, 4-7=NotUsed [0|7] + "LatCtlRampType_D_Rq": 0, # 0=Slow, 1=Medium, 2=Fast, 3=Immediate [0|3] + "LatCtlPrecision_D_Rq": 1, # 0=Comfortable, 1=Precise, 2/3=NotUsed [0|3] + "LatCtlPathOffst_L_Actl": path_offset, # [-5.12|5.11] meter + "LatCtlPath_An_Actl": path_angle, # [-0.5|0.5235] radians + "LatCtlCurv_No_Actl": curvature, # [-0.02|0.02094] 1/meter + "LatCtlCrv_NoRate2_Actl": curvature_rate, # [-0.001024|0.001023] 1/meter^2 + "HandsOffCnfm_B_Rq": 0, # 0=Inactive, 1=Active [0|1] + "LatCtlPath_No_Cnt": counter, # [0|15] + "LatCtlPath_No_Cs": 0, # [0|255] + } + + # calculate checksum + dat = packer.make_can_msg("LateralMotionControl2", CANBUS.main, values)[2] + values["LatCtlPath_No_Cs"] = calculate_lat_ctl2_checksum(mode, counter, dat) + + return packer.make_can_msg("LateralMotionControl2", CANBUS.main, values) + + +def create_acc_command(packer, long_active: bool, gas: float, accel: float, precharge_brake: bool, decel: bool): + """ + Creates a CAN message for the Ford ACC Command. + + This command can be used to enable ACC, to set the ACC gas/brake/decel values + and to disable ACC. + + Frequency is 50Hz. + """ + + values = { + "AccBrkTot_A_Rq": accel, # Brake total accel request: [-20|11.9449] m/s^2 + "Cmbb_B_Enbl": 1 if long_active else 0, # Enabled: 0=No, 1=Yes + "AccPrpl_A_Rq": gas, # Acceleration request: [-5|5.23] m/s^2 + "AccBrkPrchg_B_Rq": 1 if precharge_brake else 0, # Pre-charge brake request: 0=No, 1=Yes + "AccBrkDecel_B_Rq": 1 if decel else 0, # Deceleration request: 0=Inactive, 1=Active + } + return packer.make_can_msg("ACCDATA", CANBUS.main, values) + + +def create_lkas_ui_msg(packer, main_on: bool, enabled: bool, steer_alert: bool, hud_control, stock_values: dict): """ Creates a CAN message for the Ford IPC IPMA/LKAS status. @@ -107,16 +163,15 @@ def create_lkas_ui_command(packer, main_on: bool, enabled: bool, steer_alert: bo values = { **stock_values, - "LaActvStats_D_Dsply": lines, # LKAS status (lines) [0|31] - "LaHandsOff_D_Dsply": hands_on_wheel_dsply, # 0=HandsOn, 1=Level1 (w/o chime), 2=Level2 (w/ chime), 3=Suppressed + "LaActvStats_D_Dsply": lines, # LKAS status (lines) [0|31] + "LaHandsOff_D_Dsply": hands_on_wheel_dsply, # 0=HandsOn, 1=Level1 (w/o chime), 2=Level2 (w/ chime), 3=Suppressed } return packer.make_can_msg("IPMA_Data", CANBUS.main, values) -def create_acc_ui_command(packer, main_on: bool, enabled: bool, hud_control, stock_values: dict): +def create_acc_ui_msg(packer, main_on: bool, enabled: bool, hud_control, stock_values: dict): """ - Creates a CAN message for the Ford IPC adaptive cruise, forward collision warning and traffic jam - assist status. + Creates a CAN message for the Ford IPC adaptive cruise, forward collision warning and traffic jam assist status. Stock functionality is maintained by passing through unmodified signals. @@ -148,7 +203,8 @@ def create_acc_ui_command(packer, main_on: bool, enabled: bool, hud_control, sto return packer.make_can_msg("ACCDATA_3", CANBUS.main, values) -def create_button_command(packer, stock_values: dict, cancel = False, resume = False, tja_toggle = False, bus: int = CANBUS.camera): +def create_button_msg(packer, stock_values: dict, cancel=False, resume=False, tja_toggle=False, + bus: int = CANBUS.camera): """ Creates a CAN message for the Ford SCCM buttons/switches. diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index 4943db076..9e1366618 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -1,46 +1,53 @@ #!/usr/bin/env python3 from cereal import car from common.conversions import Conversions as CV -from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config -from selfdrive.car.ford.values import CAR, Ecu, TransmissionType, GearShifter +from selfdrive.car import STD_CARGO_KG, get_safety_config +from selfdrive.car.ford.values import CAR, Ecu from selfdrive.car.interfaces import CarInterfaceBase -CarParams = car.CarParams +TransmissionType = car.CarParams.TransmissionType +GearShifter = car.CarState.GearShifter class CarInterface(CarInterfaceBase): @staticmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None, experimental_long=False): - if car_fw is None: - car_fw = [] - - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) - + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "ford" + ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.ford)] + + # These cars are dashcam only until the port is finished ret.dashcamOnly = True - ret.safetyConfigs = [get_safety_config(CarParams.SafetyModel.ford)] - # Angle-based steering - ret.steerControlType = CarParams.SteerControlType.angle - ret.steerActuatorDelay = 0.4 + ret.radarUnavailable = True + ret.steerControlType = car.CarParams.SteerControlType.angle + ret.steerActuatorDelay = 0.2 ret.steerLimitTimer = 1.0 - tire_stiffness_factor = 1.0 - if candidate == CAR.ESCAPE_MK4: + if candidate == CAR.BRONCO_SPORT_MK1: + ret.wheelbase = 2.67 + ret.steerRatio = 17.7 + ret.mass = 1625 + STD_CARGO_KG + + elif candidate == CAR.ESCAPE_MK4: ret.wheelbase = 2.71 - ret.steerRatio = 14.3 # Copied from Focus + ret.steerRatio = 16.7 ret.mass = 1750 + STD_CARGO_KG elif candidate == CAR.EXPLORER_MK6: ret.wheelbase = 3.025 - ret.steerRatio = 16.8 # learned + ret.steerRatio = 16.8 ret.mass = 2050 + STD_CARGO_KG elif candidate == CAR.FOCUS_MK4: ret.wheelbase = 2.7 - ret.steerRatio = 13.8 # learned + ret.steerRatio = 15.0 ret.mass = 1350 + STD_CARGO_KG + elif candidate == CAR.MAVERICK_MK1: + ret.wheelbase = 3.076 + ret.steerRatio = 17.0 + ret.mass = 1650 + STD_CARGO_KG + else: raise ValueError(f"Unsupported car: {candidate}") @@ -60,10 +67,7 @@ class CarInterface(CarInterfaceBase): ret.minSteerSpeed = 0. ret.autoResumeSng = ret.minEnableSpeed == -1. - ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) ret.centerToFront = ret.wheelbase * 0.44 - ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront, - tire_stiffness_factor=tire_stiffness_factor) return ret def _update(self, c): @@ -74,5 +78,5 @@ class CarInterface(CarInterfaceBase): return ret - def apply(self, c): - return self.CC.update(c, self.CS) + def apply(self, c, now_nanos): + return self.CC.update(c, self.CS, now_nanos) diff --git a/selfdrive/car/ford/radar_interface.py b/selfdrive/car/ford/radar_interface.py index c94270300..ee4efb311 100644 --- a/selfdrive/car/ford/radar_interface.py +++ b/selfdrive/car/ford/radar_interface.py @@ -47,7 +47,7 @@ class RadarInterface(RadarInterfaceBase): self.updated_messages = set() self.track_id = 0 self.radar = DBC[CP.carFingerprint]['radar'] - if self.radar is None: + if self.radar is None or CP.radarUnavailable: self.rcp = None elif self.radar == RADAR.DELPHI_ESR: self.rcp = _create_delphi_esr_radar_can_parser() diff --git a/selfdrive/car/ford/values.py b/selfdrive/car/ford/values.py index 5114f8d06..526b74b16 100644 --- a/selfdrive/car/ford/values.py +++ b/selfdrive/car/ford/values.py @@ -1,23 +1,21 @@ -from collections import defaultdict, namedtuple +from collections import defaultdict from dataclasses import dataclass from enum import Enum -from typing import Dict, List, Union +from typing import Dict, List, Set, Union from cereal import car -from selfdrive.car import dbc_dict +from selfdrive.car import AngleRateLimit, dbc_dict from selfdrive.car.docs_definitions import CarInfo, Harness from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries Ecu = car.CarParams.Ecu -TransmissionType = car.CarParams.TransmissionType -GearShifter = car.CarState.GearShifter - -AngleRateLimit = namedtuple('AngleRateLimit', ['speed_points', 'max_angle_diff_points']) class CarControllerParams: # Messages: Lane_Assist_Data1, LateralMotionControl - LKAS_STEER_STEP = 5 + STEER_STEP = 5 + # Message: ACCDATA + ACC_CONTROL_STEP = 2 # Message: IPMA_Data LKAS_UI_STEP = 100 # Message: ACCDATA_3 @@ -25,12 +23,19 @@ class CarControllerParams: # Message: Steering_Data_FD1, but send twice as fast BUTTONS_STEP = 10 / 2 - LKAS_STEER_RATIO = 2.75 # Approximate ratio between LatCtlPath_An_Actl and steering angle in radians - # TODO: remove this once we understand how the EPS calculates the steering angle better - STEER_DRIVER_ALLOWANCE = 0.8 # Driver intervention threshold in Nm + CURVATURE_MAX = 0.02 # Max curvature for steering command, m^-1 + STEER_DRIVER_ALLOWANCE = 0.8 # Driver intervention threshold, Nm - RATE_LIMIT_UP = AngleRateLimit(speed_points=[0., 5., 15.], max_angle_diff_points=[5., .8, .15]) - RATE_LIMIT_DOWN = AngleRateLimit(speed_points=[0., 5., 15.], max_angle_diff_points=[5., 3.5, 0.4]) + # Curvature rate limits + # TODO: unify field names used by curvature and angle control cars + ANGLE_RATE_LIMIT_UP = AngleRateLimit(speed_bp=[5, 15, 25], angle_v=[0.005, 0.00056, 0.0002]) + ANGLE_RATE_LIMIT_DOWN = AngleRateLimit(speed_bp=[5, 15, 25], angle_v=[0.008, 0.00089, 0.00032]) + + ACCEL_MAX = 2.0 # m/s^s max acceleration + ACCEL_MIN = -3.5 # m/s^s max deceleration + + def __init__(self, CP): + pass class CANBUS: @@ -40,9 +45,14 @@ class CANBUS: class CAR: + BRONCO_SPORT_MK1 = "FORD BRONCO SPORT 1ST GEN" ESCAPE_MK4 = "FORD ESCAPE 4TH GEN" EXPLORER_MK6 = "FORD EXPLORER 6TH GEN" FOCUS_MK4 = "FORD FOCUS 4TH GEN" + MAVERICK_MK1 = "FORD MAVERICK 1ST GEN" + + +CANFD_CARS: Set[str] = set() class RADAR: @@ -60,12 +70,17 @@ class FordCarInfo(CarInfo): CAR_INFO: Dict[str, Union[CarInfo, List[CarInfo]]] = { + CAR.BRONCO_SPORT_MK1: FordCarInfo("Ford Bronco Sport 2021-22"), CAR.ESCAPE_MK4: [ FordCarInfo("Ford Escape 2020-21"), FordCarInfo("Ford Kuga 2020-21", "Driver Assistance Pack"), ], - CAR.EXPLORER_MK6: FordCarInfo("Ford Explorer 2020-22"), + CAR.EXPLORER_MK6: [ + FordCarInfo("Ford Explorer 2020-22"), + FordCarInfo("Lincoln Aviator 2021", "Co-Pilot360 Plus"), + ], CAR.FOCUS_MK4: FordCarInfo("Ford Focus EU 2019", "Driver Assistance Pack"), + CAR.MAVERICK_MK1: FordCarInfo("Ford Maverick 2022", "Co-Pilot360 Assist"), } FW_QUERY_CONFIG = FwQueryConfig( @@ -85,6 +100,30 @@ FW_QUERY_CONFIG = FwQueryConfig( ) FW_VERSIONS = { + CAR.BRONCO_SPORT_MK1: { + (Ecu.eps, 0x730, None): [ + b'LX6C-14D003-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LX6C-14D003-AK\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x760, None): [ + b'LX6C-2D053-RD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LX6C-2D053-RE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x764, None): [ + b'LB5T-14D049-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x706, None): [ + b'M1PT-14F397-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7E0, None): [ + b'M1PA-14C204-GF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'N1PA-14C204-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.shiftByWire, 0x732, None): [ + b'LX6P-14G395-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PZ1P-14G395-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + }, CAR.ESCAPE_MK4: { (Ecu.eps, 0x730, None): [ b'LX6C-14D003-AF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -129,13 +168,16 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x706, None): [ b'LB5T-14F397-AE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LB5T-14F397-AF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LC5T-14F397-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x7E0, None): [ b'LB5A-14C204-EAC\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'MB5A-14C204-MD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'MB5A-14C204-RC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'NB5A-14C204-HB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.shiftByWire, 0x732, None): [ + b'L1MP-14C561-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'L1MP-14G395-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'L1MP-14G395-AE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'L1MP-14G395-JB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -160,4 +202,26 @@ FW_VERSIONS = { (Ecu.shiftByWire, 0x732, None): [ ], }, + CAR.MAVERICK_MK1: { + (Ecu.eps, 0x730, None): [ + b'NZ6C-14D003-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x760, None): [ + b'NZ6C-2D053-AG\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x764, None): [ + b'NZ6T-14D049-AA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x706, None): [ + b'NZ6T-14F397-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7E0, None): [ + b'NZ6A-14C204-AAA\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'NZ6A-14C204-PA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'NZ6A-14C204-ZA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.shiftByWire, 0x732, None): [ + b'NZ6P-14G395-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + }, } diff --git a/selfdrive/car/fw_query_definitions.py b/selfdrive/car/fw_query_definitions.py index c7e4d4eb3..dd3b19f6d 100755 --- a/selfdrive/car/fw_query_definitions.py +++ b/selfdrive/car/fw_query_definitions.py @@ -57,11 +57,16 @@ class Request: whitelist_ecus: List[int] = field(default_factory=list) rx_offset: int = 0x8 bus: int = 1 + # FW responses from these queries will not be used for fingerprinting + logging: bool = False + # These requests are done once OBD multiplexing is disabled, after all others + non_obd: bool = False @dataclass class FwQueryConfig: requests: List[Request] + # TODO: make this automatic and remove hardcoded lists, or do fingerprinting with ecus # Overrides and removes from essential ecus for specific models and ecus (exact matching) non_essential_ecus: Dict[capnp.lib.capnp._EnumModule, List[str]] = field(default_factory=dict) # Ecus added for data collection, not to be fingerprinted on diff --git a/selfdrive/car/fw_versions.py b/selfdrive/car/fw_versions.py index f4d92ab96..8092ac0b7 100755 --- a/selfdrive/car/fw_versions.py +++ b/selfdrive/car/fw_versions.py @@ -5,6 +5,7 @@ from tqdm import tqdm import panda.python.uds as uds from cereal import car +from common.params import Params from selfdrive.car.ecu_addrs import get_ecu_addrs from selfdrive.car.interfaces import get_interface_attr from selfdrive.car.fingerprints import FW_VERSIONS @@ -29,10 +30,9 @@ def chunks(l, n=128): def build_fw_dict(fw_versions, filter_brand=None): fw_versions_dict = defaultdict(set) for fw in fw_versions: - if filter_brand is None or fw.brand == filter_brand: - addr = fw.address + if (filter_brand is None or fw.brand == filter_brand) and not fw.logging: sub_addr = fw.subAddress if fw.subAddress != 0 else None - fw_versions_dict[(addr, sub_addr)].add(fw.fwVersion) + fw_versions_dict[(fw.address, sub_addr)].add(fw.fwVersion) return dict(fw_versions_dict) @@ -90,7 +90,7 @@ def match_fw_to_car_fuzzy(fw_versions_dict, log=True, exclude=None): return set() -def match_fw_to_car_exact(fw_versions_dict): +def match_fw_to_car_exact(fw_versions_dict) -> Set[str]: """Do an exact FW match. Returns all cars that match the given FW versions for a list of "essential" ECUs. If an ECU is not considered essential the FW version can be missing to get a fingerprint, but if it's present it @@ -146,12 +146,16 @@ def match_fw_to_car(fw_versions, allow_exact=True, allow_fuzzy=True): return True, set() -def get_present_ecus(logcan, sendcan): +def get_present_ecus(logcan, sendcan, num_pandas=1) -> Set[Tuple[int, Optional[int], int]]: queries = list() parallel_queries = list() responses = set() for brand, r in REQUESTS: + # Skip query if no panda available + if r.bus > num_pandas * 4 - 1: + continue + for brand_versions in VERSIONS[brand].values(): for ecu_type, addr, sub_addr in brand_versions: # Only query ecus in whitelist if whitelist is not empty @@ -171,7 +175,7 @@ def get_present_ecus(logcan, sendcan): queries.insert(0, parallel_queries) - ecu_responses: Set[Tuple[int, Optional[int], int]] = set() + ecu_responses = set() for query in queries: ecu_responses.update(get_ecu_addrs(logcan, sendcan, set(query), responses, timeout=0.1)) return ecu_responses @@ -194,11 +198,21 @@ def get_brand_ecu_matches(ecu_rx_addrs): return brand_matches +def disable_obd_multiplexing(params): + if not params.get_bool("ObdMultiplexingDisabled"): + params.put_bool("FirmwareObdQueryDone", True) + + cloudlog.warning("Waiting for OBD multiplexing to be disabled") + params.get_bool("ObdMultiplexingDisabled", block=True) + cloudlog.warning("OBD multiplexing disabled") + + def get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, timeout=0.1, num_pandas=1, debug=False, progress=False): """Queries for FW versions ordering brands by likelihood, breaks when exact match is found""" all_car_fw = [] brand_matches = get_brand_ecu_matches(ecu_rx_addrs) + matched_brand: Optional[str] = None for brand in sorted(brand_matches, key=lambda b: len(brand_matches[b]), reverse=True): car_fw = get_fw_versions(logcan, sendcan, query_brand=brand, timeout=timeout, num_pandas=num_pandas, debug=debug, progress=progress) @@ -206,12 +220,20 @@ def get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, timeout=0.1, num_pand # Try to match using FW returned from this brand only matches = match_fw_to_car_exact(build_fw_dict(car_fw)) if len(matches) == 1: + matched_brand = brand break + disable_obd_multiplexing(Params()) + + # Do non-OBD queries for matched brand, or all if no match is found + for brand in FW_QUERY_CONFIGS.keys(): + if brand == matched_brand or matched_brand is None: + all_car_fw.extend(get_fw_versions(logcan, sendcan, query_brand=brand, timeout=timeout, num_pandas=num_pandas, obd_multiplexed=False, debug=debug, progress=progress)) + return all_car_fw -def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, num_pandas=1, debug=False, progress=False): +def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, num_pandas=1, obd_multiplexed=True, debug=False, progress=False): versions = VERSIONS.copy() # Each brand can define extra ECUs to query for data collection @@ -228,15 +250,19 @@ def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, # ECUs using a subaddress need be queried one by one, the rest can be done in parallel addrs = [] parallel_addrs = [] + logging_addrs = [] ecu_types = {} for brand, brand_versions in versions.items(): - for c in brand_versions.values(): - for ecu_type, addr, sub_addr in c.keys(): + for candidate, ecu in brand_versions.items(): + for ecu_type, addr, sub_addr in ecu.keys(): a = (brand, addr, sub_addr) if a not in ecu_types: ecu_types[a] = ecu_type + if a not in logging_addrs and candidate == "debug": + logging_addrs.append(a) + if sub_addr is None: if a not in parallel_addrs: parallel_addrs.append(a) @@ -255,6 +281,9 @@ def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, # Skip query if no panda available if r.bus > num_pandas * 4 - 1: continue + # Or if request is not designated for current multiplexing mode + elif r.non_obd == obd_multiplexed: + continue try: addrs = [(a, s) for (b, a, s) in addr_chunk if b in (brand, 'any') and @@ -265,13 +294,15 @@ def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, for (tx_addr, sub_addr), version in query.get_data(timeout).items(): f = car.CarParams.CarFw.new_message() - f.ecu = ecu_types.get((brand, tx_addr, sub_addr), Ecu.unknown) + ecu_key = (brand, tx_addr, sub_addr) + f.ecu = ecu_types.get(ecu_key, Ecu.unknown) f.fwVersion = version f.address = tx_addr f.responseAddress = uds.get_rx_addr_for_tx_addr(tx_addr, r.rx_offset) f.request = r.request f.brand = brand f.bus = r.bus + f.logging = r.logging or ecu_key in logging_addrs if sub_addr is not None: f.subAddress = sub_addr diff --git a/selfdrive/car/gm/carcontroller.py b/selfdrive/car/gm/carcontroller.py index a6cd2f19b..73085d30b 100644 --- a/selfdrive/car/gm/carcontroller.py +++ b/selfdrive/car/gm/carcontroller.py @@ -13,6 +13,8 @@ LongCtrlState = car.CarControl.Actuators.LongControlState # Camera cancels up to 0.1s after brake is pressed, ECM allows 0.5s CAMERA_CANCEL_DELAY_FRAMES = 10 +# Enforce a minimum interval between steering messages to avoid a fault +MIN_STEER_MSG_INTERVAL_MS = 15 class CarController: @@ -37,7 +39,7 @@ class CarController: self.packer_obj = CANPacker(DBC[self.CP.carFingerprint]['radar']) self.packer_ch = CANPacker(DBC[self.CP.carFingerprint]['chassis']) - def update(self, CC, CS): + def update(self, CC, CS, now_nanos): actuators = CC.actuators hud_control = CC.hudControl hud_alert = hud_control.visualAlert @@ -49,21 +51,28 @@ class CarController: can_sends = [] # Steering (Active: 50Hz, inactive: 10Hz) - # Attempt to sync with camera on startup at 50Hz, first few msgs are blocked - init_lka_counter = not self.sent_lka_steering_cmd and self.CP.networkLocation == NetworkLocation.fwdCamera - steer_step = self.params.INACTIVE_STEER_STEP - if CC.latActive or init_lka_counter: - steer_step = self.params.ACTIVE_STEER_STEP + steer_step = self.params.STEER_STEP if CC.latActive else self.params.INACTIVE_STEER_STEP + + if self.CP.networkLocation == NetworkLocation.fwdCamera: + # Also send at 50Hz: + # - on startup, first few msgs are blocked + # - until we're in sync with camera so counters align when relay closes, preventing a fault. + # openpilot can subtly drift, so this is activated throughout a drive to stay synced + out_of_sync = self.lka_steering_cmd_counter % 4 != (CS.cam_lka_steering_cmd_counter + 1) % 4 + if not self.sent_lka_steering_cmd or out_of_sync: + steer_step = self.params.STEER_STEP - # Avoid GM EPS faults when transmitting messages too close together: skip this transmit if we just received the - # next Panda loopback confirmation in the current CS frame. if CS.loopback_lka_steering_cmd_updated: self.lka_steering_cmd_counter += 1 self.sent_lka_steering_cmd = True - elif (self.frame - self.last_steer_frame) >= steer_step: + + # Avoid GM EPS faults when transmitting messages too close together: skip this transmit if we + # received the ASCMLKASteeringCmd loopback confirmation too recently + last_lka_steer_msg_ms = (now_nanos - CS.loopback_lka_steering_cmd_ts_nanos) * 1e-6 + if (self.frame - self.last_steer_frame) >= steer_step and last_lka_steer_msg_ms > MIN_STEER_MSG_INTERVAL_MS: # Initialize ASCMLKASteeringCmd counter using the camera until we get a msg on the bus - if init_lka_counter: - self.lka_steering_cmd_counter = CS.camera_lka_steering_cmd_counter + 1 + if not self.sent_lka_steering_cmd: + self.lka_steering_cmd_counter = CS.pt_lka_steering_cmd_counter + 1 if CC.latActive: new_steer = int(round(actuators.steer * self.params.STEER_MAX)) @@ -109,7 +118,7 @@ class CarController: # Radar needs to know current speed and yaw rate (50hz), # and that ADAS is alive (10hz) - if not self.CP.radarOffCan: + if not self.CP.radarUnavailable: tt = self.frame * DT_CTRL time_and_headlights_step = 10 if self.frame % time_and_headlights_step == 0: @@ -158,6 +167,7 @@ class CarController: new_actuators = actuators.copy() new_actuators.steer = self.apply_steer_last / self.params.STEER_MAX + new_actuators.steerOutputCan = self.apply_steer_last new_actuators.gas = self.apply_gas new_actuators.brake = self.apply_brake diff --git a/selfdrive/car/gm/carstate.py b/selfdrive/car/gm/carstate.py index df1b4b286..3c7d35f2d 100644 --- a/selfdrive/car/gm/carstate.py +++ b/selfdrive/car/gm/carstate.py @@ -17,8 +17,13 @@ class CarState(CarStateBase): super().__init__(CP) can_define = CANDefine(DBC[CP.carFingerprint]["pt"]) self.shifter_values = can_define.dv["ECMPRDNL2"]["PRNDL2"] + self.cluster_speed_hyst_gap = CV.KPH_TO_MS / 2. + self.cluster_min_speed = CV.KPH_TO_MS / 2. + self.loopback_lka_steering_cmd_updated = False - self.camera_lka_steering_cmd_counter = 0 + self.loopback_lka_steering_cmd_ts_nanos = 0 + self.pt_lka_steering_cmd_counter = 0 + self.cam_lka_steering_cmd_counter = 0 self.buttons_counter = 0 def update(self, pt_cp, cam_cp, loopback_cp): @@ -32,8 +37,10 @@ class CarState(CarStateBase): # Variables used for avoiding LKAS faults self.loopback_lka_steering_cmd_updated = len(loopback_cp.vl_all["ASCMLKASteeringCmd"]["RollingCounter"]) > 0 + self.loopback_lka_steering_cmd_ts_nanos = loopback_cp.ts_nanos["ASCMLKASteeringCmd"]["RollingCounter"] if self.CP.networkLocation == NetworkLocation.fwdCamera: - self.camera_lka_steering_cmd_counter = cam_cp.vl["ASCMLKASteeringCmd"]["RollingCounter"] + self.pt_lka_steering_cmd_counter = pt_cp.vl["ASCMLKASteeringCmd"]["RollingCounter"] + self.cam_lka_steering_cmd_counter = cam_cp.vl["ASCMLKASteeringCmd"]["RollingCounter"] ret.wheelSpeeds = self.get_wheel_speeds( pt_cp.vl["EBCMWheelSpdFront"]["FLWheelSpd"], @@ -93,13 +100,17 @@ class CarState(CarStateBase): ret.parkingBrake = pt_cp.vl["VehicleIgnitionAlt"]["ParkBrake"] == 1 ret.cruiseState.available = pt_cp.vl["ECMEngineStatus"]["CruiseMainOn"] != 0 ret.espDisabled = pt_cp.vl["ESPStatus"]["TractionControlOn"] != 1 - ret.accFaulted = pt_cp.vl["AcceleratorPedal2"]["CruiseState"] == AccState.FAULTED + ret.accFaulted = (pt_cp.vl["AcceleratorPedal2"]["CruiseState"] == AccState.FAULTED or + pt_cp.vl["EBCMFrictionBrakeStatus"]["FrictionBrakeUnavailable"] == 1) ret.cruiseState.enabled = pt_cp.vl["AcceleratorPedal2"]["CruiseState"] != AccState.OFF ret.cruiseState.standstill = pt_cp.vl["AcceleratorPedal2"]["CruiseState"] == AccState.STANDSTILL if self.CP.networkLocation == NetworkLocation.fwdCamera: ret.cruiseState.speed = cam_cp.vl["ASCMActiveCruiseControlStatus"]["ACCSpeedSetpoint"] * CV.KPH_TO_MS ret.stockAeb = cam_cp.vl["AEBCmd"]["AEBCmdActive"] != 0 + # openpilot controls nonAdaptive when not pcmCruise + if self.CP.pcmCruise: + ret.cruiseState.nonAdaptive = cam_cp.vl["ASCMActiveCruiseControlStatus"]["ACCCruiseState"] not in (2, 3) return ret @@ -112,6 +123,7 @@ class CarState(CarStateBase): ("AEBCmdActive", "AEBCmd"), ("RollingCounter", "ASCMLKASteeringCmd"), ("ACCSpeedSetpoint", "ASCMActiveCruiseControlStatus"), + ("ACCCruiseState", "ASCMActiveCruiseControlStatus"), ] checks += [ ("AEBCmd", 10), @@ -144,6 +156,7 @@ class CarState(CarStateBase): ("RLWheelSpd", "EBCMWheelSpdRear"), ("RRWheelSpd", "EBCMWheelSpdRear"), ("MovingBackward", "EBCMWheelSpdRear"), + ("FrictionBrakeUnavailable", "EBCMFrictionBrakeStatus"), ("PRNDL2", "ECMPRDNL2"), ("ManualMode", "ECMPRDNL2"), ("LKADriverAppldTrq", "PSCMStatus"), @@ -169,6 +182,7 @@ class CarState(CarStateBase): ("VehicleIgnitionAlt", 10), ("EBCMWheelSpdFront", 20), ("EBCMWheelSpdRear", 20), + ("EBCMFrictionBrakeStatus", 20), ("AcceleratorPedal2", 33), ("ASCMSteeringButton", 33), ("ECMEngineStatus", 100), @@ -176,6 +190,15 @@ class CarState(CarStateBase): ("ECMAcceleratorPos", 80), ] + # Used to read back last counter sent to PT by camera + if CP.networkLocation == NetworkLocation.fwdCamera: + signals += [ + ("RollingCounter", "ASCMLKASteeringCmd"), + ] + checks += [ + ("ASCMLKASteeringCmd", 0), + ] + if CP.transmissionType == TransmissionType.direct: signals.append(("RegenPaddle", "EBCMRegenPaddle")) checks.append(("EBCMRegenPaddle", 50)) diff --git a/selfdrive/car/gm/gmcan.py b/selfdrive/car/gm/gmcan.py index 56c981326..63189bcd8 100644 --- a/selfdrive/car/gm/gmcan.py +++ b/selfdrive/car/gm/gmcan.py @@ -6,7 +6,16 @@ def create_buttons(packer, bus, idx, button): values = { "ACCButtons": button, "RollingCounter": idx, + "ACCAlwaysOne": 1, + "DistanceButton": 0, } + + checksum = 240 + int(values["ACCAlwaysOne"] * 0xf) + checksum += values["RollingCounter"] * (0x4ef if values["ACCAlwaysOne"] != 0 else 0x3f0) + checksum -= int(values["ACCButtons"] - 1) << 4 # not correct if value is 0 + checksum -= 2 * values["DistanceButton"] + + values["SteeringButtonChecksum"] = checksum return packer.make_can_msg("ASCMSteeringButton", bus, values) diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index eb5ab7329..864db19f7 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -4,7 +4,7 @@ from math import fabs from panda import Panda from common.conversions import Conversions as CV -from selfdrive.car import STD_CARGO_KG, create_button_event, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config +from selfdrive.car import STD_CARGO_KG, create_button_event, scale_tire_stiffness, get_safety_config from selfdrive.car.gm.values import CAR, CruiseButtons, CarControllerParams, EV_CAR, CAMERA_ACC_CAR from selfdrive.car.interfaces import CarInterfaceBase @@ -44,8 +44,7 @@ class CarInterface(CarInterfaceBase): return CarInterfaceBase.get_steer_feedforward_default @staticmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None, experimental_long=False): - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "gm" ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.gm)] ret.autoResumeSng = False @@ -64,32 +63,34 @@ class CarInterface(CarInterfaceBase): if candidate in CAMERA_ACC_CAR: ret.experimentalLongitudinalAvailable = True ret.networkLocation = NetworkLocation.fwdCamera - ret.radarOffCan = True # no radar + ret.radarUnavailable = True # no radar ret.pcmCruise = True ret.safetyConfigs[0].safetyParam |= Panda.FLAG_GM_HW_CAM ret.minEnableSpeed = 5 * CV.KPH_TO_MS + ret.minSteerSpeed = 10 * CV.KPH_TO_MS + + # Tuning for experimental long + ret.longitudinalTuning.kpV = [2.0, 1.5] + ret.longitudinalTuning.kiV = [0.72] + ret.stopAccel = -2.0 + ret.stoppingDecelRate = 2.0 # reach brake quickly after enabling + ret.vEgoStopping = 0.25 + ret.vEgoStarting = 0.25 + ret.longitudinalActuatorDelayUpperBound = 0.5 if experimental_long: ret.pcmCruise = False ret.openpilotLongitudinalControl = True ret.safetyConfigs[0].safetyParam |= Panda.FLAG_GM_HW_CAM_LONG - # Tuning - ret.longitudinalTuning.kpV = [2.0, 1.5] - ret.longitudinalTuning.kiV = [0.72] - ret.stopAccel = -2.0 - ret.stoppingDecelRate = 2.0 # reach brake quickly after enabling - ret.vEgoStopping = 0.25 - ret.vEgoStarting = 0.25 - ret.longitudinalActuatorDelayUpperBound = 0.5 - else: # ASCM, OBD-II harness ret.openpilotLongitudinalControl = True ret.networkLocation = NetworkLocation.gateway - ret.radarOffCan = False + ret.radarUnavailable = False ret.pcmCruise = False # stock non-adaptive cruise control is kept off # supports stop and go, but initial engage must (conservatively) be above 18mph ret.minEnableSpeed = 18 * CV.MPH_TO_MS + ret.minSteerSpeed = 7 * CV.MPH_TO_MS # Tuning ret.longitudinalTuning.kpV = [2.4, 1.5] @@ -98,11 +99,9 @@ class CarInterface(CarInterfaceBase): # These cars have been put into dashcam only due to both a lack of users and test coverage. # These cars likely still work fine. Once a user confirms each car works and a test route is # added to selfdrive/car/tests/routes.py, we can remove it from this list. - ret.dashcamOnly = candidate in {CAR.CADILLAC_ATS, CAR.HOLDEN_ASTRA, CAR.MALIBU, CAR.BUICK_REGAL, CAR.EQUINOX, CAR.BOLT_EV} + ret.dashcamOnly = candidate in {CAR.CADILLAC_ATS, CAR.HOLDEN_ASTRA, CAR.MALIBU, CAR.BUICK_REGAL, CAR.EQUINOX} # Start with a baseline tuning for all GM vehicles. Override tuning as needed in each model section below. - # Some GMs need some tolerance above 10 kph to avoid a fault - ret.minSteerSpeed = 10.1 * CV.KPH_TO_MS ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2], [0.00]] ret.lateralTuning.pid.kf = 0.00004 # full torque for 20 deg at 80mph means 0.00007818594 @@ -160,6 +159,14 @@ class CarInterface(CarInterfaceBase): ret.steerRatio = 15.3 ret.centerToFront = ret.wheelbase * 0.5 + elif candidate == CAR.ESCALADE: + ret.minEnableSpeed = -1. # engage speed is decided by pcm + ret.mass = 5653. * CV.LB_TO_KG + STD_CARGO_KG # (5552+5815)/2 + ret.wheelbase = 2.95 # 116 inches in meters + ret.steerRatio = 17.3 + ret.centerToFront = ret.wheelbase * 0.5 + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) + elif candidate == CAR.ESCALADE_ESV: ret.minEnableSpeed = -1. # engage speed is decided by pcm ret.mass = 2739. + STD_CARGO_KG @@ -171,11 +178,11 @@ class CarInterface(CarInterfaceBase): ret.lateralTuning.pid.kf = 0.000045 tire_stiffness_factor = 1.0 - elif candidate in (CAR.BOLT_EV, CAR.BOLT_EUV): + elif candidate == CAR.BOLT_EUV: ret.mass = 1669. + STD_CARGO_KG ret.wheelbase = 2.63779 ret.steerRatio = 16.8 - ret.centerToFront = 2.15 # measured + ret.centerToFront = ret.wheelbase * 0.4 tire_stiffness_factor = 1.0 ret.steerActuatorDelay = 0.2 CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) @@ -195,10 +202,6 @@ class CarInterface(CarInterfaceBase): ret.centerToFront = ret.wheelbase * 0.4 CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) - # TODO: get actual value, for now starting with reasonable value for - # civic and scaling by mass and wheelbase - ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) - # TODO: start from empirically derived lateral slip stiffness for the civic and scale by # mass and CG position, so all cars will have approximately similar dyn behaviors ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront, @@ -241,5 +244,5 @@ class CarInterface(CarInterfaceBase): return ret - def apply(self, c): - return self.CC.update(c, self.CS) + def apply(self, c, now_nanos): + return self.CC.update(c, self.CS, now_nanos) diff --git a/selfdrive/car/gm/radar_interface.py b/selfdrive/car/gm/radar_interface.py index 6904e6f89..b86a85f91 100755 --- a/selfdrive/car/gm/radar_interface.py +++ b/selfdrive/car/gm/radar_interface.py @@ -3,7 +3,7 @@ import math from cereal import car from common.conversions import Conversions as CV from opendbc.can.parser import CANParser -from selfdrive.car.gm.values import DBC, CAR, CanBus +from selfdrive.car.gm.values import DBC, CanBus from selfdrive.car.interfaces import RadarInterfaceBase RADAR_HEADER_MSG = 1120 @@ -16,9 +16,6 @@ LAST_RADAR_MSG = RADAR_HEADER_MSG + NUM_SLOTS def create_radar_can_parser(car_fingerprint): - if car_fingerprint not in (CAR.VOLT, CAR.MALIBU, CAR.HOLDEN_ASTRA, CAR.ACADIA, CAR.CADILLAC_ATS, CAR.ESCALADE_ESV): - return None - # C1A-ARS3-A by Continental radar_targets = list(range(SLOT_1_MSG, SLOT_1_MSG + NUM_SLOTS)) signals = list(zip(['FLRRNumValidTargets', @@ -34,11 +31,12 @@ def create_radar_can_parser(car_fingerprint): return CANParser(DBC[car_fingerprint]['radar'], signals, checks, CanBus.OBSTACLE) + class RadarInterface(RadarInterfaceBase): def __init__(self, CP): super().__init__(CP) - self.rcp = create_radar_can_parser(CP.carFingerprint) + self.rcp = None if CP.radarUnavailable else create_radar_can_parser(CP.carFingerprint) self.trigger_msg = LAST_RADAR_MSG self.updated_messages = set() diff --git a/selfdrive/car/gm/values.py b/selfdrive/car/gm/values.py index 03392ba0f..de5b7be55 100644 --- a/selfdrive/car/gm/values.py +++ b/selfdrive/car/gm/values.py @@ -1,5 +1,5 @@ from collections import defaultdict -from dataclasses import dataclass, field +from dataclasses import dataclass from enum import Enum from typing import Dict, List, Union @@ -11,10 +11,10 @@ Ecu = car.CarParams.Ecu class CarControllerParams: STEER_MAX = 300 # GM limit is 3Nm. Used by carcontroller to generate LKA output - ACTIVE_STEER_STEP = 2 # Active control frames per command (50hz) + STEER_STEP = 3 # Active control frames per command (~33hz) INACTIVE_STEER_STEP = 10 # Inactive control frames per command (10hz) - STEER_DELTA_UP = 7 # Delta rates require review due to observed EPS weakness - STEER_DELTA_DOWN = 17 + STEER_DELTA_UP = 10 # Delta rates require review due to observed EPS weakness + STEER_DELTA_DOWN = 25 STEER_DRIVER_ALLOWANCE = 50 STEER_DRIVER_MULTIPLIER = 4 STEER_DRIVER_FACTOR = 100 @@ -67,8 +67,8 @@ class CAR: MALIBU = "CHEVROLET MALIBU PREMIER 2017" ACADIA = "GMC ACADIA DENALI 2018" BUICK_REGAL = "BUICK REGAL ESSENCE 2018" + ESCALADE = "CADILLAC ESCALADE 2017" ESCALADE_ESV = "CADILLAC ESCALADE ESV 2016" - BOLT_EV = "CHEVROLET BOLT EV 2022" BOLT_EUV = "CHEVROLET BOLT EUV 2022" SILVERADO = "CHEVROLET SILVERADO 1500 2020" EQUINOX = "CHEVROLET EQUINOX 2019" @@ -76,7 +76,7 @@ class CAR: class Footnote(Enum): OBD_II = CarFootnote( - 'Requires a community built ASCM harness. ' + + 'Requires a community built ASCM harness. ' + 'NOTE: disconnecting the ASCM disables Automatic Emergency Braking (AEB).', Column.MODEL) @@ -84,8 +84,13 @@ class Footnote(Enum): @dataclass class GMCarInfo(CarInfo): package: str = "Adaptive Cruise Control (ACC)" - harness: Enum = Harness.obd_ii - footnotes: List[Enum] = field(default_factory=lambda: [Footnote.OBD_II]) + + def init_make(self, CP: car.CarParams): + if CP.networkLocation == car.CarParams.NetworkLocation.fwdCamera: + self.harness = Harness.gm + else: + self.harness = Harness.obd_ii + self.footnotes.append(Footnote.OBD_II) CAR_INFO: Dict[str, Union[GMCarInfo, List[GMCarInfo]]] = { @@ -95,14 +100,17 @@ CAR_INFO: Dict[str, Union[GMCarInfo, List[GMCarInfo]]] = { CAR.MALIBU: GMCarInfo("Chevrolet Malibu Premier 2017"), CAR.ACADIA: GMCarInfo("GMC Acadia 2018", video_link="https://www.youtube.com/watch?v=0ZN6DdsBUZo"), CAR.BUICK_REGAL: GMCarInfo("Buick Regal Essence 2018"), + CAR.ESCALADE: GMCarInfo("Cadillac Escalade 2017", "Driver Assist Package"), CAR.ESCALADE_ESV: GMCarInfo("Cadillac Escalade ESV 2016", "Adaptive Cruise Control (ACC) & LKAS"), - CAR.BOLT_EV: GMCarInfo("Chevrolet Bolt EV 2022-23", footnotes=[], harness=Harness.gm), - CAR.BOLT_EUV: GMCarInfo("Chevrolet Bolt EUV 2022-23", "Premier or Premier Redline Trim without Super Cruise Package", "https://youtu.be/xvwzGMUA210", footnotes=[], harness=Harness.gm), - CAR.SILVERADO: [ - GMCarInfo("Chevrolet Silverado 1500 2020-21", "Safety Package II", footnotes=[], harness=Harness.gm), - GMCarInfo("GMC Sierra 1500 2020-21", "Driver Alert Package II", footnotes=[], harness=Harness.gm), + CAR.BOLT_EUV: [ + GMCarInfo("Chevrolet Bolt EUV 2022-23", "Premier or Premier Redline Trim without Super Cruise Package", "https://youtu.be/xvwzGMUA210"), + GMCarInfo("Chevrolet Bolt EV 2022-23", "2LT Trim with Adaptive Cruise Control Package"), ], - CAR.EQUINOX: GMCarInfo("Chevrolet Equinox 2019-22", footnotes=[], harness=Harness.gm), + CAR.SILVERADO: [ + GMCarInfo("Chevrolet Silverado 1500 2020-21", "Safety Package II"), + GMCarInfo("GMC Sierra 1500 2020-21", "Driver Alert Package II", "https://youtu.be/5HbNoBLzRwE"), + ], + CAR.EQUINOX: GMCarInfo("Chevrolet Equinox 2019-22"), } @@ -168,6 +176,10 @@ FINGERPRINTS = { { 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 208: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 289: 8, 298: 8, 304: 1, 309: 8, 313: 8, 320: 3, 322: 7, 328: 1, 338: 6, 340: 6, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 393: 8, 398: 8, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 454: 8, 455: 7, 462: 4, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 510: 8, 532: 6, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 567: 5, 573: 1, 577: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 961: 8, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1225: 8, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1328: 4, 1417: 8, 1601: 8, 1906: 7, 1907: 7, 1912: 7, 1914: 7, 1919: 7, 1920: 7, 1930: 7, 2016: 8, 2024: 8 }], + CAR.ESCALADE: [ + { + 170: 8, 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 208: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 393: 7, 398: 8, 407: 4, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 454: 8, 455: 7, 460: 5, 462: 4, 463: 3, 479: 3, 481: 7, 485: 8, 487: 8, 489: 8, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 510: 8, 532: 6, 534: 2, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 573: 1, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 715: 8, 717: 5, 719: 5, 761: 7, 801: 8, 804: 3, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 961: 8, 967: 4, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1017: 8, 1019: 2, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1223: 2, 1225: 7, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1609: 8, 1613: 8, 1649: 8, 1792: 8, 1798: 8, 1824: 8, 1825: 8, 1840: 8, 1842: 8, 1858: 8, 1860: 8, 1863: 8, 1872: 8, 1875: 8, 1882: 8, 1888: 8, 1889: 8, 1892: 8, 1906: 7, 1907: 7, 1912: 7, 1914: 7, 1917: 7, 1918: 7, 1919: 7, 1920: 7, 1930: 7, 1937: 8, 1953: 8, 1968: 8, 2001: 8, 2017: 8, 2018: 8, 2020: 8, 2026: 8 + }], CAR.ESCALADE_ESV: [ { 309: 1, 848: 8, 849: 8, 850: 8, 851: 8, 852: 8, 853: 8, 854: 3, 1056: 6, 1057: 8, 1058: 8, 1059: 8, 1060: 8, 1061: 8, 1062: 8, 1063: 8, 1064: 8, 1065: 8, 1066: 8, 1067: 8, 1068: 8, 1120: 8, 1121: 8, 1122: 8, 1123: 8, 1124: 8, 1125: 8, 1126: 8, 1127: 8, 1128: 8, 1129: 8, 1130: 8, 1131: 8, 1132: 8, 1133: 8, 1134: 8, 1135: 8, 1136: 8, 1137: 8, 1138: 8, 1139: 8, 1140: 8, 1141: 8, 1142: 8, 1143: 8, 1146: 8, 1147: 8, 1148: 8, 1149: 8, 1150: 8, 1151: 8, 1216: 8, 1217: 8, 1218: 8, 1219: 8, 1220: 8, 1221: 8, 1222: 8, 1223: 8, 1224: 8, 1225: 8, 1226: 8, 1232: 8, 1233: 8, 1234: 8, 1235: 8, 1236: 8, 1237: 8, 1238: 8, 1239: 8, 1240: 8, 1241: 8, 1242: 8, 1787: 8, 1788: 8 @@ -188,9 +200,9 @@ FINGERPRINTS = { DBC: Dict[str, Dict[str, str]] = defaultdict(lambda: dbc_dict('gm_global_a_powertrain_generated', 'gm_global_a_object', chassis_dbc='gm_global_a_chassis')) -EV_CAR = {CAR.VOLT, CAR.BOLT_EV, CAR.BOLT_EUV} +EV_CAR = {CAR.VOLT, CAR.BOLT_EUV} # We're integrated at the camera with VOACC on these cars (instead of ASCM w/ OBD-II harness) -CAMERA_ACC_CAR = {CAR.BOLT_EV, CAR.BOLT_EUV, CAR.SILVERADO, CAR.EQUINOX} +CAMERA_ACC_CAR = {CAR.BOLT_EUV, CAR.SILVERADO, CAR.EQUINOX} STEER_THRESHOLD = 1.0 diff --git a/selfdrive/car/honda/carcontroller.py b/selfdrive/car/honda/carcontroller.py index 790dce181..4dc1dc813 100644 --- a/selfdrive/car/honda/carcontroller.py +++ b/selfdrive/car/honda/carcontroller.py @@ -124,7 +124,7 @@ class CarController: self.brake = 0.0 self.last_steer = 0.0 - def update(self, CC, CS): + def update(self, CC, CS, now_nanos): actuators = CC.actuators hud_control = CC.hudControl hud_v_cruise = hud_control.setSpeed * CV.MS_TO_KPH if hud_control.speedVisible else 255 @@ -189,14 +189,14 @@ class CarController: clip(CS.out.vEgo + 0.0, 0.0, 100.0), clip(CS.out.vEgo + 5.0, 0.0, 100.0)] pcm_speed = interp(gas - brake, pcm_speed_BP, pcm_speed_V) - pcm_accel = int(1.0 * 0xc6) + pcm_accel = int(1.0 * self.params.NIDEC_GAS_MAX) else: pcm_speed_V = [0.0, clip(CS.out.vEgo - 2.0, 0.0, 100.0), clip(CS.out.vEgo + 2.0, 0.0, 100.0), clip(CS.out.vEgo + 5.0, 0.0, 100.0)] pcm_speed = interp(gas - brake, pcm_speed_BP, pcm_speed_V) - pcm_accel = int(clip((accel / 1.44) / max_accel, 0.0, 1.0) * 0xc6) + pcm_accel = int(clip((accel / 1.44) / max_accel, 0.0, 1.0) * self.params.NIDEC_GAS_MAX) if not self.CP.openpilotLongitudinalControl: if self.frame % 2 == 0 and self.CP.carFingerprint not in HONDA_BOSCH_RADARLESS: # radarless cars don't have supplemental message @@ -254,7 +254,7 @@ class CarController: self.speed = pcm_speed if not self.CP.enableGasInterceptor: - self.gas = pcm_accel / 0xc6 + self.gas = pcm_accel / self.params.NIDEC_GAS_MAX new_actuators = actuators.copy() new_actuators.speed = self.speed @@ -262,6 +262,7 @@ class CarController: new_actuators.gas = self.gas new_actuators.brake = self.brake new_actuators.steer = self.last_steer + new_actuators.steerOutputCan = apply_steer self.frame += 1 return new_actuators, can_sends diff --git a/selfdrive/car/honda/carstate.py b/selfdrive/car/honda/carstate.py index a37667fd3..16880d1b1 100644 --- a/selfdrive/car/honda/carstate.py +++ b/selfdrive/car/honda/carstate.py @@ -274,7 +274,7 @@ class CarState(CarStateBase): ret.cruiseState.available = bool(cp.vl[self.main_on_sig_msg]["MAIN_ON"]) # Gets rid of Pedal Grinding noise when brake is pressed at slow speeds for some models - if self.CP.carFingerprint in (CAR.PILOT, CAR.PASSPORT, CAR.RIDGELINE): + if self.CP.carFingerprint in (CAR.PILOT, CAR.RIDGELINE): if ret.brake > 0.1: ret.brakePressed = True diff --git a/selfdrive/car/honda/hondacan.py b/selfdrive/car/honda/hondacan.py index 87f8e6c5d..17681444a 100644 --- a/selfdrive/car/honda/hondacan.py +++ b/selfdrive/car/honda/hondacan.py @@ -13,7 +13,8 @@ def get_pt_bus(car_fingerprint): def get_lkas_cmd_bus(car_fingerprint, radar_disabled=False): - if radar_disabled: + no_radar = car_fingerprint in HONDA_BOSCH_RADARLESS + if radar_disabled or no_radar: # when radar is disabled, steering commands are sent directly to powertrain bus return get_pt_bus(car_fingerprint) # normally steering commands are sent to radar, which forwards them to powertrain bus @@ -104,13 +105,13 @@ def create_bosch_supplemental_1(packer, car_fingerprint): def create_ui_commands(packer, CP, enabled, pcm_speed, hud, is_metric, acc_hud, lkas_hud): commands = [] bus_pt = get_pt_bus(CP.carFingerprint) - radar_disabled = CP.carFingerprint in HONDA_BOSCH and CP.openpilotLongitudinalControl + radar_disabled = CP.carFingerprint in (HONDA_BOSCH - HONDA_BOSCH_RADARLESS) and CP.openpilotLongitudinalControl bus_lkas = get_lkas_cmd_bus(CP.carFingerprint, radar_disabled) if CP.openpilotLongitudinalControl: acc_hud_values = { 'CRUISE_SPEED': hud.v_cruise, - 'ENABLE_MINI_CAR': 1, + 'ENABLE_MINI_CAR': 1 if enabled else 0, 'HUD_DISTANCE': 0, # max distance setting on display 'IMPERIAL_UNIT': int(not is_metric), 'HUD_LEAD': 2 if enabled and hud.lead_visible else 1 if enabled else 0, @@ -153,7 +154,7 @@ def create_ui_commands(packer, CP, enabled, pcm_speed, hud, is_metric, acc_hud, else: commands.append(packer.make_can_msg('LKAS_HUD', bus_lkas, lkas_hud_values)) - if radar_disabled and CP.carFingerprint in HONDA_BOSCH: + if radar_disabled: radar_hud_values = { 'CMBS_OFF': 0x01, 'SET_TO_1': 0x01, diff --git a/selfdrive/car/honda/interface.py b/selfdrive/car/honda/interface.py index e397f0283..d3cf9fa89 100755 --- a/selfdrive/car/honda/interface.py +++ b/selfdrive/car/honda/interface.py @@ -4,7 +4,7 @@ from panda import Panda from common.conversions import Conversions as CV from common.numpy_fast import interp from selfdrive.car.honda.values import CarControllerParams, CruiseButtons, HondaFlags, CAR, HONDA_BOSCH, HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_ALT_BRAKE_SIGNAL, HONDA_BOSCH_RADARLESS -from selfdrive.car import STD_CARGO_KG, CivicParams, create_button_event, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config +from selfdrive.car import STD_CARGO_KG, CivicParams, create_button_event, scale_tire_stiffness, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.disable_ecu import disable_ecu @@ -21,6 +21,8 @@ class CarInterface(CarInterfaceBase): def get_pid_accel_limits(CP, current_speed, cruise_speed): if CP.carFingerprint in HONDA_BOSCH: return CarControllerParams.BOSCH_ACCEL_MIN, CarControllerParams.BOSCH_ACCEL_MAX + elif CP.enableGasInterceptor: + return CarControllerParams.NIDEC_ACCEL_MIN, CarControllerParams.NIDEC_ACCEL_MAX else: # NIDECs don't allow acceleration near cruise_speed, # so limit limits of pid to prevent windup @@ -29,13 +31,12 @@ class CarInterface(CarInterfaceBase): return CarControllerParams.NIDEC_ACCEL_MIN, interp(current_speed, ACCEL_MAX_BP, ACCEL_MAX_VALS) @staticmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[], experimental_long=False): # pylint: disable=dangerous-default-value - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "honda" if candidate in HONDA_BOSCH: ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.hondaBosch)] - ret.radarOffCan = True + ret.radarUnavailable = True if candidate not in HONDA_BOSCH_RADARLESS: # Disable the radar and let openpilot control longitudinal @@ -232,11 +233,11 @@ class CarInterface(CarInterfaceBase): else: ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end - elif candidate in (CAR.PILOT, CAR.PASSPORT): - ret.mass = 4204. * CV.LB_TO_KG + STD_CARGO_KG # average weight - ret.wheelbase = 2.82 + elif candidate == CAR.PILOT: + ret.mass = 4278. * CV.LB_TO_KG + STD_CARGO_KG # average weight + ret.wheelbase = 2.86 ret.centerToFront = ret.wheelbase * 0.428 - ret.steerRatio = 17.25 # as spec + ret.steerRatio = 16.0 # as spec ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end tire_stiffness_factor = 0.444 ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.38], [0.11]] @@ -291,10 +292,6 @@ class CarInterface(CarInterfaceBase): stop_and_go = candidate in (HONDA_BOSCH | {CAR.CIVIC}) or ret.enableGasInterceptor ret.minEnableSpeed = -1. if stop_and_go else 25.5 * CV.MPH_TO_MS - # TODO: get actual value, for now starting with reasonable value for - # civic and scaling by mass and wheelbase - ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) - # TODO: start from empirically derived lateral slip stiffness for the civic and scale by # mass and CG position, so all cars will have approximately similar dyn behaviors ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront, @@ -353,5 +350,5 @@ class CarInterface(CarInterfaceBase): # pass in a car.CarControl # to be called @ 100hz - def apply(self, c): - return self.CC.update(c, self.CS) + def apply(self, c, now_nanos): + return self.CC.update(c, self.CS, now_nanos) diff --git a/selfdrive/car/honda/radar_interface.py b/selfdrive/car/honda/radar_interface.py index 629ab01d4..660be4c44 100755 --- a/selfdrive/car/honda/radar_interface.py +++ b/selfdrive/car/honda/radar_interface.py @@ -21,7 +21,7 @@ class RadarInterface(RadarInterfaceBase): self.track_id = 0 self.radar_fault = False self.radar_wrong_config = False - self.radar_off_can = CP.radarOffCan + self.radar_off_can = CP.radarUnavailable self.radar_ts = CP.radarTimeStep self.delay = int(round(0.1 / CP.radarTimeStep)) # 0.1s delay of radar diff --git a/selfdrive/car/honda/values.py b/selfdrive/car/honda/values.py index 2510f2e1f..c085c3fe8 100644 --- a/selfdrive/car/honda/values.py +++ b/selfdrive/car/honda/values.py @@ -4,9 +4,10 @@ from typing import Dict, List, Optional, Union from cereal import car from common.conversions import Conversions as CV +from panda.python import uds from selfdrive.car import dbc_dict from selfdrive.car.docs_definitions import CarFootnote, CarInfo, Column, Harness -from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries +from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries, p16 Ecu = car.CarParams.Ecu VisualAlert = car.CarControl.HUDControl.VisualAlert @@ -27,6 +28,7 @@ class CarControllerParams: NIDEC_MAX_ACCEL_V = [0.5, 2.4, 1.4, 0.6] NIDEC_MAX_ACCEL_BP = [0.0, 4.0, 10., 20.] + NIDEC_GAS_MAX = 198 # 0xc6 NIDEC_BRAKE_MAX = 1024 // 4 BOSCH_ACCEL_MIN = -3.5 # m/s^2 @@ -90,7 +92,6 @@ class CAR: ACURA_RDX = "ACURA RDX 2018" ACURA_RDX_3G = "ACURA RDX 2020" PILOT = "HONDA PILOT 2017" - PASSPORT = "HONDA PASSPORT 2021" RIDGELINE = "HONDA RIDGELINE 2017" INSIGHT = "HONDA INSIGHT 2019" HONDA_E = "HONDA E 2020" @@ -106,48 +107,91 @@ class Footnote(Enum): class HondaCarInfo(CarInfo): package: str = "Honda Sensing" + def init_make(self, CP: car.CarParams): + if CP.carFingerprint in HONDA_BOSCH: + self.harness = Harness.bosch_b if CP.carFingerprint in HONDA_BOSCH_RADARLESS else Harness.bosch_a + else: + self.harness = Harness.nidec + CAR_INFO: Dict[str, Optional[Union[HondaCarInfo, List[HondaCarInfo]]]] = { CAR.ACCORD: [ - HondaCarInfo("Honda Accord 2018-22", "All", "https://www.youtube.com/watch?v=mrUwlj3Mi58", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), - HondaCarInfo("Honda Inspire 2018", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), + HondaCarInfo("Honda Accord 2018-22", "All", "https://www.youtube.com/watch?v=mrUwlj3Mi58", min_steer_speed=3. * CV.MPH_TO_MS), + HondaCarInfo("Honda Inspire 2018", "All", min_steer_speed=3. * CV.MPH_TO_MS), ], - CAR.ACCORDH: HondaCarInfo("Honda Accord Hybrid 2018-22", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), - CAR.CIVIC: HondaCarInfo("Honda Civic 2016-18", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.nidec, video_link="https://youtu.be/-IkImTe1NYE"), + CAR.ACCORDH: HondaCarInfo("Honda Accord Hybrid 2018-22", "All", min_steer_speed=3. * CV.MPH_TO_MS), + CAR.CIVIC: HondaCarInfo("Honda Civic 2016-18", min_steer_speed=12. * CV.MPH_TO_MS, video_link="https://youtu.be/-IkImTe1NYE"), CAR.CIVIC_BOSCH: [ - HondaCarInfo("Honda Civic 2019-21", "All", "https://www.youtube.com/watch?v=4Iz1Mz5LGF8", [Footnote.CIVIC_DIESEL], 2. * CV.MPH_TO_MS, harness=Harness.bosch_a), - HondaCarInfo("Honda Civic Hatchback 2017-21", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.bosch_a), + HondaCarInfo("Honda Civic 2019-21", "All", "https://www.youtube.com/watch?v=4Iz1Mz5LGF8", [Footnote.CIVIC_DIESEL], 2. * CV.MPH_TO_MS), + HondaCarInfo("Honda Civic Hatchback 2017-21", min_steer_speed=12. * CV.MPH_TO_MS), ], CAR.CIVIC_BOSCH_DIESEL: None, # same platform CAR.CIVIC_2022: [ - HondaCarInfo("Honda Civic 2022", "All", harness=Harness.bosch_b), - HondaCarInfo("Honda Civic Hatchback 2022", "All", harness=Harness.bosch_b), + HondaCarInfo("Honda Civic 2022", "All"), + HondaCarInfo("Honda Civic Hatchback 2022", "All"), ], - CAR.ACURA_ILX: HondaCarInfo("Acura ILX 2016-19", "AcuraWatch Plus", min_steer_speed=25. * CV.MPH_TO_MS, harness=Harness.nidec), - CAR.CRV: HondaCarInfo("Honda CR-V 2015-16", "Touring Trim", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.nidec), - CAR.CRV_5G: HondaCarInfo("Honda CR-V 2017-22", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.bosch_a), + CAR.ACURA_ILX: HondaCarInfo("Acura ILX 2016-19", "AcuraWatch Plus", min_steer_speed=25. * CV.MPH_TO_MS), + CAR.CRV: HondaCarInfo("Honda CR-V 2015-16", "Touring Trim", min_steer_speed=12. * CV.MPH_TO_MS), + CAR.CRV_5G: HondaCarInfo("Honda CR-V 2017-22", min_steer_speed=12. * CV.MPH_TO_MS), CAR.CRV_EU: None, # HondaCarInfo("Honda CR-V EU", "Touring"), # Euro version of CRV Touring - CAR.CRV_HYBRID: HondaCarInfo("Honda CR-V Hybrid 2017-19", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.bosch_a), - CAR.FIT: HondaCarInfo("Honda Fit 2018-20", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.nidec), - CAR.FREED: HondaCarInfo("Honda Freed 2020", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.nidec), - CAR.HRV: HondaCarInfo("Honda HR-V 2019-22", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.nidec), - CAR.ODYSSEY: HondaCarInfo("Honda Odyssey 2018-20", harness=Harness.nidec), + CAR.CRV_HYBRID: HondaCarInfo("Honda CR-V Hybrid 2017-19", min_steer_speed=12. * CV.MPH_TO_MS), + CAR.FIT: HondaCarInfo("Honda Fit 2018-20", min_steer_speed=12. * CV.MPH_TO_MS), + CAR.FREED: HondaCarInfo("Honda Freed 2020", min_steer_speed=12. * CV.MPH_TO_MS), + CAR.HRV: HondaCarInfo("Honda HR-V 2019-22", min_steer_speed=12. * CV.MPH_TO_MS), + CAR.ODYSSEY: HondaCarInfo("Honda Odyssey 2018-20"), CAR.ODYSSEY_CHN: None, # Chinese version of Odyssey - CAR.ACURA_RDX: HondaCarInfo("Acura RDX 2016-18", "AcuraWatch Plus", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.nidec), - CAR.ACURA_RDX_3G: HondaCarInfo("Acura RDX 2019-22", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), - CAR.PILOT: HondaCarInfo("Honda Pilot 2016-22", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.nidec), - CAR.PASSPORT: HondaCarInfo("Honda Passport 2019-21", "All", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.nidec), - CAR.RIDGELINE: HondaCarInfo("Honda Ridgeline 2017-22", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.nidec), - CAR.INSIGHT: HondaCarInfo("Honda Insight 2019-22", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), - CAR.HONDA_E: HondaCarInfo("Honda e 2020", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), + CAR.ACURA_RDX: HondaCarInfo("Acura RDX 2016-18", "AcuraWatch Plus", min_steer_speed=12. * CV.MPH_TO_MS), + CAR.ACURA_RDX_3G: HondaCarInfo("Acura RDX 2019-22", "All", min_steer_speed=3. * CV.MPH_TO_MS), + CAR.PILOT: [ + HondaCarInfo("Honda Pilot 2016-22", min_steer_speed=12. * CV.MPH_TO_MS), + HondaCarInfo("Honda Passport 2019-21", "All", min_steer_speed=12. * CV.MPH_TO_MS), + ], + CAR.RIDGELINE: HondaCarInfo("Honda Ridgeline 2017-22", min_steer_speed=12. * CV.MPH_TO_MS), + CAR.INSIGHT: HondaCarInfo("Honda Insight 2019-22", "All", min_steer_speed=3. * CV.MPH_TO_MS), + CAR.HONDA_E: HondaCarInfo("Honda e 2020", "All", min_steer_speed=3. * CV.MPH_TO_MS), } +HONDA_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ + p16(0xF112) +HONDA_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \ + p16(0xF112) + FW_QUERY_CONFIG = FwQueryConfig( requests=[ + # Currently used to fingerprint Request( [StdQueries.UDS_VERSION_REQUEST], [StdQueries.UDS_VERSION_RESPONSE], + bus=1, ), + + # Data collection requests: + # Log extra identifiers for current ECUs + Request( + [HONDA_VERSION_REQUEST], + [HONDA_VERSION_RESPONSE], + bus=1, + logging=True, + ), + # Nidec PT bus + Request( + [StdQueries.UDS_VERSION_REQUEST], + [StdQueries.UDS_VERSION_RESPONSE], + bus=0, + logging=True, + ), + # Bosch PT bus + Request( + [StdQueries.UDS_VERSION_REQUEST], + [StdQueries.UDS_VERSION_RESPONSE], + bus=1, + logging=True, + non_obd=True, + ), + ], + extra_ecus=[ + # The only other ECU on PT bus accessible by camera on radarless Civic + (Ecu.unknown, 0x18DAB3F1, None), ], ) @@ -236,7 +280,7 @@ FW_VERSIONS = { b'39990-TVA-A340\x00\x00', b'39990-TVA-X030\x00\x00', b'39990-TVA-X040\x00\x00', - b'39990-TVA,A150\x00\x00', + b'39990-TVA,A150\x00\x00', # modified firmware b'39990-TVE-H130\x00\x00', ], (Ecu.unknown, 0x18da3af1, None): [ @@ -1060,6 +1104,8 @@ FW_VERSIONS = { b'28101-5EZ-A060\x00\x00', b'28101-5EZ-A100\x00\x00', b'28101-5EZ-A210\x00\x00', + b'28101-5EZ-A600\x00\x00', + b'28101-5EZ-A430\x00\x00', ], (Ecu.programmedFuelInjection, 0x18da10f1, None): [ b'37805-RLV-4060\x00\x00', @@ -1071,6 +1117,9 @@ FW_VERSIONS = { b'37805-RLV-C520\x00\x00', b'37805-RLV-C530\x00\x00', b'37805-RLV-C910\x00\x00', + b'37805-RLV-B220\x00\x00', + b'37805-RLV-B210\x00\x00', + b'37805-RLV-L160\x00\x00', ], (Ecu.gateway, 0x18daeff1, None): [ b'38897-TG7-A030\x00\x00', @@ -1103,6 +1152,7 @@ FW_VERSIONS = { b'36161-TGS-A130\x00\x00', b'36161-TGT-A030\x00\x00', b'36161-TGT-A130\x00\x00', + b'36161-TGS-A030\x00\x00', ], (Ecu.srs, 0x18da53f1, None): [ b'77959-TG7-A020\x00\x00', @@ -1140,6 +1190,9 @@ FW_VERSIONS = { b'78109-TGS-AP20\x00\x00', b'78109-TGT-AJ20\x00\x00', b'78109-TGT-AK30\x00\x00', + b'78109-TGS-AT20\x00\x00', + b'78109-TGS-AX20\x00\x00', + b'78109-TGS-AJ20\x00\x00', ], (Ecu.vsa, 0x18da28f1, None): [ b'57114-TG7-A130\x00\x00', @@ -1156,38 +1209,6 @@ FW_VERSIONS = { b'57114-TGT-A530\x00\x00', ], }, - CAR.PASSPORT: { - (Ecu.programmedFuelInjection, 0x18da10f1, None): [ - b'37805-RLV-B220\x00\x00', - b'37805-RLV-B210\x00\x00', - ], - (Ecu.eps, 0x18da30f1, None): [ - b'39990-TGS-A230\x00\x00', - ], - (Ecu.fwdRadar, 0x18dab0f1, None): [ - b'36161-TGS-A030\x00\x00', - b'36161-TGS-A130\x00\x00', - ], - (Ecu.gateway, 0x18daeff1, None): [ - b'38897-TG7-A040\x00\x00', - ], - (Ecu.srs, 0x18da53f1, None): [ - b'77959-TGS-A010\x00\x00', - ], - (Ecu.shiftByWire, 0x18da0bf1, None): [ - b'54008-TG7-A530\x00\x00', - ], - (Ecu.transmission, 0x18da1ef1, None): [ - b'28101-5EZ-A600\x00\x00', - ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-TGS-AT20\x00\x00', - b'78109-TGS-AX20\x00\x00', - ], - (Ecu.vsa, 0x18da28f1, None): [ - b'57114-TGS-A530\x00\x00', - ], - }, CAR.ACURA_RDX: { (Ecu.vsa, 0x18da28f1, None): [ b'57114-TX5-A220\x00\x00', @@ -1429,6 +1450,7 @@ FW_VERSIONS = { (Ecu.eps, 0x18DA30F1, None): [ b'39990-T39-A130\x00\x00', b'39990-T43-J020\x00\x00', + b'39990-T24-T120\x00\x00', ], (Ecu.gateway, 0x18DAEFF1, None): [ b'38897-T20-A020\x00\x00', @@ -1436,11 +1458,13 @@ FW_VERSIONS = { b'38897-T21-A010\x00\x00', b'38897-T20-A210\x00\x00', b'38897-T20-A310\x00\x00', + b'38897-T24-Z120\x00\x00', ], (Ecu.srs, 0x18DA53F1, None): [ b'77959-T20-A970\x00\x00', b'77959-T47-A940\x00\x00', b'77959-T47-A950\x00\x00', + b'77959-T20-M820\x00\x00', ], (Ecu.combinationMeter, 0x18DA60F1, None): [ b'78108-T21-A220\x00\x00', @@ -1448,16 +1472,26 @@ FW_VERSIONS = { b'78108-T23-A110\x00\x00', b'78108-T21-A230\x00\x00', b'78108-T22-A020\x00\x00', + b'78108-T21-MB10\x00\x00', + ], + (Ecu.fwdRadar, 0x18dab0f1, None): [ + b'36161-T20-A070\x00\x00', + b'36161-T20-A080\x00\x00', + b'36161-T20-A060\x00\x00', + b'36161-T47-A070\x00\x00', + b'36161-T24-T070\x00\x00', ], (Ecu.vsa, 0x18DA28F1, None): [ b'57114-T20-AB40\x00\x00', b'57114-T43-JB30\x00\x00', + b'57114-T24-TB30\x00\x00', ], (Ecu.transmission, 0x18da1ef1, None): [ b'28101-65D-A020\x00\x00', b'28101-65D-A120\x00\x00', b'28101-65H-A020\x00\x00', b'28101-65H-A120\x00\x00', + b'28101-65J-N010\x00\x00', ], (Ecu.programmedFuelInjection, 0x18da10f1, None): [ b'37805-64L-A540\x00\x00', @@ -1465,6 +1499,7 @@ FW_VERSIONS = { b'37805-64S-A720\x00\x00', b'37805-64A-A540\x00\x00', b'37805-64A-A620\x00\x00', + b'37805-64D-P510\x00\x00', ], }, } @@ -1488,7 +1523,6 @@ DBC = { CAR.ODYSSEY: dbc_dict('honda_odyssey_exl_2018_generated', 'acura_ilx_2016_nidec'), CAR.ODYSSEY_CHN: dbc_dict('honda_odyssey_extreme_edition_2018_china_can_generated', 'acura_ilx_2016_nidec'), CAR.PILOT: dbc_dict('acura_ilx_2016_can_generated', 'acura_ilx_2016_nidec'), - CAR.PASSPORT: dbc_dict('acura_ilx_2016_can_generated', 'acura_ilx_2016_nidec'), CAR.RIDGELINE: dbc_dict('acura_ilx_2016_can_generated', 'acura_ilx_2016_nidec'), CAR.INSIGHT: dbc_dict('honda_insight_ex_2019_can_generated', None), CAR.HONDA_E: dbc_dict('acura_rdx_2020_can_generated', None), @@ -1503,7 +1537,7 @@ STEER_THRESHOLD = { HONDA_NIDEC_ALT_PCM_ACCEL = {CAR.ODYSSEY} HONDA_NIDEC_ALT_SCM_MESSAGES = {CAR.ACURA_ILX, CAR.ACURA_RDX, CAR.CRV, CAR.CRV_EU, CAR.FIT, CAR.FREED, CAR.HRV, CAR.ODYSSEY_CHN, - CAR.PILOT, CAR.PASSPORT, CAR.RIDGELINE} + CAR.PILOT, CAR.RIDGELINE} HONDA_BOSCH = {CAR.ACCORD, CAR.ACCORDH, CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_5G, CAR.CRV_HYBRID, CAR.INSIGHT, CAR.ACURA_RDX_3G, CAR.HONDA_E, CAR.CIVIC_2022} HONDA_BOSCH_ALT_BRAKE_SIGNAL = {CAR.ACCORD, CAR.CRV_5G, CAR.ACURA_RDX_3G} diff --git a/selfdrive/car/hyundai/carcontroller.py b/selfdrive/car/hyundai/carcontroller.py index e5fdbfd57..1e6f78af2 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -54,16 +54,12 @@ class CarController: self.car_fingerprint = CP.carFingerprint self.last_button_frame = 0 - def update(self, CC, CS): + def update(self, CC, CS, now_nanos): actuators = CC.actuators hud_control = CC.hudControl # steering torque - steer = actuators.steer - if self.CP.carFingerprint in (CAR.KONA, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022): - # these cars have significantly more torque than most HKG; limit to 70% of max - steer = clip(steer, -0.7, 0.7) - new_steer = int(round(steer * self.params.STEER_MAX)) + new_steer = int(round(actuators.steer * self.params.STEER_MAX)) apply_steer = apply_std_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, self.params) if not CC.latActive: @@ -86,11 +82,16 @@ class CarController: # tester present - w/ no response (keeps relevant ECU disabled) if self.frame % 100 == 0 and not (self.CP.flags & HyundaiFlags.CANFD_CAMERA_SCC.value) and self.CP.openpilotLongitudinalControl: + # for longitudinal control, either radar or ADAS driving ECU addr, bus = 0x7d0, 0 if self.CP.flags & HyundaiFlags.CANFD_HDA2.value: addr, bus = 0x730, 5 can_sends.append([addr, 0, b"\x02\x3E\x80\x00\x00\x00\x00\x00", bus]) + # for blinkers + if self.CP.flags & HyundaiFlags.ENABLE_BLINKERS: + can_sends.append([0x7b1, 0, b"\x02\x3E\x80\x00\x00\x00\x00\x00", 5]) + # >90 degree steering fault prevention # Count up to MAX_ANGLE_FRAMES, at which point we need to cut torque to avoid a steering fault if CC.latActive and abs(CS.out.steeringAngleDeg) >= MAX_ANGLE: @@ -121,6 +122,10 @@ class CarController: if self.frame % 5 == 0 and (not hda2 or hda2_long): can_sends.append(hyundaicanfd.create_lfahda_cluster(self.packer, self.CP, CC.enabled)) + # blinkers + if hda2 and self.CP.flags & HyundaiFlags.ENABLE_BLINKERS: + can_sends.extend(hyundaicanfd.create_spas_messages(self.packer, self.frame, CC.leftBlinker, CC.rightBlinker)) + if self.CP.openpilotLongitudinalControl: if hda2: can_sends.extend(hyundaicanfd.create_adrv_messages(self.packer, self.frame)) @@ -164,7 +169,8 @@ class CarController: if (self.frame - self.last_button_frame) * DT_CTRL > 0.1: # send 25 messages at a time to increases the likelihood of resume being accepted can_sends.extend([hyundaican.create_clu11(self.packer, self.frame, CS.clu11, Buttons.RES_ACCEL, self.CP.carFingerprint)] * 25) - self.last_button_frame = self.frame + if (self.frame - self.last_button_frame) * DT_CTRL >= 0.15: + self.last_button_frame = self.frame if self.frame % 2 == 0 and self.CP.openpilotLongitudinalControl: # TODO: unclear if this is needed @@ -173,10 +179,7 @@ class CarController: hud_control.leadVisible, set_speed_in_units, stopping, CC.cruiseControl.override)) # 20 Hz LFA MFA message - if self.frame % 5 == 0 and self.car_fingerprint in (CAR.SONATA, CAR.PALISADE, CAR.IONIQ, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV_2021, - CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KIA_CEED, CAR.KIA_SELTOS, CAR.KONA_EV, CAR.KONA_EV_2022, - CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.SANTA_FE_2022, - CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.GENESIS_G70_2020, CAR.SANTA_FE_PHEV_2022, CAR.KIA_STINGER_2022): + if self.frame % 5 == 0 and self.CP.flags & HyundaiFlags.SEND_LFA.value: can_sends.append(hyundaican.create_lfahda_mfc(self.packer, CC.enabled)) # 5 Hz ACC options @@ -189,6 +192,7 @@ class CarController: new_actuators = actuators.copy() new_actuators.steer = apply_steer / self.params.STEER_MAX + new_actuators.steerOutputCan = apply_steer new_actuators.accel = accel self.frame += 1 diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index 2c309fa0d..22934c05b 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -6,7 +6,8 @@ from cereal import car from common.conversions import Conversions as CV from opendbc.can.parser import CANParser from opendbc.can.can_define import CANDefine -from selfdrive.car.hyundai.values import HyundaiFlags, DBC, FEATURES, CAMERA_SCC_CAR, CANFD_CAR, EV_CAR, HYBRID_CAR, Buttons, CarControllerParams +from selfdrive.car.hyundai.hyundaicanfd import get_e_can_bus +from selfdrive.car.hyundai.values import HyundaiFlags, CAR, DBC, FEATURES, CAMERA_SCC_CAR, CANFD_CAR, EV_CAR, HYBRID_CAR, Buttons, CarControllerParams from selfdrive.car.interfaces import CarStateBase PREV_BUTTON_SAMPLES = 8 @@ -21,7 +22,9 @@ class CarState(CarStateBase): self.cruise_buttons = deque([Buttons.NONE] * PREV_BUTTON_SAMPLES, maxlen=PREV_BUTTON_SAMPLES) self.main_buttons = deque([Buttons.NONE] * PREV_BUTTON_SAMPLES, maxlen=PREV_BUTTON_SAMPLES) - self.gear_msg_canfd = "GEAR_ALT" if CP.flags & HyundaiFlags.CANFD_ALT_GEARS else "GEAR_SHIFTER" + self.gear_msg_canfd = "GEAR_ALT_2" if CP.flags & HyundaiFlags.CANFD_ALT_GEARS_2 else \ + "GEAR_ALT" if CP.flags & HyundaiFlags.CANFD_ALT_GEARS else \ + "GEAR_SHIFTER" if CP.carFingerprint in CANFD_CAR: self.shifter_values = can_define.dv[self.gear_msg_canfd]["GEAR"] elif self.CP.carFingerprint in FEATURES["use_cluster_gears"]: @@ -32,7 +35,6 @@ class CarState(CarStateBase): self.shifter_values = can_define.dv["LVR12"]["CF_Lvr_Gear"] self.is_metric = False - self.brake_error = False self.buttons_counter = 0 self.cruise_info = {} @@ -72,8 +74,10 @@ class CarState(CarStateBase): self.cluster_speed = cp.vl["CLU15"]["CF_Clu_VehicleSpeed"] self.cluster_speed_counter = 0 - # mimic how dash converts to imperial - if not self.is_metric: + # Mimic how dash converts to imperial. + # Sorento is the only platform where CF_Clu_VehicleSpeed is already imperial when not is_metric + # TODO: CGW_USM1->CF_Gway_DrLockSoundRValue may describe this + if not self.is_metric and self.CP.carFingerprint not in (CAR.KIA_SORENTO,): self.cluster_speed = math.floor(self.cluster_speed * CV.KPH_TO_MPH + CV.KPH_TO_MPH) ret.vEgoCluster = self.cluster_speed * speed_conv @@ -85,7 +89,7 @@ class CarState(CarStateBase): 50, cp.vl["CGW1"]["CF_Gway_TurnSigLh"], cp.vl["CGW1"]["CF_Gway_TurnSigRh"]) ret.steeringTorque = cp.vl["MDPS12"]["CR_Mdps_StrColTq"] ret.steeringTorqueEps = cp.vl["MDPS12"]["CR_Mdps_OutTq"] - ret.steeringPressed = abs(ret.steeringTorque) > self.params.STEER_THRESHOLD + ret.steeringPressed = self.update_steering_pressed(abs(ret.steeringTorque) > self.params.STEER_THRESHOLD, 5) ret.steerFaultTemporary = cp.vl["MDPS12"]["CF_Mdps_ToiUnavail"] != 0 or cp.vl["MDPS12"]["CF_Mdps_ToiFlt"] != 0 # cruise state @@ -103,8 +107,9 @@ class CarState(CarStateBase): # TODO: Find brake pressure ret.brake = 0 ret.brakePressed = cp.vl["TCS13"]["DriverBraking"] != 0 - ret.brakeHoldActive = cp.vl["TCS15"]["AVH_LAMP"] == 2 # 0 OFF, 1 ERROR, 2 ACTIVE, 3 READY + ret.brakeHoldActive = cp.vl["TCS15"]["AVH_LAMP"] == 2 # 0 OFF, 1 ERROR, 2 ACTIVE, 3 READY ret.parkingBrake = cp.vl["TCS13"]["PBRAKE_ACT"] == 1 + ret.accFaulted = cp.vl["TCS13"]["ACCEnable"] != 0 # 0 ACC CONTROL ENABLED, 1-3 ACC CONTROL DISABLED if self.CP.carFingerprint in (HYBRID_CAR | EV_CAR): if self.CP.carFingerprint in HYBRID_CAR: @@ -130,8 +135,8 @@ class CarState(CarStateBase): ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(gear)) if not self.CP.openpilotLongitudinalControl: - aeb_src = "FCA11" if self.CP.carFingerprint in FEATURES["use_fca"] else "SCC12" - aeb_sig = "FCA_CmdAct" if self.CP.carFingerprint in FEATURES["use_fca"] else "AEB_CmdAct" + aeb_src = "FCA11" if self.CP.flags & HyundaiFlags.USE_FCA.value else "SCC12" + aeb_sig = "FCA_CmdAct" if self.CP.flags & HyundaiFlags.USE_FCA.value else "AEB_CmdAct" aeb_warning = cp_cruise.vl[aeb_src]["CF_VSM_Warn"] != 0 aeb_braking = cp_cruise.vl[aeb_src]["CF_VSM_DecCmdAct"] != 0 or cp_cruise.vl[aeb_src][aeb_sig] != 0 ret.stockFcw = aeb_warning and not aeb_braking @@ -145,7 +150,6 @@ class CarState(CarStateBase): self.lkas11 = copy.copy(cp_cam.vl["LKAS11"]) self.clu11 = copy.copy(cp.vl["CLU11"]) self.steer_state = cp.vl["MDPS12"]["CF_Mdps_ToiActive"] # 0 NOT ACTIVE, 1 ACTIVE - self.brake_error = cp.vl["TCS13"]["ACCEnable"] != 0 # 0 ACC CONTROL ENABLED, 1-3 ACC CONTROL DISABLED self.prev_cruise_buttons = self.cruise_buttons[-1] self.cruise_buttons.extend(cp.vl_all["CLU11"]["CF_Clu_CruiseSwState"]) self.main_buttons.extend(cp.vl_all["CLU11"]["CF_Clu_CruiseSwMain"]) @@ -155,6 +159,9 @@ class CarState(CarStateBase): def update_canfd(self, cp, cp_cam): ret = car.CarState.new_message() + self.is_metric = cp.vl["CRUISE_BUTTONS_ALT"]["DISTANCE_UNIT"] != 1 + speed_factor = CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS + if self.CP.carFingerprint in (EV_CAR | HYBRID_CAR): if self.CP.carFingerprint in EV_CAR: ret.gas = cp.vl["ACCELERATOR"]["ACCELERATOR_PEDAL"] / 255. @@ -187,7 +194,7 @@ class CarState(CarStateBase): ret.steeringAngleDeg = cp.vl["STEERING_SENSORS"]["STEERING_ANGLE"] * -1 ret.steeringTorque = cp.vl["MDPS"]["STEERING_COL_TORQUE"] ret.steeringTorqueEps = cp.vl["MDPS"]["STEERING_OUT_TORQUE"] - ret.steeringPressed = abs(ret.steeringTorque) > self.params.STEER_THRESHOLD + ret.steeringPressed = self.update_steering_pressed(abs(ret.steeringTorque) > self.params.STEER_THRESHOLD, 5) ret.steerFaultTemporary = cp.vl["MDPS"]["LKA_FAULT"] != 0 ret.leftBlinker, ret.rightBlinker = self.update_blinker_from_lamp(50, cp.vl["BLINKERS"]["LEFT_LAMP"], @@ -196,14 +203,18 @@ class CarState(CarStateBase): ret.leftBlindspot = cp.vl["BLINDSPOTS_REAR_CORNERS"]["FL_INDICATOR"] != 0 ret.rightBlindspot = cp.vl["BLINDSPOTS_REAR_CORNERS"]["FR_INDICATOR"] != 0 - ret.cruiseState.available = True - self.is_metric = cp.vl["CLUSTER_INFO"]["DISTANCE_UNIT"] != 1 - if not self.CP.openpilotLongitudinalControl: - speed_factor = CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS + # cruise state + # CAN FD cars enable on main button press, set available if no TCS faults preventing engagement + ret.cruiseState.available = cp.vl["TCS"]["ACCEnable"] == 0 + if self.CP.openpilotLongitudinalControl: + # These are not used for engage/disengage since openpilot keeps track of state using the buttons + ret.cruiseState.enabled = cp.vl["TCS"]["ACC_REQ"] == 1 + ret.cruiseState.standstill = False + else: cp_cruise_info = cp_cam if self.CP.flags & HyundaiFlags.CANFD_CAMERA_SCC else cp - ret.cruiseState.speed = cp_cruise_info.vl["SCC_CONTROL"]["VSetDis"] * speed_factor - ret.cruiseState.standstill = cp_cruise_info.vl["SCC_CONTROL"]["CRUISE_STANDSTILL"] == 1 ret.cruiseState.enabled = cp_cruise_info.vl["SCC_CONTROL"]["ACCMode"] in (1, 2) + ret.cruiseState.standstill = cp_cruise_info.vl["SCC_CONTROL"]["CRUISE_STANDSTILL"] == 1 + ret.cruiseState.speed = cp_cruise_info.vl["SCC_CONTROL"]["VSetDis"] * speed_factor self.cruise_info = copy.copy(cp_cruise_info.vl["SCC_CONTROL"]) cruise_btn_msg = "CRUISE_BUTTONS_ALT" if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS else "CRUISE_BUTTONS" @@ -211,6 +222,7 @@ class CarState(CarStateBase): self.cruise_buttons.extend(cp.vl_all[cruise_btn_msg]["CRUISE_BUTTONS"]) self.main_buttons.extend(cp.vl_all[cruise_btn_msg]["ADAPTIVE_CRUISE_MAIN_BTN"]) self.buttons_counter = cp.vl[cruise_btn_msg]["COUNTER"] + ret.accFaulted = cp.vl["TCS"]["ACCEnable"] != 0 # 0 ACC CONTROL ENABLED, 1-3 ACC CONTROL DISABLED if self.CP.flags & HyundaiFlags.CANFD_HDA2: self.cam_0x2a4 = copy.copy(cp_cam.vl["CAM_0x2a4"]) @@ -305,7 +317,7 @@ class CarState(CarStateBase): ("SCC12", 50), ] - if CP.carFingerprint in FEATURES["use_fca"]: + if CP.flags & HyundaiFlags.USE_FCA.value: signals += [ ("FCA_CmdAct", "FCA11"), ("CF_VSM_Warn", "FCA11"), @@ -396,7 +408,7 @@ class CarState(CarStateBase): ("SCC12", 50), ] - if CP.carFingerprint in FEATURES["use_fca"]: + if CP.flags & HyundaiFlags.USE_FCA.value: signals += [ ("FCA_CmdAct", "FCA11"), ("CF_VSM_Warn", "FCA11"), @@ -416,7 +428,9 @@ class CarState(CarStateBase): def get_can_parser_canfd(CP): cruise_btn_msg = "CRUISE_BUTTONS_ALT" if CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS else "CRUISE_BUTTONS" - gear_msg = "GEAR_ALT" if CP.flags & HyundaiFlags.CANFD_ALT_GEARS else "GEAR_SHIFTER" + gear_msg = "GEAR_ALT_2" if CP.flags & HyundaiFlags.CANFD_ALT_GEARS_2 else \ + "GEAR_ALT" if CP.flags & HyundaiFlags.CANFD_ALT_GEARS else \ + "GEAR_SHIFTER" signals = [ ("WHEEL_SPEED_1", "WHEEL_SPEEDS"), ("WHEEL_SPEED_2", "WHEEL_SPEEDS"), @@ -432,12 +446,13 @@ class CarState(CarStateBase): ("LKA_FAULT", "MDPS"), ("DriverBraking", "TCS"), + ("ACCEnable", "TCS"), + ("ACC_REQ", "TCS"), ("COUNTER", cruise_btn_msg), ("CRUISE_BUTTONS", cruise_btn_msg), ("ADAPTIVE_CRUISE_MAIN_BTN", cruise_btn_msg), - - ("DISTANCE_UNIT", "CLUSTER_INFO"), + ("DISTANCE_UNIT", "CRUISE_BUTTONS_ALT"), ("LEFT_LAMP", "BLINKERS"), ("RIGHT_LAMP", "BLINKERS"), @@ -452,12 +467,14 @@ class CarState(CarStateBase): ("STEERING_SENSORS", 100), ("MDPS", 100), ("TCS", 50), - (cruise_btn_msg, 50), - ("CLUSTER_INFO", 4), + ("CRUISE_BUTTONS_ALT", 50), ("BLINKERS", 4), ("DOORS_SEATBELTS", 4), ] + if not (CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS): + checks.append(("CRUISE_BUTTONS", 50)) + if CP.enableBsm: signals += [ ("FL_INDICATOR", "BLINDSPOTS_REAR_CORNERS"), @@ -499,8 +516,7 @@ class CarState(CarStateBase): ("ACCELERATOR_BRAKE_ALT", 100), ] - bus = 5 if CP.flags & HyundaiFlags.CANFD_HDA2 else 4 - return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, bus) + return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, get_e_can_bus(CP)) @staticmethod def get_cam_can_parser_canfd(CP): diff --git a/selfdrive/car/hyundai/hyundaican.py b/selfdrive/car/hyundai/hyundaican.py index c2ffffbf2..858f3d087 100644 --- a/selfdrive/car/hyundai/hyundaican.py +++ b/selfdrive/car/hyundai/hyundaican.py @@ -21,7 +21,7 @@ def create_lkas11(packer, frame, car_fingerprint, apply_steer, steer_req, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KIA_SELTOS, CAR.ELANTRA_2021, CAR.GENESIS_G70_2020, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, - CAR.SANTA_FE_PHEV_2022, CAR.KIA_STINGER_2022): + CAR.SANTA_FE_PHEV_2022, CAR.KIA_STINGER_2022, CAR.KIA_K5_HEV_2020): values["CF_Lkas_LdwsActivemode"] = int(left_lane) + (int(right_lane) << 1) values["CF_Lkas_LdwsOpt_USM"] = 2 diff --git a/selfdrive/car/hyundai/hyundaicanfd.py b/selfdrive/car/hyundai/hyundaicanfd.py index 8b53e7c37..af7239571 100644 --- a/selfdrive/car/hyundai/hyundaicanfd.py +++ b/selfdrive/car/hyundai/hyundaicanfd.py @@ -1,7 +1,6 @@ from common.numpy_fast import clip from selfdrive.car.hyundai.values import HyundaiFlags - def get_e_can_bus(CP): # On the CAN-FD platforms, the LKAS camera is on both A-CAN and E-CAN. HDA2 cars # have a different harness than the HDA1 and non-HDA variants in order to split @@ -73,8 +72,6 @@ def create_acc_control(packer, CP, enabled, accel_last, accel, stopping, gas_ove else: a_raw = accel a_val = clip(accel, accel_last - jn, accel_last + jn) - if stopping: - a_raw = 0 values = { "ACCMode": 0 if not enabled else (2 if gas_override else 1), @@ -84,6 +81,7 @@ def create_acc_control(packer, CP, enabled, accel_last, accel, stopping, gas_ove "aReqRaw": a_raw, "VSetDis": set_speed, "JerkLowerLimit": jerk if enabled else 1, + "JerkUpperLimit": 3.0, "ACC_ObjDist": 1, "ObjValid": 0, @@ -91,13 +89,31 @@ def create_acc_control(packer, CP, enabled, accel_last, accel, stopping, gas_ove "SET_ME_2": 0x4, "SET_ME_3": 0x3, "SET_ME_TMP_64": 0x64, - "NEW_SIGNAL_10": 4, "DISTANCE_SETTING": 4, } return packer.make_can_msg("SCC_CONTROL", get_e_can_bus(CP), values) +def create_spas_messages(packer, frame, left_blink, right_blink): + ret = [] + + values = { + } + ret.append(packer.make_can_msg("SPAS1", 5, values)) + + blink = 0 + if left_blink: + blink = 3 + elif right_blink: + blink = 4 + values = { + "BLINKER_CONTROL": blink, + } + ret.append(packer.make_can_msg("SPAS2", 5, values)) + + return ret + def create_adrv_messages(packer, frame): # messages needed to car happy after disabling diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 8738aabd1..d2cc5b4ec 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -2,9 +2,9 @@ from cereal import car from panda import Panda from common.conversions import Conversions as CV -from selfdrive.car.hyundai.values import HyundaiFlags, CAR, DBC, CANFD_CAR, CAMERA_SCC_CAR, CANFD_RADAR_SCC_CAR, EV_CAR, HYBRID_CAR, LEGACY_SAFETY_MODE_CAR, Buttons, CarControllerParams +from selfdrive.car.hyundai.values import HyundaiFlags, CAR, DBC, CANFD_CAR, CAMERA_SCC_CAR, CANFD_RADAR_SCC_CAR, EV_CAR, HYBRID_CAR, LEGACY_SAFETY_MODE_CAR, Buttons from selfdrive.car.hyundai.radar_interface import RADAR_START_ADDR -from selfdrive.car import STD_CARGO_KG, create_button_event, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config +from selfdrive.car import STD_CARGO_KG, create_button_event, scale_tire_stiffness, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.disable_ecu import disable_ecu @@ -18,15 +18,9 @@ BUTTONS_DICT = {Buttons.RES_ACCEL: ButtonType.accelCruise, Buttons.SET_DECEL: Bu class CarInterface(CarInterfaceBase): @staticmethod - def get_pid_accel_limits(CP, current_speed, cruise_speed): - return CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX - - @staticmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[], experimental_long=False): # pylint: disable=dangerous-default-value - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) - + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "hyundai" - ret.radarOffCan = RADAR_START_ADDR not in fingerprint[1] or DBC[ret.carFingerprint]["radar"] is None + ret.radarUnavailable = RADAR_START_ADDR not in fingerprint[1] or DBC[ret.carFingerprint]["radar"] is None # These cars have been put into dashcam only due to both a lack of users and test coverage. # These cars likely still work fine. Once a user confirms each car works and a test route is @@ -41,11 +35,22 @@ class CarInterface(CarInterfaceBase): # non-HDA2 if 0x1cf not in fingerprint[4]: ret.flags |= HyundaiFlags.CANFD_ALT_BUTTONS.value - # ICE cars do not have 0x130; GEARS message on 0x40 instead + # ICE cars do not have 0x130; GEARS message on 0x40 or 0x70 instead if 0x130 not in fingerprint[4]: - ret.flags |= HyundaiFlags.CANFD_ALT_GEARS.value + if 0x40 not in fingerprint[4]: + ret.flags |= HyundaiFlags.CANFD_ALT_GEARS_2.value + else: + ret.flags |= HyundaiFlags.CANFD_ALT_GEARS.value if candidate not in CANFD_RADAR_SCC_CAR: ret.flags |= HyundaiFlags.CANFD_CAMERA_SCC.value + else: + # Send LFA message on cars with HDA + if 0x485 in fingerprint[2]: + ret.flags |= HyundaiFlags.SEND_LFA.value + + # These cars use the FCA11 message for the AEB and FCW signals, all others use SCC12 + if 0x38d in fingerprint[0] or 0x38d in fingerprint[2]: + ret.flags |= HyundaiFlags.USE_FCA.value ret.steerActuatorDelay = 0.1 # Default delay ret.steerLimitTimer = 0.4 @@ -120,8 +125,8 @@ class CarInterface(CarInterfaceBase): ret.wheelbase = 2.67 ret.steerRatio = 14.00 * 1.15 tire_stiffness_factor = 0.385 - elif candidate == CAR.TUCSON_HYBRID_4TH_GEN: - ret.mass = 1680. + STD_CARGO_KG # average of all 3 trims + elif candidate in (CAR.TUCSON_4TH_GEN, CAR.TUCSON_HYBRID_4TH_GEN): + ret.mass = 1630. + STD_CARGO_KG # average ret.wheelbase = 2.756 ret.steerRatio = 16. tire_stiffness_factor = 0.385 @@ -135,8 +140,8 @@ class CarInterface(CarInterfaceBase): ret.mass = 1985. + STD_CARGO_KG ret.wheelbase = 2.78 ret.steerRatio = 14.4 * 1.1 # 10% higher at the center seems reasonable - elif candidate in (CAR.KIA_NIRO_EV, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021): - ret.mass = 1737. + STD_CARGO_KG + elif candidate in (CAR.KIA_NIRO_EV, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.KIA_NIRO_HEV_2ND_GEN): + ret.mass = 3452. * CV.LB_TO_KG + STD_CARGO_KG # average of all the cars ret.wheelbase = 2.7 ret.steerRatio = 13.9 if CAR.KIA_NIRO_HEV_2021 else 13.73 # Spec tire_stiffness_factor = 0.385 @@ -172,7 +177,7 @@ class CarInterface(CarInterfaceBase): ret.wheelbase = 2.65 ret.steerRatio = 13.75 tire_stiffness_factor = 0.5 - elif candidate == CAR.KIA_K5_2021: + elif candidate in (CAR.KIA_K5_2021, CAR.KIA_K5_HEV_2020): ret.mass = 3228. * CV.LB_TO_KG ret.wheelbase = 2.85 ret.steerRatio = 13.27 # 2021 Kia K5 Steering Ratio (all trims) @@ -191,8 +196,19 @@ class CarInterface(CarInterfaceBase): ret.mass = 1767. + STD_CARGO_KG # SX Prestige trim support only ret.wheelbase = 2.756 ret.steerRatio = 13.6 + elif candidate in (CAR.KIA_SORENTO_4TH_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN): + ret.wheelbase = 2.81 + ret.steerRatio = 13.5 # average of the platforms + if candidate == CAR.KIA_SORENTO_4TH_GEN: + ret.mass = 3957 * CV.LB_TO_KG + STD_CARGO_KG + else: + ret.mass = 4537 * CV.LB_TO_KG + STD_CARGO_KG # Genesis + elif candidate == CAR.GENESIS_GV60_EV_1ST_GEN: + ret.mass = 2205 + STD_CARGO_KG + ret.wheelbase = 2.9 + ret.steerRatio = 12.6 # https://www.motor1.com/reviews/586376/2023-genesis-gv60-first-drive/#:~:text=Relative%20to%20the%20related%20Ioniq,5%2FEV6%27s%2014.3%3A1. elif candidate == CAR.GENESIS_G70: ret.steerActuatorDelay = 0.1 ret.mass = 1640.0 + STD_CARGO_KG @@ -230,7 +246,7 @@ class CarInterface(CarInterfaceBase): ret.stoppingControl = True ret.startingState = True ret.vEgoStarting = 0.1 - ret.startAccel = 2.0 + ret.startAccel = 1.0 ret.longitudinalActuatorDelayLowerBound = 0.5 ret.longitudinalActuatorDelayUpperBound = 0.5 @@ -269,11 +285,11 @@ class CarInterface(CarInterfaceBase): elif candidate in EV_CAR: ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_EV_GAS - ret.centerToFront = ret.wheelbase * 0.4 + if candidate in (CAR.KONA, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022): + ret.flags |= HyundaiFlags.ALT_LIMITS.value + ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_ALT_LIMITS - # TODO: get actual value, for now starting with reasonable value for - # civic and scaling by mass and wheelbase - ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) + ret.centerToFront = ret.wheelbase * 0.4 # TODO: start from empirically derived lateral slip stiffness for the civic and scale by # mass and CG position, so all cars will have approximately similar dyn behaviors @@ -289,6 +305,10 @@ class CarInterface(CarInterfaceBase): addr, bus = 0x730, 5 disable_ecu(logcan, sendcan, bus=bus, addr=addr, com_cont_req=b'\x28\x83\x01') + # for blinkers + if CP.flags & HyundaiFlags.ENABLE_BLINKERS: + disable_ecu(logcan, sendcan, bus=5, addr=0x7B1, com_cont_req=b'\x28\x83\x01') + def _update(self, c): ret = self.CS.update(self.cp, self.cp_cam) @@ -306,9 +326,6 @@ class CarInterface(CarInterfaceBase): allow_enable = any(btn in ENABLE_BUTTONS for btn in self.CS.cruise_buttons) or any(self.CS.main_buttons) events = self.create_common_events(ret, pcm_enable=self.CS.CP.pcmCruise, allow_enable=allow_enable) - if self.CS.brake_error: - events.add(EventName.brakeUnavailable) - # low speed steer alert hysteresis logic (only for cars with steer cut off above 10 m/s) if ret.vEgo < (self.CP.minSteerSpeed + 2.) and self.CP.minSteerSpeed > 10.: self.low_speed_alert = True @@ -321,5 +338,5 @@ class CarInterface(CarInterfaceBase): return ret - def apply(self, c): - return self.CC.update(c, self.CS) + def apply(self, c, now_nanos): + return self.CC.update(c, self.CS, now_nanos) diff --git a/selfdrive/car/hyundai/radar_interface.py b/selfdrive/car/hyundai/radar_interface.py index 0d22611fb..4ecca542b 100644 --- a/selfdrive/car/hyundai/radar_interface.py +++ b/selfdrive/car/hyundai/radar_interface.py @@ -37,7 +37,7 @@ class RadarInterface(RadarInterfaceBase): self.trigger_msg = RADAR_START_ADDR + RADAR_MSG_COUNT - 1 self.track_id = 0 - self.radar_off_can = CP.radarOffCan + self.radar_off_can = CP.radarUnavailable self.rcp = get_radar_can_parser(CP) def update(self, can_strings): diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index ecba7b749..d0a4e4dd1 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -1,12 +1,12 @@ -from enum import IntFlag from dataclasses import dataclass +from enum import Enum, IntFlag from typing import Dict, List, Optional, Union from cereal import car from panda.python import uds from common.conversions import Conversions as CV from selfdrive.car import dbc_dict -from selfdrive.car.docs_definitions import CarInfo, Harness +from selfdrive.car.docs_definitions import CarFootnote, CarInfo, Column, Harness from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, p16 Ecu = car.CarParams.Ecu @@ -23,6 +23,7 @@ class CarControllerParams: self.STEER_DRIVER_MULTIPLIER = 2 self.STEER_DRIVER_FACTOR = 1 self.STEER_THRESHOLD = 150 + self.STEER_STEP = 1 # 100 Hz if CP.carFingerprint in CANFD_CAR: self.STEER_MAX = 270 @@ -34,11 +35,17 @@ class CarControllerParams: # To determine the limit for your car, find the maximum value that the stock LKAS will request. # If the max stock LKAS request is <384, add your car to this list. - elif CP.carFingerprint in (CAR.GENESIS_G80, CAR.GENESIS_G90, CAR.ELANTRA, CAR.HYUNDAI_GENESIS, CAR.IONIQ, + elif CP.carFingerprint in (CAR.GENESIS_G80, CAR.GENESIS_G90, CAR.ELANTRA, CAR.IONIQ, CAR.IONIQ_EV_LTD, CAR.SANTA_FE_PHEV_2022, CAR.SONATA_LF, CAR.KIA_FORTE, CAR.KIA_NIRO_PHEV, CAR.KIA_OPTIMA_H, CAR.KIA_SORENTO): self.STEER_MAX = 255 + # these cars have significantly more torque than most HKG; limit to 70% of max + elif CP.flags & HyundaiFlags.ALT_LIMITS: + self.STEER_MAX = 270 + self.STEER_DELTA_UP = 2 + self.STEER_DELTA_DOWN = 3 + # Default for most HKG else: self.STEER_MAX = 384 @@ -50,6 +57,12 @@ class HyundaiFlags(IntFlag): CANFD_ALT_GEARS = 4 CANFD_CAMERA_SCC = 8 + ALT_LIMITS = 16 + ENABLE_BLINKERS = 32 + CANFD_ALT_GEARS_2 = 64 + SEND_LFA = 128 + USE_FCA = 256 + class CAR: # Hyundai @@ -78,21 +91,26 @@ class CAR: VELOSTER = "HYUNDAI VELOSTER 2019" SONATA_HYBRID = "HYUNDAI SONATA HYBRID 2021" IONIQ_5 = "HYUNDAI IONIQ 5 2022" + TUCSON_4TH_GEN = "HYUNDAI TUCSON 4TH GEN" TUCSON_HYBRID_4TH_GEN = "HYUNDAI TUCSON HYBRID 4TH GEN" SANTA_CRUZ_1ST_GEN = "HYUNDAI SANTA CRUZ 1ST GEN" # Kia KIA_FORTE = "KIA FORTE E 2018 & GT 2021" KIA_K5_2021 = "KIA K5 2021" + KIA_K5_HEV_2020 = "KIA K5 HYBRID 2020" KIA_NIRO_EV = "KIA NIRO EV 2020" KIA_NIRO_PHEV = "KIA NIRO HYBRID 2019" KIA_NIRO_HEV_2021 = "KIA NIRO HYBRID 2021" + KIA_NIRO_HEV_2ND_GEN = "KIA NIRO HYBRID 2ND GEN" KIA_OPTIMA_G4 = "KIA OPTIMA 4TH GEN" KIA_OPTIMA_G4_FL = "KIA OPTIMA 4TH GEN FACELIFT" KIA_OPTIMA_H = "KIA OPTIMA HYBRID 2017 & SPORTS 2019" KIA_SELTOS = "KIA SELTOS 2021" KIA_SPORTAGE_5TH_GEN = "KIA SPORTAGE 5TH GEN" KIA_SORENTO = "KIA SORENTO GT LINE 2018" + KIA_SORENTO_4TH_GEN = "KIA SORENTO 4TH GEN" + KIA_SORENTO_PHEV_4TH_GEN = "KIA SORENTO PLUG-IN HYBRID 4TH GEN" KIA_SPORTAGE_HYBRID_5TH_GEN = "KIA SPORTAGE HYBRID 5TH GEN" KIA_STINGER = "KIA STINGER GT2 2018" KIA_STINGER_2022 = "KIA STINGER 2022" @@ -100,6 +118,7 @@ class CAR: KIA_EV6 = "KIA EV6 2022" # Genesis + GENESIS_GV60_EV_1ST_GEN = "GENESIS GV60 ELECTRIC 1ST GEN" GENESIS_G70 = "GENESIS G70 2018" GENESIS_G70_2020 = "GENESIS G70 2020" GENESIS_GV70_1ST_GEN = "GENESIS GV70 1ST GEN" @@ -107,20 +126,35 @@ class CAR: GENESIS_G90 = "GENESIS G90 2017" +class Footnote(Enum): + # footnotes which mention "red panda" will be replaced with the CAN FD panda kit on the shop page + CANFD = CarFootnote( + "Requires a red panda for this CAN FD car. " + + "All the hardware needed is sold in the CAN FD kit.", + Column.MODEL, shop_footnote=True) + + @dataclass class HyundaiCarInfo(CarInfo): package: str = "Smart Cruise Control (SCC)" + def init_make(self, CP: car.CarParams): + if CP.carFingerprint in CANFD_CAR: + self.footnotes.insert(0, Footnote.CANFD) + CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { CAR.ELANTRA: [ HyundaiCarInfo("Hyundai Elantra 2017-19", min_enable_speed=19 * CV.MPH_TO_MS, harness=Harness.hyundai_b), HyundaiCarInfo("Hyundai Elantra GT 2017-19", harness=Harness.hyundai_e), - HyundaiCarInfo("Hyundai i30 2019", harness=Harness.hyundai_e), + HyundaiCarInfo("Hyundai i30 2017-19", harness=Harness.hyundai_e), ], - CAR.ELANTRA_2021: HyundaiCarInfo("Hyundai Elantra 2021-22", video_link="https://youtu.be/_EdYQtV52-c", harness=Harness.hyundai_k), + CAR.ELANTRA_2021: HyundaiCarInfo("Hyundai Elantra 2021-23", video_link="https://youtu.be/_EdYQtV52-c", harness=Harness.hyundai_k), CAR.ELANTRA_HEV_2021: HyundaiCarInfo("Hyundai Elantra Hybrid 2021-23", video_link="https://youtu.be/_EdYQtV52-c", harness=Harness.hyundai_k), - CAR.HYUNDAI_GENESIS: HyundaiCarInfo("Hyundai Genesis 2015-16", min_enable_speed=19 * CV.MPH_TO_MS, harness=Harness.hyundai_j), # TODO: check 2015 packages + CAR.HYUNDAI_GENESIS: [ + HyundaiCarInfo("Hyundai Genesis 2015-16", min_enable_speed=19 * CV.MPH_TO_MS, harness=Harness.hyundai_j), # TODO: check 2015 packages + HyundaiCarInfo("Genesis G80 2017", "All", min_enable_speed=19 * CV.MPH_TO_MS, harness=Harness.hyundai_j), + ], CAR.IONIQ: HyundaiCarInfo("Hyundai Ioniq Hybrid 2017-19", harness=Harness.hyundai_c), CAR.IONIQ_HEV_2022: HyundaiCarInfo("Hyundai Ioniq Hybrid 2020-22", harness=Harness.hyundai_h), # TODO: confirm 2020-21 harness CAR.IONIQ_EV_LTD: HyundaiCarInfo("Hyundai Ioniq Electric 2019", harness=Harness.hyundai_c), @@ -135,7 +169,7 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { CAR.SANTA_FE_2022: HyundaiCarInfo("Hyundai Santa Fe 2021-22", "All", "https://youtu.be/VnHzSTygTS4", harness=Harness.hyundai_l), CAR.SANTA_FE_HEV_2022: HyundaiCarInfo("Hyundai Santa Fe Hybrid 2022", "All", harness=Harness.hyundai_l), CAR.SANTA_FE_PHEV_2022: HyundaiCarInfo("Hyundai Santa Fe Plug-in Hybrid 2022", "All", harness=Harness.hyundai_l), - CAR.SONATA: HyundaiCarInfo("Hyundai Sonata 2020-22", "All", "https://www.youtube.com/watch?v=ix63r9kE3Fw", harness=Harness.hyundai_a), + CAR.SONATA: HyundaiCarInfo("Hyundai Sonata 2020-23", "All", "https://www.youtube.com/watch?v=ix63r9kE3Fw", harness=Harness.hyundai_a), CAR.SONATA_LF: HyundaiCarInfo("Hyundai Sonata 2018-19", harness=Harness.hyundai_e), CAR.TUCSON: [ HyundaiCarInfo("Hyundai Tucson 2021", min_enable_speed=19 * CV.MPH_TO_MS, harness=Harness.hyundai_l), @@ -148,26 +182,36 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { CAR.VELOSTER: HyundaiCarInfo("Hyundai Veloster 2019-20", min_enable_speed=5. * CV.MPH_TO_MS, harness=Harness.hyundai_e), CAR.SONATA_HYBRID: HyundaiCarInfo("Hyundai Sonata Hybrid 2020-22", "All", harness=Harness.hyundai_a), CAR.IONIQ_5: [ - HyundaiCarInfo("Hyundai Ioniq 5 (without HDA II) 2022-23" , "Highway Driving Assist", harness=Harness.hyundai_k), + HyundaiCarInfo("Hyundai Ioniq 5 (Southeast Asia only) 2022-23", "All", harness=Harness.hyundai_q), + HyundaiCarInfo("Hyundai Ioniq 5 (without HDA II) 2022-23", "Highway Driving Assist", harness=Harness.hyundai_k), HyundaiCarInfo("Hyundai Ioniq 5 (with HDA II) 2022-23", "Highway Driving Assist II", harness=Harness.hyundai_q), ], + CAR.TUCSON_4TH_GEN: [ + HyundaiCarInfo("Hyundai Tucson 2022", harness=Harness.hyundai_n), + HyundaiCarInfo("Hyundai Tucson 2023", "All", harness=Harness.hyundai_n), + ], CAR.TUCSON_HYBRID_4TH_GEN: HyundaiCarInfo("Hyundai Tucson Hybrid 2022", "All", harness=Harness.hyundai_n), - CAR.SANTA_CRUZ_1ST_GEN: HyundaiCarInfo("Hyundai Santa Cruz 2021-22", "Smart Cruise Control (SCC)", harness=Harness.hyundai_n), + CAR.SANTA_CRUZ_1ST_GEN: HyundaiCarInfo("Hyundai Santa Cruz 2021-22", harness=Harness.hyundai_n), # Kia CAR.KIA_FORTE: HyundaiCarInfo("Kia Forte 2019-21", harness=Harness.hyundai_g), CAR.KIA_K5_2021: HyundaiCarInfo("Kia K5 2021-22", harness=Harness.hyundai_a), + CAR.KIA_K5_HEV_2020: HyundaiCarInfo("Kia K5 Hybrid 2020", harness=Harness.hyundai_a), CAR.KIA_NIRO_EV: [ HyundaiCarInfo("Kia Niro EV 2019", "All", "https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_h), HyundaiCarInfo("Kia Niro EV 2020", "All", "https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_f), HyundaiCarInfo("Kia Niro EV 2021", "All", "https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_c), HyundaiCarInfo("Kia Niro EV 2022", "All", "https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_h), ], - CAR.KIA_NIRO_PHEV: HyundaiCarInfo("Kia Niro Plug-in Hybrid 2018-19", "All", min_enable_speed=10. * CV.MPH_TO_MS, harness=Harness.hyundai_c), + CAR.KIA_NIRO_PHEV: [ + HyundaiCarInfo("Kia Niro Plug-in Hybrid 2018-19", "All", min_enable_speed=10. * CV.MPH_TO_MS, harness=Harness.hyundai_c), + HyundaiCarInfo("Kia Niro Plug-in Hybrid 2020", "All", harness=Harness.hyundai_d), + ], CAR.KIA_NIRO_HEV_2021: [ HyundaiCarInfo("Kia Niro Hybrid 2021", harness=Harness.hyundai_f), # TODO: could be hyundai_d, verify HyundaiCarInfo("Kia Niro Hybrid 2022", harness=Harness.hyundai_h), ], + CAR.KIA_NIRO_HEV_2ND_GEN: HyundaiCarInfo("Kia Niro Hybrid 2023", harness=Harness.hyundai_a), CAR.KIA_OPTIMA_G4: HyundaiCarInfo("Kia Optima 2017", "Advanced Smart Cruise Control", harness=Harness.hyundai_b), # TODO: may support 2016, 2018 CAR.KIA_OPTIMA_G4_FL: HyundaiCarInfo("Kia Optima 2019-20", harness=Harness.hyundai_g), CAR.KIA_OPTIMA_H: [ @@ -175,25 +219,32 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { HyundaiCarInfo("Kia Optima Hybrid 2019"), ], CAR.KIA_SELTOS: HyundaiCarInfo("Kia Seltos 2021", harness=Harness.hyundai_a), - CAR.KIA_SPORTAGE_5TH_GEN: HyundaiCarInfo("Kia Sportage 2023", "Smart Cruise Control (SCC)", harness=Harness.hyundai_n), + CAR.KIA_SPORTAGE_5TH_GEN: HyundaiCarInfo("Kia Sportage 2023", harness=Harness.hyundai_n), CAR.KIA_SORENTO: [ HyundaiCarInfo("Kia Sorento 2018", "Advanced Smart Cruise Control", "https://www.youtube.com/watch?v=Fkh3s6WHJz8", harness=Harness.hyundai_c), HyundaiCarInfo("Kia Sorento 2019", video_link="https://www.youtube.com/watch?v=Fkh3s6WHJz8", harness=Harness.hyundai_e), ], + CAR.KIA_SORENTO_4TH_GEN: HyundaiCarInfo("Kia Sorento 2022-23", harness=Harness.hyundai_k), + CAR.KIA_SORENTO_PHEV_4TH_GEN: HyundaiCarInfo("Kia Sorento Plug-in Hybrid 2022-23", harness=Harness.hyundai_a), CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: HyundaiCarInfo("Kia Sportage Hybrid 2023", harness=Harness.hyundai_n), CAR.KIA_STINGER: HyundaiCarInfo("Kia Stinger 2018-20", video_link="https://www.youtube.com/watch?v=MJ94qoofYw0", harness=Harness.hyundai_c), CAR.KIA_STINGER_2022: HyundaiCarInfo("Kia Stinger 2022", "All", harness=Harness.hyundai_k), CAR.KIA_CEED: HyundaiCarInfo("Kia Ceed 2019", harness=Harness.hyundai_e), CAR.KIA_EV6: [ + HyundaiCarInfo("Kia EV6 (Southeast Asia only) 2022-23", "All", harness=Harness.hyundai_p), HyundaiCarInfo("Kia EV6 (without HDA II) 2022", "Highway Driving Assist", harness=Harness.hyundai_l), HyundaiCarInfo("Kia EV6 (with HDA II) 2022", "Highway Driving Assist II", harness=Harness.hyundai_p) ], # Genesis + CAR.GENESIS_GV60_EV_1ST_GEN: [ + HyundaiCarInfo("Genesis GV60 (Advanced Trim) 2023", "All", harness=Harness.hyundai_a), + HyundaiCarInfo("Genesis GV60 (Performance Trim) 2023", "All", harness=Harness.hyundai_k), + ], CAR.GENESIS_G70: HyundaiCarInfo("Genesis G70 2018-19", "All", harness=Harness.hyundai_f), CAR.GENESIS_G70_2020: HyundaiCarInfo("Genesis G70 2020", "All", harness=Harness.hyundai_f), CAR.GENESIS_GV70_1ST_GEN: HyundaiCarInfo("Genesis GV70 2022-23", "All", harness=Harness.hyundai_l), - CAR.GENESIS_G80: HyundaiCarInfo("Genesis G80 2017-19", "All", harness=Harness.hyundai_h), + CAR.GENESIS_G80: HyundaiCarInfo("Genesis G80 2018-19", "All", harness=Harness.hyundai_h), CAR.GENESIS_G90: HyundaiCarInfo("Genesis G90 2017-18", "All", harness=Harness.hyundai_c), } @@ -325,6 +376,13 @@ FW_QUERY_CONFIG = FwQueryConfig( ) FW_VERSIONS = { + CAR.HYUNDAI_GENESIS: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00DH LKAS 1.1 -150210', + b'\xf1\x00DH LKAS 1.4 -140110', + b'\xf1\x00DH LKAS 1.5 -140425', + ], + }, CAR.IONIQ: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00AEhe SCC H-CUP 1.01 1.01 96400-G2000 ', @@ -362,29 +420,32 @@ FW_VERSIONS = { }, CAR.IONIQ_PHEV: { (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\000AEhe SCC FHCUP 1.00 1.02 99110-G2100 ', + b'\xf1\x00AEhe SCC FHCUP 1.00 1.02 99110-G2100 ', b'\xf1\x00AEhe SCC F-CUP 1.00 1.00 99110-G2200 ', b'\xf1\x00AEhe SCC F-CUP 1.00 1.00 99110-G2600 ', + b'\xf1\x00AEhe SCC F-CUP 1.00 1.02 99110-G2100 ', ], (Ecu.eps, 0x7d4, None): [ - b'\xf1\000AE MDPS C 1.00 1.01 56310/G2510 4APHC101', + b'\xf1\x00AE MDPS C 1.00 1.01 56310/G2510 4APHC101', b'\xf1\x00AE MDPS C 1.00 1.01 56310/G2560 4APHC101', b'\xf1\x00AE MDPS C 1.00 1.01 56310G2510\x00 4APHC101', ], (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\000AEP MFC AT USA LHD 1.00 1.01 95740-G2600 190819', + b'\xf1\x00AEP MFC AT USA LHD 1.00 1.01 95740-G2600 190819', b'\xf1\x00AEP MFC AT EUR RHD 1.00 1.01 95740-G2600 190819', b'\xf1\x00AEP MFC AT USA LHD 1.00 1.00 95740-G2700 201027', ], (Ecu.engine, 0x7e0, None): [ b'\xf1\x816H6F6051\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x816H6G6051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x816H6G5051\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x816U3J9051\000\000\xf1\0006U3H1_C2\000\0006U3J9051\000\000PAE0G16NL0\x82zT\xd2', b'\xf1\x816U3J8051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J8051\x00\x00PAETG16UL0\x00\x00\x00\x00', - b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL2\xad\xeb\xabt', b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL2\x00\x00\x00\x00', + b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL0\x00\x00\x00\x00', + b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL2\xad\xeb\xabt', ], }, CAR.IONIQ_EV_2020: { @@ -412,16 +473,19 @@ FW_VERSIONS = { b'\xf1\x00AE MDPS C 1.00 1.04 56310/G7501 4AEEC104', b'\xf1\x00AE MDPS C 1.00 1.03 56310/G7300 4AEEC103', b'\xf1\x00AE MDPS C 1.00 1.03 56310G7300\x00 4AEEC103', + b'\xf1\x00AE MDPS C 1.00 1.04 56310/G7301 4AEEC104', ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.00 95740-G7200 160418', b'\xf1\x00AEE MFC AT USA LHD 1.00 1.00 95740-G2400 180222', b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.00 95740-G2300 170703', + b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.00 95740-G2400 180222', ], }, CAR.IONIQ_HEV_2022: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00AEhe SCC F-CUP 1.00 1.00 99110-G2600 ', + b'\xf1\x00AEhe SCC FHCUP 1.00 1.00 99110-G2600 ', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00AE MDPS C 1.00 1.01 56310G2510\x00 4APHC101', @@ -434,6 +498,7 @@ FW_VERSIONS = { ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00HAE0G16NL2\x00\x00\x00\x00', + b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00HAE0G16NL2\x96\xda\xd4\xee', ], }, CAR.SONATA: { @@ -479,6 +544,8 @@ FW_VERSIONS = { b'HM6M1_0a0_G20', b'HM6M2_0a0_BD0', b'\xf1\x8739110-2S278\xf1\x82DNDVD5GMCCXXXL5B', + b'\xf1\x8739110-2S041\xf1\x81HM6M1_0a0_M00', + b'\xf1\x81HM6M1_0a0_G20', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00DN8 MDPS C 1,00 1,01 56310L0010\x00 4DNAC101', # modified firmware @@ -496,6 +563,7 @@ FW_VERSIONS = { b'\xf1\x8756310L0210\x00\xf1\x00DN8 MDPS C 1.00 1.01 56310L0210\x00 4DNAC101', b'\xf1\x8757700-L0000\xf1\x00DN8 MDPS R 1.00 1.00 57700-L0000 4DNAP100', b'\xf1\x00DN8 MDPS R 1.00 1.00 57700-L0000 4DNAP101', + b'\xf1\x00DN8 MDPS C 1.00 1.01 56310-L0210 4DNAC102', ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00DN8 MFC AT KOR LHD 1.00 1.02 99211-L1000 190422', @@ -505,6 +573,7 @@ FW_VERSIONS = { b'\xf1\x00DN8 MFC AT USA LHD 1.00 1.03 99211-L0000 210603', b'\xf1\x00DN8 MFC AT USA LHD 1.00 1.05 99211-L1000 201109', b'\xf1\x00DN8 MFC AT USA LHD 1.00 1.06 99211-L1000 210325', + b'\xf1\x00DN8 MFC AT USA LHD 1.00 1.07 99211-L1000 211223', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', @@ -515,6 +584,7 @@ FW_VERSIONS = { b'\xf1\x00HT6WA250BLHT6WA910A1SDN8G25NB1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x00HT6WA250BLHT6WA910A1SDN8G25NB1\x00\x00\x00\x00\x00\x00\x96\xa1\xf1\x92', b'\xf1\x00HT6WA280BLHT6WAD10A1SDN8G25NB2\x00\x00\x00\x00\x00\x00\x08\xc9O:', + b'\xf1\x00HT6WA280BLHT6WAD10A1SDN8G25NB4\x00\x00\x00\x00\x00\x00g!l[', b'\xf1\x00T02601BL T02730A1 VDN8T25XXX730NS5\xf7_\x92\xf5', b'\xf1\x00T02601BL T02832A1 VDN8T25XXX832NS8G\x0e\xfeE', b'\xf1\x87954A02N060\x00\x00\x00\x00\x00\xf1\x81T02730A1 \xf1\x00T02601BL T02730A1 VDN8T25XXX730NS5\xf7_\x92\xf5', @@ -566,6 +636,7 @@ FW_VERSIONS = { b'\xf1\x87SALFBA7460044GJ2gx\x87\x88Vf\x86hx\x88\x87\x88wwwwgw\x86wd?\xfa\xff\x86U_\xff\xaf\x1f\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', b'\xf1\x87SAMFBA8105254GJ2wx\x87\x88Vf\x86hx\x88\x87\x88wwwwwwww\x86O\xfa\xff\x99\x88\x7f\xffZG\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', b'\xf1\x87SANFB45889451GC7wx\x87\x88gw\x87x\x88\x88x\x88\x87wxw\x87wxw\x87\x8f\xfc\xffeU\x8f\xff+Q\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', + b'\xf1\x00T02601BL T02900A1 VDN8T25XXX900NSA\xb9\x13\xf9p', ], }, CAR.SONATA_LF: { @@ -615,14 +686,16 @@ FW_VERSIONS = { }, CAR.SANTA_FE: { (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00TM__ SCC F-CUP 1.00 1.00 99110-S1210 ', b'\xf1\x00TM__ SCC F-CUP 1.00 1.01 99110-S2000 ', b'\xf1\x00TM__ SCC F-CUP 1.00 1.02 99110-S2000 ', b'\xf1\x00TM__ SCC F-CUP 1.00 1.03 99110-S2000 ', ], (Ecu.abs, 0x7d1, None): [ b'\xf1\x00TM ESC \r 100\x18\x031 58910-S2650', + b'\xf1\x00TM ESC \r 105\x19\x05# 58910-S1500', b'\xf1\x00TM ESC \r 103\x18\x11\x08 58910-S2650', - b'\xf1\x00TM ESC \r 104\x19\a\b 58910-S2650', + b'\xf1\x00TM ESC \r 104\x19\x07\x08 58910-S2650', b'\xf1\x00TM ESC \x02 100\x18\x030 58910-S2600', b'\xf1\x00TM ESC \x02 102\x18\x07\x01 58910-S2600', b'\xf1\x00TM ESC \x02 103\x18\x11\x07 58910-S2600', @@ -639,11 +712,15 @@ FW_VERSIONS = { b'\xf1\x00TM MDPS C 1.00 1.00 56340-S2000 8409', b'\xf1\x00TM MDPS C 1.00 1.00 56340-S2000 8A12', b'\xf1\x00TM MDPS C 1.00 1.01 56340-S2000 9129', + b'\xf1\x00TM MDPS R 1.00 1.02 57700-S1100 4TMDP102' ], (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00TM MFC AT EUR LHD 1.00 1.01 99211-S1010 181207', b'\xf1\x00TM MFC AT USA LHD 1.00 1.00 99211-S2000 180409', ], (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x00bcsh8p54 U833\x00\x00\x00\x00\x00\x00TTM4V22US3_<]\xf1', + b'\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM4T20NS5\x00\x00\x00\x00', b'\xf1\x87LBJSGA7082574HG0\x87www\x98\x88\x88\x88\x99\xaa\xb9\x9afw\x86gx\x99\xa7\x89co\xf8\xffvU_\xffR\xaf\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM2T20NS1\x00\xa6\xe0\x91', b'\xf1\x87LBKSGA0458404HG0vfvg\x87www\x89\x99\xa8\x99y\xaa\xa7\x9ax\x88\xa7\x88t_\xf9\xff\x86w\x8f\xff\x15x\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM2T20NS1\x00\x00\x00\x00', b'\xf1\x87LDJUEA6010814HG1\x87w\x87x\x86gvw\x88\x88\x98\x88gw\x86wx\x88\x97\x88\x85o\xf8\xff\x86f_\xff\xd37\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM4T20NS0\xf8\x19\x92g', @@ -685,12 +762,14 @@ FW_VERSIONS = { b'\xf1\x8758910-S1DA0\xf1\x00TM ESC \x1e 102 \x08\x08 58910-S1DA0', b'\xf1\x8758910-S2GA0\xf1\x00TM ESC \x04 102!\x04\x05 58910-S2GA0', b'\xf1\x00TM ESC \x04 102!\x04\x05 58910-S2GA0', + b'\xf1\x00TM ESC \x04 101 \x08\x04 58910-S2GA0', ], (Ecu.engine, 0x7e0, None): [ b'\xf1\x82TACVN5GMI3XXXH0A', b'\xf1\x82TMBZN5TMD3XXXG2E', b'\xf1\x82TACVN5GSI3XXXH0A', b'\xf1\x82TMCFD5MMCXXXXG0A', + b'\xf1\x81HM6M1_0a0_G20', b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x82TMDWN5TMD3TXXJ1A', b'\xf1\x81HM6M2_0a0_G00', b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M1_0a0_J10', @@ -715,23 +794,28 @@ FW_VERSIONS = { b'\xf1\x00HT6TA290BLHT6TAF00A1STM0M25GS1\x00\x00\x00\x00\x00\x006\xd8\x97\x15', b'\xf1\x00T02601BL T02900A1 VTMPT25XXX900NS8\xb7\xaa\xfe\xfc', b'\xf1\x87954A02N250\x00\x00\x00\x00\x00\xf1\x81T02900A1 \xf1\x00T02601BL T02900A1 VTMPT25XXX900NS8\xb7\xaa\xfe\xfc', + b'\xf1\x00T02601BL T02800A1 VTMPT25XXX800NS4\xed\xaf\xed\xf5', ], }, CAR.SANTA_FE_HEV_2022: { (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x8799110CL500\xf1\x00TMhe SCC FHCUP 1.00 1.00 99110-CL500 ', + b'\xf1\x00TMhe SCC FHCUP 1.00 1.00 99110-CL500 ', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00TM MDPS C 1.00 1.02 56310-CLAC0 4TSHC102', + b'\xf1\x00TM MDPS R 1.00 1.05 57700-CL000 4TSHP105', ], (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00TMH MFC AT EUR LHD 1.00 1.06 99211-S1500 220727', b'\xf1\x00TMH MFC AT USA LHD 1.00 1.03 99211-S1500 210224', ], (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TTM2H16UA3I\x94\xac\x8f', b'\xf1\x87959102T250\x00\x00\x00\x00\x00\xf1\x81E14\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2333 E14\x00\x00\x00\x00\x00\x00\x00TTM2H16SA2\x80\xd7l\xb2', ], (Ecu.engine, 0x7e0, None): [ b'\xf1\x87391312MTC1', + b'\xf1\x87391312MTE0', ], }, CAR.SANTA_FE_PHEV_2022: { @@ -758,6 +842,7 @@ FW_VERSIONS = { b'\xf1\x00CK__ SCC F_CUP 1.00 1.01 96400-J5100 ', b'\xf1\x00CK__ SCC F_CUP 1.00 1.03 96400-J5100 ', b'\xf1\x00CK__ SCC F_CUP 1.00 1.01 96400-J5000 ', + b'\xf1\x00CK__ SCC F_CUP 1.00 1.02 96400-J5100 ', ], (Ecu.engine, 0x7e0, None): [ b'\xf1\x81606DE051\x00\x00\x00\x00\x00\x00\x00\x00', @@ -765,6 +850,7 @@ FW_VERSIONS = { b'\xf1\x82CKJN3TMSDE0B\x00\x00\x00\x00', b'\xf1\x82CKKN3TMD_H0A\x00\x00\x00\x00', b'\xe0\x19\xff\xe7\xe7g\x01\xa2\x00\x0f\x00\x9e\x00\x06\x00\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff\xff\xff\x00\x00\x0f\x0e\x0f\x0f\x0e\r\x00\x00\x7f\x02.\xff\x00\x00~p\x00\x00\x00\x00u\xff\xf9\xff\x00\x00\x00\x00V\t\xd5\x01\xc0\x00\x00\x00\x007\xfb\xfc\x0b\x8d\x00', + b'\xf1\x81640H0051\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00CK MDPS R 1.00 1.04 57700-J5200 4C2CL104', @@ -772,6 +858,7 @@ FW_VERSIONS = { b'\xf1\x00CK MDPS R 1.00 1.04 57700-J5420 4C4VL104', b'\xf1\x00CK MDPS R 1.00 1.06 57700-J5420 4C4VL106', b'\xf1\x00CK MDPS R 1.00 1.07 57700-J5220 4C2VL107', + b'\xf1\x00CK MDPS R 1.00 1.06 57700-J5220 4C2VL106', ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00CK MFC AT USA LHD 1.00 1.03 95740-J5000 170822', @@ -779,6 +866,7 @@ FW_VERSIONS = { b'\xf1\x00CK MFC AT EUR LHD 1.00 1.03 95740-J5000 170822', ], (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SCK0T33NB2\xb3\xee\xba\xdc', b'\xf1\x87VCJLE17622572DK0vd6D\x99\x98y\x97vwVffUfvfC%CuT&Dx\x87o\xff{\x1c\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\x88\xa2\xe6\xf0', b'\xf1\x87VDHLG17000192DK2xdFffT\xa5VUD$DwT\x86wveVeeD&T\x99\xba\x8f\xff\xcc\x99\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\x88\xa2\xe6\xf0', b'\xf1\x87VDHLG17000192DK2xdFffT\xa5VUD$DwT\x86wveVeeD&T\x99\xba\x8f\xff\xcc\x99\xf1\x89E21\x00\x00\x00\x00\x00\x00\x00\xf1\x82SCK0T33NB0', @@ -819,6 +907,7 @@ FW_VERSIONS = { b'\xf1\x00LX2_ SCC FHCUP 1.00 1.04 99110-S8100 ', b'\xf1\x00LX2_ SCC FHCUP 1.00 1.05 99110-S8100 ', b'\xf1\x00ON__ FCA FHCUP 1.00 1.02 99110-S9100 ', + b'\xf1\x00ON__ FCA FHCUP 1.00 1.01 99110-S9110 ', ], (Ecu.abs, 0x7d1, None): [ b'\xf1\x00LX ESC \x01 103\x19\t\x10 58910-S8360', @@ -832,6 +921,7 @@ FW_VERSIONS = { b'\xf1\x00ON ESC \x0b 100\x18\x12\x18 58910-S9360', b'\xf1\x00ON ESC \x0b 101\x19\t\x08 58910-S9360', b'\xf1\x00ON ESC \x0b 101\x19\t\x05 58910-S9320', + b'\xf1\x00ON ESC \x01 101\x19\t\x08 58910-S9360', ], (Ecu.engine, 0x7e0, None): [ b'\xf1\x81640J0051\x00\x00\x00\x00\x00\x00\x00\x00', @@ -854,11 +944,13 @@ FW_VERSIONS = { b'\xf1\x00ON MFC AT USA LHD 1.00 1.01 99211-S9100 181105', b'\xf1\x00ON MFC AT USA LHD 1.00 1.03 99211-S9100 200720', b'\xf1\x00LX2 MFC AT USA LHD 1.00 1.00 99211-S8110 210226', + b'\xf1\x00ON MFC AT USA LHD 1.00 1.04 99211-S9100 211227', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x00bcsh8p54 U872\x00\x00\x00\x00\x00\x00TON4G38NB1\x96z28', b'\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', b'\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00TON4G38NB2[v\\\xb6', + b'\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00TON2G38NB5j\x94.\xde', b'\xf1\x87LBLUFN591307KF25vgvw\x97wwwy\x99\xa7\x99\x99\xaa\xa9\x9af\x88\x96h\x95o\xf7\xff\x99f/\xff\xe4c\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB2\xd7\xc1/\xd1', b'\xf1\x87LBLUFN650868KF36\xa9\x98\x89\x88\xa8\x88\x88\x88h\x99\xa6\x89fw\x86gw\x88\x97x\xaa\x7f\xf6\xff\xbb\xbb\x8f\xff+\x82\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB3\xd1\xc3\xf8\xa8', b'\xf1\x87LBLUFN655162KF36\x98\x88\x88\x88\x98\x88\x88\x88x\x99\xa7\x89x\x99\xa7\x89x\x99\x97\x89g\x7f\xf7\xffwU_\xff\xe9!\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB3\xd1\xc3\xf8\xa8', @@ -966,6 +1058,25 @@ FW_VERSIONS = { b'\xf1\x81640H0051\x00\x00\x00\x00\x00\x00\x00\x00', ], }, + CAR.GENESIS_G80: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00DH__ SCC F-CUP 1.00 1.01 96400-B1120 ', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00DH LKAS AT USA LHD 1.01 1.03 95895-B1500 180713', + b'\xf1\x00DH LKAS AT USA LHD 1.01 1.02 95895-B1500 170810', + b'\xf1\x00DH LKAS AT USA LHD 1.01 1.01 95895-B1500 161014', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SDH0T33NH4\xd7O\x9e\xc9', + b'\xf1\x00bcsh8p54 E18\x00\x00\x00\x00\x00\x00\x00TDH0G38NH3:-\xa9n', + b'\xf1\x00bcsh8p54 E18\x00\x00\x00\x00\x00\x00\x00SDH0G38NH2j\x9dA\x1c', + b'\xf1\x00bcsh8p54 E18\x00\x00\x00\x00\x00\x00\x00SDH0T33NH3\x97\xe6\xbc\xb8', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x81640F0051\x00\x00\x00\x00\x00\x00\x00\x00', + ], + }, CAR.GENESIS_G90: { (Ecu.transmission, 0x7e1, None): [b'\xf1\x87VDGMD15866192DD3x\x88x\x89wuFvvfUf\x88vWwgwwwvfVgx\x87o\xff\xbc^\xf1\x81E14\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcshcm49 E14\x00\x00\x00\x00\x00\x00\x00SHI0G50NB1tc5\xb7'], (Ecu.fwdRadar, 0x7d0, None): [b'\xf1\x00HI__ SCC F-CUP 1.00 1.01 96400-D2100 '], @@ -1021,11 +1132,13 @@ FW_VERSIONS = { b'\xf1\x8799110L2000\xf1\000DL3_ SCC FHCUP 1.00 1.03 99110-L2000 ', b'\xf1\x8799110L2100\xf1\x00DL3_ SCC F-CUP 1.00 1.03 99110-L2100 ', b'\xf1\x8799110L2100\xf1\x00DL3_ SCC FHCUP 1.00 1.03 99110-L2100 ', + b'\xf1\x00DL3_ SCC F-CUP 1.00 1.03 99110-L2100 ', ], (Ecu.eps, 0x7D4, None): [ b'\xf1\x8756310-L3110\xf1\000DL3 MDPS C 1.00 1.01 56310-L3110 4DLAC101', b'\xf1\x8756310-L3220\xf1\x00DL3 MDPS C 1.00 1.01 56310-L3220 4DLAC101', b'\xf1\x8757700-L3000\xf1\x00DL3 MDPS R 1.00 1.02 57700-L3000 4DLAP102', + b'\xf1\x00DL3 MDPS C 1.00 1.01 56310-L3220 4DLAC101', ], (Ecu.fwdCamera, 0x7C4, None): [ b'\xf1\x00DL3 MFC AT USA LHD 1.00 1.03 99210-L3000 200915', @@ -1036,6 +1149,7 @@ FW_VERSIONS = { b'\xf1\x8758910-L3200\xf1\000DL ESC \006 101 \004\002 58910-L3200', b'\xf1\x8758910-L3800\xf1\x00DL ESC \t 101 \x07\x02 58910-L3800', b'\xf1\x8758910-L3600\xf1\x00DL ESC \x03 100 \x08\x02 58910-L3600', + b'\xf1\x00DL ESC \t 100 \x06\x02 58910-L3800', ], (Ecu.engine, 0x7E0, None): [ b'\xf1\x87391212MKT0', @@ -1048,6 +1162,24 @@ FW_VERSIONS = { b'\xf1\x87SALFEA6046104GK2wvwgeTeFg\x88\x96xwwwwffvfe?\xfd\xff\x86fo\xff\x97A\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL2T16NB1ia\x0b\xb8', b'\xf1\x87SCMSAA8572454GK1\x87x\x87\x88Vf\x86hgwvwvwwgvwwgT?\xfb\xff\x97fo\xffH\xb8\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL4T16NB05\x94t\x18', b'\xf1\x87954A02N300\x00\x00\x00\x00\x00\xf1\x81T02730A1 \xf1\x00T02601BL T02730A1 WDL3T25XXX730NS2b\x1f\xb8%', + b'\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL4T16NB05\x94t\x18', + ], + }, + CAR.KIA_K5_HEV_2020: { + (Ecu.fwdRadar, 0x7D0, None): [ + b'\xf1\x00DLhe SCC FHCUP 1.00 1.02 99110-L7000 ', + ], + (Ecu.eps, 0x7D4, None): [ + b'\xf1\x00DL3 MDPS C 1.00 1.02 56310-L7000 4DLHC102', + ], + (Ecu.fwdCamera, 0x7C4, None): [ + b'\xf1\x00DL3HMFC AT KOR LHD 1.00 1.02 99210-L2000 200309', + ], + (Ecu.engine, 0x7E0, None): [ + b'\xf1\x87391162JLA0', + ], + (Ecu.transmission, 0x7E1, None): [ + b'\xf1\x00PSBG2323 E08\x00\x00\x00\x00\x00\x00\x00TDL2H20KA2\xe3\xc6cz', ], }, CAR.KONA_EV: { @@ -1136,22 +1268,27 @@ FW_VERSIONS = { (Ecu.engine, 0x7e0, None): [ b'\xf1\x816H6F4051\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x816H6D1051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x816H6F6051\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.transmission, 0x7e1, None): [ b"\xf1\x816U3J2051\x00\x00\xf1\x006U3H0_C2\x00\x006U3J2051\x00\x00PDE0G16NS2\xf4'\\\x91", b'\xf1\x816U3J2051\x00\x00\xf1\x006U3H0_C2\x00\x006U3J2051\x00\x00PDE0G16NS2\x00\x00\x00\x00', b'\xf1\x816U3H3051\x00\x00\xf1\x006U3H0_C2\x00\x006U3H3051\x00\x00PDE0G16NS1\x00\x00\x00\x00', b'\xf1\x816U3H3051\x00\x00\xf1\x006U3H0_C2\x00\x006U3H3051\x00\x00PDE0G16NS1\x13\xcd\x88\x92', + b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PDE0G16NL2&[\xc3\x01', ], (Ecu.eps, 0x7D4, None): [ b'\xf1\x00DE MDPS C 1.00 1.09 56310G5301\x00 4DEHC109', + b'\xf1\x00DE MDPS C 1.00 1.01 56310G5520\x00 4DEPC101', ], (Ecu.fwdCamera, 0x7C4, None): [ b'\xf1\x00DEP MFC AT USA LHD 1.00 1.01 95740-G5010 170424', b'\xf1\x00DEP MFC AT USA LHD 1.00 1.00 95740-G5010 170117', + b'\xf1\x00DEP MFC AT USA LHD 1.00 1.05 99211-G5000 190826', ], (Ecu.fwdRadar, 0x7D0, None): [ b'\xf1\x00DEhe SCC H-CUP 1.01 1.02 96400-G5100 ', + b'\xf1\x00DEhe SCC F-CUP 1.00 1.02 99110-G5100 ', ], }, CAR.KIA_NIRO_HEV_2021: { @@ -1240,43 +1377,49 @@ FW_VERSIONS = { CAR.ELANTRA: { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00PD LKAS AT USA LHD 1.01 1.01 95740-G3100 A54', + b'\xf1\x00PD LKAS AT KOR LHD 1.00 1.02 95740-G3000 A51', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DPD0H16NS0e\x0e\xcd\x8e', + b'\xf1\x006U2U0_C2\x00\x006U2T0051\x00\x00DPD0D16KS0u\xce\x1fk', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00PD MDPS C 1.00 1.04 56310/G3300 4PDDC104', + b'\xf1\x00PD MDPS C 1.00 1.00 56310G3300\x00 4PDDC100', ], (Ecu.abs, 0x7d1, None): [ b'\xf1\x00PD ESC \x0b 104\x18\t\x03 58920-G3350', + b'\xf1\x00PD ESC \t 104\x18\t\x03 58920-G3350', ], (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00PD__ SCC F-CUP 1.00 1.00 96400-G3300 ', + b'\xf1\x00PD__ SCC FNCUP 1.01 1.00 96400-G3000 ', ], }, CAR.ELANTRA_2021: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00CN7_ SCC F-CUP 1.00 1.01 99110-AA000 ', b'\xf1\x00CN7_ SCC FHCUP 1.00 1.01 99110-AA000 ', + b'\xf1\x00CN7_ SCC FNCUP 1.00 1.01 99110-AA000 ', b'\xf1\x8799110AA000\xf1\x00CN7_ SCC FHCUP 1.00 1.01 99110-AA000 ', b'\xf1\x8799110AA000\xf1\x00CN7_ SCC F-CUP 1.00 1.01 99110-AA000 ', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x87\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x00CN7 MDPS C 1.00 1.06 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 4CNDC106', b'\xf1\x8756310/AA070\xf1\x00CN7 MDPS C 1.00 1.06 56310/AA070 4CNDC106', - b'\xf1\x8756310AA050\x00\xf1\x00CN7 MDPS C 1.00 1.06 56310AA050\x00 4CNDC106', b'\xf1\x8756310AA050\x00\xf1\x00CN7 MDPS C 1.00 1.06 56310AA050\x00 4CNDC106\xf1\xa01.06', + b'\xf1\x00CN7 MDPS C 1.00 1.06 56310AA050\x00 4CNDC106', ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.00 99210-AB000 200819', b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.03 99210-AA000 200819', b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.01 99210-AB000 210205', b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.06 99210-AA000 220111', + b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.03 99210-AB000 220426', ], (Ecu.abs, 0x7d1, None): [ b'\xf1\x00CN ESC \t 101 \x10\x03 58910-AB800', b'\xf1\x8758910-AA800\xf1\x00CN ESC \t 104 \x08\x03 58910-AA800', - b'\xf1\x8758910-AB800\xf1\x00CN ESC \t 101 \x10\x03 58910-AB800', b'\xf1\x8758910-AA800\xf1\x00CN ESC \t 105 \x10\x03 58910-AA800', b'\xf1\x8758910-AB800\xf1\x00CN ESC \t 101 \x10\x03 58910-AB800\xf1\xa01.01', ], @@ -1294,6 +1437,7 @@ FW_VERSIONS = { b'\xf1\x81HM6M2_0a0_FF0', b'\xf1\x82CNCVD0AMFCXCSFFB', b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M2_0a0_G80', + b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M2_0a0_HC0', ], }, CAR.ELANTRA_HEV_2021: { @@ -1390,33 +1534,53 @@ FW_VERSIONS = { b'\xf1\x81640F0051\x00\x00\x00\x00\x00\x00\x00\x00' ], }, + CAR.KIA_SORENTO_PHEV_4TH_GEN: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00MQhe SCC FHCUP 1.00 1.06 99110-P4000 ', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00MQ4HMFC AT USA LHD 1.00 1.11 99210-P2000 211217', + ] + }, CAR.KIA_EV6: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00CV1_ RDR ----- 1.00 1.01 99110-CV000 ', - b'\xf1\x8799110CV000\xf1\x00CV1_ RDR ----- 1.00 1.01 99110-CV000 ', ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.05 99210-CV000 211027', b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.06 99210-CV000 220328', b'\xf1\x00CV1 MFC AT EUR LHD 1.00 1.05 99210-CV000 211027', b'\xf1\x00CV1 MFC AT EUR LHD 1.00 1.06 99210-CV000 220328', + b'\xf1\x00CV1 MFC AT EUR RHD 1.00 1.00 99210-CV100 220630', + b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.00 99210-CV100 220630', ], }, CAR.IONIQ_5: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00NE1_ RDR ----- 1.00 1.00 99110-GI000 ', - b'\xf1\x8799110GI000\xf1\x00NE1_ RDR ----- 1.00 1.00 99110-GI000 ', ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.02 99211-GI010 211206', b'\xf1\x00NE1 MFC AT EUR LHD 1.00 1.06 99211-GI000 210813', b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.05 99211-GI010 220614', + b'\xf1\x00NE1 MFC AT EUR RHD 1.00 1.01 99211-GI010 211007', + b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.01 99211-GI010 211007', + b'\xf1\x00NE1 MFC AT EUR RHD 1.00 1.02 99211-GI010 211206', + ], + }, + CAR.TUCSON_4TH_GEN: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.01 99211-N9240 14T', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00NX4__ 1.01 1.00 99110-N9100 ', ], }, CAR.TUCSON_HYBRID_4TH_GEN: { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9240 14Q', b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9220 14K', + b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.01 99211-N9100 14A', ], (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00NX4__ 1.00 1.00 99110-N9100 ', @@ -1451,15 +1615,42 @@ FW_VERSIONS = { CAR.GENESIS_GV70_1ST_GEN: { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00JK1 MFC AT USA LHD 1.00 1.04 99211-AR000 210204', + b'\xf1\x00JK1 MFC AT USA LHD 1.00 1.01 99211-AR200 220125', ], (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00JK1_ SCC FHCUP 1.00 1.02 99110-AR000 ', + b'\xf1\x00JK1_ SCC FHCUP 1.00 1.00 99110-AR200 ', + ], + }, + CAR.GENESIS_GV60_EV_1ST_GEN: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00JW1 MFC AT USA LHD 1.00 1.02 99211-CU100 211215', + b'\xf1\x00JW1 MFC AT USA LHD 1.00 1.02 99211-CU000 211215', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00JW1_ RDR ----- 1.00 1.00 99110-CU000 ', + ], + }, + CAR.KIA_SORENTO_4TH_GEN: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00MQ4 MFC AT USA LHD 1.00 1.05 99210-R5000 210623', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00MQ4_ SCC FHCUP 1.00 1.06 99110-P2000 ', + ], + }, + CAR.KIA_NIRO_HEV_2ND_GEN: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00SG2HMFC AT USA LHD 1.01 1.08 99211-AT000 220531', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00SG2_ RDR ----- 1.00 1.01 99110-AT000 ', ], }, } CHECKSUM = { - "crc8": [CAR.SANTA_FE, CAR.SONATA, CAR.PALISADE, CAR.KIA_SELTOS, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022], + "crc8": [CAR.SANTA_FE, CAR.SONATA, CAR.PALISADE, CAR.KIA_SELTOS, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.KIA_K5_HEV_2020], "6B": [CAR.KIA_SORENTO, CAR.HYUNDAI_GENESIS], } @@ -1467,25 +1658,23 @@ FEATURES = { # which message has the gear "use_cluster_gears": {CAR.ELANTRA, CAR.KONA}, "use_tcu_gears": {CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.SONATA_LF, CAR.VELOSTER, CAR.TUCSON}, - "use_elect_gears": {CAR.KIA_NIRO_EV, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.KIA_OPTIMA_H, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.IONIQ, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.KONA_EV_2022}, - - # these cars use the FCA11 message for the AEB and FCW signals, all others use SCC12 - "use_fca": {CAR.SONATA, CAR.SONATA_HYBRID, CAR.ELANTRA, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.KIA_STINGER, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KONA_EV, CAR.KIA_FORTE, CAR.KIA_NIRO_EV, CAR.PALISADE, CAR.GENESIS_G70, CAR.GENESIS_G70_2020, CAR.KONA, CAR.SANTA_FE, CAR.KIA_SELTOS, CAR.KONA_HEV, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.TUCSON, CAR.KONA_EV_2022, CAR.KIA_STINGER_2022}, + "use_elect_gears": {CAR.KIA_NIRO_EV, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.KIA_OPTIMA_H, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.IONIQ, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.KONA_EV_2022, CAR.KIA_K5_HEV_2020}, } -CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.SANTA_CRUZ_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN, CAR.GENESIS_GV70_1ST_GEN} +CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.TUCSON_4TH_GEN, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.SANTA_CRUZ_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN, CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.GENESIS_GV60_EV_1ST_GEN, CAR.KIA_SORENTO_4TH_GEN, CAR.KIA_NIRO_HEV_2ND_GEN} # The radar does SCC on these cars when HDA I, rather than the camera -CANFD_RADAR_SCC_CAR = {CAR.GENESIS_GV70_1ST_GEN, } +CANFD_RADAR_SCC_CAR = {CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.KIA_SORENTO_4TH_GEN} # The camera does SCC on these cars, rather than the radar CAMERA_SCC_CAR = {CAR.KONA_EV_2022, } -HYBRID_CAR = {CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN} # these cars use a different gas signal -EV_CAR = {CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.KIA_NIRO_EV, CAR.KONA_EV_2022, CAR.KIA_EV6, CAR.IONIQ_5} +HYBRID_CAR = {CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.KIA_K5_HEV_2020, CAR.KIA_NIRO_HEV_2ND_GEN} # these cars use a different gas signal +EV_CAR = {CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.KIA_NIRO_EV, CAR.KONA_EV_2022, CAR.KIA_EV6, CAR.IONIQ_5, CAR.GENESIS_GV60_EV_1ST_GEN} # these cars require a special panda safety mode due to missing counters and checksums in the messages -LEGACY_SAFETY_MODE_CAR = {CAR.HYUNDAI_GENESIS, CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.IONIQ_PHEV, CAR.IONIQ, CAR.KONA_EV, CAR.KIA_SORENTO, CAR.SONATA_LF, CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.VELOSTER, CAR.KIA_STINGER, CAR.GENESIS_G70, CAR.GENESIS_G80, CAR.KIA_CEED, CAR.ELANTRA, CAR.IONIQ_HEV_2022} +LEGACY_SAFETY_MODE_CAR = {CAR.HYUNDAI_GENESIS, CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.IONIQ_PHEV, CAR.IONIQ, CAR.KONA_EV, CAR.KIA_SORENTO, CAR.SONATA_LF, CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.VELOSTER, + CAR.GENESIS_G70, CAR.GENESIS_G80, CAR.KIA_CEED, CAR.ELANTRA, CAR.IONIQ_HEV_2022} # If 0x500 is present on bus 1 it probably has a Mando radar outputting radar points. # If no points are outputted by default it might be possible to turn it on using selfdrive/debug/hyundai_enable_radar_points.py @@ -1506,6 +1695,7 @@ DBC = { CAR.IONIQ_HEV_2022: dbc_dict('hyundai_kia_generic', None), CAR.KIA_FORTE: dbc_dict('hyundai_kia_generic', None), CAR.KIA_K5_2021: dbc_dict('hyundai_kia_generic', None), + CAR.KIA_K5_HEV_2020: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'), CAR.KIA_NIRO_EV: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'), CAR.KIA_NIRO_PHEV: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'), CAR.KIA_NIRO_HEV_2021: dbc_dict('hyundai_kia_generic', None), @@ -1532,10 +1722,15 @@ DBC = { CAR.KIA_CEED: dbc_dict('hyundai_kia_generic', None), CAR.KIA_EV6: dbc_dict('hyundai_canfd', None), CAR.SONATA_HYBRID: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'), + CAR.TUCSON_4TH_GEN: dbc_dict('hyundai_canfd', None), CAR.TUCSON_HYBRID_4TH_GEN: dbc_dict('hyundai_canfd', None), CAR.IONIQ_5: dbc_dict('hyundai_canfd', None), CAR.SANTA_CRUZ_1ST_GEN: dbc_dict('hyundai_canfd', None), CAR.KIA_SPORTAGE_5TH_GEN: dbc_dict('hyundai_canfd', None), CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: dbc_dict('hyundai_canfd', None), CAR.GENESIS_GV70_1ST_GEN: dbc_dict('hyundai_canfd', None), + CAR.KIA_SORENTO_PHEV_4TH_GEN: dbc_dict('hyundai_canfd', None), + CAR.GENESIS_GV60_EV_1ST_GEN: dbc_dict('hyundai_canfd', None), + CAR.KIA_SORENTO_4TH_GEN: dbc_dict('hyundai_canfd', None), + CAR.KIA_NIRO_HEV_2ND_GEN: dbc_dict('hyundai_canfd', None), } diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index 8e8872a53..249818369 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -8,10 +8,10 @@ from cereal import car from common.basedir import BASEDIR from common.conversions import Conversions as CV from common.kalman.simple_kalman import KF1D -from common.numpy_fast import interp +from common.numpy_fast import clip, interp from common.realtime import DT_CTRL -from selfdrive.car import apply_hysteresis, gen_empty_fingerprint -from selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, apply_deadzone +from selfdrive.car import apply_hysteresis, gen_empty_fingerprint, scale_rot_inertia, scale_tire_stiffness +from selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, apply_center_deadzone from selfdrive.controls.lib.events import Events from selfdrive.controls.lib.vehicle_model import VehicleModel @@ -87,10 +87,35 @@ class CarInterfaceBase(ABC): def get_pid_accel_limits(CP, current_speed, cruise_speed): return ACCEL_MIN, ACCEL_MAX + @classmethod + def get_non_essential_params(cls, candidate: str): + """ + Parameters essential to controlling the car may be incomplete or wrong without FW versions or fingerprints. + """ + return cls.get_params(candidate, gen_empty_fingerprint(), list(), False) + + @classmethod + def get_params(cls, candidate: str, fingerprint: Dict[int, Dict[int, int]], car_fw: List[car.CarParams.CarFw], experimental_long: bool): + ret = CarInterfaceBase.get_std_params(candidate) + ret = cls._get_params(ret, candidate, fingerprint, car_fw, experimental_long) + + # Set common params using fields set by the car interface + # TODO: get actual value, for now starting with reasonable value for + # civic and scaling by mass and wheelbase + ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) + + # TODO: some car interfaces set stiffness factor + if ret.tireStiffnessFront == 0 or ret.tireStiffnessRear == 0: + # TODO: start from empirically derived lateral slip stiffness for the civic and scale by + # mass and CG position, so all cars will have approximately similar dyn behaviors + ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront) + + return ret + @staticmethod @abstractmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None, experimental_long=False): - pass + def _get_params(ret: car.CarParams, candidate: str, fingerprint: Dict[int, Dict[int, int]], car_fw: List[car.CarParams.CarFw], experimental_long: bool): + raise NotImplementedError @staticmethod def init(CP, logcan, sendcan): @@ -109,7 +134,7 @@ class CarInterfaceBase(ABC): def torque_from_lateral_accel_linear(lateral_accel_value, torque_params, lateral_accel_error, lateral_accel_deadzone, friction_compensation): # The default is a linear relationship between torque and lateral acceleration (accounting for road roll and steering friction) friction_interp = interp( - apply_deadzone(lateral_accel_error, lateral_accel_deadzone), + apply_center_deadzone(lateral_accel_error, lateral_accel_deadzone), [-FRICTION_THRESHOLD, FRICTION_THRESHOLD], [-torque_params.friction, torque_params.friction] ) @@ -121,7 +146,7 @@ class CarInterfaceBase(ABC): # returns a set of default params to avoid repetition in car specific params @staticmethod - def get_std_params(candidate, fingerprint): + def get_std_params(candidate): ret = car.CarParams.new_message() ret.carFingerprint = candidate @@ -208,7 +233,7 @@ class CarInterfaceBase(ABC): return reader @abstractmethod - def apply(self, c: car.CarControl) -> Tuple[car.CarControl.Actuators, List[bytes]]: + def apply(self, c: car.CarControl, now_nanos: int) -> Tuple[car.CarControl.Actuators, List[bytes]]: pass def create_common_events(self, cs_out, extra_gears=None, pcm_enable=True, allow_enable=True, @@ -303,6 +328,7 @@ class CarStateBase(ABC): self.cruise_buttons = 0 self.left_blinker_cnt = 0 self.right_blinker_cnt = 0 + self.steering_pressed_cnt = 0 self.left_blinker_prev = False self.right_blinker_prev = False self.cluster_speed_hyst_gap = 0.0 @@ -340,6 +366,12 @@ class CarStateBase(ABC): self.right_blinker_cnt = blinker_time if right_blinker_lamp else max(self.right_blinker_cnt - 1, 0) return self.left_blinker_cnt > 0, self.right_blinker_cnt > 0 + def update_steering_pressed(self, steering_pressed, steering_pressed_min_count): + """Applies filtering on steering pressed for noisy driver torque signals.""" + self.steering_pressed_cnt += 1 if steering_pressed else -1 + self.steering_pressed_cnt = clip(self.steering_pressed_cnt, 0, steering_pressed_min_count * 2) + return self.steering_pressed_cnt > steering_pressed_min_count + def update_blinker_from_stalk(self, blinker_time: int, left_blinker_stalk: bool, right_blinker_stalk: bool): """Update blinkers from stalk position. When stalk is seen the blinker will be on for at least blinker_time, or until the stalk is turned off, whichever is longer. If the opposite stalk direction is seen the blinker @@ -369,15 +401,15 @@ class CarStateBase(ABC): return GearShifter.unknown d: Dict[str, car.CarState.GearShifter] = { - 'P': GearShifter.park, 'PARK': GearShifter.park, - 'R': GearShifter.reverse, 'REVERSE': GearShifter.reverse, - 'N': GearShifter.neutral, 'NEUTRAL': GearShifter.neutral, - 'E': GearShifter.eco, 'ECO': GearShifter.eco, - 'T': GearShifter.manumatic, 'MANUAL': GearShifter.manumatic, - 'D': GearShifter.drive, 'DRIVE': GearShifter.drive, - 'S': GearShifter.sport, 'SPORT': GearShifter.sport, - 'L': GearShifter.low, 'LOW': GearShifter.low, - 'B': GearShifter.brake, 'BRAKE': GearShifter.brake, + 'P': GearShifter.park, 'PARK': GearShifter.park, + 'R': GearShifter.reverse, 'REVERSE': GearShifter.reverse, + 'N': GearShifter.neutral, 'NEUTRAL': GearShifter.neutral, + 'E': GearShifter.eco, 'ECO': GearShifter.eco, + 'T': GearShifter.manumatic, 'MANUAL': GearShifter.manumatic, + 'D': GearShifter.drive, 'DRIVE': GearShifter.drive, + 'S': GearShifter.sport, 'SPORT': GearShifter.sport, + 'L': GearShifter.low, 'LOW': GearShifter.low, + 'B': GearShifter.brake, 'BRAKE': GearShifter.brake, } return d.get(gear.upper(), GearShifter.unknown) diff --git a/selfdrive/car/isotp_parallel_query.py b/selfdrive/car/isotp_parallel_query.py index 4b4bdcc0c..d9c658a14 100644 --- a/selfdrive/car/isotp_parallel_query.py +++ b/selfdrive/car/isotp_parallel_query.py @@ -137,17 +137,17 @@ class IsoTpParallelQuery: else: response_timeouts[tx_addr] = 0 request_done[tx_addr] = True - cloudlog.warning(f"iso-tp query bad response: {tx_addr} - 0x{dat.hex()}") + cloudlog.error(f"iso-tp query bad response: {tx_addr} - 0x{dat.hex()}") cur_time = time.monotonic() if cur_time - max(response_timeouts.values()) > 0: for tx_addr in msgs: if request_counter[tx_addr] > 0 and not request_done[tx_addr]: - cloudlog.warning(f"iso-tp query timeout after receiving response: {tx_addr}") + cloudlog.error(f"iso-tp query timeout after receiving response: {tx_addr}") break if cur_time - start_time > total_timeout: - cloudlog.warning("iso-tp query timeout while receiving data") + cloudlog.error("iso-tp query timeout while receiving data") break return results diff --git a/selfdrive/car/mazda/carcontroller.py b/selfdrive/car/mazda/carcontroller.py index 2add59ccb..524a02a37 100644 --- a/selfdrive/car/mazda/carcontroller.py +++ b/selfdrive/car/mazda/carcontroller.py @@ -15,7 +15,7 @@ class CarController: self.brake_counter = 0 self.frame = 0 - def update(self, CC, CS): + def update(self, CC, CS, now_nanos): can_sends = [] apply_steer = 0 @@ -59,6 +59,7 @@ class CarController: new_actuators = CC.actuators.copy() new_actuators.steer = apply_steer / CarControllerParams.STEER_MAX + new_actuators.steerOutputCan = apply_steer self.frame += 1 return new_actuators, can_sends diff --git a/selfdrive/car/mazda/interface.py b/selfdrive/car/mazda/interface.py index 7c42431e3..2930b002d 100755 --- a/selfdrive/car/mazda/interface.py +++ b/selfdrive/car/mazda/interface.py @@ -2,7 +2,7 @@ from cereal import car from common.conversions import Conversions as CV from selfdrive.car.mazda.values import CAR, LKAS_LIMITS -from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config +from selfdrive.car import STD_CARGO_KG, scale_tire_stiffness, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase ButtonType = car.CarState.ButtonEvent.Type @@ -11,12 +11,10 @@ EventName = car.CarEvent.EventName class CarInterface(CarInterfaceBase): @staticmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None, experimental_long=False): - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) - + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "mazda" ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.mazda)] - ret.radarOffCan = True + ret.radarUnavailable = True ret.dashcamOnly = candidate not in (CAR.CX5_2022, CAR.CX9_2021) @@ -48,10 +46,6 @@ class CarInterface(CarInterfaceBase): ret.centerToFront = ret.wheelbase * 0.41 - # TODO: get actual value, for now starting with reasonable value for - # civic and scaling by mass and wheelbase - ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) - # TODO: start from empirically derived lateral slip stiffness for the civic and scale by # mass and CG position, so all cars will have approximately similar dyn behaviors ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront, @@ -75,5 +69,5 @@ class CarInterface(CarInterfaceBase): return ret - def apply(self, c): - return self.CC.update(c, self.CS) + def apply(self, c, now_nanos): + return self.CC.update(c, self.CS, now_nanos) diff --git a/selfdrive/car/mazda/values.py b/selfdrive/car/mazda/values.py index 129273efe..598b598a1 100644 --- a/selfdrive/car/mazda/values.py +++ b/selfdrive/car/mazda/values.py @@ -20,6 +20,10 @@ class CarControllerParams: STEER_DRIVER_MULTIPLIER = 1 # weight driver torque STEER_DRIVER_FACTOR = 1 # from dbc STEER_ERROR_MAX = 350 # max delta between torque cmd and torque motor + STEER_STEP = 1 # 100 Hz + + def __init__(self, CP): + pass class CAR: @@ -42,7 +46,7 @@ CAR_INFO: Dict[str, Union[MazdaCarInfo, List[MazdaCarInfo]]] = { CAR.CX9: MazdaCarInfo("Mazda CX-9 2016-20"), CAR.MAZDA3: MazdaCarInfo("Mazda 3 2017-18"), CAR.MAZDA6: MazdaCarInfo("Mazda 6 2017-20"), - CAR.CX9_2021: MazdaCarInfo("Mazda CX-9 2021-22", video_link="https://youtu.be/dA3duO4a0O4"), + CAR.CX9_2021: MazdaCarInfo("Mazda CX-9 2021-23", video_link="https://youtu.be/dA3duO4a0O4"), CAR.CX5_2022: MazdaCarInfo("Mazda CX-5 2022-23"), } @@ -283,6 +287,7 @@ FW_VERSIONS = { b'TC3M-3210X-A-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ + b'PXGW-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXM4-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXM4-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXM6-188K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -299,6 +304,7 @@ FW_VERSIONS = { b'GSH7-67XK2-N\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'GSH7-67XK2-P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'GSH7-67XK2-S\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'GSH7-67XK2-T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.transmission, 0x7e1, None): [ b'PXM4-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', diff --git a/selfdrive/car/mock/interface.py b/selfdrive/car/mock/interface.py index a3194cd79..13210c86d 100755 --- a/selfdrive/car/mock/interface.py +++ b/selfdrive/car/mock/interface.py @@ -1,39 +1,28 @@ #!/usr/bin/env python3 -import math from cereal import car -from common.conversions import Conversions as CV from system.swaglog import cloudlog import cereal.messaging as messaging -from selfdrive.car import gen_empty_fingerprint, get_safety_config +from selfdrive.car import get_safety_config from selfdrive.car.interfaces import CarInterfaceBase + # mocked car interface to work with chffrplus -TS = 0.01 # 100Hz -YAW_FR = 0.2 # ~0.8s time constant on yaw rate filter -# low pass gain -LPG = 2 * math.pi * YAW_FR * TS / (1 + 2 * math.pi * YAW_FR * TS) - - class CarInterface(CarInterfaceBase): def __init__(self, CP, CarController, CarState): super().__init__(CP, CarController, CarState) cloudlog.debug("Using Mock Car Interface") - self.sm = messaging.SubMaster(['gyroscope', 'gpsLocation', 'gpsLocationExternal']) + self.sm = messaging.SubMaster(['gpsLocation', 'gpsLocationExternal']) self.speed = 0. self.prev_speed = 0. - self.yaw_rate = 0. - self.yaw_rate_meas = 0. @staticmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None, experimental_long=False): - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "mock" ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.noOutput)] ret.mass = 1700. - ret.rotationalInertia = 2500. ret.wheelbase = 2.70 ret.centerToFront = ret.wheelbase * 0.5 ret.steerRatio = 13. # reasonable @@ -45,11 +34,6 @@ class CarInterface(CarInterfaceBase): # returns a car.CarState def _update(self, c): self.sm.update(0) - - # get basic data from phone and gps since CAN isn't connected - if self.sm.updated['gyroscope']: - self.yaw_rate_meas = -self.sm['gyroscope'].gyroUncalibrated.v[0] - gps_sock = 'gpsLocationExternal' if self.sm.rcv_frame['gpsLocationExternal'] > 1 else 'gpsLocation' if self.sm.updated[gps_sock]: self.prev_speed = self.speed @@ -61,10 +45,9 @@ class CarInterface(CarInterfaceBase): # speeds ret.vEgo = self.speed ret.vEgoRaw = self.speed - a = self.speed - self.prev_speed - ret.aEgo = a - ret.brakePressed = a < -0.5 + ret.aEgo = self.speed - self.prev_speed + ret.brakePressed = ret.aEgo < -0.5 ret.standstill = self.speed < 0.01 ret.wheelSpeeds.fl = self.speed @@ -72,13 +55,9 @@ class CarInterface(CarInterfaceBase): ret.wheelSpeeds.rl = self.speed ret.wheelSpeeds.rr = self.speed - self.yawRate = LPG * self.yaw_rate_meas + (1. - LPG) * self.yaw_rate - curvature = self.yaw_rate / max(self.speed, 1.) - ret.steeringAngleDeg = curvature * self.CP.steerRatio * self.CP.wheelbase * CV.RAD_TO_DEG - return ret - def apply(self, c): + def apply(self, c, now_nanos): # in mock no carcontrols actuators = car.CarControl.Actuators.new_message() return actuators, [] diff --git a/selfdrive/car/nissan/carcontroller.py b/selfdrive/car/nissan/carcontroller.py index dbc2b33c6..45c3dd720 100644 --- a/selfdrive/car/nissan/carcontroller.py +++ b/selfdrive/car/nissan/carcontroller.py @@ -1,6 +1,6 @@ from cereal import car -from common.numpy_fast import clip, interp from opendbc.can.packer import CANPacker +from selfdrive.car import apply_std_steer_angle_limits from selfdrive.car.nissan import nissancan from selfdrive.car.nissan.values import CAR, CarControllerParams @@ -14,11 +14,11 @@ class CarController: self.frame = 0 self.lkas_max_torque = 0 - self.last_angle = 0 + self.apply_angle_last = 0 self.packer = CANPacker(dbc_name) - def update(self, CC, CS): + def update(self, CC, CS, now_nanos): actuators = CC.actuators hud_control = CC.hudControl pcm_cancel_cmd = CC.cruiseControl.cancel @@ -28,18 +28,11 @@ class CarController: ### STEER ### lkas_hud_msg = CS.lkas_hud_msg lkas_hud_info_msg = CS.lkas_hud_info_msg - apply_angle = actuators.steeringAngleDeg - steer_hud_alert = 1 if hud_control.visualAlert in (VisualAlert.steerRequired, VisualAlert.ldw) else 0 if CC.latActive: # windup slower - if self.last_angle * apply_angle > 0. and abs(apply_angle) > abs(self.last_angle): - angle_rate_lim = interp(CS.out.vEgo, CarControllerParams.ANGLE_DELTA_BP, CarControllerParams.ANGLE_DELTA_V) - else: - angle_rate_lim = interp(CS.out.vEgo, CarControllerParams.ANGLE_DELTA_BP, CarControllerParams.ANGLE_DELTA_VU) - - apply_angle = clip(apply_angle, self.last_angle - angle_rate_lim, self.last_angle + angle_rate_lim) + apply_angle = apply_std_steer_angle_limits(actuators.steeringAngleDeg, self.apply_angle_last, CS.out.vEgo, CarControllerParams) # Max torque from driver before EPS will give up and not apply torque if not bool(CS.out.steeringPressed): @@ -57,7 +50,7 @@ class CarController: apply_angle = CS.out.steeringAngleDeg self.lkas_max_torque = 0 - self.last_angle = apply_angle + self.apply_angle_last = apply_angle if self.CP.carFingerprint in (CAR.ROGUE, CAR.XTRAIL, CAR.ALTIMA) and pcm_cancel_cmd: can_sends.append(nissancan.create_acc_cancel_cmd(self.packer, self.car_fingerprint, CS.cruise_throttle_msg)) diff --git a/selfdrive/car/nissan/interface.py b/selfdrive/car/nissan/interface.py index e095ceb46..074cd1cc5 100644 --- a/selfdrive/car/nissan/interface.py +++ b/selfdrive/car/nissan/interface.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 from cereal import car -from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config +from selfdrive.car import STD_CARGO_KG, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.nissan.values import CAR @@ -8,9 +8,7 @@ from selfdrive.car.nissan.values import CAR class CarInterface(CarInterfaceBase): @staticmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None, experimental_long=False): - - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "nissan" ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.nissan)] ret.autoResumeSng = False @@ -20,6 +18,9 @@ class CarInterface(CarInterfaceBase): ret.steerActuatorDelay = 0.1 ret.steerRatio = 17 + ret.steerControlType = car.CarParams.SteerControlType.angle + ret.radarUnavailable = True + if candidate in (CAR.ROGUE, CAR.XTRAIL): ret.mass = 1610 + STD_CARGO_KG ret.wheelbase = 2.705 @@ -35,17 +36,6 @@ class CarInterface(CarInterfaceBase): ret.wheelbase = 2.824 ret.centerToFront = ret.wheelbase * 0.44 - ret.steerControlType = car.CarParams.SteerControlType.angle - ret.radarOffCan = True - - # TODO: get actual value, for now starting with reasonable value for - # civic and scaling by mass and wheelbase - ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) - - # TODO: start from empirically derived lateral slip stiffness for the civic and scale by - # mass and CG position, so all cars will have approximately similar dyn behaviors - ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront) - return ret # returns a car.CarState @@ -66,5 +56,5 @@ class CarInterface(CarInterfaceBase): return ret - def apply(self, c): - return self.CC.update(c, self.CS) + def apply(self, c, now_nanos): + return self.CC.update(c, self.CS, now_nanos) diff --git a/selfdrive/car/nissan/values.py b/selfdrive/car/nissan/values.py index 09bd7ca83..e9af828e2 100644 --- a/selfdrive/car/nissan/values.py +++ b/selfdrive/car/nissan/values.py @@ -4,7 +4,7 @@ from enum import Enum from cereal import car from panda.python import uds -from selfdrive.car import dbc_dict +from selfdrive.car import AngleRateLimit, dbc_dict from selfdrive.car.docs_definitions import CarInfo, Harness from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries @@ -12,12 +12,14 @@ Ecu = car.CarParams.Ecu class CarControllerParams: - ANGLE_DELTA_BP = [0., 5., 15.] - ANGLE_DELTA_V = [5., .8, .15] # windup limit - ANGLE_DELTA_VU = [5., 3.5, 0.4] # unwind limit + ANGLE_RATE_LIMIT_UP = AngleRateLimit(speed_bp=[0., 5., 15.], angle_v=[5., .8, .15]) + ANGLE_RATE_LIMIT_DOWN = AngleRateLimit(speed_bp=[0., 5., 15.], angle_v=[5., 3.5, 0.4]) LKAS_MAX_TORQUE = 1 # A value of 1 is easy to overpower STEER_THRESHOLD = 1.0 + def __init__(self, CP): + pass + class CAR: XTRAIL = "NISSAN X-TRAIL 2017" @@ -37,7 +39,7 @@ class NissanCarInfo(CarInfo): CAR_INFO: Dict[str, Optional[Union[NissanCarInfo, List[NissanCarInfo]]]] = { CAR.XTRAIL: NissanCarInfo("Nissan X-Trail 2017"), - CAR.LEAF: NissanCarInfo("Nissan Leaf 2018-22"), + CAR.LEAF: NissanCarInfo("Nissan Leaf 2018-22", video_link="https://youtu.be/vaMbtAh_0cY"), CAR.LEAF_IC: None, # same platforms CAR.ROGUE: NissanCarInfo("Nissan Rogue 2018-20"), CAR.ALTIMA: NissanCarInfo("Nissan Altima 2019-20", harness=Harness.nissan_b), diff --git a/selfdrive/car/subaru/carcontroller.py b/selfdrive/car/subaru/carcontroller.py index b5429daef..24d85877d 100644 --- a/selfdrive/car/subaru/carcontroller.py +++ b/selfdrive/car/subaru/carcontroller.py @@ -19,7 +19,7 @@ class CarController: self.p = CarControllerParams(CP) self.packer = CANPacker(DBC[CP.carFingerprint]['pt']) - def update(self, CC, CS): + def update(self, CC, CS, now_nanos): actuators = CC.actuators hud_control = CC.hudControl pcm_cancel_cmd = CC.cruiseControl.cancel @@ -87,6 +87,7 @@ class CarController: new_actuators = actuators.copy() new_actuators.steer = self.apply_steer_last / self.p.STEER_MAX + new_actuators.steerOutputCan = self.apply_steer_last self.frame += 1 return new_actuators, can_sends diff --git a/selfdrive/car/subaru/interface.py b/selfdrive/car/subaru/interface.py index a920c0253..733482ef8 100644 --- a/selfdrive/car/subaru/interface.py +++ b/selfdrive/car/subaru/interface.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 from cereal import car from panda import Panda -from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config +from selfdrive.car import STD_CARGO_KG, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.subaru.values import CAR, GLOBAL_GEN2, PREGLOBAL_CARS @@ -9,11 +9,9 @@ from selfdrive.car.subaru.values import CAR, GLOBAL_GEN2, PREGLOBAL_CARS class CarInterface(CarInterfaceBase): @staticmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None, experimental_long=False): - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) - + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "subaru" - ret.radarOffCan = True + ret.radarUnavailable = True ret.dashcamOnly = candidate in PREGLOBAL_CARS ret.autoResumeSng = False @@ -103,14 +101,6 @@ class CarInterface(CarInterfaceBase): else: raise ValueError(f"unknown car: {candidate}") - # TODO: get actual value, for now starting with reasonable value for - # civic and scaling by mass and wheelbase - ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) - - # TODO: start from empirically derived lateral slip stiffness for the civic and scale by - # mass and CG position, so all cars will have approximately similar dyn behaviors - ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront) - return ret # returns a car.CarState @@ -122,5 +112,5 @@ class CarInterface(CarInterfaceBase): return ret - def apply(self, c): - return self.CC.update(c, self.CS) + def apply(self, c, now_nanos): + return self.CC.update(c, self.CS, now_nanos) diff --git a/selfdrive/car/subaru/values.py b/selfdrive/car/subaru/values.py index 7a1e9a8a3..5c7d3f5e4 100644 --- a/selfdrive/car/subaru/values.py +++ b/selfdrive/car/subaru/values.py @@ -63,7 +63,7 @@ CAR_INFO: Dict[str, Union[SubaruCarInfo, List[SubaruCarInfo]]] = { ], CAR.IMPREZA_2020: [ SubaruCarInfo("Subaru Impreza 2020-22"), - SubaruCarInfo("Subaru Crosstrek 2020-21"), + SubaruCarInfo("Subaru Crosstrek 2020-23"), SubaruCarInfo("Subaru XV 2020-21"), ], CAR.FORESTER: SubaruCarInfo("Subaru Forester 2019-21", "All"), @@ -235,12 +235,14 @@ FW_VERSIONS = { b'\x9a\xc0\000\000', b'\n\xc0\004\000', b'\x9a\xc0\x04\x00', + b'\n\xc0\x04\x01', ], (Ecu.fwdCamera, 0x787, None): [ b'\000\000eb\037@ \"', b'\000\000e\x8f\037@ )', b'\x00\x00eq\x1f@ "', b'\x00\x00eq\x00\x00\x00\x00', + b'\x00\x00e\x8f\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ b'\xca!ap\a', @@ -250,6 +252,8 @@ FW_VERSIONS = { b'\xcc!fp\a', b'\xca!f@\x07', b'\xca!fp\x07', + b'\xf3"f@\x07', + b'\xe6!fp\x07', ], (Ecu.transmission, 0x7e1, None): [ b'\xe6\xf5\004\000\000', @@ -258,6 +262,8 @@ FW_VERSIONS = { b'\xe7\xf5D0\000', b'\xf1\x00\xd7\x10@', b'\xe6\xf5D0\x00', + b'\xe9\xf6F0\x00', + b'\xe9\xf5B0\x00', ], }, CAR.FORESTER: { @@ -461,6 +467,7 @@ FW_VERSIONS = { b'\xa1 \x08\x02', b'\xa1 \x06\x02', b'\xa1 \x08\x00', + b'\xa1 "\t\x00', ], (Ecu.eps, 0x746, None): [ b'\x9b\xc0\x10\x00', @@ -482,6 +489,7 @@ FW_VERSIONS = { b'\xe2"`p\x07', b'\xf1\x82\xe2,\xa0@\x07', b'\xbc"`q\x07', + b'\xe3,\xa0@\x07', ], (Ecu.transmission, 0x7e1, None): [ b'\xa5\xfe\xf7@\x00', diff --git a/selfdrive/car/tesla/carcontroller.py b/selfdrive/car/tesla/carcontroller.py index cf43b8ef0..1f18c3d0e 100644 --- a/selfdrive/car/tesla/carcontroller.py +++ b/selfdrive/car/tesla/carcontroller.py @@ -1,5 +1,6 @@ -from common.numpy_fast import clip, interp +from common.numpy_fast import clip from opendbc.can.packer import CANPacker +from selfdrive.car import apply_std_steer_angle_limits from selfdrive.car.tesla.teslacan import TeslaCAN from selfdrive.car.tesla.values import DBC, CANBUS, CarControllerParams @@ -8,12 +9,12 @@ class CarController: def __init__(self, dbc_name, CP, VM): self.CP = CP self.frame = 0 - self.last_angle = 0 + self.apply_angle_last = 0 self.packer = CANPacker(dbc_name) self.pt_packer = CANPacker(DBC[CP.carFingerprint]['pt']) self.tesla_can = TeslaCAN(self.packer, self.pt_packer) - def update(self, CC, CS): + def update(self, CC, CS, now_nanos): actuators = CC.actuators pcm_cancel_cmd = CC.cruiseControl.cancel @@ -24,20 +25,15 @@ class CarController: lkas_enabled = CC.latActive and not hands_on_fault if lkas_enabled: - apply_angle = actuators.steeringAngleDeg - # Angular rate limit based on speed - steer_up = self.last_angle * apply_angle > 0. and abs(apply_angle) > abs(self.last_angle) - rate_limit = CarControllerParams.RATE_LIMIT_UP if steer_up else CarControllerParams.RATE_LIMIT_DOWN - max_angle_diff = interp(CS.out.vEgo, rate_limit.speed_points, rate_limit.max_angle_diff_points) - apply_angle = clip(apply_angle, self.last_angle - max_angle_diff, self.last_angle + max_angle_diff) + apply_angle = apply_std_steer_angle_limits(actuators.steeringAngleDeg, self.apply_angle_last, CS.out.vEgo, CarControllerParams) # To not fault the EPS apply_angle = clip(apply_angle, CS.out.steeringAngleDeg - 20, CS.out.steeringAngleDeg + 20) else: apply_angle = CS.out.steeringAngleDeg - self.last_angle = apply_angle + self.apply_angle_last = apply_angle can_sends.append(self.tesla_can.create_steering_control(apply_angle, lkas_enabled, self.frame)) # Longitudinal control (in sync with stock message, about 40Hz) diff --git a/selfdrive/car/tesla/interface.py b/selfdrive/car/tesla/interface.py index 2eb29efb4..70d49896c 100755 --- a/selfdrive/car/tesla/interface.py +++ b/selfdrive/car/tesla/interface.py @@ -2,14 +2,13 @@ from cereal import car from panda import Panda from selfdrive.car.tesla.values import CANBUS, CAR -from selfdrive.car import STD_CARGO_KG, gen_empty_fingerprint, scale_rot_inertia, scale_tire_stiffness, get_safety_config +from selfdrive.car import STD_CARGO_KG, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase class CarInterface(CarInterfaceBase): @staticmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None, experimental_long=False): - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "tesla" # There is no safe way to do steer blending with user torque, @@ -24,7 +23,6 @@ class CarInterface(CarInterfaceBase): ret.longitudinalTuning.kpV = [0] ret.longitudinalTuning.kiBP = [0] ret.longitudinalTuning.kiV = [0] - ret.stopAccel = 0.0 ret.longitudinalActuatorDelayUpperBound = 0.5 # s ret.radarTimeStep = (1.0 / 8) # 8Hz @@ -51,9 +49,6 @@ class CarInterface(CarInterfaceBase): else: raise ValueError(f"Unsupported car: {candidate}") - ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) - ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront) - return ret def _update(self, c): @@ -63,5 +58,5 @@ class CarInterface(CarInterfaceBase): return ret - def apply(self, c): - return self.CC.update(c, self.CS) + def apply(self, c, now_nanos): + return self.CC.update(c, self.CS, now_nanos) diff --git a/selfdrive/car/tesla/values.py b/selfdrive/car/tesla/values.py index e28666c62..bac025a9c 100644 --- a/selfdrive/car/tesla/values.py +++ b/selfdrive/car/tesla/values.py @@ -2,14 +2,13 @@ from collections import namedtuple from typing import Dict, List, Union from cereal import car -from selfdrive.car import dbc_dict +from selfdrive.car import AngleRateLimit, dbc_dict from selfdrive.car.docs_definitions import CarInfo from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries Ecu = car.CarParams.Ecu Button = namedtuple('Button', ['event_type', 'can_addr', 'can_msg', 'values']) -AngleRateLimit = namedtuple('AngleRateLimit', ['speed_points', 'max_angle_diff_points']) class CAR: @@ -99,13 +98,16 @@ BUTTONS = [ Button(car.CarState.ButtonEvent.Type.rightBlinker, "STW_ACTN_RQ", "TurnIndLvr_Stat", [2]), Button(car.CarState.ButtonEvent.Type.accelCruise, "STW_ACTN_RQ", "SpdCtrlLvr_Stat", [4, 16]), Button(car.CarState.ButtonEvent.Type.decelCruise, "STW_ACTN_RQ", "SpdCtrlLvr_Stat", [8, 32]), - Button(car.CarState.ButtonEvent.Type.cancel, "STW_ACTN_RQ", "SpdCtrlLvr_Stat", [2]), - Button(car.CarState.ButtonEvent.Type.resumeCruise, "STW_ACTN_RQ", "SpdCtrlLvr_Stat", [1]), + Button(car.CarState.ButtonEvent.Type.cancel, "STW_ACTN_RQ", "SpdCtrlLvr_Stat", [1]), + Button(car.CarState.ButtonEvent.Type.resumeCruise, "STW_ACTN_RQ", "SpdCtrlLvr_Stat", [2]), ] class CarControllerParams: - RATE_LIMIT_UP = AngleRateLimit(speed_points=[0., 5., 15.], max_angle_diff_points=[5., .8, .15]) - RATE_LIMIT_DOWN = AngleRateLimit(speed_points=[0., 5., 15.], max_angle_diff_points=[5., 3.5, 0.4]) + ANGLE_RATE_LIMIT_UP = AngleRateLimit(speed_bp=[0., 5., 15.], angle_v=[5., .8, .15]) + ANGLE_RATE_LIMIT_DOWN = AngleRateLimit(speed_bp=[0., 5., 15.], angle_v=[5., 3.5, 0.4]) JERK_LIMIT_MAX = 8 JERK_LIMIT_MIN = -8 ACCEL_TO_SPEED_MULTIPLIER = 3 + + def __init__(self, CP): + pass diff --git a/selfdrive/car/tests/test_car_interfaces.py b/selfdrive/car/tests/test_car_interfaces.py index 8d2217367..ac8213e4c 100755 --- a/selfdrive/car/tests/test_car_interfaces.py +++ b/selfdrive/car/tests/test_car_interfaces.py @@ -25,14 +25,23 @@ class TestCarInterfaces(unittest.TestCase): car_fw = [] - car_params = CarInterface.get_params(car_name, fingerprints, car_fw) + car_params = CarInterface.get_params(car_name, fingerprints, car_fw, experimental_long=False) car_interface = CarInterface(car_params, CarController, CarState) assert car_params assert car_interface self.assertGreater(car_params.mass, 1) + self.assertGreater(car_params.wheelbase, 0) + # centerToFront is center of gravity to front wheels, assert a reasonable range + self.assertTrue(car_params.wheelbase * 0.3 < car_params.centerToFront < car_params.wheelbase * 0.7) self.assertGreater(car_params.maxLateralAccel, 0) + # Longitudinal sanity checks + self.assertEqual(len(car_params.longitudinalTuning.kpV), len(car_params.longitudinalTuning.kpBP)) + self.assertEqual(len(car_params.longitudinalTuning.kiV), len(car_params.longitudinalTuning.kiBP)) + self.assertEqual(len(car_params.longitudinalTuning.deadzoneV), len(car_params.longitudinalTuning.deadzoneBP)) + + # Lateral sanity checks if car_params.steerControlType != car.CarParams.SteerControlType.angle: tune = car_params.lateralTuning if tune.which() == 'pid': @@ -51,15 +60,15 @@ class TestCarInterfaces(unittest.TestCase): CC = car.CarControl.new_message() for _ in range(10): car_interface.update(CC, []) - car_interface.apply(CC) - car_interface.apply(CC) + car_interface.apply(CC, 0) + car_interface.apply(CC, 0) CC = car.CarControl.new_message() CC.enabled = True for _ in range(10): car_interface.update(CC, []) - car_interface.apply(CC) - car_interface.apply(CC) + car_interface.apply(CC, 0) + car_interface.apply(CC, 0) # Test radar interface RadarInterface = importlib.import_module(f'selfdrive.car.{car_params.carName}.radar_interface').RadarInterface @@ -68,7 +77,7 @@ class TestCarInterfaces(unittest.TestCase): # Run radar interface once radar_interface.update([]) - if not car_params.radarOffCan and radar_interface.rcp is not None and \ + if not car_params.radarUnavailable and radar_interface.rcp is not None and \ hasattr(radar_interface, '_update') and hasattr(radar_interface, 'trigger_msg'): radar_interface._update([radar_interface.trigger_msg]) diff --git a/selfdrive/car/torque_data/override.yaml b/selfdrive/car/torque_data/override.yaml index 6ec782444..cc1681bce 100644 --- a/selfdrive/car/torque_data/override.yaml +++ b/selfdrive/car/torque_data/override.yaml @@ -12,9 +12,11 @@ TESLA AP1 MODEL S: [.nan, 2.5, .nan] TESLA AP2 MODEL S: [.nan, 2.5, .nan] # Guess +FORD BRONCO SPORT 1ST GEN: [.nan, 1.5, .nan] FORD ESCAPE 4TH GEN: [.nan, 1.5, .nan] FORD EXPLORER 6TH GEN: [.nan, 1.5, .nan] FORD FOCUS 4TH GEN: [.nan, 1.5, .nan] +FORD MAVERICK 1ST GEN: [.nan, 1.5, .nan] ### # No steering wheel @@ -24,7 +26,7 @@ COMMA BODY: [.nan, 1000, .nan] RAM 1500 5TH GEN: [2.0, 2.0, 0.0] RAM HD 5TH GEN: [1.4, 1.4, 0.0] SUBARU OUTBACK 6TH GEN: [2.3, 2.3, 0.11] -CHEVROLET BOLT EV 2022: [2.0, 2.0, 0.05] +CADILLAC ESCALADE 2017: [1.899999976158142, 1.842270016670227, 0.1120000034570694] CHEVROLET BOLT EUV 2022: [2.0, 2.0, 0.05] CHEVROLET SILVERADO 1500 2020: [1.9, 1.9, 0.112] CHEVROLET EQUINOX 2019: [2.0, 2.0, 0.05] @@ -34,6 +36,10 @@ HYUNDAI SANTA CRUZ 1ST GEN: [2.7, 2.7, 0.1] KIA SPORTAGE 5TH GEN: [2.7, 2.7, 0.1] KIA SPORTAGE HYBRID 5TH GEN: [2.5, 2.5, 0.1] GENESIS GV70 1ST GEN: [2.42, 2.42, 0.1] +KIA SORENTO PLUG-IN HYBRID 4TH GEN: [2.5, 2.5, 0.1] +GENESIS GV60 ELECTRIC 1ST GEN: [2.5, 2.5, 0.1] +KIA SORENTO 4TH GEN: [2.5, 2.5, 0.1] +KIA NIRO HYBRID 2ND GEN: [2.42, 2.5, 0.12] # Dashcam or fallback configured as ideal car mock: [10.0, 10, 0.0] diff --git a/selfdrive/car/torque_data/params.yaml b/selfdrive/car/torque_data/params.yaml index a9023b4ed..6f8cfe0ce 100644 --- a/selfdrive/car/torque_data/params.yaml +++ b/selfdrive/car/torque_data/params.yaml @@ -4,11 +4,11 @@ ACURA RDX 2020: [1.4314459806646749, 0.33874701282109954, 0.18048847083897598] AUDI A3 3RD GEN: [1.5122414863077502, 1.7443517531719404, 0.15194151892450905] AUDI Q3 2ND GEN: [1.4439223359448605, 1.2254955789112076, 0.1413798895978097] CHEVROLET VOLT PREMIER 2017: [1.5961527626411784, 1.8422651988094612, 0.1572393918005158] -CHRYSLER PACIFICA 2018: [1.593387270257916, 1.3366521181047952, 0.13776367250652022] -CHRYSLER PACIFICA 2020: [1.4323553627965695, 1.509076559398423, 0.14328246159386085] -CHRYSLER PACIFICA HYBRID 2017: [1.3032470208409048, 1.06831764583744, 0.13287170990024627] -CHRYSLER PACIFICA HYBRID 2018: [1.6068280248761635, 1.2943025830995154, 0.1358557824293823] -CHRYSLER PACIFICA HYBRID 2019: [1.4624643614072217, 1.1958788168371808, 0.15748488008472716] +CHRYSLER PACIFICA 2018: [2.07140, 1.3366521181047952, 0.13776367250652022] +CHRYSLER PACIFICA 2020: [1.86206, 1.509076559398423, 0.14328246159386085] +CHRYSLER PACIFICA HYBRID 2017: [1.79422, 1.06831764583744, 0.116237] +CHRYSLER PACIFICA HYBRID 2018: [2.08887, 1.2943025830995154, 0.114818] +CHRYSLER PACIFICA HYBRID 2019: [1.90120, 1.1958788168371808, 0.131520] GENESIS G70 2018: [3.8520195946707947, 2.354697063349854, 0.06830285485626221] GMC ACADIA DENALI 2018: [1.3181430320331884, 1.1853735340610179, 0.3450592280031644] HONDA ACCORD 2018: [1.7135052593468778, 0.3461280068322071, 0.21579936052863807] @@ -22,15 +22,15 @@ HONDA FIT 2018: [1.5719981427109775, 0.5712761407108976, 0.110773383324281] HONDA HRV 2019: [2.0661212805710205, 0.7521343418694775, 0.17760375789242094] HONDA INSIGHT 2019: [1.5201671214069354, 0.5660229120683284, 0.25808042580281876] HONDA ODYSSEY 2018: [1.8774809275211801, 0.8394431662987996, 0.2096978613792822] -HONDA PASSPORT 2021: [1.5305538930036766, 0.7956063674638759, 0.19599407381531284] HONDA PILOT 2017: [1.7262026201812795, 0.9470005614967523, 0.21351430733218763] HONDA RIDGELINE 2017: [1.4146525028237624, 0.7356572861629564, 0.23307177552211328] HYUNDAI ELANTRA 2021: [3.169, 2.1259108157250735, 0.0819] -HYUNDAI GENESIS 2015-2016: [1.8466226943929824, 1.5552063647830634, 0.0984484465421171] +HYUNDAI GENESIS 2015-2016: [2.7807965280270794, 2.325, 0.0984484465421171] +HYUNDAI IONIQ 5 2022: [3.172929, 2.713050, 0.096019] HYUNDAI IONIQ ELECTRIC LIMITED 2019: [1.7662975472852054, 1.613755614526594, 0.17087579756306276] HYUNDAI IONIQ PHEV 2020: [3.2928700076638537, 2.1193482926455656, 0.12463700961468778] HYUNDAI IONIQ PLUG-IN HYBRID 2019: [2.970807902012267, 1.6312321830002083, 0.1088964990357482] -HYUNDAI KONA ELECTRIC 2019: [4.398306735170212, 3.2961956260770484, 0.08651833437845884] +HYUNDAI KONA ELECTRIC 2019: [3.078814714619148, 3.2961956260770484, 0.12359762054065548] HYUNDAI PALISADE 2020: [2.544642494803999, 1.8721703683337008, 0.1301424599248651] HYUNDAI SANTA FE 2019: [3.0787027729757632, 2.6173437483495565, 0.1207019341823945] HYUNDAI SANTA FE HYBRID 2022: [3.501877602644835, 2.729064118456137, 0.10384068104538963] @@ -39,9 +39,9 @@ HYUNDAI SONATA 2019: [2.2200457811703953, 1.2967330275895228, 0.1403992098658639 HYUNDAI SONATA 2020: [2.9638737459977467, 2.1259108157250735, 0.07813665616927593] HYUNDAI SONATA HYBRID 2021: [2.8990264092395734, 2.061410192222139, 0.0899805488717382] HYUNDAI TUCSON HYBRID 4TH GEN: [2.035545, 2.035545, 0.110272] -JEEP GRAND CHEROKEE 2019: [1.7321233388827006, 1.289689569171081, 0.15046331002097185] -JEEP GRAND CHEROKEE V6 2018: [1.8776598027756923, 1.4057367824262523, 0.11725947414922003] -KIA EV6 2022: [3.2, 3.0, 0.05] +JEEP GRAND CHEROKEE 2019: [2.30972, 1.289689569171081, 0.117048] +JEEP GRAND CHEROKEE V6 2018: [2.27116, 1.4057367824262523, 0.11725947414922003] +KIA EV6 2022: [3.2, 2.093457, 0.05] KIA K5 2021: [2.405339728085138, 1.460032270828705, 0.11650989850813716] KIA NIRO EV 2020: [2.9215954981365337, 2.1500583840260044, 0.09236802474810267] KIA SORENTO GT LINE 2018: [2.464854685101844, 1.5335274218367956, 0.12056170567599558] @@ -77,7 +77,7 @@ TOYOTA HIGHLANDER 2020: [2.022340166827233, 1.6183134804881791, 0.14592306380054 TOYOTA HIGHLANDER HYBRID 2018: [1.752033, 1.6433903296845025, 0.144600] TOYOTA HIGHLANDER HYBRID 2020: [1.901174, 2.104015182965606, 0.14447040132184993] TOYOTA MIRAI 2021: [2.506899832157829, 1.7417213930750164, 0.20182618449440565] -TOYOTA PRIUS 2017: [1.746445, 1.5023147650693636, 0.151515] +TOYOTA PRIUS 2017: [1.60, 1.5023147650693636, 0.151515] TOYOTA PRIUS TSS2 2021: [1.972600, 1.9104337425537743, 0.170968] TOYOTA RAV4 2017: [2.085695074355425, 2.2142832316984733, 0.13339165270103975] TOYOTA RAV4 2019: [2.331293, 2.0993589721530252, 0.129822] diff --git a/selfdrive/car/torque_data/substitute.yaml b/selfdrive/car/torque_data/substitute.yaml index 77236e393..61243424f 100644 --- a/selfdrive/car/torque_data/substitute.yaml +++ b/selfdrive/car/torque_data/substitute.yaml @@ -9,6 +9,7 @@ TOYOTA ALPHARD 2020: TOYOTA SIENNA 2018 TOYOTA PRIUS v 2017 : TOYOTA PRIUS 2017 TOYOTA RAV4 2022: TOYOTA RAV4 HYBRID 2022 TOYOTA C-HR HYBRID 2018: TOYOTA C-HR 2018 +TOYOTA C-HR HYBRID 2022: TOYOTA C-HR 2021 LEXUS IS 2018: LEXUS NX 2018 LEXUS CT HYBRID 2018 : LEXUS NX 2018 LEXUS ES HYBRID 2018: TOYOTA CAMRY HYBRID 2018 @@ -29,14 +30,15 @@ HYUNDAI VELOSTER 2019: HYUNDAI SONATA 2019 HYUNDAI KONA 2020: HYUNDAI KONA ELECTRIC 2019 HYUNDAI KONA HYBRID 2020: HYUNDAI KONA ELECTRIC 2019 HYUNDAI KONA ELECTRIC 2022: HYUNDAI KONA ELECTRIC 2019 -HYUNDAI IONIQ 5 2022: KIA EV6 2022 HYUNDAI IONIQ HYBRID 2017-2019: HYUNDAI IONIQ PLUG-IN HYBRID 2019 HYUNDAI IONIQ HYBRID 2020-2022: HYUNDAI IONIQ PLUG-IN HYBRID 2019 HYUNDAI IONIQ ELECTRIC 2020: HYUNDAI IONIQ PLUG-IN HYBRID 2019 HYUNDAI ELANTRA 2017: HYUNDAI SONATA 2019 HYUNDAI ELANTRA HYBRID 2021: HYUNDAI SONATA 2020 HYUNDAI TUCSON 2019: HYUNDAI SANTA FE 2019 +HYUNDAI TUCSON 4TH GEN: HYUNDAI TUCSON HYBRID 4TH GEN HYUNDAI SANTA FE 2022: HYUNDAI SANTA FE HYBRID 2022 +KIA K5 HYBRID 2020: KIA K5 2021 KIA STINGER 2022: KIA STINGER GT2 2018 GENESIS G90 2017: GENESIS G70 2018 GENESIS G80 2017: GENESIS G70 2018 @@ -59,6 +61,7 @@ SKODA SCALA 1ST GEN: SKODA SUPERB 3RD GEN SKODA KODIAQ 1ST GEN: SKODA SUPERB 3RD GEN SKODA KAROQ 1ST GEN: SKODA SUPERB 3RD GEN SKODA KAMIQ 1ST GEN: SKODA SUPERB 3RD GEN +VOLKSWAGEN CRAFTER 2ND GEN: VOLKSWAGEN TIGUAN 2ND GEN VOLKSWAGEN T-ROC 1ST GEN: VOLKSWAGEN TIGUAN 2ND GEN VOLKSWAGEN T-CROSS 1ST GEN: VOLKSWAGEN TIGUAN 2ND GEN VOLKSWAGEN TOURAN 2ND GEN: VOLKSWAGEN TIGUAN 2ND GEN diff --git a/selfdrive/car/toyota/carcontroller.py b/selfdrive/car/toyota/carcontroller.py index 4ec950017..2e0d7009c 100644 --- a/selfdrive/car/toyota/carcontroller.py +++ b/selfdrive/car/toyota/carcontroller.py @@ -34,7 +34,7 @@ class CarController: self.gas = 0 self.accel = 0 - def update(self, CC, CS): + def update(self, CC, CS, now_nanos): actuators = CC.actuators hud_control = CC.hudControl pcm_cancel_cmd = CC.cruiseControl.cancel @@ -143,10 +143,10 @@ class CarController: # forcing the pcm to disengage causes a bad fault sound so play a good sound instead send_ui = True - if self.frame % 100 == 0 or send_ui: + if self.frame % 20 == 0 or send_ui: can_sends.append(create_ui_command(self.packer, steer_alert, pcm_cancel_cmd, hud_control.leftLaneVisible, hud_control.rightLaneVisible, hud_control.leftLaneDepart, - hud_control.rightLaneDepart, CC.enabled)) + hud_control.rightLaneDepart, CC.enabled, CS.lkas_hud)) if (self.frame % 100 == 0 or send_ui) and self.CP.enableDsu: can_sends.append(create_fcw_command(self.packer, fcw_alert)) @@ -158,6 +158,7 @@ class CarController: new_actuators = actuators.copy() new_actuators.steer = apply_steer / CarControllerParams.STEER_MAX + new_actuators.steerOutputCan = apply_steer new_actuators.accel = self.accel new_actuators.gas = self.gas diff --git a/selfdrive/car/toyota/carstate.py b/selfdrive/car/toyota/carstate.py index 8efb2c79e..050f8747a 100644 --- a/selfdrive/car/toyota/carstate.py +++ b/selfdrive/car/toyota/carstate.py @@ -1,3 +1,5 @@ +import copy + from cereal import car from common.conversions import Conversions as CV from common.numpy_fast import mean @@ -6,7 +8,7 @@ from common.realtime import DT_CTRL from opendbc.can.can_define import CANDefine from opendbc.can.parser import CANParser from selfdrive.car.interfaces import CarStateBase -from selfdrive.car.toyota.values import ToyotaFlags, DBC, STEER_THRESHOLD, NO_STOP_TIMER_CAR, TSS2_CAR, RADAR_ACC_CAR, EPS_SCALE, UNSUPPORTED_DSU_CAR +from selfdrive.car.toyota.values import ToyotaFlags, CAR, DBC, STEER_THRESHOLD, NO_STOP_TIMER_CAR, TSS2_CAR, RADAR_ACC_CAR, EPS_SCALE, UNSUPPORTED_DSU_CAR class CarState(CarStateBase): @@ -26,6 +28,7 @@ class CarState(CarStateBase): self.low_speed_lockout = False self.acc_type = 1 + self.lkas_hud = {} def update(self, cp, cp_cam): ret = car.CarState.new_message() @@ -85,16 +88,20 @@ class CarState(CarStateBase): ret.steeringTorqueEps = cp.vl["STEER_TORQUE_SENSOR"]["STEER_TORQUE_EPS"] * self.eps_torque_scale # we could use the override bit from dbc, but it's triggered at too high torque values ret.steeringPressed = abs(ret.steeringTorque) > STEER_THRESHOLD - # steer rate fault, goes to 21 or 25 for 1 frame, then 9 for ~2 seconds - ret.steerFaultTemporary = cp.vl["EPS_STATUS"]["LKA_STATE"] in (0, 9, 21, 25) + # steer rate fault: goes to 21 or 25 for 1 frame, then 9 for 2 seconds + # lka msg drop out: goes to 9 then 11 for a combined total of 2 seconds + ret.steerFaultTemporary = cp.vl["EPS_STATUS"]["LKA_STATE"] in (0, 9, 11, 21, 25) # 17 is a fault from a prolonged high torque delta between cmd and user - ret.steerFaultPermanent = cp.vl["EPS_STATUS"]["LKA_STATE"] == 17 + # 3 is a fault from the lka command message not being received by the EPS + ret.steerFaultPermanent = cp.vl["EPS_STATUS"]["LKA_STATE"] in (3, 17) if self.CP.carFingerprint in UNSUPPORTED_DSU_CAR: + # TODO: find the bit likely in DSU_CRUISE that describes an ACC fault. one may also exist in CLUTCH ret.cruiseState.available = cp.vl["DSU_CRUISE"]["MAIN_ON"] != 0 ret.cruiseState.speed = cp.vl["DSU_CRUISE"]["SET_SPEED"] * CV.KPH_TO_MS cluster_set_speed = cp.vl["PCM_CRUISE_ALT"]["UI_SET_SPEED"] else: + ret.accFaulted = cp.vl["PCM_CRUISE_2"]["ACC_FAULTED"] != 0 ret.cruiseState.available = cp.vl["PCM_CRUISE_2"]["MAIN_ON"] != 0 ret.cruiseState.speed = cp.vl["PCM_CRUISE_2"]["SET_SPEED"] * CV.KPH_TO_MS cluster_set_speed = cp.vl["PCM_CRUISE_SM"]["UI_SET_SPEED"] @@ -120,11 +127,8 @@ class CarState(CarStateBase): self.low_speed_lockout = cp.vl["PCM_CRUISE_2"]["LOW_SPEED_LOCKOUT"] == 2 self.pcm_acc_status = cp.vl["PCM_CRUISE"]["CRUISE_STATE"] - if self.CP.carFingerprint in NO_STOP_TIMER_CAR or self.CP.enableGasInterceptor: - # ignore standstill in hybrid vehicles, since pcm allows to restart without - # receiving any special command. Also if interceptor is detected - ret.cruiseState.standstill = False - else: + if self.CP.carFingerprint not in (NO_STOP_TIMER_CAR - TSS2_CAR): + # ignore standstill state in certain vehicles, since pcm allows to restart with just an acceleration request ret.cruiseState.standstill = self.pcm_acc_status == 7 ret.cruiseState.enabled = bool(cp.vl["PCM_CRUISE"]["CRUISE_ACTIVE"]) ret.cruiseState.nonAdaptive = cp.vl["PCM_CRUISE"]["CRUISE_STATE"] in (1, 2, 3, 4, 5, 6) @@ -139,6 +143,9 @@ class CarState(CarStateBase): ret.leftBlindspot = (cp.vl["BSM"]["L_ADJACENT"] == 1) or (cp.vl["BSM"]["L_APPROACHING"] == 1) ret.rightBlindspot = (cp.vl["BSM"]["R_ADJACENT"] == 1) or (cp.vl["BSM"]["R_APPROACHING"] == 1) + if self.CP.carFingerprint != CAR.PRIUS_V: + self.lkas_hud = copy.copy(cp_cam.vl["LKAS_HUD"]) + return ret @staticmethod @@ -208,6 +215,7 @@ class CarState(CarStateBase): else: signals.append(("MAIN_ON", "PCM_CRUISE_2")) signals.append(("SET_SPEED", "PCM_CRUISE_2")) + signals.append(("ACC_FAULTED", "PCM_CRUISE_2")) signals.append(("LOW_SPEED_LOCKOUT", "PCM_CRUISE_2")) checks.append(("PCM_CRUISE_2", 33)) @@ -252,6 +260,18 @@ class CarState(CarStateBase): signals = [] checks = [] + if CP.carFingerprint != CAR.PRIUS_V: + signals += [ + ("LANE_SWAY_FLD", "LKAS_HUD"), + ("LANE_SWAY_BUZZER", "LKAS_HUD"), + ("LANE_SWAY_WARNING", "LKAS_HUD"), + ("LANE_SWAY_SENSITIVITY", "LKAS_HUD"), + ("LANE_SWAY_TOGGLE", "LKAS_HUD"), + ] + checks += [ + ("LKAS_HUD", 1), + ] + if CP.carFingerprint in (TSS2_CAR - RADAR_ACC_CAR): signals += [ ("PRECOLLISION_ACTIVE", "PRE_COLLISION"), diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index 3f4edb36d..8b3fd048d 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -3,7 +3,7 @@ from cereal import car from common.conversions import Conversions as CV from panda import Panda from selfdrive.car.toyota.values import Ecu, CAR, ToyotaFlags, TSS2_CAR, RADAR_ACC_CAR, NO_DSU_CAR, MIN_ACC_SPEED, EPS_SCALE, EV_HYBRID_CAR, UNSUPPORTED_DSU_CAR, CarControllerParams, NO_STOP_TIMER_CAR -from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config +from selfdrive.car import STD_CARGO_KG, scale_tire_stiffness, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase EventName = car.CarEvent.EventName @@ -15,9 +15,7 @@ class CarInterface(CarInterfaceBase): return CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX @staticmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[], experimental_long=False): # pylint: disable=dangerous-default-value - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) - + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "toyota" ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.toyota)] ret.safetyConfigs[0].safetyParam = EPS_SCALE[candidate] @@ -42,7 +40,8 @@ class CarInterface(CarInterfaceBase): # Only give steer angle deadzone to for bad angle sensor prius for fw in car_fw: if fw.ecu == "eps" and not fw.fwVersion == b'8965B47060\x00\x00\x00\x00\x00\x00': - steering_angle_deadzone_deg = 1.0 + steering_angle_deadzone_deg = 0.2 + ret.steerActuatorDelay = 0.25 CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning, steering_angle_deadzone_deg) elif candidate == CAR.PRIUS_V: @@ -51,7 +50,6 @@ class CarInterface(CarInterfaceBase): ret.steerRatio = 17.4 tire_stiffness_factor = 0.5533 ret.mass = 3340. * CV.LB_TO_KG + STD_CARGO_KG - CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning, steering_angle_deadzone_deg) elif candidate in (CAR.RAV4, CAR.RAV4H): stop_and_go = True if (candidate in CAR.RAV4H) else False @@ -74,7 +72,7 @@ class CarInterface(CarInterfaceBase): tire_stiffness_factor = 0.5533 ret.mass = 4481. * CV.LB_TO_KG + STD_CARGO_KG # mean between min and max - elif candidate in (CAR.CHR, CAR.CHRH): + elif candidate in (CAR.CHR, CAR.CHRH, CAR.CHR_TSS2, CAR.CHRH_TSS2): stop_and_go = True ret.wheelbase = 2.63906 ret.steerRatio = 13.6 @@ -190,10 +188,6 @@ class CarInterface(CarInterfaceBase): ret.centerToFront = ret.wheelbase * 0.44 - # TODO: get actual value, for now starting with reasonable value for - # civic and scaling by mass and wheelbase - ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) - # TODO: start from empirically derived lateral slip stiffness for the civic and scale by # mass and CG position, so all cars will have approximately similar dyn behaviors ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront, @@ -231,6 +225,8 @@ class CarInterface(CarInterfaceBase): tune.kiBP = [0., 5., 12., 20., 27.] tune.kiV = [.35, .23, .20, .17, .1] if candidate in TSS2_CAR: + ret.vEgoStopping = 0.25 + ret.vEgoStarting = 0.25 ret.stoppingDecelRate = 0.3 # reach stopping target smoothly else: tune.kpBP = [0., 5., 35.] @@ -247,16 +243,19 @@ class CarInterface(CarInterfaceBase): # events events = self.create_common_events(ret) - if self.CS.low_speed_lockout and self.CP.openpilotLongitudinalControl: - events.add(EventName.lowSpeedLockout) - if ret.vEgo < self.CP.minEnableSpeed and self.CP.openpilotLongitudinalControl: - events.add(EventName.belowEngageSpeed) - if c.actuators.accel > 0.3: - # some margin on the actuator to not false trigger cancellation while stopping - events.add(EventName.speedTooLow) - if ret.vEgo < 0.001: - # while in standstill, send a user alert - events.add(EventName.manualRestart) + if self.CP.openpilotLongitudinalControl: + if ret.cruiseState.standstill and not ret.brakePressed and not self.CP.enableGasInterceptor: + events.add(EventName.resumeRequired) + if self.CS.low_speed_lockout: + events.add(EventName.lowSpeedLockout) + if ret.vEgo < self.CP.minEnableSpeed: + events.add(EventName.belowEngageSpeed) + if c.actuators.accel > 0.3: + # some margin on the actuator to not false trigger cancellation while stopping + events.add(EventName.speedTooLow) + if ret.vEgo < 0.001: + # while in standstill, send a user alert + events.add(EventName.manualRestart) ret.events = events.to_msg() @@ -264,5 +263,5 @@ class CarInterface(CarInterfaceBase): # pass in a car.CarControl # to be called @ 100hz - def apply(self, c): - return self.CC.update(c, self.CS) + def apply(self, c, now_nanos): + return self.CC.update(c, self.CS, now_nanos) diff --git a/selfdrive/car/toyota/toyotacan.py b/selfdrive/car/toyota/toyotacan.py index 7ab3ab3e7..7e360cc4e 100644 --- a/selfdrive/car/toyota/toyotacan.py +++ b/selfdrive/car/toyota/toyotacan.py @@ -66,13 +66,13 @@ def create_fcw_command(packer, fcw): return packer.make_can_msg("ACC_HUD", 0, values) -def create_ui_command(packer, steer, chime, left_line, right_line, left_lane_depart, right_lane_depart, enabled): +def create_ui_command(packer, steer, chime, left_line, right_line, left_lane_depart, right_lane_depart, enabled, stock_lkas_hud): values = { "TWO_BEEPS": chime, "LDA_ALERT": steer, "RIGHT_LINE": 3 if right_lane_depart else 1 if right_line else 2, "LEFT_LINE": 3 if left_lane_depart else 1 if left_line else 2, - "BARRIERS" : 1 if enabled else 0, + "BARRIERS": 1 if enabled else 0, # static signals "SET_ME_X02": 2, @@ -96,4 +96,9 @@ def create_ui_command(packer, steer, chime, left_line, right_line, left_lane_dep "ADJUSTING_CAMERA": 0, "LDW_EXIST": 1, } + + # lane sway functionality + # not all cars have LKAS_HUD — update with camera values if available + values.update(stock_lkas_hud) + return packer.make_can_msg("LKAS_HUD", 0, values) diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index ba26c7e03..b6a556438 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -18,6 +18,7 @@ class CarControllerParams: ACCEL_MAX = 1.5 # m/s2, lower than allowed 2.0 m/s2 for tuning reasons ACCEL_MIN = -3.5 # m/s2 + STEER_STEP = 1 STEER_MAX = 1500 STEER_ERROR_MAX = 350 # max delta between torque cmd and torque motor @@ -48,7 +49,9 @@ class CAR: CAMRY_TSS2 = "TOYOTA CAMRY 2021" # TSS 2.5 CAMRYH_TSS2 = "TOYOTA CAMRY HYBRID 2021" CHR = "TOYOTA C-HR 2018" + CHR_TSS2 = "TOYOTA C-HR 2021" CHRH = "TOYOTA C-HR HYBRID 2018" + CHRH_TSS2 = "TOYOTA C-HR HYBRID 2022" COROLLA = "TOYOTA COROLLA 2017" COROLLA_TSS2 = "TOYOTA COROLLA TSS2 2019" # LSS2 Lexus UX Hybrid is same as a TSS2 Corolla Hybrid @@ -113,9 +116,11 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = { CAR.CAMRY: ToyotaCarInfo("Toyota Camry 2018-20", video_link="https://www.youtube.com/watch?v=fkcjviZY9CM", footnotes=[Footnote.CAMRY]), CAR.CAMRYH: ToyotaCarInfo("Toyota Camry Hybrid 2018-20", video_link="https://www.youtube.com/watch?v=Q2DYY0AWKgk"), CAR.CAMRY_TSS2: ToyotaCarInfo("Toyota Camry 2021-22", footnotes=[Footnote.CAMRY]), - CAR.CAMRYH_TSS2: ToyotaCarInfo("Toyota Camry Hybrid 2021-22"), - CAR.CHR: ToyotaCarInfo("Toyota C-HR 2017-21"), + CAR.CAMRYH_TSS2: ToyotaCarInfo("Toyota Camry Hybrid 2021-23"), + CAR.CHR: ToyotaCarInfo("Toyota C-HR 2017-20"), + CAR.CHR_TSS2: ToyotaCarInfo("Toyota C-HR 2021"), CAR.CHRH: ToyotaCarInfo("Toyota C-HR Hybrid 2017-19"), + CAR.CHRH_TSS2: ToyotaCarInfo("Toyota C-HR Hybrid 2022"), CAR.COROLLA: ToyotaCarInfo("Toyota Corolla 2017-19"), CAR.COROLLA_TSS2: [ ToyotaCarInfo("Toyota Corolla 2020-22", video_link="https://www.youtube.com/watch?v=_66pXk0CBYA"), @@ -124,13 +129,14 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = { ], CAR.COROLLAH_TSS2: [ ToyotaCarInfo("Toyota Corolla Hybrid 2020-22"), + ToyotaCarInfo("Toyota Corolla Hybrid (Non-US only) 2020-23", min_enable_speed=7.5), ToyotaCarInfo("Toyota Corolla Cross Hybrid (Non-US only) 2020-22", min_enable_speed=7.5), ToyotaCarInfo("Lexus UX Hybrid 2019-22"), ], CAR.HIGHLANDER: ToyotaCarInfo("Toyota Highlander 2017-19", video_link="https://www.youtube.com/watch?v=0wS0wXSLzoo"), - CAR.HIGHLANDER_TSS2: ToyotaCarInfo("Toyota Highlander 2020-22"), + CAR.HIGHLANDER_TSS2: ToyotaCarInfo("Toyota Highlander 2020-23"), CAR.HIGHLANDERH: ToyotaCarInfo("Toyota Highlander Hybrid 2017-19"), - CAR.HIGHLANDERH_TSS2: ToyotaCarInfo("Toyota Highlander Hybrid 2020-22"), + CAR.HIGHLANDERH_TSS2: ToyotaCarInfo("Toyota Highlander Hybrid 2020-23"), CAR.PRIUS: [ ToyotaCarInfo("Toyota Prius 2016", "Toyota Safety Sense P", "https://www.youtube.com/watch?v=8zopPJI8XQ0"), ToyotaCarInfo("Toyota Prius 2017-20", video_link="https://www.youtube.com/watch?v=8zopPJI8XQ0"), @@ -160,7 +166,7 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = { CAR.LEXUS_CTH: ToyotaCarInfo("Lexus CT Hybrid 2017-18", "Lexus Safety System+"), CAR.LEXUS_ESH: ToyotaCarInfo("Lexus ES Hybrid 2017-18", "Lexus Safety System+"), CAR.LEXUS_ES_TSS2: ToyotaCarInfo("Lexus ES 2019-22"), - CAR.LEXUS_ESH_TSS2: ToyotaCarInfo("Lexus ES Hybrid 2019-22", video_link="https://youtu.be/BZ29osRVJeg?t=12"), + CAR.LEXUS_ESH_TSS2: ToyotaCarInfo("Lexus ES Hybrid 2019-23", video_link="https://youtu.be/BZ29osRVJeg?t=12"), CAR.LEXUS_IS: ToyotaCarInfo("Lexus IS 2017-19"), CAR.LEXUS_NX: ToyotaCarInfo("Lexus NX 2018-19"), CAR.LEXUS_NXH: ToyotaCarInfo("Lexus NX Hybrid 2018-19"), @@ -209,16 +215,19 @@ FW_QUERY_CONFIG = FwQueryConfig( Request( [StdQueries.SHORT_TESTER_PRESENT_REQUEST, TOYOTA_VERSION_REQUEST], [StdQueries.SHORT_TESTER_PRESENT_RESPONSE, TOYOTA_VERSION_RESPONSE], + whitelist_ecus=[Ecu.fwdCamera, Ecu.fwdRadar, Ecu.dsu, Ecu.abs, Ecu.eps], bus=0, ), Request( [StdQueries.SHORT_TESTER_PRESENT_REQUEST, StdQueries.OBD_VERSION_REQUEST], [StdQueries.SHORT_TESTER_PRESENT_RESPONSE, StdQueries.OBD_VERSION_RESPONSE], + whitelist_ecus=[Ecu.engine], bus=0, ), Request( [StdQueries.TESTER_PRESENT_REQUEST, StdQueries.DEFAULT_DIAGNOSTIC_REQUEST, StdQueries.EXTENDED_DIAGNOSTIC_REQUEST, StdQueries.UDS_VERSION_REQUEST], [StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.DEFAULT_DIAGNOSTIC_RESPONSE, StdQueries.EXTENDED_DIAGNOSTIC_RESPONSE, StdQueries.UDS_VERSION_RESPONSE], + whitelist_ecus=[Ecu.engine, Ecu.fwdRadar, Ecu.fwdCamera, Ecu.abs, Ecu.eps], bus=0, ), ], @@ -226,7 +235,7 @@ FW_QUERY_CONFIG = FwQueryConfig( # FIXME: On some models, abs can sometimes be missing Ecu.abs: [CAR.RAV4, CAR.COROLLA, CAR.HIGHLANDER, CAR.SIENNA, CAR.LEXUS_IS], # On some models, the engine can show on two different addresses - Ecu.engine: [CAR.CAMRY, CAR.COROLLA_TSS2, CAR.CHR, CAR.LEXUS_IS], + Ecu.engine: [CAR.CAMRY, CAR.COROLLA_TSS2, CAR.CHR, CAR.CHR_TSS2, CAR.LEXUS_IS, CAR.LEXUS_RC], } ) @@ -343,6 +352,7 @@ FW_VERSIONS = { ], (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821F6201200\x00\x00\x00\x00', + b'\x018821F6201300\x00\x00\x00\x00', ], (Ecu.fwdCamera, 0x750, 0x6d): [ b'\x028646F4104100\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', @@ -548,10 +558,12 @@ FW_VERSIONS = { (Ecu.abs, 0x7b0, None): [ b'F152633D00\x00\x00\x00\x00\x00\x00', b'F152633D60\x00\x00\x00\x00\x00\x00', + b'F152633310\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x700, None): [ b'\x018966306Q6000\x00\x00\x00\x00', b'\x018966306Q7000\x00\x00\x00\x00', + b'\x018966306V1000\x00\x00\x00\x00', b'\x01896633T20000\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 15): [ @@ -562,6 +574,7 @@ FW_VERSIONS = { b'\x028646F3305200\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', b'\x028646F3305300\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', b'\x028646F3305300\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', + b'\x028646F3305500\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', ], }, CAR.CHR: { @@ -624,6 +637,30 @@ FW_VERSIONS = { b'8646FF407000 ', ], }, + CAR.CHR_TSS2: { + (Ecu.abs, 0x7b0, None): [ + b'F152610260\x00\x00\x00\x00\x00\x00', + b'F1526F4270\x00\x00\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'8965B10091\x00\x00\x00\x00\x00\x00', + b'8965B10110\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x700, None): [ + b'\x0189663F459000\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7e0, None): [ + b'\x0331014000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203402\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'\x018821FF410200\x00\x00\x00\x00', + b'\x018821FF410300\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'\x028646FF410200\x00\x00\x00\x008646GF408200\x00\x00\x00\x00', + b'\x028646FF411100\x00\x00\x00\x008646GF409000\x00\x00\x00\x00', + ], + }, CAR.CHRH: { (Ecu.engine, 0x700, None): [ b'\x0289663F405100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', @@ -677,6 +714,23 @@ FW_VERSIONS = { b'8646FF407000 ', ], }, + CAR.CHRH_TSS2: { + (Ecu.eps, 0x7a1, None): [ + b'8965B10092\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x7b0, None): [ + b'F152610041\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x700, None): [ + b'\x0189663F438000\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 15): [ + b'\x018821FF410500\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 109): [ + b'\x028646FF413100\x00\x00\x00\x008646GF411100\x00\x00\x00\x00', + ], + }, CAR.COROLLA: { (Ecu.engine, 0x7e0, None): [ b'\x0230ZC2000\x00\x00\x00\x00\x00\x00\x00\x0050212000\x00\x00\x00\x00\x00\x00\x00\x00', @@ -735,6 +789,7 @@ FW_VERSIONS = { b'\x018966312Q8000\x00\x00\x00\x00', b'\x018966312R0000\x00\x00\x00\x00', b'\x018966312R0100\x00\x00\x00\x00', + b'\x018966312R0200\x00\x00\x00\x00', b'\x018966312R1000\x00\x00\x00\x00', b'\x018966312R1100\x00\x00\x00\x00', b'\x018966312R3100\x00\x00\x00\x00', @@ -742,6 +797,7 @@ FW_VERSIONS = { b'\x018966312S7000\x00\x00\x00\x00', b'\x018966312W3000\x00\x00\x00\x00', b'\x018966312W9000\x00\x00\x00\x00', + b'\x01896637644000\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ b'\x0230A10000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', @@ -756,6 +812,7 @@ FW_VERSIONS = { b'\x03312N6100\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203402\x00\x00\x00\x00', b'\x03312N6200\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203202\x00\x00\x00\x00', b'\x03312N6200\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203302\x00\x00\x00\x00', + b'\x03312N6200\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203402\x00\x00\x00\x00', b'\x02312K4000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x02312U5000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', ], @@ -769,6 +826,7 @@ FW_VERSIONS = { b'\x018965B1255000\x00\x00\x00\x00', b'8965B12361\x00\x00\x00\x00\x00\x00', b'8965B16011\x00\x00\x00\x00\x00\x00', + b'8965B76012\x00\x00\x00\x00\x00\x00', b'\x018965B12510\x00\x00\x00\x00\x00\x00', b'\x018965B1256000\x00\x00\x00\x00', ], @@ -786,6 +844,7 @@ FW_VERSIONS = { b'\x01F152612B60\x00\x00\x00\x00\x00\x00', b'\x01F152612B61\x00\x00\x00\x00\x00\x00', b'\x01F152612B62\x00\x00\x00\x00\x00\x00', + b'\x01F152612B70\x00\x00\x00\x00\x00\x00', b'\x01F152612B71\x00\x00\x00\x00\x00\x00', b'\x01F152612B81\x00\x00\x00\x00\x00\x00', b'\x01F152612B90\x00\x00\x00\x00\x00\x00', @@ -794,6 +853,7 @@ FW_VERSIONS = { b'\x01F152612862\x00\x00\x00\x00\x00\x00', b'\x01F152612B91\x00\x00\x00\x00\x00\x00', b'\x01F15260A070\x00\x00\x00\x00\x00\x00', + b'\x01F152676250\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821F3301100\x00\x00\x00\x00', @@ -819,18 +879,22 @@ FW_VERSIONS = { b'\x01896630ZJ1000\x00\x00\x00\x00', b'\x01896630ZU8000\x00\x00\x00\x00', b'\x01896637621000\x00\x00\x00\x00', + b'\x01896637623000\x00\x00\x00\x00', b'\x01896637624000\x00\x00\x00\x00', b'\x01896637626000\x00\x00\x00\x00', + b'\x01896637639000\x00\x00\x00\x00', b'\x01896637648000\x00\x00\x00\x00', b'\x01896637643000\x00\x00\x00\x00', b'\x02896630A07000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x02896630A21000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x02896630ZJ5000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x02896630ZK8000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x02896630ZN8000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x02896630ZQ3000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x02896630ZR2000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x02896630ZT8000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x02896630ZT9000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x02896630ZZ0000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x028966312K6000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x028966312L0000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x028966312Q3000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', @@ -843,6 +907,7 @@ FW_VERSIONS = { b'8965B12361\x00\x00\x00\x00\x00\x00', b'8965B12451\x00\x00\x00\x00\x00\x00', b'8965B16011\x00\x00\x00\x00\x00\x00', + b'8965B16101\x00\x00\x00\x00\x00\x00', b'8965B76012\x00\x00\x00\x00\x00\x00', b'8965B76050\x00\x00\x00\x00\x00\x00', b'\x018965B12350\x00\x00\x00\x00\x00\x00', @@ -890,6 +955,7 @@ FW_VERSIONS = { b'\x028646F1202100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', b'\x028646F1202200\x00\x00\x00\x008646G2601500\x00\x00\x00\x00', b'\x028646F1601100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', + b'\x028646F1601200\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', b"\x028646F1601300\x00\x00\x00\x008646G2601400\x00\x00\x00\x00", b'\x028646F4203400\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', b'\x028646F76020C0\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', @@ -967,12 +1033,14 @@ FW_VERSIONS = { b'8965B48241\x00\x00\x00\x00\x00\x00', b'8965B48310\x00\x00\x00\x00\x00\x00', b'8965B48320\x00\x00\x00\x00\x00\x00', + b'8965B48400\x00\x00\x00\x00\x00\x00', ], (Ecu.abs, 0x7b0, None): [ b'\x01F15260E051\x00\x00\x00\x00\x00\x00', b'\x01F15260E061\x00\x00\x00\x00\x00\x00', b'\x01F15260E110\x00\x00\x00\x00\x00\x00', b'\x01F15260E170\x00\x00\x00\x00\x00\x00', + b'\x01F15260E05300\x00\x00\x00\x00', ], (Ecu.engine, 0x700, None): [ b'\x01896630E62100\x00\x00\x00\x00', @@ -990,6 +1058,8 @@ FW_VERSIONS = { b'\x01896630ED9100\x00\x00\x00\x00', b'\x01896630EE1000\x00\x00\x00\x00', b'\x01896630EE1100\x00\x00\x00\x00', + b'\x01896630EG3000\x00\x00\x00\x00', + b'\x01896630EG5000\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821F3301400\x00\x00\x00\x00', @@ -1000,20 +1070,24 @@ FW_VERSIONS = { b'\x028646F0E02100\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', b'\x028646F4803000\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', b'\x028646F4803000\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', + b'\x028646F4803200\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', ], }, CAR.HIGHLANDERH_TSS2: { (Ecu.eps, 0x7a1, None): [ b'8965B48241\x00\x00\x00\x00\x00\x00', b'8965B48310\x00\x00\x00\x00\x00\x00', + b'8965B48400\x00\x00\x00\x00\x00\x00', ], (Ecu.abs, 0x7b0, None): [ b'\x01F15264872300\x00\x00\x00\x00', b'\x01F15264872400\x00\x00\x00\x00', b'\x01F15264872500\x00\x00\x00\x00', + b'\x01F15264872600\x00\x00\x00\x00', b'\x01F15264873500\x00\x00\x00\x00', b'\x01F152648C6300\x00\x00\x00\x00', b'\x01F152648J4000\x00\x00\x00\x00', + b'\x01F152648J5000\x00\x00\x00\x00', b'\x01F152648J6000\x00\x00\x00\x00', ], (Ecu.engine, 0x700, None): [ @@ -1021,7 +1095,9 @@ FW_VERSIONS = { b'\x01896630EA1000\x00\x00\x00\x00', b'\x01896630EE4000\x00\x00\x00\x00', b'\x01896630EE4100\x00\x00\x00\x00', + b'\x01896630EE5000\x00\x00\x00\x00', b'\x01896630EE6000\x00\x00\x00\x00', + b'\x01896630EF8000\x00\x00\x00\x00', b'\x02896630E66000\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', b'\x02896630E66100\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', b'\x01896630EA1000\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', @@ -1037,6 +1113,7 @@ FW_VERSIONS = { b'\x028646F0E02100\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', b'\x028646F4803000\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', b'\x028646F4803000\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', + b'\x028646F4803200\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', ], }, CAR.LEXUS_IS: { @@ -1320,6 +1397,7 @@ FW_VERSIONS = { b'\x01F15260R302\x00\x00\x00\x00\x00\x00', b'\x01F152642551\x00\x00\x00\x00\x00\x00', b'\x01F152642561\x00\x00\x00\x00\x00\x00', + b'\x01F152642601\x00\x00\x00\x00\x00\x00', b'\x01F152642700\x00\x00\x00\x00\x00\x00', b'\x01F152642701\x00\x00\x00\x00\x00\x00', b'\x01F152642710\x00\x00\x00\x00\x00\x00', @@ -1560,12 +1638,14 @@ FW_VERSIONS = { b'\x028966333V4000\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', b'\x02896633T09000\x00\x00\x00\x00897CF3307001\x00\x00\x00\x00', b'\x01896633T38000\x00\x00\x00\x00', + b'\x01896633T58000\x00\x00\x00\x00', ], (Ecu.abs, 0x7b0, None): [ b'F152633423\x00\x00\x00\x00\x00\x00', b'F152633680\x00\x00\x00\x00\x00\x00', b'F152633681\x00\x00\x00\x00\x00\x00', b'F152633F50\x00\x00\x00\x00\x00\x00', + b'F152633F51\x00\x00\x00\x00\x00\x00', ], (Ecu.eps, 0x7a1, None): [ b'8965B33252\x00\x00\x00\x00\x00\x00', @@ -1581,6 +1661,7 @@ FW_VERSIONS = { b'\x018821F6201300\x00\x00\x00\x00', ], (Ecu.fwdCamera, 0x750, 0x6d): [ + b'\x028646F0610000\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', b'\x028646F33030D0\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', b'\x028646F3303100\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', b'\x028646F3303200\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', @@ -1646,6 +1727,7 @@ FW_VERSIONS = { b'\x018966378B2100\x00\x00\x00\x00', b'\x018966378B3000\x00\x00\x00\x00', b'\x018966378G3000\x00\x00\x00\x00', + b'\x018966378B2000\x00\x00\x00\x00', ], (Ecu.abs, 0x7b0, None): [ b'\x01F152678221\x00\x00\x00\x00\x00\x00', @@ -1712,22 +1794,29 @@ FW_VERSIONS = { ], }, CAR.LEXUS_RC: { + (Ecu.engine, 0x700, None): [ + b'\x01896632478200\x00\x00\x00\x00', + ], (Ecu.engine, 0x7e0, None): [ b'\x0232484000\x00\x00\x00\x00\x00\x00\x00\x0052422000\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.abs, 0x7b0, None): [ + b'F152624150\x00\x00\x00\x00\x00\x00', b'F152624221\x00\x00\x00\x00\x00\x00', ], (Ecu.dsu, 0x791, None): [ + b'881512407000\x00\x00\x00\x00', b'881512409100\x00\x00\x00\x00', ], (Ecu.eps, 0x7a1, None): [ b'8965B24081\x00\x00\x00\x00\x00\x00', + b'8965B24320\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ b'8821F4702300\x00\x00\x00\x00', ], (Ecu.fwdCamera, 0x750, 0x6d): [ + b'8646F2401200\x00\x00\x00\x00', b'8646F2402200\x00\x00\x00\x00', ], }, @@ -1986,7 +2075,9 @@ DBC = { CAR.LEXUS_RX_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), CAR.LEXUS_RXH_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), CAR.CHR: dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'), + CAR.CHR_TSS2: dbc_dict('toyota_nodsu_pt_generated', None), CAR.CHRH: dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'), + CAR.CHRH_TSS2: dbc_dict('toyota_nodsu_pt_generated', None), CAR.CAMRY: dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'), CAR.CAMRYH: dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'), CAR.CAMRY_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), @@ -2028,7 +2119,7 @@ EPS_SCALE = defaultdict(lambda: 73, {CAR.PRIUS: 66, CAR.COROLLA: 88, CAR.LEXUS_I # Toyota/Lexus Safety Sense 2.0 and 2.5 TSS2_CAR = {CAR.RAV4_TSS2, CAR.RAV4_TSS2_2022, CAR.COROLLA_TSS2, CAR.COROLLAH_TSS2, CAR.LEXUS_ES_TSS2, CAR.LEXUS_ESH_TSS2, CAR.RAV4H_TSS2, CAR.RAV4H_TSS2_2022, CAR.LEXUS_RX_TSS2, CAR.LEXUS_RXH_TSS2, CAR.HIGHLANDER_TSS2, CAR.HIGHLANDERH_TSS2, CAR.PRIUS_TSS2, CAR.CAMRY_TSS2, CAR.CAMRYH_TSS2, - CAR.MIRAI, CAR.LEXUS_NX_TSS2, CAR.LEXUS_NXH_TSS2, CAR.ALPHARD_TSS2, CAR.AVALON_TSS2, CAR.AVALONH_TSS2, CAR.ALPHARDH_TSS2} + CAR.MIRAI, CAR.LEXUS_NX_TSS2, CAR.LEXUS_NXH_TSS2, CAR.ALPHARD_TSS2, CAR.AVALON_TSS2, CAR.AVALONH_TSS2, CAR.ALPHARDH_TSS2, CAR.CHR_TSS2, CAR.CHRH_TSS2} NO_DSU_CAR = TSS2_CAR | {CAR.CHR, CAR.CHRH, CAR.CAMRY, CAR.CAMRYH} @@ -2036,9 +2127,9 @@ NO_DSU_CAR = TSS2_CAR | {CAR.CHR, CAR.CHRH, CAR.CAMRY, CAR.CAMRYH} UNSUPPORTED_DSU_CAR = {CAR.LEXUS_IS, CAR.LEXUS_RC} # these cars have a radar which sends ACC messages instead of the camera -RADAR_ACC_CAR = {CAR.RAV4H_TSS2_2022, CAR.RAV4_TSS2_2022} +RADAR_ACC_CAR = {CAR.RAV4H_TSS2_2022, CAR.RAV4_TSS2_2022, CAR.CHR_TSS2, CAR.CHRH_TSS2} -EV_HYBRID_CAR = {CAR.AVALONH_2019, CAR.AVALONH_TSS2, CAR.CAMRYH, CAR.CAMRYH_TSS2, CAR.CHRH, CAR.COROLLAH_TSS2, CAR.HIGHLANDERH, CAR.HIGHLANDERH_TSS2, CAR.PRIUS, +EV_HYBRID_CAR = {CAR.AVALONH_2019, CAR.AVALONH_TSS2, CAR.CAMRYH, CAR.CAMRYH_TSS2, CAR.CHRH, CAR.CHRH_TSS2, CAR.COROLLAH_TSS2, CAR.HIGHLANDERH, CAR.HIGHLANDERH_TSS2, CAR.PRIUS, CAR.PRIUS_V, CAR.RAV4H, CAR.RAV4H_TSS2, CAR.RAV4H_TSS2_2022, CAR.LEXUS_CTH, CAR.MIRAI, CAR.LEXUS_ESH, CAR.LEXUS_ESH_TSS2, CAR.LEXUS_NXH, CAR.LEXUS_RXH, CAR.LEXUS_RXH_TSS2, CAR.LEXUS_NXH_TSS2, CAR.PRIUS_TSS2, CAR.ALPHARDH_TSS2} diff --git a/selfdrive/car/volkswagen/carcontroller.py b/selfdrive/car/volkswagen/carcontroller.py index fff554867..5d00b5a52 100644 --- a/selfdrive/car/volkswagen/carcontroller.py +++ b/selfdrive/car/volkswagen/carcontroller.py @@ -2,6 +2,7 @@ from cereal import car from opendbc.can.packer import CANPacker from common.numpy_fast import clip from common.conversions import Conversions as CV +from common.realtime import DT_CTRL from selfdrive.car import apply_std_steer_torque_limits from selfdrive.car.volkswagen import mqbcan, pqcan from selfdrive.car.volkswagen.values import CANBUS, PQ_CARS, CarControllerParams @@ -23,14 +24,14 @@ class CarController: self.hcaSameTorqueCount = 0 self.hcaEnabledFrameCount = 0 - def update(self, CC, CS, ext_bus): + def update(self, CC, CS, ext_bus, now_nanos): actuators = CC.actuators hud_control = CC.hudControl can_sends = [] # **** Steering Controls ************************************************ # - if self.frame % self.CCP.HCA_STEP == 0: + if self.frame % self.CCP.STEER_STEP == 0: # Logic to avoid HCA state 4 "refused": # * Don't steer unless HCA is in state 3 "ready" or 5 "active" # * Don't steer at standstill @@ -49,14 +50,14 @@ class CarController: self.hcaEnabledFrameCount = 0 else: self.hcaEnabledFrameCount += 1 - if self.hcaEnabledFrameCount >= 118 * (100 / self.CCP.HCA_STEP): # 118s + if self.hcaEnabledFrameCount >= 118 * (100 / self.CCP.STEER_STEP): # 118s hcaEnabled = False self.hcaEnabledFrameCount = 0 else: hcaEnabled = True if self.apply_steer_last == apply_steer: self.hcaSameTorqueCount += 1 - if self.hcaSameTorqueCount > 1.9 * (100 / self.CCP.HCA_STEP): # 1.9s + if self.hcaSameTorqueCount > 1.9 * (100 / self.CCP.STEER_STEP): # 1.9s apply_steer -= (1, -1)[apply_steer < 0] self.hcaSameTorqueCount = 0 else: @@ -88,10 +89,13 @@ class CarController: CS.out.steeringPressed, hud_alert, hud_control)) if self.frame % self.CCP.ACC_HUD_STEP == 0 and self.CP.openpilotLongitudinalControl: + lead_distance = 0 + if hud_control.leadVisible and self.frame * DT_CTRL > 1.0: # Don't display lead until we know the scaling factor + lead_distance = 512 if CS.upscale_lead_car_signal else 8 acc_hud_status = self.CCS.acc_hud_status_value(CS.out.cruiseState.available, CS.out.accFaulted, CC.longActive) set_speed = hud_control.setSpeed * CV.MS_TO_KPH # FIXME: follow the recent displayed-speed updates, also use mph_kmh toggle to fix display rounding problem? can_sends.append(self.CCS.create_acc_hud_control(self.packer_pt, CANBUS.pt, acc_hud_status, set_speed, - hud_control.leadVisible)) + lead_distance)) # **** Stock ACC Button Controls **************************************** # @@ -103,6 +107,7 @@ class CarController: new_actuators = actuators.copy() new_actuators.steer = self.apply_steer_last / self.CCP.STEER_MAX + new_actuators.steerOutputCan = self.apply_steer_last self.gra_acc_counter_last = CS.gra_stock_values["COUNTER"] self.frame += 1 diff --git a/selfdrive/car/volkswagen/carstate.py b/selfdrive/car/volkswagen/carstate.py index def14ab01..64d124688 100644 --- a/selfdrive/car/volkswagen/carstate.py +++ b/selfdrive/car/volkswagen/carstate.py @@ -13,6 +13,7 @@ class CarState(CarStateBase): self.CCP = CarControllerParams(CP) self.button_states = {button.event_type: False for button in self.CCP.BUTTONS} self.esp_hold_confirmation = False + self.upscale_lead_car_signal = False def create_button_events(self, pt_cp, buttons): button_events = [] @@ -43,7 +44,7 @@ class CarState(CarStateBase): ret.vEgoRaw = float(np.mean([ret.wheelSpeeds.fl, ret.wheelSpeeds.fr, ret.wheelSpeeds.rl, ret.wheelSpeeds.rr])) ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) - ret.standstill = ret.vEgo < 0.1 + ret.standstill = ret.vEgoRaw == 0 # Update steering angle, rate, yaw rate, and driver input torque. VW send # the sign/direction in a separate signal so they must be recombined. @@ -62,7 +63,9 @@ class CarState(CarStateBase): ret.gas = pt_cp.vl["Motor_20"]["MO_Fahrpedalrohwert_01"] / 100.0 ret.gasPressed = ret.gas > 0 ret.brake = pt_cp.vl["ESP_05"]["ESP_Bremsdruck"] / 250.0 # FIXME: this is pressure in Bar, not sure what OP expects - ret.brakePressed = bool(pt_cp.vl["ESP_05"]["ESP_Fahrer_bremst"]) + brake_pedal_pressed = bool(pt_cp.vl["Motor_14"]["MO_Fahrer_bremst"]) + brake_pressure_detected = bool(pt_cp.vl["ESP_05"]["ESP_Fahrer_bremst"]) + ret.brakePressed = brake_pedal_pressed or brake_pressure_detected ret.parkingBrake = bool(pt_cp.vl["Kombi_01"]["KBI_Handbremse"]) # FIXME: need to include an EPB check as well # Update gear and/or clutch position data. @@ -139,6 +142,9 @@ class CarState(CarStateBase): # Additional safety checks performed in CarInterface. ret.espDisabled = pt_cp.vl["ESP_21"]["ESP_Tastung_passiv"] != 0 + # Digital instrument clusters expect the ACC HUD lead car distance to be scaled differently + self.upscale_lead_car_signal = bool(pt_cp.vl["Kombi_03"]["KBI_Variante"]) + return ret def update_pq(self, pt_cp, cam_cp, ext_cp, trans_type): @@ -154,7 +160,7 @@ class CarState(CarStateBase): # vEgo obtained from Bremse_1 vehicle speed rather than Bremse_3 wheel speeds because Bremse_3 isn't present on NSF ret.vEgoRaw = pt_cp.vl["Bremse_1"]["Geschwindigkeit_neu__Bremse_1_"] * CV.KPH_TO_MS ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) - ret.standstill = ret.vEgo < 0.1 + ret.standstill = ret.vEgoRaw == 0 # Update steering angle, rate, yaw rate, and driver input torque. VW send # the sign/direction in a separate signal so they must be recombined. @@ -218,12 +224,13 @@ class CarState(CarStateBase): ret.stockAeb = False # Update ACC radar status. - self.acc_type = 0 # TODO: this is ACC "basic" with nonzero min speed, support FtS (1) later + self.acc_type = ext_cp.vl["ACC_System"]["ACS_Typ_ACC"] ret.cruiseState.available = bool(pt_cp.vl["Motor_5"]["GRA_Hauptschalter"]) - ret.cruiseState.enabled = bool(pt_cp.vl["Motor_2"]["GRA_Status"]) + ret.cruiseState.enabled = pt_cp.vl["Motor_2"]["GRA_Status"] in (1, 2) if self.CP.pcmCruise: ret.accFaulted = ext_cp.vl["ACC_GRA_Anziege"]["ACA_StaACC"] in (6, 7) - # TODO: update opendbc with PQ TSK state for OP long accFaulted + else: + ret.accFaulted = pt_cp.vl["Motor_2"]["GRA_Status"] == 3 # Update ACC setpoint. When the setpoint reads as 255, the driver has not # yet established an ACC setpoint, so treat it as zero. @@ -268,8 +275,9 @@ class CarState(CarStateBase): ("Comfort_Signal_Right", "Blinkmodi_02"), # Right turn signal including comfort blink interval ("AB_Gurtschloss_FA", "Airbag_02"), # Seatbelt status, driver ("AB_Gurtschloss_BF", "Airbag_02"), # Seatbelt status, passenger - ("ESP_Fahrer_bremst", "ESP_05"), # Brake pedal pressed - ("ESP_Bremsdruck", "ESP_05"), # Brake pressure applied + ("ESP_Fahrer_bremst", "ESP_05"), # Driver applied brake pressure over threshold + ("MO_Fahrer_bremst", "Motor_14"), # Brake pedal switch + ("ESP_Bremsdruck", "ESP_05"), # Brake pressure ("MO_Fahrpedalrohwert_01", "Motor_20"), # Accelerator pedal value ("EPS_Lenkmoment", "LH_EPS_03"), # Absolute driver torque input ("EPS_VZ_Lenkmoment", "LH_EPS_03"), # Driver torque input sign @@ -277,6 +285,7 @@ class CarState(CarStateBase): ("ESP_Tastung_passiv", "ESP_21"), # Stability control disabled ("ESP_Haltebestaetigung", "ESP_21"), # ESP hold confirmation ("KBI_Handbremse", "Kombi_01"), # Manual handbrake applied + ("KBI_Variante", "Kombi_03"), # Digital/full-screen instrument cluster installed ("TSK_Status", "TSK_06"), # ACC engagement status from drivetrain coordinator ("GRA_Hauptschalter", "GRA_ACC_01"), # ACC button, on/off ("GRA_Abbrechen", "GRA_ACC_01"), # ACC button, cancel @@ -304,9 +313,11 @@ class CarState(CarStateBase): ("ESP_02", 50), # From J104 ABS/ESP controller ("GRA_ACC_01", 33), # From J533 CAN gateway (via LIN from steering wheel controls) ("Gateway_72", 10), # From J533 CAN gateway (aggregated data) + ("Motor_14", 10), # From J623 Engine control module ("Airbag_02", 5), # From J234 Airbag control module ("Kombi_01", 2), # From J285 Instrument cluster ("Blinkmodi_02", 1), # From J519 BCM (sent at 1Hz when no lights active, 50Hz when active) + ("Kombi_03", 0), # From J285 instrument cluster (not present on older cars, 1Hz when present) ] if CP.transmissionType == TransmissionType.automatic: @@ -318,7 +329,6 @@ class CarState(CarStateBase): elif CP.transmissionType == TransmissionType.manual: signals += [("MO_Kuppl_schalter", "Motor_14"), # Clutch switch ("BCM1_Rueckfahrlicht_Schalter", "Gateway_72")] # Reverse light from BCM - checks.append(("Motor_14", 10)) # From J623 Engine control module if CP.networkLocation == NetworkLocation.fwdCamera: # Radars are here on CANBUS.pt @@ -482,15 +492,15 @@ class MqbExtraSignals: # Additional signal and message lists for optional or bus-portable controllers fwd_radar_signals = [ ("ACC_Wunschgeschw_02", "ACC_02"), # ACC set speed - ("ACC_Typ", "ACC_06"), # Basic vs F2S vs SNG + ("ACC_Typ", "ACC_06"), # Basic vs FtS vs SnG ("AWV2_Freigabe", "ACC_10"), # FCW brake jerk release ("ANB_Teilbremsung_Freigabe", "ACC_10"), # AEB partial braking release ("ANB_Zielbremsung_Freigabe", "ACC_10"), # AEB target braking release ] fwd_radar_checks = [ - ("ACC_06", 50), # From J428 ACC radar control module - ("ACC_10", 50), # From J428 ACC radar control module - ("ACC_02", 17), # From J428 ACC radar control module + ("ACC_06", 50), # From J428 ACC radar control module + ("ACC_10", 50), # From J428 ACC radar control module + ("ACC_02", 17), # From J428 ACC radar control module ] bsm_radar_signals = [ ("SWA_Infostufe_SWA_li", "SWA_01"), # Blind spot object info, left @@ -499,24 +509,26 @@ class MqbExtraSignals: ("SWA_Warnung_SWA_re", "SWA_01"), # Blind spot object warning, right ] bsm_radar_checks = [ - ("SWA_01", 20), # From J1086 Lane Change Assist + ("SWA_01", 20), # From J1086 Lane Change Assist ] class PqExtraSignals: # Additional signal and message lists for optional or bus-portable controllers fwd_radar_signals = [ - ("ACA_StaACC", "ACC_GRA_Anziege", 0), # ACC drivetrain coordinator status - ("ACA_V_Wunsch", "ACC_GRA_Anziege", 0), # ACC set speed + ("ACS_Typ_ACC", "ACC_System"), # Basic vs FtS (no SnG support on PQ) + ("ACA_StaACC", "ACC_GRA_Anziege"), # ACC drivetrain coordinator status + ("ACA_V_Wunsch", "ACC_GRA_Anziege"), # ACC set speed ] fwd_radar_checks = [ - ("ACC_GRA_Anziege", 25), # From J428 ACC radar control module + ("ACC_System", 50), # From J428 ACC radar control module + ("ACC_GRA_Anziege", 25), # From J428 ACC radar control module ] bsm_radar_signals = [ - ("SWA_Infostufe_SWA_li", "SWA_1", 0), # Blind spot object info, left - ("SWA_Warnung_SWA_li", "SWA_1", 0), # Blind spot object warning, left - ("SWA_Infostufe_SWA_re", "SWA_1", 0), # Blind spot object info, right - ("SWA_Warnung_SWA_re", "SWA_1", 0), # Blind spot object warning, right + ("SWA_Infostufe_SWA_li", "SWA_1"), # Blind spot object info, left + ("SWA_Warnung_SWA_li", "SWA_1"), # Blind spot object warning, left + ("SWA_Infostufe_SWA_re", "SWA_1"), # Blind spot object info, right + ("SWA_Warnung_SWA_re", "SWA_1"), # Blind spot object warning, right ] bsm_radar_checks = [ - ("SWA_1", 20), # From J1086 Lane Change Assist + ("SWA_1", 20), # From J1086 Lane Change Assist ] diff --git a/selfdrive/car/volkswagen/interface.py b/selfdrive/car/volkswagen/interface.py index 816e7fcf3..521c68184 100644 --- a/selfdrive/car/volkswagen/interface.py +++ b/selfdrive/car/volkswagen/interface.py @@ -1,8 +1,7 @@ from cereal import car from panda import Panda from common.conversions import Conversions as CV -from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, \ - gen_empty_fingerprint, get_safety_config +from selfdrive.car import STD_CARGO_KG, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.volkswagen.values import CAR, PQ_CARS, CANBUS, NetworkLocation, TransmissionType, GearShifter @@ -22,10 +21,9 @@ class CarInterface(CarInterfaceBase): self.cp_ext = self.cp_cam @staticmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None, experimental_long=False): - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "volkswagen" - ret.radarOffCan = True + ret.radarUnavailable = True use_off_car_defaults = len(fingerprint[0]) == 0 # Pick sensible carParams during offline doc generation/CI jobs @@ -74,7 +72,6 @@ class CarInterface(CarInterfaceBase): ret.steerActuatorDelay = 0.1 ret.steerLimitTimer = 0.4 ret.steerRatio = 15.6 # Let the params learner figure this out - tire_stiffness_factor = 1.0 # Let the params learner figure this out ret.lateralTuning.pid.kpBP = [0.] ret.lateralTuning.pid.kiBP = [0.] ret.lateralTuning.pid.kf = 0.00006 @@ -110,6 +107,11 @@ class CarInterface(CarInterfaceBase): ret.mass = 2011 + STD_CARGO_KG ret.wheelbase = 2.98 + elif candidate == CAR.CRAFTER_MK2: + ret.mass = 2100 + STD_CARGO_KG + ret.wheelbase = 3.64 # SWB, LWB is 4.49, TBD how to detect difference + ret.minSteerSpeed = 50 * CV.KPH_TO_MS + elif candidate == CAR.GOLF_MK7: ret.mass = 1397 + STD_CARGO_KG ret.wheelbase = 2.62 @@ -137,7 +139,6 @@ class CarInterface(CarInterfaceBase): elif candidate == CAR.SHARAN_MK2: ret.mass = 1639 + STD_CARGO_KG ret.wheelbase = 2.92 - ret.minEnableSpeed = 30 * CV.KPH_TO_MS ret.minSteerSpeed = 50 * CV.KPH_TO_MS ret.steerActuatorDelay = 0.2 @@ -214,10 +215,7 @@ class CarInterface(CarInterfaceBase): raise ValueError(f"unsupported car {candidate}") ret.autoResumeSng = ret.minEnableSpeed == -1 - ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) ret.centerToFront = ret.wheelbase * 0.45 - ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront, - tire_stiffness_factor=tire_stiffness_factor) return ret # returns a car.CarState @@ -237,7 +235,7 @@ class CarInterface(CarInterfaceBase): events.add(EventName.belowSteerSpeed) if self.CS.CP.openpilotLongitudinalControl: - if ret.vEgo < self.CP.minEnableSpeed + 2.: + if ret.vEgo < self.CP.minEnableSpeed + 0.5: events.add(EventName.belowEngageSpeed) if c.enabled and ret.vEgo < self.CP.minEnableSpeed: events.add(EventName.speedTooLow) @@ -246,5 +244,5 @@ class CarInterface(CarInterfaceBase): return ret - def apply(self, c): - return self.CC.update(c, self.CS, self.ext_bus) + def apply(self, c, now_nanos): + return self.CC.update(c, self.CS, self.ext_bus, now_nanos) diff --git a/selfdrive/car/volkswagen/mqbcan.py b/selfdrive/car/volkswagen/mqbcan.py index 25a710dbb..30a51f6fe 100644 --- a/selfdrive/car/volkswagen/mqbcan.py +++ b/selfdrive/car/volkswagen/mqbcan.py @@ -96,13 +96,13 @@ def create_acc_accel_control(packer, bus, acc_type, enabled, accel, acc_control, return commands -def create_acc_hud_control(packer, bus, acc_hud_status, set_speed, lead_visible): +def create_acc_hud_control(packer, bus, acc_hud_status, set_speed, lead_distance): values = { "ACC_Status_Anzeige": acc_hud_status, "ACC_Wunschgeschw_02": set_speed if set_speed < 250 else 327.36, "ACC_Gesetzte_Zeitluecke": 3, "ACC_Display_Prio": 3, - # TODO: ACC_Abstandsindex for lead car distance, must determine analog vs digital cluster for scaling + "ACC_Abstandsindex": lead_distance, } return packer.make_can_msg("ACC_02", bus, values) diff --git a/selfdrive/car/volkswagen/pqcan.py b/selfdrive/car/volkswagen/pqcan.py index 0bcbf6abb..130f10795 100644 --- a/selfdrive/car/volkswagen/pqcan.py +++ b/selfdrive/car/volkswagen/pqcan.py @@ -66,6 +66,7 @@ def create_acc_accel_control(packer, bus, acc_type, enabled, accel, acc_control, "ACS_Sta_ADR": acc_control, "ACS_StSt_Info": acc_control != 1, "ACS_Typ_ACC": acc_type, + "ACS_Anhaltewunsch": acc_type == 1 and stopping, "ACS_Sollbeschl": accel if acc_control == 1 else 3.01, "ACS_zul_Regelabw": 0.2 if acc_control == 1 else 1.27, "ACS_max_AendGrad": 3.0 if acc_control == 1 else 5.08, @@ -76,12 +77,12 @@ def create_acc_accel_control(packer, bus, acc_type, enabled, accel, acc_control, return commands -def create_acc_hud_control(packer, bus, acc_hud_status, set_speed, lead_visible): +def create_acc_hud_control(packer, bus, acc_hud_status, set_speed, lead_distance): values = { "ACA_StaACC": acc_hud_status, "ACA_Zeitluecke": 2, "ACA_V_Wunsch": set_speed, - "ACA_gemZeitl": 8 if lead_visible else 0, + "ACA_gemZeitl": lead_distance, # TODO: ACA_ID_StaACC, ACA_AnzDisplay, ACA_kmh_mph, ACA_PrioDisp, ACA_Aend_Zeitluecke # display/display-prio handling probably needed to stop confusing the instrument cluster # kmh_mph handling probably needed to resolve rounding errors in displayed setpoint diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index cb2343e08..cd30bef73 100755 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -1,5 +1,5 @@ from collections import defaultdict, namedtuple -from dataclasses import dataclass, field +from dataclasses import dataclass from enum import Enum from typing import Dict, List, Union @@ -18,7 +18,7 @@ Button = namedtuple('Button', ['event_type', 'can_addr', 'can_msg', 'values']) class CarControllerParams: - HCA_STEP = 2 # HCA_01/HCA_1 message frequency 50Hz + STEER_STEP = 2 # HCA_01/HCA_1 message frequency 50Hz ACC_CONTROL_STEP = 2 # ACC_06/ACC_07/ACC_System frequency 50Hz ACCEL_MAX = 2.0 # 2.0 m/s max acceleration @@ -111,6 +111,7 @@ class CANBUS: class CAR: ARTEON_MK1 = "VOLKSWAGEN ARTEON 1ST GEN" # Chassis AN, Mk1 VW Arteon and variants ATLAS_MK1 = "VOLKSWAGEN ATLAS 1ST GEN" # Chassis CA, Mk1 VW Atlas and Atlas Cross Sport + CRAFTER_MK2 = "VOLKSWAGEN CRAFTER 2ND GEN" # Chassis SY/SZ, Mk2 VW Crafter, VW Grand California, MAN TGE GOLF_MK7 = "VOLKSWAGEN GOLF 7TH GEN" # Chassis 5G/AU/BA/BE, Mk7 VW Golf and variants JETTA_MK7 = "VOLKSWAGEN JETTA 7TH GEN" # Chassis BU, Mk7 VW Jetta PASSAT_MK8 = "VOLKSWAGEN PASSAT 8TH GEN" # Chassis 3G, Mk8 VW Passat and variants @@ -122,7 +123,7 @@ class CAR: TIGUAN_MK2 = "VOLKSWAGEN TIGUAN 2ND GEN" # Chassis AD/BW, Mk2 VW Tiguan and variants TOURAN_MK2 = "VOLKSWAGEN TOURAN 2ND GEN" # Chassis 1T, Mk2 VW Touran and variants TRANSPORTER_T61 = "VOLKSWAGEN TRANSPORTER T6.1" # Chassis 7H/7L, T6-facelift Transporter/Multivan/Caravelle/California - TROC_MK1 = "VOLKSWAGEN T-ROC 1ST GEN" # Chassis A1, Mk1 VW VW T-Roc and variants + TROC_MK1 = "VOLKSWAGEN T-ROC 1ST GEN" # Chassis A1, Mk1 VW T-Roc and variants AUDI_A3_MK3 = "AUDI A3 3RD GEN" # Chassis 8V/FF, Mk3 Audi A3 and variants AUDI_Q2_MK1 = "AUDI Q2 1ST GEN" # Chassis GA, Mk1 Audi Q2 (RoW) and Q2L (China only) AUDI_Q3_MK2 = "AUDI Q3 2ND GEN" # Chassis 8U/F3/FS, Mk2 Audi Q3 and variants @@ -165,7 +166,9 @@ class Footnote(Enum): class VWCarInfo(CarInfo): package: str = "Adaptive Cruise Control (ACC) & Lane Assist" harness: Enum = Harness.j533 - footnotes: List[Enum] = field(default_factory=lambda: [Footnote.VW_EXP_LONG]) + + def init_make(self, CP: car.CarParams): + self.footnotes.insert(0, Footnote.VW_EXP_LONG) CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = { @@ -182,6 +185,13 @@ CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = { VWCarInfo("Volkswagen Teramont Cross Sport 2021-22"), VWCarInfo("Volkswagen Teramont X 2021-22"), ], + CAR.CRAFTER_MK2: [ + VWCarInfo("Volkswagen Crafter 2017-23", video_link="https://youtu.be/4100gLeabmo"), + VWCarInfo("Volkswagen e-Crafter 2018-23", video_link="https://youtu.be/4100gLeabmo"), + VWCarInfo("Volkswagen Grand California 2019-23", video_link="https://youtu.be/4100gLeabmo"), + VWCarInfo("MAN TGE 2017-23", video_link="https://youtu.be/4100gLeabmo"), + VWCarInfo("MAN eTGE 2020-23", video_link="https://youtu.be/4100gLeabmo"), + ], CAR.GOLF_MK7: [ VWCarInfo("Volkswagen e-Golf 2014-20"), VWCarInfo("Volkswagen Golf 2015-20"), @@ -197,28 +207,28 @@ CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = { VWCarInfo("Volkswagen Jetta GLI 2021-22"), ], CAR.PASSAT_MK8: [ - VWCarInfo("Volkswagen Passat 2015-22", footnotes=[Footnote.VW_EXP_LONG, Footnote.PASSAT]), + VWCarInfo("Volkswagen Passat 2015-22", footnotes=[Footnote.PASSAT]), VWCarInfo("Volkswagen Passat Alltrack 2015-22"), VWCarInfo("Volkswagen Passat GTE 2015-22"), ], CAR.PASSAT_NMS: VWCarInfo("Volkswagen Passat NMS 2017-22"), CAR.POLO_MK6: [ - VWCarInfo("Volkswagen Polo 2020-22", footnotes=[Footnote.VW_EXP_LONG, Footnote.VW_MQB_A0]), - VWCarInfo("Volkswagen Polo GTI 2020-22", footnotes=[Footnote.VW_EXP_LONG, Footnote.VW_MQB_A0]), + VWCarInfo("Volkswagen Polo 2020-22", footnotes=[Footnote.VW_MQB_A0]), + VWCarInfo("Volkswagen Polo GTI 2020-22", footnotes=[Footnote.VW_MQB_A0]), ], CAR.SHARAN_MK2: [ VWCarInfo("Volkswagen Sharan 2018-22"), VWCarInfo("SEAT Alhambra 2018-20"), ], CAR.TAOS_MK1: VWCarInfo("Volkswagen Taos 2022"), - CAR.TCROSS_MK1: VWCarInfo("Volkswagen T-Cross 2021", footnotes=[Footnote.VW_EXP_LONG, Footnote.VW_MQB_A0]), - CAR.TIGUAN_MK2: VWCarInfo("Volkswagen Tiguan 2019-22"), + CAR.TCROSS_MK1: VWCarInfo("Volkswagen T-Cross 2021", footnotes=[Footnote.VW_MQB_A0]), + CAR.TIGUAN_MK2: VWCarInfo("Volkswagen Tiguan 2018-23"), CAR.TOURAN_MK2: VWCarInfo("Volkswagen Touran 2017"), CAR.TRANSPORTER_T61: [ VWCarInfo("Volkswagen Caravelle 2020"), VWCarInfo("Volkswagen California 2021"), ], - CAR.TROC_MK1: VWCarInfo("Volkswagen T-Roc 2021", footnotes=[Footnote.VW_EXP_LONG, Footnote.VW_MQB_A0]), + CAR.TROC_MK1: VWCarInfo("Volkswagen T-Roc 2021", footnotes=[Footnote.VW_MQB_A0]), CAR.AUDI_A3_MK3: [ VWCarInfo("Audi A3 2014-19"), VWCarInfo("Audi A3 Sportback e-tron 2017-18"), @@ -229,10 +239,10 @@ CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = { CAR.AUDI_Q3_MK2: VWCarInfo("Audi Q3 2019-23"), CAR.SEAT_ATECA_MK1: VWCarInfo("SEAT Ateca 2018"), CAR.SEAT_LEON_MK3: VWCarInfo("SEAT Leon 2014-20"), - CAR.SKODA_KAMIQ_MK1: VWCarInfo("Škoda Kamiq 2021", footnotes=[Footnote.VW_EXP_LONG, Footnote.VW_MQB_A0, Footnote.KAMIQ]), + CAR.SKODA_KAMIQ_MK1: VWCarInfo("Škoda Kamiq 2021", footnotes=[Footnote.VW_MQB_A0, Footnote.KAMIQ]), CAR.SKODA_KAROQ_MK1: VWCarInfo("Škoda Karoq 2019-21"), CAR.SKODA_KODIAQ_MK1: VWCarInfo("Škoda Kodiaq 2018-19"), - CAR.SKODA_SCALA_MK1: VWCarInfo("Škoda Scala 2020", footnotes=[Footnote.VW_EXP_LONG, Footnote.VW_MQB_A0]), + CAR.SKODA_SCALA_MK1: VWCarInfo("Škoda Scala 2020", footnotes=[Footnote.VW_MQB_A0]), CAR.SKODA_SUPERB_MK3: VWCarInfo("Škoda Superb 2015-22"), CAR.SKODA_OCTAVIA_MK3: [ VWCarInfo("Škoda Octavia 2015, 2018-19"), @@ -278,12 +288,14 @@ FW_VERSIONS = { CAR.ARTEON_MK1: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x873G0906259F \xf1\x890004', + b'\xf1\x873G0906259N \xf1\x890004', b'\xf1\x873G0906259P \xf1\x890001', b'\xf1\x875NA907115H \xf1\x890002', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x8709G927158L \xf1\x893611', b'\xf1\x870GC300011L \xf1\x891401', + b'\xf1\x870GC300014M \xf1\x892802', b'\xf1\x870GC300040P \xf1\x891401', ], (Ecu.srs, 0x715, None): [ @@ -295,8 +307,10 @@ FW_VERSIONS = { b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571B41815A1', b'\xf1\x873Q0909144L \xf1\x895081\xf1\x82\x0571B00817A1', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\00567B0020800', + b'\xf1\x875WA907145M \xf1\x891051\xf1\x82\x002MB4092M7N', ], (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x872Q0907572AA\xf1\x890396', b'\xf1\x872Q0907572T \xf1\x890383', b'\xf1\x875Q0907572J \xf1\x890654', ], @@ -314,11 +328,13 @@ FW_VERSIONS = { b'\xf1\x8703H906026J \xf1\x899971', b'\xf1\x8703H906026S \xf1\x896693', b'\xf1\x8703H906026S \xf1\x899970', + b'\xf1\x873CN906259 \xf1\x890005', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x8709G927158A \xf1\x893387', b'\xf1\x8709G927158DR\xf1\x893536', b'\xf1\x8709G927158DR\xf1\x893742', + b'\xf1\x8709G927158F \xf1\x893489', b'\xf1\x8709G927158FT\xf1\x893835', b'\xf1\x8709G927158GL\xf1\x893939', ], @@ -332,6 +348,7 @@ FW_VERSIONS = { (Ecu.eps, 0x712, None): [ b'\xf1\x873QF909144B \xf1\x891582\xf1\x82\00571B60924A1', b'\xf1\x873QF909144B \xf1\x891582\xf1\x82\x0571B6G920A1', + b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820528B6080105', b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820528B6090105', ], (Ecu.fwdRadar, 0x757, None): [ @@ -343,6 +360,23 @@ FW_VERSIONS = { b'\xf1\x875Q0907572P \xf1\x890682', ], }, + CAR.CRAFTER_MK2: { + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8704L906056EK\xf1\x896391', + ], + # Only current upstreamed vehicle has a manual transmission + #(Ecu.transmission, 0x7e1, None): [ + #], + (Ecu.srs, 0x715, None): [ + b'\xf1\x873Q0959655BG\xf1\x890703\xf1\x82\x0e16120016130012051G1313052900', + ], + (Ecu.eps, 0x712, None): [ + b'\xf1\x872N0909143E \xf1\x897021\xf1\x82\x05163AZ306A2', + ], + (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x872Q0907572M \xf1\x890233', + ], + }, CAR.GOLF_MK7: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8704E906016A \xf1\x897697', @@ -364,6 +398,7 @@ FW_VERSIONS = { b'\xf1\x8704L906056CR\xf1\x895813', b'\xf1\x8704L906056HE\xf1\x893758', b'\xf1\x8704L906056HN\xf1\x896590', + b'\xf1\x8704L906056HT\xf1\x896591', b'\xf1\x870EA906016A \xf1\x898343', b'\xf1\x870EA906016E \xf1\x894219', b'\xf1\x870EA906016F \xf1\x894238', @@ -371,6 +406,7 @@ FW_VERSIONS = { b'\xf1\x870EA906016Q \xf1\x895993', b'\xf1\x870EA906016S \xf1\x897207', b'\xf1\x875G0906259 \xf1\x890007', + b'\xf1\x875G0906259D \xf1\x890002', b'\xf1\x875G0906259J \xf1\x890002', b'\xf1\x875G0906259L \xf1\x890002', b'\xf1\x875G0906259N \xf1\x890003', @@ -395,6 +431,7 @@ FW_VERSIONS = { b'\xf1\x870CW300041H \xf1\x891010', b'\xf1\x870CW300042F \xf1\x891604', b'\xf1\x870CW300043B \xf1\x891601', + b'\xf1\x870CW300043E \xf1\x891603', b'\xf1\x870CW300044S \xf1\x894530', b'\xf1\x870CW300044T \xf1\x895245', b'\xf1\x870CW300045 \xf1\x894531', @@ -406,6 +443,7 @@ FW_VERSIONS = { b'\xf1\x870D9300012 \xf1\x895045', b'\xf1\x870D9300014M \xf1\x895004', b'\xf1\x870D9300014Q \xf1\x895006', + b'\xf1\x870D9300020J \xf1\x894902', b'\xf1\x870D9300020Q \xf1\x895201', b'\xf1\x870D9300020S \xf1\x895201', b'\xf1\x870D9300040A \xf1\x893613', @@ -431,6 +469,7 @@ FW_VERSIONS = { b'\xf1\x875Q0959655AA\xf1\x890388\xf1\x82\x111413001113120053114317121C111C9113', b'\xf1\x875Q0959655BH\xf1\x890336\xf1\x82\x1314160011123300314211012230229333463100', b'\xf1\x875Q0959655BS\xf1\x890403\xf1\x82\x1314160011123300314240012250229333463100', + b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x13141600111233003142404A2251229333463100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x13141600111233003142404A2252229333463100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x13141600111233003142405A2252229333463100', b'\xf1\x875Q0959655C \xf1\x890361\xf1\x82\x111413001112120004110415121610169112', @@ -453,6 +492,7 @@ FW_VERSIONS = { b'\xf1\x873Q0909144F \xf1\x895043\xf1\x82\x0561A01612A0', b'\xf1\x873Q0909144H \xf1\x895061\xf1\x82\x0566A0J612A1', b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566A00514A1', + b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566A01613A1', b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566A0J712A1', b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571A0J714A1', b'\xf1\x873Q0909144L \xf1\x895081\xf1\x82\x0571A0JA15A1', @@ -553,6 +593,7 @@ FW_VERSIONS = { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8703N906026E \xf1\x892114', b'\xf1\x8704E906023AH\xf1\x893379', + b'\xf1\x8704L906026DP\xf1\x891538', b'\xf1\x8704L906026ET\xf1\x891990', b'\xf1\x8704L906026FP\xf1\x892012', b'\xf1\x8704L906026GA\xf1\x892013', @@ -571,6 +612,7 @@ FW_VERSIONS = { ], (Ecu.srs, 0x715, None): [ b'\xf1\x873Q0959655AE\xf1\x890195\xf1\x82\r56140056130012416612124111', + b'\xf1\x873Q0959655AF\xf1\x890195\xf1\x82\r56140056130012026612120211', b'\xf1\x873Q0959655AN\xf1\x890306\xf1\x82\r58160058140013036914110311', b'\xf1\x873Q0959655BA\xf1\x890195\xf1\x82\r56140056130012516612125111', b'\xf1\x873Q0959655BB\xf1\x890195\xf1\x82\r56140056130012026612120211', @@ -581,6 +623,7 @@ FW_VERSIONS = { (Ecu.eps, 0x712, None): [ b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566B00611A1', b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566B00711A1', + b'\xf1\x875Q0909143K \xf1\x892033\xf1\x820514B0060703', b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820522B0060803', b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820522B0080803', b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\00521B00606A1', @@ -589,6 +632,7 @@ FW_VERSIONS = { b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567B0020600', ], (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x873Q0907572A \xf1\x890126', b'\xf1\x873Q0907572A \xf1\x890130', b'\xf1\x873Q0907572B \xf1\x890192', b'\xf1\x873Q0907572C \xf1\x890195', @@ -685,39 +729,49 @@ FW_VERSIONS = { }, CAR.TIGUAN_MK2: { (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8703N906026D \xf1\x893680', b'\xf1\x8704E906027NB\xf1\x899504', b'\xf1\x8704L906026EJ\xf1\x893661', b'\xf1\x8704L906027G \xf1\x899893', b'\xf1\x875N0906259 \xf1\x890002', + b'\xf1\x875NA906259H \xf1\x890002', b'\xf1\x875NA907115E \xf1\x890005', b'\xf1\x8783A907115B \xf1\x890005', + b'\xf1\x8783A907115F \xf1\x890002', b'\xf1\x8783A907115G \xf1\x890001', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x8709G927158DT\xf1\x893698', + b'\xf1\x8709G927158FM\xf1\x893757', b'\xf1\x8709G927158GC\xf1\x893821', b'\xf1\x8709G927158GD\xf1\x893820', b'\xf1\x870D9300043 \xf1\x895202', b'\xf1\x870DL300011N \xf1\x892001', b'\xf1\x870DL300011N \xf1\x892012', + b'\xf1\x870DL300012P \xf1\x892103', b'\xf1\x870DL300013A \xf1\x893005', b'\xf1\x870DL300013G \xf1\x892119', b'\xf1\x870DL300013G \xf1\x892120', + b'\xf1\x870DL300014C \xf1\x893703', ], (Ecu.srs, 0x715, None): [ b'\xf1\x875Q0959655AR\xf1\x890317\xf1\x82\02331310031333334313132573732379333313100', + b'\xf1\x875Q0959655BJ\xf1\x890336\xf1\x82\x1312110031333300314232583732379333423100', b'\xf1\x875Q0959655BM\xf1\x890403\xf1\x82\02316143231313500314641011750179333423100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\02312110031333300314240583752379333423100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\02331310031333336313140013950399333423100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x1331310031333334313140013750379333423100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x1331310031333334313140573752379333423100', b'\xf1\x875Q0959655CB\xf1\x890421\xf1\x82\x1316143231313500314647021750179333613100', + b'\xf1\x875Q0959655CG\xf1\x890421\xf1\x82\x1331310031333300314240024050409333613100', ], (Ecu.eps, 0x712, None): [ b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820529A6060603', b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521A60604A1', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567A6000600', + b'\xf1\x875QF909144A \xf1\x895581\xf1\x82\x0571A60834A1', b'\xf1\x875QF909144B \xf1\x895582\xf1\x82\00571A60634A1', + b'\xf1\x875QF909144B \xf1\x895582\xf1\x82\x0571A62A32A1', b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\x0521A60604A1', b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\x0521A60604A1', b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\00521A60804A1', @@ -725,6 +779,7 @@ FW_VERSIONS = { (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x872Q0907572AA\xf1\x890396', b'\xf1\x872Q0907572J \xf1\x890156', + b'\xf1\x872Q0907572M \xf1\x890233', b'\xf1\x872Q0907572Q \xf1\x890342', b'\xf1\x872Q0907572R \xf1\x890372', b'\xf1\x872Q0907572T \xf1\x890383', @@ -795,6 +850,7 @@ FW_VERSIONS = { b'\xf1\x875G0906259L \xf1\x890002', b'\xf1\x875G0906259Q \xf1\x890002', b'\xf1\x878V0906259F \xf1\x890002', + b'\xf1\x878V0906259H \xf1\x890002', b'\xf1\x878V0906259J \xf1\x890002', b'\xf1\x878V0906259K \xf1\x890001', b'\xf1\x878V0906264B \xf1\x890003', @@ -805,6 +861,7 @@ FW_VERSIONS = { b'\xf1\x870CW300044T \xf1\x895245', b'\xf1\x870CW300048 \xf1\x895201', b'\xf1\x870D9300012 \xf1\x894912', + b'\xf1\x870D9300012 \xf1\x894931', b'\xf1\x870D9300012K \xf1\x894513', b'\xf1\x870D9300013B \xf1\x894931', b'\xf1\x870D9300041N \xf1\x894512', @@ -827,12 +884,14 @@ FW_VERSIONS = { b'\xf1\x875Q0959655J \xf1\x890825\xf1\x82\x13111112111111--241115141112221291163221', b'\xf1\x875Q0959655J \xf1\x890825\xf1\x82\023111112111111--171115141112221291163221', b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\023121111111211--261117141112231291163221', + b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13111112111111--241115141112221291163221', b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13121111111111--341117141212231291163221', b'\xf1\x875Q0959655N \xf1\x890361\xf1\x82\0211212001112110004110411111421149114', b'\xf1\x875Q0959655N \xf1\x890361\xf1\x82\0211212001112111104110411111521159114', ], (Ecu.eps, 0x712, None): [ b'\xf1\x873Q0909144H \xf1\x895061\xf1\x82\00566G0HA14A1', + b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566G0HA14A1', b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571G01A16A1', b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571G0HA16A1', b'\xf1\x873Q0909144L \xf1\x895081\xf1\x82\x0571G0JA14A1', @@ -917,25 +976,33 @@ FW_VERSIONS = { b'\xf1\x8704L906021EL\xf1\x897542', b'\xf1\x8704L906026BP\xf1\x891198', b'\xf1\x8704L906026BP\xf1\x897608', + b'\xf1\x8704L906056CR\xf1\x892797', b'\xf1\x8705E906018AS\xf1\x899596', + b'\xf1\x878V0906264H \xf1\x890005', ], (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x870CW300041D \xf1\x891004', + b'\xf1\x870CW300041G \xf1\x891003', b'\xf1\x870CW300050J \xf1\x891908', b'\xf1\x870D9300042M \xf1\x895016', ], (Ecu.srs, 0x715, None): [ b'\xf1\x873Q0959655AC\xf1\x890189\xf1\x82\r11110011110011021511110200', + b'\xf1\x873Q0959655AS\xf1\x890200\xf1\x82\r11110011110011021511110200', b'\xf1\x873Q0959655AS\xf1\x890200\xf1\x82\r12110012120012021612110200', + b'\xf1\x873Q0959655BH\xf1\x890703\xf1\x82\x0e1312001313001305171311052900', b'\xf1\x873Q0959655CM\xf1\x890720\xf1\x82\0161312001313001305171311052900', ], (Ecu.eps, 0x712, None): [ b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\00521N01342A1', b'\xf1\x875Q0909144P \xf1\x891043\xf1\x82\00511N01805A0', + b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521N01309A1', b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\00521N05808A1', ], (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x875Q0907572B \xf1\x890200\xf1\x82\00101', b'\xf1\x875Q0907572H \xf1\x890620', + b'\xf1\x875Q0907572K \xf1\x890402\xf1\x82\x0101', b'\xf1\x875Q0907572P \xf1\x890682', ], }, @@ -1007,6 +1074,7 @@ FW_VERSIONS = { }, CAR.SKODA_OCTAVIA_MK3: { (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8704C906025L \xf1\x896198', b'\xf1\x8704E906016ER\xf1\x895823', b'\xf1\x8704E906027HD\xf1\x893742', b'\xf1\x8704E906027MH\xf1\x894786', @@ -1018,6 +1086,7 @@ FW_VERSIONS = { b'\xf1\x870CW300041L \xf1\x891601', b'\xf1\x870CW300041N \xf1\x891605', b'\xf1\x870CW300043B \xf1\x891601', + b'\xf1\x870CW300043P \xf1\x891605', b'\xf1\x870D9300041C \xf1\x894936', b'\xf1\x870D9300041J \xf1\x894902', b'\xf1\x870D9300041P \xf1\x894507', @@ -1027,6 +1096,7 @@ FW_VERSIONS = { b'\xf1\x873Q0959655AQ\xf1\x890200\xf1\x82\r11120011100010312212113100', b'\xf1\x873Q0959655AS\xf1\x890200\xf1\x82\r11120011100010022212110200', b'\xf1\x873Q0959655BH\xf1\x890703\xf1\x82\0163221003221002105755331052100', + b'\xf1\x873Q0959655CM\xf1\x890720\xf1\x82\x0e3221003221002105755331052100', b'\xf1\x873Q0959655CN\xf1\x890720\xf1\x82\x0e3221003221002105755331052100', b'\xf1\x875QD959655 \xf1\x890388\xf1\x82\x111101000011110006110411111111119111', ], @@ -1053,6 +1123,7 @@ FW_VERSIONS = { b'\xf1\x870CW300050 \xf1\x891709', ], (Ecu.srs, 0x715, None): [ + b'\xf1\x872Q0959655AJ\xf1\x890250\xf1\x82\x1211110411110411--04040404131111112H14', b'\xf1\x872Q0959655AM\xf1\x890351\xf1\x82\022111104111104112104040404111111112H14', ], (Ecu.eps, 0x712, None): [ diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index a395f8558..2c359ec32 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -17,11 +17,11 @@ from selfdrive.boardd.boardd import can_list_to_can_capnp from selfdrive.car.car_helpers import get_car, get_startup_event, get_one_can from selfdrive.controls.lib.lateral_planner import CAMERA_OFFSET from selfdrive.controls.lib.drive_helpers import VCruiseHelper, get_lag_adjusted_curvature -from selfdrive.controls.lib.latcontrol import LatControl +from selfdrive.controls.lib.latcontrol import LatControl, MIN_LATERAL_CONTROL_SPEED from selfdrive.controls.lib.longcontrol import LongControl from selfdrive.controls.lib.latcontrol_pid import LatControlPID from selfdrive.controls.lib.latcontrol_indi import LatControlINDI -from selfdrive.controls.lib.latcontrol_angle import LatControlAngle +from selfdrive.controls.lib.latcontrol_angle import LatControlAngle, STEER_ANGLE_SATURATION_THRESHOLD from selfdrive.controls.lib.latcontrol_torque import LatControlTorque from selfdrive.controls.lib.events import Events, ET from selfdrive.controls.lib.alertmanager import AlertManager, set_offroad_alert @@ -38,7 +38,7 @@ REPLAY = "REPLAY" in os.environ SIMULATION = "SIMULATION" in os.environ NOSENSOR = "NOSENSOR" in os.environ IGNORE_PROCESSES = {"uploader", "deleter", "loggerd", "logmessaged", "tombstoned", "statsd", - "logcatd", "proclogd", "clocksd", "updated", "timezoned", "manage_athenad", "laikad"} | \ + "logcatd", "proclogd", "clocksd", "updated", "timezoned", "manage_athenad"} | \ {k for k, v in managed_processes.items() if not v.enabled} ThermalStatus = log.DeviceState.ThermalStatus @@ -99,11 +99,12 @@ class Controls: get_one_can(self.can_sock) num_pandas = len(messaging.recv_one_retry(self.sm.sock['pandaStates']).pandaStates) - self.CI, self.CP = get_car(self.can_sock, self.pm.sock['sendcan'], num_pandas) + experimental_long_allowed = self.params.get_bool("ExperimentalLongitudinalEnabled") and not is_release_branch() + self.CI, self.CP = get_car(self.can_sock, self.pm.sock['sendcan'], experimental_long_allowed, num_pandas) else: self.CI, self.CP = CI, CI.CP - self.joystick_mode = self.params.get_bool("JoystickDebugMode") or (self.CP.notCar and sm is None) + self.joystick_mode = self.params.get_bool("JoystickDebugMode") or self.CP.notCar # set alternative experiences from parameters self.disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator") @@ -132,9 +133,6 @@ class Controls: safety_config.safetyModel = car.CarParams.SafetyModel.noOutput self.CP.safetyConfigs = [safety_config] - if is_release_branch(): - self.CP.experimentalLongitudinalAvailable = False - # Write CarParams for radard cp_bytes = self.CP.to_bytes() self.params.put("CarParams", cp_bytes) @@ -142,7 +140,7 @@ class Controls: put_nonblocking("CarParamsPersistent", cp_bytes) # cleanup old params - if not self.CP.experimentalLongitudinalAvailable: + if not self.CP.experimentalLongitudinalAvailable or is_release_branch(): self.params.remove("ExperimentalLongitudinalEnabled") if not self.CP.openpilotLongitudinalControl: self.params.remove("ExperimentalMode") @@ -169,12 +167,13 @@ class Controls: self.state = State.disabled self.enabled = False self.active = False - self.can_rcv_timeout = False self.soft_disable_timer = 0 self.mismatch_counter = 0 self.cruise_mismatch_counter = 0 - self.can_rcv_timeout_counter = 0 + self.can_rcv_timeout_counter = 0 # conseuctive timeout count + self.can_rcv_cum_timeout_counter = 0 # cumulative timeout count self.last_blinker_frame = 0 + self.last_steering_pressed_frame = 0 self.distance_traveled = 0 self.last_functional_fan_frame = 0 self.events_prev = [] @@ -184,10 +183,12 @@ class Controls: self.steer_limited = False self.desired_curvature = 0.0 self.desired_curvature_rate = 0.0 + self.experimental_mode = False self.v_cruise_helper = VCruiseHelper(self.CP) # TODO: no longer necessary, aside from process replay self.sm['liveParameters'].valid = True + self.can_log_mono_time = 0 self.startup_event = get_startup_event(car_recognized, controller_available, len(self.CP.carFw) > 0) @@ -249,6 +250,9 @@ class Controls: (CS.regenBraking and (not self.CS_prev.regenBraking or not CS.standstill)): self.events.add(EventName.pedalPressed) + if CS.brakePressed and CS.standstill: + self.events.add(EventName.preEnableStandstill) + if CS.gasPressed: self.events.add(EventName.gasPressedOverride) @@ -275,7 +279,7 @@ class Controls: # self.events.add(EventName.highCpuUsage) # Alert if fan isn't spinning for 5 seconds - if self.sm['peripheralState'].pandaType == PandaType.dos: + if self.sm['peripheralState'].pandaType != log.PandaState.PandaType.unknown: if self.sm['peripheralState'].fanSpeedRpm == 0 and self.sm['deviceState'].fanSpeedPercentDesired > 50: if (self.sm.frame - self.last_functional_fan_frame) * DT_CTRL > 5.0: self.events.add(EventName.fanMalfunction) @@ -346,9 +350,10 @@ class Controls: self.events.add(EventName.canError) # generic catch-all. ideally, a more specific event should be added above instead + can_rcv_timeout = self.can_rcv_timeout_counter >= 5 has_disable_events = self.events.any(ET.NO_ENTRY) and (self.events.any(ET.SOFT_DISABLE) or self.events.any(ET.IMMEDIATE_DISABLE)) no_system_errors = (not has_disable_events) or (len(self.events) == num_events) - if (not self.sm.all_checks() or self.can_rcv_timeout) and no_system_errors: + if (not self.sm.all_checks() or can_rcv_timeout) and no_system_errors: if not self.sm.all_alive(): self.events.add(EventName.commIssue) elif not self.sm.all_freq_ok(): @@ -360,7 +365,7 @@ class Controls: 'invalid': [s for s, valid in self.sm.valid.items() if not valid], 'not_alive': [s for s, alive in self.sm.alive.items() if not alive], 'not_freq_ok': [s for s, freq_ok in self.sm.freq_ok.items() if not freq_ok], - 'can_rcv_timeout': self.can_rcv_timeout, + 'can_rcv_timeout': can_rcv_timeout, } if logs != self.logged_comm_issue: cloudlog.event("commIssue", error=True, **logs) @@ -417,22 +422,14 @@ class Controls: if self.sm['liveLocationKalman'].excessiveResets: self.events.add(EventName.localizerMalfunction) - # Only allow engagement with brake pressed when stopped behind another stopped car - speeds = self.sm['longitudinalPlan'].speeds - if len(speeds) > 1: - v_future = speeds[-1] - else: - v_future = 100.0 - if CS.brakePressed and v_future >= self.CP.vEgoStarting \ - and self.CP.openpilotLongitudinalControl and CS.vEgo < 0.3: - self.events.add(EventName.noTarget) - def data_sample(self): """Receive data from sockets and update carState""" # Update carState from CAN can_strs = messaging.drain_sock_raw(self.can_sock, wait_for_one=True) CS = self.CI.update(self.CC, can_strs) + if len(can_strs) and REPLAY: + self.can_log_mono_time = messaging.log_from_bytes(can_strs[0]).logMonoTime self.sm.update(0) @@ -450,9 +447,9 @@ class Controls: # Check for CAN timeout if not can_strs: self.can_rcv_timeout_counter += 1 - self.can_rcv_timeout = True + self.can_rcv_cum_timeout_counter += 1 else: - self.can_rcv_timeout = False + self.can_rcv_timeout_counter = 0 # When the panda and controlsd do not agree on controls_allowed # we want to disengage openpilot. However the status from the panda goes through @@ -518,10 +515,7 @@ class Controls: # PRE ENABLING elif self.state == State.preEnabled: - if self.events.any(ET.NO_ENTRY): - self.state = State.disabled - self.current_alert_types.append(ET.NO_ENTRY) - elif not self.events.any(ET.PRE_ENABLE): + if not self.events.any(ET.PRE_ENABLE): self.state = State.enabled else: self.current_alert_types.append(ET.PRE_ENABLE) @@ -551,7 +545,7 @@ class Controls: else: self.state = State.enabled self.current_alert_types.append(ET.ENABLE) - self.v_cruise_helper.initialize_v_cruise(CS) + self.v_cruise_helper.initialize_v_cruise(CS, self.experimental_mode) # Check if openpilot is engaged and actuators are enabled self.enabled = self.state in ENABLED_STATES @@ -579,14 +573,21 @@ class Controls: CC = car.CarControl.new_message() CC.enabled = self.enabled + # Check which actuators can be enabled + standstill = CS.vEgo <= max(self.CP.minSteerSpeed, MIN_LATERAL_CONTROL_SPEED) or CS.standstill CC.latActive = self.active and not CS.steerFaultTemporary and not CS.steerFaultPermanent and \ - CS.vEgo > self.CP.minSteerSpeed and not CS.standstill - CC.longActive = self.active and not self.events.any(ET.OVERRIDE_LONGITUDINAL) and self.CP.openpilotLongitudinalControl + (not standstill or self.joystick_mode) + CC.longActive = self.enabled and not self.events.any(ET.OVERRIDE_LONGITUDINAL) and self.CP.openpilotLongitudinalControl actuators = CC.actuators actuators.longControlState = self.LoC.long_control_state + # Enable blinkers while lane changing + if self.sm['lateralPlan'].laneChangeState != LaneChangeState.off: + CC.leftBlinker = self.sm['lateralPlan'].laneChangeDirection == LaneChangeDirection.left + CC.rightBlinker = self.sm['lateralPlan'].laneChangeDirection == LaneChangeDirection.right + if CS.leftBlinker or CS.rightBlinker: self.last_blinker_frame = self.sm.frame @@ -611,6 +612,7 @@ class Controls: actuators.steer, actuators.steeringAngleDeg, lac_log = self.LaC.update(CC.latActive, CS, self.VM, lp, self.last_actuators, self.steer_limited, self.desired_curvature, self.desired_curvature_rate, self.sm['liveLocationKalman']) + actuators.curvature = self.desired_curvature else: lac_log = log.ControlsState.LateralDebugState.new_message() if self.sm.rcv_frame['testJoystick'] > 0: @@ -627,29 +629,34 @@ class Controls: lac_log.output = actuators.steer lac_log.saturated = abs(actuators.steer) >= 0.9 + if CS.steeringPressed: + self.last_steering_pressed_frame = self.sm.frame + recent_steer_pressed = (self.sm.frame - self.last_steering_pressed_frame)*DT_CTRL < 2.0 + # Send a "steering required alert" if saturation count has reached the limit - if lac_log.active and not CS.steeringPressed and self.CP.lateralTuning.which() == 'torque' and not self.joystick_mode: - undershooting = abs(lac_log.desiredLateralAccel) / abs(1e-3 + lac_log.actualLateralAccel) > 1.2 - turning = abs(lac_log.desiredLateralAccel) > 1.0 - good_speed = CS.vEgo > 5 - max_torque = abs(self.last_actuators.steer) > 0.99 - if undershooting and turning and good_speed and max_torque: - self.events.add(EventName.steerSaturated) - elif lac_log.active and not CS.steeringPressed and lac_log.saturated: - dpath_points = lat_plan.dPathPoints - if len(dpath_points): - # Check if we deviated from the path - # TODO use desired vs actual curvature - if self.CP.steerControlType == car.CarParams.SteerControlType.angle: - steering_value = actuators.steeringAngleDeg - else: - steering_value = actuators.steer + if lac_log.active and not recent_steer_pressed: + if self.CP.lateralTuning.which() == 'torque' and not self.joystick_mode: + undershooting = abs(lac_log.desiredLateralAccel) / abs(1e-3 + lac_log.actualLateralAccel) > 1.2 + turning = abs(lac_log.desiredLateralAccel) > 1.0 + good_speed = CS.vEgo > 5 + max_torque = abs(self.last_actuators.steer) > 0.99 + if undershooting and turning and good_speed and max_torque: + lac_log.active and self.events.add(EventName.steerSaturated) + elif lac_log.saturated: + dpath_points = lat_plan.dPathPoints + if len(dpath_points): + # Check if we deviated from the path + # TODO use desired vs actual curvature + if self.CP.steerControlType == car.CarParams.SteerControlType.angle: + steering_value = actuators.steeringAngleDeg + else: + steering_value = actuators.steer - left_deviation = steering_value > 0 and dpath_points[0] < -0.20 - right_deviation = steering_value < 0 and dpath_points[0] > 0.20 + left_deviation = steering_value > 0 and dpath_points[0] < -0.20 + right_deviation = steering_value < 0 and dpath_points[0] > 0.20 - if left_deviation or right_deviation: - self.events.add(EventName.steerSaturated) + if left_deviation or right_deviation: + self.events.add(EventName.steerSaturated) # Ensure no NaNs/Infs for p in ACTUATOR_FIELDS: @@ -729,10 +736,15 @@ class Controls: if not self.read_only and self.initialized: # send car controls over can - self.last_actuators, can_sends = self.CI.apply(CC) + now_nanos = self.can_log_mono_time if REPLAY else int(sec_since_boot() * 1e9) + self.last_actuators, can_sends = self.CI.apply(CC, now_nanos) self.pm.send('sendcan', can_list_to_can_capnp(can_sends, msgtype='sendcan', valid=CS.canValid)) CC.actuatorsOutput = self.last_actuators - self.steer_limited = abs(CC.actuators.steer - CC.actuatorsOutput.steer) > 1e-2 + if self.CP.steerControlType == car.CarParams.SteerControlType.angle: + self.steer_limited = abs(CC.actuators.steeringAngleDeg - CC.actuatorsOutput.steeringAngleDeg) > \ + STEER_ANGLE_SATURATION_THRESHOLD + else: + self.steer_limited = abs(CC.actuators.steer - CC.actuatorsOutput.steer) > 1e-2 force_decel = (self.sm['driverMonitoringState'].awarenessStatus < 0.) or \ (self.state == State.softDisabling) @@ -756,7 +768,6 @@ class Controls: controlsState.alertType = current_alert.alert_type controlsState.alertSound = current_alert.audible_alert - controlsState.canMonoTimes = list(CS.canMonoTimes) controlsState.longitudinalPlanMonoTime = self.sm.logMonoTime['longitudinalPlan'] controlsState.lateralPlanMonoTime = self.sm.logMonoTime['lateralPlan'] controlsState.enabled = self.enabled @@ -776,8 +787,8 @@ class Controls: controlsState.cumLagMs = -self.rk.remaining * 1000. controlsState.startMonoTime = int(start_time * 1e9) controlsState.forceDecel = bool(force_decel) - controlsState.canErrorCounter = self.can_rcv_timeout_counter - controlsState.experimentalMode = self.params.get_bool("ExperimentalMode") and self.CP.openpilotLongitudinalControl + controlsState.canErrorCounter = self.can_rcv_cum_timeout_counter + controlsState.experimentalMode = self.experimental_mode lat_tuning = self.CP.lateralTuning.which() if self.joystick_mode: @@ -828,6 +839,7 @@ class Controls: self.prof.checkpoint("Ratekeeper", ignore=True) self.is_metric = self.params.get_bool("IsMetric") + self.experimental_mode = self.params.get_bool("ExperimentalMode") and self.CP.openpilotLongitudinalControl # Sample data from sockets and get a carState CS = self.data_sample() diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index bdbdb7023..a332d0676 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -8,10 +8,13 @@ from selfdrive.modeld.constants import T_IDXS # WARNING: this value was determined based on the model's training distribution, # model predictions above this speed can be unpredictable -V_CRUISE_MAX = 145 # kph -V_CRUISE_MIN = 8 # kph -V_CRUISE_ENABLE_MIN = 40 # kph -V_CRUISE_INITIAL = 255 # kph +# V_CRUISE's are in kph +V_CRUISE_MIN = 8 +V_CRUISE_MAX = 145 +V_CRUISE_UNSET = 255 +V_CRUISE_INITIAL = 40 +V_CRUISE_INITIAL_EXPERIMENTAL_MODE = 105 +IMPERIAL_INCREMENT = 1.6 # should be CV.MPH_TO_KPH, but this causes rounding errors MIN_SPEED = 1.0 LAT_MPC_N = 16 @@ -38,15 +41,15 @@ CRUISE_INTERVAL_SIGN = { class VCruiseHelper: def __init__(self, CP): self.CP = CP - self.v_cruise_kph = V_CRUISE_INITIAL - self.v_cruise_cluster_kph = V_CRUISE_INITIAL + self.v_cruise_kph = V_CRUISE_UNSET + self.v_cruise_cluster_kph = V_CRUISE_UNSET self.v_cruise_kph_last = 0 self.button_timers = {ButtonType.decelCruise: 0, ButtonType.accelCruise: 0} self.button_change_states = {btn: {"standstill": False, "enabled": False} for btn in self.button_timers} @property def v_cruise_initialized(self): - return self.v_cruise_kph != V_CRUISE_INITIAL + return self.v_cruise_kph != V_CRUISE_UNSET def update_v_cruise(self, CS, enabled, is_metric): self.v_cruise_kph_last = self.v_cruise_kph @@ -61,8 +64,8 @@ class VCruiseHelper: self.v_cruise_kph = CS.cruiseState.speed * CV.MS_TO_KPH self.v_cruise_cluster_kph = CS.cruiseState.speedCluster * CV.MS_TO_KPH else: - self.v_cruise_kph = V_CRUISE_INITIAL - self.v_cruise_cluster_kph = V_CRUISE_INITIAL + self.v_cruise_kph = V_CRUISE_UNSET + self.v_cruise_cluster_kph = V_CRUISE_UNSET def _update_v_cruise_non_pcm(self, CS, enabled, is_metric): # handle button presses. TODO: this should be in state_control, but a decelCruise press @@ -73,8 +76,7 @@ class VCruiseHelper: long_press = False button_type = None - # should be CV.MPH_TO_KPH, but this causes rounding errors - v_cruise_delta = 1. if is_metric else 1.6 + v_cruise_delta = 1. if is_metric else IMPERIAL_INCREMENT for b in CS.buttonEvents: if b.type.raw in self.button_timers and not b.pressed: @@ -125,16 +127,18 @@ class VCruiseHelper: self.button_timers[b.type.raw] = 1 if b.pressed else 0 self.button_change_states[b.type.raw] = {"standstill": CS.cruiseState.standstill, "enabled": enabled} - def initialize_v_cruise(self, CS): + def initialize_v_cruise(self, CS, experimental_mode: bool) -> None: # initializing is handled by the PCM if self.CP.pcmCruise: return + initial = V_CRUISE_INITIAL_EXPERIMENTAL_MODE if experimental_mode else V_CRUISE_INITIAL + # 250kph or above probably means we never had a set speed if any(b.type in (ButtonType.accelCruise, ButtonType.resumeCruise) for b in CS.buttonEvents) and self.v_cruise_kph_last < 250: self.v_cruise_kph = self.v_cruise_kph_last else: - self.v_cruise_kph = int(round(clip(CS.vEgo * CV.MS_TO_KPH, V_CRUISE_ENABLE_MIN, V_CRUISE_MAX))) + self.v_cruise_kph = int(round(clip(CS.vEgo * CV.MS_TO_KPH, initial, V_CRUISE_MAX))) self.v_cruise_cluster_kph = self.v_cruise_kph @@ -149,6 +153,12 @@ def apply_deadzone(error, deadzone): return error +def apply_center_deadzone(error, deadzone): + if (error > - deadzone) and (error < deadzone): + error = 0. + return error + + def rate_limit(new_value, last_value, dw_step, up_step): return clip(new_value, last_value + dw_step, last_value + up_step) diff --git a/selfdrive/controls/lib/events.py b/selfdrive/controls/lib/events.py index a761cceec..a85e48649 100644 --- a/selfdrive/controls/lib/events.py +++ b/selfdrive/controls/lib/events.py @@ -230,7 +230,7 @@ def startup_master_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubM return StartupAlert("WARNING: This branch is not tested", branch, alert_status=AlertStatus.userPrompt) def below_engage_speed_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: - return NoEntryAlert(f"Speed Below {get_display_speed(CP.minEnableSpeed, metric)}") + return NoEntryAlert(f"Drive above {get_display_speed(CP.minEnableSpeed, metric)} to engage") def below_steer_speed_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: @@ -250,10 +250,9 @@ def calibration_incomplete_alert(CP: car.CarParams, CS: car.CarState, sm: messag def no_gps_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: - gps_integrated = sm['peripheralState'].pandaType in (log.PandaState.PandaType.uno, log.PandaState.PandaType.dos) return Alert( "Poor GPS reception", - "Hardware malfunctioning if sky is visible" if gps_integrated else "Check GPS antenna placement", + "Hardware malfunctioning if sky is visible", AlertStatus.normal, AlertSize.mid, Priority.LOWER, VisualAlert.none, AudibleAlert.none, .2, creation_delay=300.) @@ -501,9 +500,9 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = { EventName.resumeRequired: { ET.WARNING: Alert( - "STOPPED", - "Press Resume to Go", - AlertStatus.userPrompt, AlertSize.mid, + "Press Resume to Exit Standstill", + "", + AlertStatus.userPrompt, AlertSize.small, Priority.LOW, VisualAlert.none, AudibleAlert.none, .2), }, @@ -616,9 +615,9 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = { visual_alert=VisualAlert.brakePressed), }, - EventName.pedalPressedPreEnable: { + EventName.preEnableStandstill: { ET.PRE_ENABLE: Alert( - "Release Pedal to Engage", + "Release Brake to Engage", "", AlertStatus.normal, AlertSize.small, Priority.LOWEST, VisualAlert.none, AudibleAlert.none, .1, creation_delay=1.), @@ -730,8 +729,8 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = { }, EventName.espDisabled: { - ET.SOFT_DISABLE: soft_disable_alert("ESP Off"), - ET.NO_ENTRY: NoEntryAlert("ESP Off"), + ET.SOFT_DISABLE: soft_disable_alert("Electronic Stability Control Disabled"), + ET.NO_ENTRY: NoEntryAlert("Electronic Stability Control Disabled"), }, EventName.lowBattery: { @@ -807,9 +806,9 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = { }, EventName.accFaulted: { - ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Cruise Faulted"), - ET.PERMANENT: NormalPermanentAlert("Cruise Faulted", ""), - ET.NO_ENTRY: NoEntryAlert("Cruise Faulted"), + ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Cruise Fault: Restart the Car"), + ET.PERMANENT: NormalPermanentAlert("Cruise Fault: Restart the car to engage"), + ET.NO_ENTRY: NoEntryAlert("Cruise Fault: Restart the Car"), }, EventName.accFaultedTemp: { @@ -917,15 +916,6 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = { ET.NO_ENTRY: NoEntryAlert("Harness Relay Malfunction"), }, - EventName.noTarget: { - ET.IMMEDIATE_DISABLE: Alert( - "openpilot Canceled", - "No close lead car", - AlertStatus.normal, AlertSize.mid, - Priority.HIGH, VisualAlert.none, AudibleAlert.disengage, 3.), - ET.NO_ENTRY: NoEntryAlert("No Close Lead Car"), - }, - EventName.speedTooLow: { ET.IMMEDIATE_DISABLE: Alert( "openpilot Canceled", diff --git a/selfdrive/controls/lib/latcontrol.py b/selfdrive/controls/lib/latcontrol.py index 78b59fda5..d38959c56 100644 --- a/selfdrive/controls/lib/latcontrol.py +++ b/selfdrive/controls/lib/latcontrol.py @@ -3,7 +3,7 @@ from abc import abstractmethod, ABC from common.numpy_fast import clip from common.realtime import DT_CTRL -MIN_STEER_SPEED = 0.3 +MIN_LATERAL_CONTROL_SPEED = 0.3 # m/s class LatControl(ABC): @@ -11,6 +11,7 @@ class LatControl(ABC): self.sat_count_rate = 1.0 * DT_CTRL self.sat_limit = CP.steerLimitTimer self.sat_count = 0. + self.sat_check_min_speed = 10. # we define the steer torque scale as [-1.0...1.0] self.steer_max = 1.0 @@ -23,7 +24,7 @@ class LatControl(ABC): self.sat_count = 0. def _check_saturation(self, saturated, CS, steer_limited): - if saturated and CS.vEgo > 10. and not steer_limited and not CS.steeringPressed: + if saturated and CS.vEgo > self.sat_check_min_speed and not steer_limited and not CS.steeringPressed: self.sat_count += self.sat_count_rate else: self.sat_count -= self.sat_count_rate diff --git a/selfdrive/controls/lib/latcontrol_angle.py b/selfdrive/controls/lib/latcontrol_angle.py index 0e5be4a97..9ed140d38 100644 --- a/selfdrive/controls/lib/latcontrol_angle.py +++ b/selfdrive/controls/lib/latcontrol_angle.py @@ -1,16 +1,20 @@ import math from cereal import log -from selfdrive.controls.lib.latcontrol import LatControl, MIN_STEER_SPEED +from selfdrive.controls.lib.latcontrol import LatControl STEER_ANGLE_SATURATION_THRESHOLD = 2.5 # Degrees class LatControlAngle(LatControl): + def __init__(self, CP, CI): + super().__init__(CP, CI) + self.sat_check_min_speed = 5. + def update(self, active, CS, VM, params, last_actuators, steer_limited, desired_curvature, desired_curvature_rate, llk): angle_log = log.ControlsState.LateralAngleState.new_message() - if CS.vEgo < MIN_STEER_SPEED or not active: + if not active: angle_log.active = False angle_steers_des = float(CS.steeringAngleDeg) else: @@ -19,7 +23,7 @@ class LatControlAngle(LatControl): angle_steers_des += params.angleOffsetDeg angle_control_saturated = abs(angle_steers_des - CS.steeringAngleDeg) > STEER_ANGLE_SATURATION_THRESHOLD - angle_log.saturated = self._check_saturation(angle_control_saturated, CS, steer_limited) + angle_log.saturated = self._check_saturation(angle_control_saturated, CS, False) angle_log.steeringAngleDeg = float(CS.steeringAngleDeg) angle_log.steeringAngleDesiredDeg = angle_steers_des return 0, float(angle_steers_des), angle_log diff --git a/selfdrive/controls/lib/latcontrol_indi.py b/selfdrive/controls/lib/latcontrol_indi.py index 2bc3cef76..dca82c672 100644 --- a/selfdrive/controls/lib/latcontrol_indi.py +++ b/selfdrive/controls/lib/latcontrol_indi.py @@ -5,7 +5,7 @@ from cereal import log from common.filter_simple import FirstOrderFilter from common.numpy_fast import clip, interp from common.realtime import DT_CTRL -from selfdrive.controls.lib.latcontrol import LatControl, MIN_STEER_SPEED +from selfdrive.controls.lib.latcontrol import LatControl class LatControlINDI(LatControl): @@ -82,7 +82,7 @@ class LatControlINDI(LatControl): rate_des = VM.get_steer_from_curvature(-desired_curvature_rate, CS.vEgo, 0) indi_log.steeringRateDesiredDeg = math.degrees(rate_des) - if CS.vEgo < MIN_STEER_SPEED or not active: + if not active: indi_log.active = False self.steer_filter.x = 0.0 output_steer = 0 diff --git a/selfdrive/controls/lib/latcontrol_pid.py b/selfdrive/controls/lib/latcontrol_pid.py index 6bd678073..6696d2e30 100644 --- a/selfdrive/controls/lib/latcontrol_pid.py +++ b/selfdrive/controls/lib/latcontrol_pid.py @@ -1,7 +1,7 @@ import math from cereal import log -from selfdrive.controls.lib.latcontrol import LatControl, MIN_STEER_SPEED +from selfdrive.controls.lib.latcontrol import LatControl from selfdrive.controls.lib.pid import PIDController @@ -28,7 +28,7 @@ class LatControlPID(LatControl): pid_log.steeringAngleDesiredDeg = angle_steers_des pid_log.angleError = error - if CS.vEgo < MIN_STEER_SPEED or not active: + if not active: output_steer = 0.0 pid_log.active = False self.pid.reset() diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index d10d39d94..2f5609437 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -2,7 +2,7 @@ import math from cereal import log from common.numpy_fast import interp -from selfdrive.controls.lib.latcontrol import LatControl, MIN_STEER_SPEED +from selfdrive.controls.lib.latcontrol import LatControl from selfdrive.controls.lib.pid import PIDController from selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY @@ -39,7 +39,7 @@ class LatControlTorque(LatControl): def update(self, active, CS, VM, params, last_actuators, steer_limited, desired_curvature, desired_curvature_rate, llk): pid_log = log.ControlsState.LateralTorqueState.new_message() - if CS.vEgo < MIN_STEER_SPEED or not active: + if not active: output_torque = 0.0 pid_log.active = False else: diff --git a/selfdrive/controls/lib/lateral_mpc_lib/SConscript b/selfdrive/controls/lib/lateral_mpc_lib/SConscript index df1e2a2a1..868b5a873 100644 --- a/selfdrive/controls/lib/lateral_mpc_lib/SConscript +++ b/selfdrive/controls/lib/lateral_mpc_lib/SConscript @@ -44,7 +44,7 @@ generated_files = [ ] + build_files acados_dir = '#third_party/acados' -acados_templates_dir = '#pyextra/acados_template/c_templates_tera' +acados_templates_dir = '#third_party/acados/acados_template/c_templates_tera' source_list = ['lat_mpc.py', f'{acados_dir}/include/acados_c/ocp_nlp_interface.h', @@ -70,8 +70,8 @@ lib_solver = lenv.SharedLibrary(f"{gen}/acados_ocp_solver_lat", LIBS=['m', 'acados', 'hpipm', 'blasfeo', 'qpOASES_e']) # generate cython stuff -acados_ocp_solver_pyx = File("#pyextra/acados_template/acados_ocp_solver_pyx.pyx") -acados_ocp_solver_common = File("#pyextra/acados_template/acados_solver_common.pxd") +acados_ocp_solver_pyx = File("#third_party/acados/acados_template/acados_ocp_solver_pyx.pyx") +acados_ocp_solver_common = File("#third_party/acados/acados_template/acados_solver_common.pxd") libacados_ocp_solver_pxd = File(f'{gen}/acados_solver.pxd') libacados_ocp_solver_c = File(f'{gen}/acados_ocp_solver_pyx.c') diff --git a/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py b/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py index 9607532ac..536f436fc 100755 --- a/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py +++ b/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py @@ -8,7 +8,7 @@ from common.realtime import sec_since_boot from selfdrive.modeld.constants import T_IDXS if __name__ == '__main__': # generating code - from pyextra.acados_template import AcadosModel, AcadosOcp, AcadosOcpSolver + from third_party.acados.acados_template import AcadosModel, AcadosOcp, AcadosOcpSolver else: from selfdrive.controls.lib.lateral_mpc_lib.c_generated_code.acados_ocp_solver_pyx import AcadosOcpSolverCython # pylint: disable=no-name-in-module, import-error diff --git a/selfdrive/controls/lib/longcontrol.py b/selfdrive/controls/lib/longcontrol.py index 92a4f1f99..e8095813f 100644 --- a/selfdrive/controls/lib/longcontrol.py +++ b/selfdrive/controls/lib/longcontrol.py @@ -10,12 +10,14 @@ LongCtrlState = car.CarControl.Actuators.LongControlState def long_control_state_trans(CP, active, long_control_state, v_ego, v_target, v_target_1sec, brake_pressed, cruise_standstill): + # Ignore cruise standstill if car has a gas interceptor + cruise_standstill = cruise_standstill and not CP.enableGasInterceptor accelerating = v_target_1sec > v_target planned_stop = (v_target < CP.vEgoStopping and v_target_1sec < CP.vEgoStopping and not accelerating) stay_stopped = (v_ego < CP.vEgoStopping and - (brake_pressed or cruise_standstill)) + (brake_pressed or cruise_standstill)) stopping_condition = planned_stop or stay_stopped starting_condition = (v_target_1sec > CP.vEgoStarting and @@ -28,10 +30,8 @@ def long_control_state_trans(CP, active, long_control_state, v_ego, v_target, long_control_state = LongCtrlState.off else: - if long_control_state == LongCtrlState.off: + if long_control_state in (LongCtrlState.off, LongCtrlState.pid): long_control_state = LongCtrlState.pid - - elif long_control_state == LongCtrlState.pid: if stopping_condition: long_control_state = LongCtrlState.stopping diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript b/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript index 5a9e69c29..e5b236060 100644 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript @@ -51,7 +51,7 @@ generated_files = [ ] + build_files acados_dir = '#third_party/acados' -acados_templates_dir = '#pyextra/acados_template/c_templates_tera' +acados_templates_dir = '#third_party/acados/acados_template/c_templates_tera' source_list = ['long_mpc.py', f'{acados_dir}/include/acados_c/ocp_nlp_interface.h', @@ -77,8 +77,8 @@ lib_solver = lenv.SharedLibrary(f"{gen}/acados_ocp_solver_long", LIBS=['m', 'acados', 'hpipm', 'blasfeo', 'qpOASES_e']) # generate cython stuff -acados_ocp_solver_pyx = File("#pyextra/acados_template/acados_ocp_solver_pyx.pyx") -acados_ocp_solver_common = File("#pyextra/acados_template/acados_solver_common.pxd") +acados_ocp_solver_pyx = File("#third_party/acados/acados_template/acados_ocp_solver_pyx.pyx") +acados_ocp_solver_common = File("#third_party/acados/acados_template/acados_solver_common.pxd") libacados_ocp_solver_pxd = File(f'{gen}/acados_solver.pxd') libacados_ocp_solver_c = File(f'{gen}/acados_ocp_solver_pyx.c') diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index 6b7981311..c01795123 100644 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -9,7 +9,7 @@ from selfdrive.modeld.constants import index_function from selfdrive.controls.lib.radar_helpers import _LEAD_ACCEL_TAU if __name__ == '__main__': # generating code - from pyextra.acados_template import AcadosModel, AcadosOcp, AcadosOcpSolver + from third_party.acados.acados_template import AcadosModel, AcadosOcp, AcadosOcpSolver else: from selfdrive.controls.lib.longitudinal_mpc_lib.c_generated_code.acados_ocp_solver_pyx import AcadosOcpSolverCython # pylint: disable=no-name-in-module, import-error @@ -306,7 +306,7 @@ class LongitudinalMpc: self.cruise_min_a = min_a self.max_a = max_a - def update(self, carstate, radarstate, v_cruise, x, v, a, j): + def update(self, radarstate, v_cruise, x, v, a, j): v_ego = self.x0[1] self.status = radarstate.leadOne.status or radarstate.leadTwo.status @@ -352,7 +352,7 @@ class LongitudinalMpc: x_and_cruise = np.column_stack([x, cruise_target]) x = np.min(x_and_cruise, axis=1) - self.source = 'e2e' if x_and_cruise[0,0] < x_and_cruise[0,1] else 'cruise' + self.source = 'e2e' if x_and_cruise[1,0] < x_and_cruise[1,1] else 'cruise' else: raise NotImplementedError(f'Planner mode {self.mode} not recognized in planner update') diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index def0a1208..0febfbafd 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -6,7 +6,6 @@ from common.numpy_fast import clip, interp import cereal.messaging as messaging from common.conversions import Conversions as CV from common.filter_simple import FirstOrderFilter -from common.params import Params from common.realtime import DT_MDL from selfdrive.modeld.constants import T_IDXS from selfdrive.controls.lib.longcontrol import LongCtrlState @@ -16,7 +15,6 @@ from selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, CONTROL_N from system.swaglog import cloudlog LON_MPC_STEP = 0.2 # first step is 0.2s -AWARENESS_DECEL = -0.2 # car smoothly decel at .2m/s^2 when user is distracted A_CRUISE_MIN = -1.2 A_CRUISE_MAX_VALS = [1.6, 1.2, 0.8, 0.6] A_CRUISE_MAX_BP = [0., 10.0, 25., 40.] @@ -48,12 +46,7 @@ def limit_accel_in_turns(v_ego, angle_steers, a_target, CP): class LongitudinalPlanner: def __init__(self, CP, init_v=0.0, init_a=0.0): self.CP = CP - self.params = Params() - self.param_read_counter = 0 - self.mpc = LongitudinalMpc() - self.read_param() - self.fcw = False self.a_desired = init_a @@ -65,10 +58,6 @@ class LongitudinalPlanner: self.j_desired_trajectory = np.zeros(CONTROL_N) self.solverExecutionTime = 0.0 - def read_param(self): - e2e = self.params.get_bool('ExperimentalMode') and self.CP.openpilotLongitudinalControl - self.mpc.mode = 'blended' if e2e else 'acc' - @staticmethod def parse_model(model_msg, model_error): if (len(model_msg.position.x) == 33 and @@ -85,10 +74,8 @@ class LongitudinalPlanner: j = np.zeros(len(T_IDXS_MPC)) return x, v, a, j - def update(self, sm, read=True): - if self.param_read_counter % 50 == 0 and read: - self.read_param() - self.param_read_counter += 1 + def update(self, sm): + self.mpc.mode = 'blended' if sm['controlsState'].experimentalMode else 'acc' v_ego = sm['carState'].vEgo v_cruise_kph = sm['controlsState'].vCruise @@ -123,9 +110,7 @@ class LongitudinalPlanner: self.v_model_error = sm['modelV2'].temporalPose.trans[0] - v_ego if force_slow_decel: - # if required so, force a smooth deceleration - accel_limits_turns[1] = min(accel_limits_turns[1], AWARENESS_DECEL) - accel_limits_turns[0] = min(accel_limits_turns[0], accel_limits_turns[1]) + v_cruise = 0.0 # clip limits, cannot init MPC outside of bounds accel_limits_turns[0] = min(accel_limits_turns[0], self.a_desired + 0.05) accel_limits_turns[1] = max(accel_limits_turns[1], self.a_desired - 0.05) @@ -134,7 +119,7 @@ class LongitudinalPlanner: self.mpc.set_accel_limits(accel_limits_turns[0], accel_limits_turns[1]) self.mpc.set_cur_state(self.v_desired_filter.x, self.a_desired) x, v, a, j = self.parse_model(sm['modelV2'], self.v_model_error) - self.mpc.update(sm['carState'], sm['radarState'], v_cruise, x, v, a, j) + self.mpc.update(sm['radarState'], v_cruise, x, v, a, j) self.v_desired_trajectory = np.interp(T_IDXS[:CONTROL_N], T_IDXS_MPC, self.mpc.v_solution) self.a_desired_trajectory = np.interp(T_IDXS[:CONTROL_N], T_IDXS_MPC, self.mpc.a_solution) diff --git a/selfdrive/controls/radard.py b/selfdrive/controls/radard.py index 3d958139d..34f0f274f 100755 --- a/selfdrive/controls/radard.py +++ b/selfdrive/controls/radard.py @@ -8,9 +8,9 @@ from cereal import car from common.numpy_fast import interp from common.params import Params from common.realtime import Ratekeeper, Priority, config_realtime_process -from selfdrive.controls.lib.cluster.fastcluster_py import cluster_points_centroid from selfdrive.controls.lib.radar_helpers import Cluster, Track, RADAR_TO_CAMERA from system.swaglog import cloudlog +from third_party.cluster.fastcluster_py import cluster_points_centroid class KalmanParams(): @@ -35,7 +35,7 @@ class KalmanParams(): self.K = [[interp(dt, dts, K0)], [interp(dt, dts, K1)]] -def laplacian_cdf(x, mu, b): +def laplacian_pdf(x, mu, b): b = max(b, 1e-4) return math.exp(-abs(x-mu)/b) @@ -45,9 +45,9 @@ def match_vision_to_cluster(v_ego, lead, clusters): offset_vision_dist = lead.x[0] - RADAR_TO_CAMERA def prob(c): - prob_d = laplacian_cdf(c.dRel, offset_vision_dist, lead.xStd[0]) - prob_y = laplacian_cdf(c.yRel, -lead.y[0], lead.yStd[0]) - prob_v = laplacian_cdf(c.vRel + v_ego, lead.v[0], lead.vStd[0]) + prob_d = laplacian_pdf(c.dRel, offset_vision_dist, lead.xStd[0]) + prob_y = laplacian_pdf(c.yRel, -lead.y[0], lead.yStd[0]) + prob_v = laplacian_pdf(c.vRel + v_ego, lead.v[0], lead.vStd[0]) # This is isn't exactly right, but good heuristic return prob_d * prob_y * prob_v @@ -165,7 +165,6 @@ class RadarD(): dat.valid = sm.all_checks() and len(rr.errors) == 0 radarState = dat.radarState radarState.mdMonoTime = sm.logMonoTime['modelV2'] - radarState.canMonoTimes = list(rr.canMonoTimes) radarState.radarErrors = list(rr.errors) radarState.carStateMonoTime = sm.logMonoTime['carState'] diff --git a/selfdrive/debug/get_fingerprint.py b/selfdrive/debug/get_fingerprint.py index e678db4f1..f7f7a1604 100755 --- a/selfdrive/debug/get_fingerprint.py +++ b/selfdrive/debug/get_fingerprint.py @@ -22,7 +22,7 @@ while True: for c in lc.can: # read also msgs sent by EON on CAN bus 0x80 and filter out the # addr with more than 11 bits - if c.src in [0, 2] and c.address < 0x800: + if c.src % 0x80 == 0 and c.address < 0x800 and c.address not in (0x7df, 0x7e0, 0x7e8): msgs[c.address] = len(c.dat) fingerprint = ', '.join("%d: %d" % v for v in sorted(msgs.items())) diff --git a/selfdrive/debug/hyundai_enable_radar_points.py b/selfdrive/debug/hyundai_enable_radar_points.py index ac7e7102d..07ce5ebdd 100755 --- a/selfdrive/debug/hyundai_enable_radar_points.py +++ b/selfdrive/debug/hyundai_enable_radar_points.py @@ -52,6 +52,10 @@ SUPPORTED_FW_VERSIONS = { b'IK__ SCC F-CUP 1.00 1.02 96400-G9100\x18\x07\x06\x17\x12 ': ConfigValues( default_config=b"\x00\x00\x00\x01\x00\x00", tracks_enabled=b"\x00\x00\x00\x01\x00\x01"), + # 2019 SANTA FE + b"TM__ SCC F-CUP 1.00 1.00 99110-S1210\x19\x01%\x168 ": ConfigValues( + default_config=b"\x00\x00\x00\x01\x00\x00", + tracks_enabled=b"\x00\x00\x00\x01\x00\x01"), } if __name__ == "__main__": diff --git a/selfdrive/debug/vw_mqb_config.py b/selfdrive/debug/vw_mqb_config.py index 8c4dbc55e..8952405b8 100755 --- a/selfdrive/debug/vw_mqb_config.py +++ b/selfdrive/debug/vw_mqb_config.py @@ -126,6 +126,7 @@ if __name__ == "__main__": uds_client.security_access(ACCESS_TYPE_LEVEL_1.SEND_KEY, struct.pack("!I", key)) # type: ignore except (NegativeResponseError, MessageTimeoutError): print("Security access failed!") + print("Open the hood and retry (disables the \"diagnostic firewall\" on newer vehicles)") quit() try: diff --git a/selfdrive/locationd/.gitignore b/selfdrive/locationd/.gitignore index 526890278..86a228a6f 100644 --- a/selfdrive/locationd/.gitignore +++ b/selfdrive/locationd/.gitignore @@ -3,3 +3,4 @@ ubloxd_test params_learner paramsd locationd +test/test_glonass_runner diff --git a/selfdrive/locationd/SConscript b/selfdrive/locationd/SConscript index 4b7fba19b..61a0ed7f4 100644 --- a/selfdrive/locationd/SConscript +++ b/selfdrive/locationd/SConscript @@ -7,8 +7,14 @@ if GetOption('kaitai'): cmd = f"kaitai-struct-compiler --target cpp_stl --outdir {generated} $SOURCES" env.Command(['generated/ubx.cpp', 'generated/ubx.h'], 'ubx.ksy', cmd) env.Command(['generated/gps.cpp', 'generated/gps.h'], 'gps.ksy', cmd) + glonass = env.Command(['generated/glonass.cpp', 'generated/glonass.h'], 'glonass.ksy', cmd) -env.Program("ubloxd", ["ubloxd.cc", "ublox_msg.cc", "generated/ubx.cpp", "generated/gps.cpp"], LIBS=loc_libs) + # kaitai issue: https://github.com/kaitai-io/kaitai_struct/issues/910 + patch = env.Command(None, 'glonass_fix.patch', 'git apply $SOURCES') + env.Depends(patch, glonass) + +glonass_obj = env.Object('generated/glonass.cpp') +env.Program("ubloxd", ["ubloxd.cc", "ublox_msg.cc", "generated/ubx.cpp", "generated/gps.cpp", glonass_obj], LIBS=loc_libs) ekf_sym_cc = env.SharedObject("#rednose/helpers/ekf_sym.cc") locationd_sources = ["locationd.cc", "models/live_kf.cc", ekf_sym_cc] @@ -20,3 +26,6 @@ lenv.Depends(locationd, libkf) if File("liblocationd.cc").exists(): liblocationd = lenv.SharedLibrary("liblocationd", ["liblocationd.cc"] + locationd_sources, LIBS=loc_libs + transformations) lenv.Depends(liblocationd, libkf) + +if GetOption('test'): + env.Program("test/test_glonass_runner", ['test/test_glonass_runner.cc', 'test/test_glonass_kaitai.cc', glonass_obj], LIBS=[loc_libs]) \ No newline at end of file diff --git a/selfdrive/locationd/generated/glonass.cpp b/selfdrive/locationd/generated/glonass.cpp new file mode 100644 index 000000000..cd0f96ab6 --- /dev/null +++ b/selfdrive/locationd/generated/glonass.cpp @@ -0,0 +1,353 @@ +// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild + +#include "glonass.h" + +glonass_t::glonass_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, glonass_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = this; + + try { + _read(); + } catch(...) { + _clean_up(); + throw; + } +} + +void glonass_t::_read() { + m_idle_chip = m__io->read_bits_int_be(1); + m_string_number = m__io->read_bits_int_be(4); + //m__io->align_to_byte(); + switch (string_number()) { + case 4: { + m_data = new string_4_t(m__io, this, m__root); + break; + } + case 1: { + m_data = new string_1_t(m__io, this, m__root); + break; + } + case 3: { + m_data = new string_3_t(m__io, this, m__root); + break; + } + case 5: { + m_data = new string_5_t(m__io, this, m__root); + break; + } + case 2: { + m_data = new string_2_t(m__io, this, m__root); + break; + } + default: { + m_data = new string_non_immediate_t(m__io, this, m__root); + break; + } + } + m_hamming_code = m__io->read_bits_int_be(8); + m_pad_1 = m__io->read_bits_int_be(11); + m_superframe_number = m__io->read_bits_int_be(16); + m_pad_2 = m__io->read_bits_int_be(8); + m_frame_number = m__io->read_bits_int_be(8); +} + +glonass_t::~glonass_t() { + _clean_up(); +} + +void glonass_t::_clean_up() { + if (m_data) { + delete m_data; m_data = 0; + } +} + +glonass_t::string_4_t::string_4_t(kaitai::kstream* p__io, glonass_t* p__parent, glonass_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + f_tau_n = false; + f_delta_tau_n = false; + + try { + _read(); + } catch(...) { + _clean_up(); + throw; + } +} + +void glonass_t::string_4_t::_read() { + m_tau_n_sign = m__io->read_bits_int_be(1); + m_tau_n_value = m__io->read_bits_int_be(21); + m_delta_tau_n_sign = m__io->read_bits_int_be(1); + m_delta_tau_n_value = m__io->read_bits_int_be(4); + m_e_n = m__io->read_bits_int_be(5); + m_not_used_1 = m__io->read_bits_int_be(14); + m_p4 = m__io->read_bits_int_be(1); + m_f_t = m__io->read_bits_int_be(4); + m_not_used_2 = m__io->read_bits_int_be(3); + m_n_t = m__io->read_bits_int_be(11); + m_n = m__io->read_bits_int_be(5); + m_m = m__io->read_bits_int_be(2); +} + +glonass_t::string_4_t::~string_4_t() { + _clean_up(); +} + +void glonass_t::string_4_t::_clean_up() { +} + +int32_t glonass_t::string_4_t::tau_n() { + if (f_tau_n) + return m_tau_n; + m_tau_n = ((tau_n_sign()) ? ((tau_n_value() * -1)) : (tau_n_value())); + f_tau_n = true; + return m_tau_n; +} + +int32_t glonass_t::string_4_t::delta_tau_n() { + if (f_delta_tau_n) + return m_delta_tau_n; + m_delta_tau_n = ((delta_tau_n_sign()) ? ((delta_tau_n_value() * -1)) : (delta_tau_n_value())); + f_delta_tau_n = true; + return m_delta_tau_n; +} + +glonass_t::string_non_immediate_t::string_non_immediate_t(kaitai::kstream* p__io, glonass_t* p__parent, glonass_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + + try { + _read(); + } catch(...) { + _clean_up(); + throw; + } +} + +void glonass_t::string_non_immediate_t::_read() { + m_data_1 = m__io->read_bits_int_be(64); + m_data_2 = m__io->read_bits_int_be(8); +} + +glonass_t::string_non_immediate_t::~string_non_immediate_t() { + _clean_up(); +} + +void glonass_t::string_non_immediate_t::_clean_up() { +} + +glonass_t::string_5_t::string_5_t(kaitai::kstream* p__io, glonass_t* p__parent, glonass_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + + try { + _read(); + } catch(...) { + _clean_up(); + throw; + } +} + +void glonass_t::string_5_t::_read() { + m_n_a = m__io->read_bits_int_be(11); + m_tau_c = m__io->read_bits_int_be(32); + m_not_used = m__io->read_bits_int_be(1); + m_n_4 = m__io->read_bits_int_be(5); + m_tau_gps = m__io->read_bits_int_be(22); + m_l_n = m__io->read_bits_int_be(1); +} + +glonass_t::string_5_t::~string_5_t() { + _clean_up(); +} + +void glonass_t::string_5_t::_clean_up() { +} + +glonass_t::string_1_t::string_1_t(kaitai::kstream* p__io, glonass_t* p__parent, glonass_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + f_x_vel = false; + f_x_accel = false; + f_x = false; + + try { + _read(); + } catch(...) { + _clean_up(); + throw; + } +} + +void glonass_t::string_1_t::_read() { + m_not_used = m__io->read_bits_int_be(2); + m_p1 = m__io->read_bits_int_be(2); + m_t_k = m__io->read_bits_int_be(12); + m_x_vel_sign = m__io->read_bits_int_be(1); + m_x_vel_value = m__io->read_bits_int_be(23); + m_x_accel_sign = m__io->read_bits_int_be(1); + m_x_accel_value = m__io->read_bits_int_be(4); + m_x_sign = m__io->read_bits_int_be(1); + m_x_value = m__io->read_bits_int_be(26); +} + +glonass_t::string_1_t::~string_1_t() { + _clean_up(); +} + +void glonass_t::string_1_t::_clean_up() { +} + +int32_t glonass_t::string_1_t::x_vel() { + if (f_x_vel) + return m_x_vel; + m_x_vel = ((x_vel_sign()) ? ((x_vel_value() * -1)) : (x_vel_value())); + f_x_vel = true; + return m_x_vel; +} + +int32_t glonass_t::string_1_t::x_accel() { + if (f_x_accel) + return m_x_accel; + m_x_accel = ((x_accel_sign()) ? ((x_accel_value() * -1)) : (x_accel_value())); + f_x_accel = true; + return m_x_accel; +} + +int32_t glonass_t::string_1_t::x() { + if (f_x) + return m_x; + m_x = ((x_sign()) ? ((x_value() * -1)) : (x_value())); + f_x = true; + return m_x; +} + +glonass_t::string_2_t::string_2_t(kaitai::kstream* p__io, glonass_t* p__parent, glonass_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + f_y_vel = false; + f_y_accel = false; + f_y = false; + + try { + _read(); + } catch(...) { + _clean_up(); + throw; + } +} + +void glonass_t::string_2_t::_read() { + m_b_n = m__io->read_bits_int_be(3); + m_p2 = m__io->read_bits_int_be(1); + m_t_b = m__io->read_bits_int_be(7); + m_not_used = m__io->read_bits_int_be(5); + m_y_vel_sign = m__io->read_bits_int_be(1); + m_y_vel_value = m__io->read_bits_int_be(23); + m_y_accel_sign = m__io->read_bits_int_be(1); + m_y_accel_value = m__io->read_bits_int_be(4); + m_y_sign = m__io->read_bits_int_be(1); + m_y_value = m__io->read_bits_int_be(26); +} + +glonass_t::string_2_t::~string_2_t() { + _clean_up(); +} + +void glonass_t::string_2_t::_clean_up() { +} + +int32_t glonass_t::string_2_t::y_vel() { + if (f_y_vel) + return m_y_vel; + m_y_vel = ((y_vel_sign()) ? ((y_vel_value() * -1)) : (y_vel_value())); + f_y_vel = true; + return m_y_vel; +} + +int32_t glonass_t::string_2_t::y_accel() { + if (f_y_accel) + return m_y_accel; + m_y_accel = ((y_accel_sign()) ? ((y_accel_value() * -1)) : (y_accel_value())); + f_y_accel = true; + return m_y_accel; +} + +int32_t glonass_t::string_2_t::y() { + if (f_y) + return m_y; + m_y = ((y_sign()) ? ((y_value() * -1)) : (y_value())); + f_y = true; + return m_y; +} + +glonass_t::string_3_t::string_3_t(kaitai::kstream* p__io, glonass_t* p__parent, glonass_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + f_gamma_n = false; + f_z_vel = false; + f_z_accel = false; + f_z = false; + + try { + _read(); + } catch(...) { + _clean_up(); + throw; + } +} + +void glonass_t::string_3_t::_read() { + m_p3 = m__io->read_bits_int_be(1); + m_gamma_n_sign = m__io->read_bits_int_be(1); + m_gamma_n_value = m__io->read_bits_int_be(10); + m_not_used = m__io->read_bits_int_be(1); + m_p = m__io->read_bits_int_be(2); + m_l_n = m__io->read_bits_int_be(1); + m_z_vel_sign = m__io->read_bits_int_be(1); + m_z_vel_value = m__io->read_bits_int_be(23); + m_z_accel_sign = m__io->read_bits_int_be(1); + m_z_accel_value = m__io->read_bits_int_be(4); + m_z_sign = m__io->read_bits_int_be(1); + m_z_value = m__io->read_bits_int_be(26); +} + +glonass_t::string_3_t::~string_3_t() { + _clean_up(); +} + +void glonass_t::string_3_t::_clean_up() { +} + +int32_t glonass_t::string_3_t::gamma_n() { + if (f_gamma_n) + return m_gamma_n; + m_gamma_n = ((gamma_n_sign()) ? ((gamma_n_value() * -1)) : (gamma_n_value())); + f_gamma_n = true; + return m_gamma_n; +} + +int32_t glonass_t::string_3_t::z_vel() { + if (f_z_vel) + return m_z_vel; + m_z_vel = ((z_vel_sign()) ? ((z_vel_value() * -1)) : (z_vel_value())); + f_z_vel = true; + return m_z_vel; +} + +int32_t glonass_t::string_3_t::z_accel() { + if (f_z_accel) + return m_z_accel; + m_z_accel = ((z_accel_sign()) ? ((z_accel_value() * -1)) : (z_accel_value())); + f_z_accel = true; + return m_z_accel; +} + +int32_t glonass_t::string_3_t::z() { + if (f_z) + return m_z; + m_z = ((z_sign()) ? ((z_value() * -1)) : (z_value())); + f_z = true; + return m_z; +} diff --git a/selfdrive/locationd/generated/glonass.h b/selfdrive/locationd/generated/glonass.h new file mode 100644 index 000000000..19867ba22 --- /dev/null +++ b/selfdrive/locationd/generated/glonass.h @@ -0,0 +1,375 @@ +#ifndef GLONASS_H_ +#define GLONASS_H_ + +// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild + +#include "kaitai/kaitaistruct.h" +#include + +#if KAITAI_STRUCT_VERSION < 9000L +#error "Incompatible Kaitai Struct C++/STL API: version 0.9 or later is required" +#endif + +class glonass_t : public kaitai::kstruct { + +public: + class string_4_t; + class string_non_immediate_t; + class string_5_t; + class string_1_t; + class string_2_t; + class string_3_t; + + glonass_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = 0, glonass_t* p__root = 0); + +private: + void _read(); + void _clean_up(); + +public: + ~glonass_t(); + + class string_4_t : public kaitai::kstruct { + + public: + + string_4_t(kaitai::kstream* p__io, glonass_t* p__parent = 0, glonass_t* p__root = 0); + + private: + void _read(); + void _clean_up(); + + public: + ~string_4_t(); + + private: + bool f_tau_n; + int32_t m_tau_n; + + public: + int32_t tau_n(); + + private: + bool f_delta_tau_n; + int32_t m_delta_tau_n; + + public: + int32_t delta_tau_n(); + + private: + bool m_tau_n_sign; + uint64_t m_tau_n_value; + bool m_delta_tau_n_sign; + uint64_t m_delta_tau_n_value; + uint64_t m_e_n; + uint64_t m_not_used_1; + bool m_p4; + uint64_t m_f_t; + uint64_t m_not_used_2; + uint64_t m_n_t; + uint64_t m_n; + uint64_t m_m; + glonass_t* m__root; + glonass_t* m__parent; + + public: + bool tau_n_sign() const { return m_tau_n_sign; } + uint64_t tau_n_value() const { return m_tau_n_value; } + bool delta_tau_n_sign() const { return m_delta_tau_n_sign; } + uint64_t delta_tau_n_value() const { return m_delta_tau_n_value; } + uint64_t e_n() const { return m_e_n; } + uint64_t not_used_1() const { return m_not_used_1; } + bool p4() const { return m_p4; } + uint64_t f_t() const { return m_f_t; } + uint64_t not_used_2() const { return m_not_used_2; } + uint64_t n_t() const { return m_n_t; } + uint64_t n() const { return m_n; } + uint64_t m() const { return m_m; } + glonass_t* _root() const { return m__root; } + glonass_t* _parent() const { return m__parent; } + }; + + class string_non_immediate_t : public kaitai::kstruct { + + public: + + string_non_immediate_t(kaitai::kstream* p__io, glonass_t* p__parent = 0, glonass_t* p__root = 0); + + private: + void _read(); + void _clean_up(); + + public: + ~string_non_immediate_t(); + + private: + uint64_t m_data_1; + uint64_t m_data_2; + glonass_t* m__root; + glonass_t* m__parent; + + public: + uint64_t data_1() const { return m_data_1; } + uint64_t data_2() const { return m_data_2; } + glonass_t* _root() const { return m__root; } + glonass_t* _parent() const { return m__parent; } + }; + + class string_5_t : public kaitai::kstruct { + + public: + + string_5_t(kaitai::kstream* p__io, glonass_t* p__parent = 0, glonass_t* p__root = 0); + + private: + void _read(); + void _clean_up(); + + public: + ~string_5_t(); + + private: + uint64_t m_n_a; + uint64_t m_tau_c; + bool m_not_used; + uint64_t m_n_4; + uint64_t m_tau_gps; + bool m_l_n; + glonass_t* m__root; + glonass_t* m__parent; + + public: + uint64_t n_a() const { return m_n_a; } + uint64_t tau_c() const { return m_tau_c; } + bool not_used() const { return m_not_used; } + uint64_t n_4() const { return m_n_4; } + uint64_t tau_gps() const { return m_tau_gps; } + bool l_n() const { return m_l_n; } + glonass_t* _root() const { return m__root; } + glonass_t* _parent() const { return m__parent; } + }; + + class string_1_t : public kaitai::kstruct { + + public: + + string_1_t(kaitai::kstream* p__io, glonass_t* p__parent = 0, glonass_t* p__root = 0); + + private: + void _read(); + void _clean_up(); + + public: + ~string_1_t(); + + private: + bool f_x_vel; + int32_t m_x_vel; + + public: + int32_t x_vel(); + + private: + bool f_x_accel; + int32_t m_x_accel; + + public: + int32_t x_accel(); + + private: + bool f_x; + int32_t m_x; + + public: + int32_t x(); + + private: + uint64_t m_not_used; + uint64_t m_p1; + uint64_t m_t_k; + bool m_x_vel_sign; + uint64_t m_x_vel_value; + bool m_x_accel_sign; + uint64_t m_x_accel_value; + bool m_x_sign; + uint64_t m_x_value; + glonass_t* m__root; + glonass_t* m__parent; + + public: + uint64_t not_used() const { return m_not_used; } + uint64_t p1() const { return m_p1; } + uint64_t t_k() const { return m_t_k; } + bool x_vel_sign() const { return m_x_vel_sign; } + uint64_t x_vel_value() const { return m_x_vel_value; } + bool x_accel_sign() const { return m_x_accel_sign; } + uint64_t x_accel_value() const { return m_x_accel_value; } + bool x_sign() const { return m_x_sign; } + uint64_t x_value() const { return m_x_value; } + glonass_t* _root() const { return m__root; } + glonass_t* _parent() const { return m__parent; } + }; + + class string_2_t : public kaitai::kstruct { + + public: + + string_2_t(kaitai::kstream* p__io, glonass_t* p__parent = 0, glonass_t* p__root = 0); + + private: + void _read(); + void _clean_up(); + + public: + ~string_2_t(); + + private: + bool f_y_vel; + int32_t m_y_vel; + + public: + int32_t y_vel(); + + private: + bool f_y_accel; + int32_t m_y_accel; + + public: + int32_t y_accel(); + + private: + bool f_y; + int32_t m_y; + + public: + int32_t y(); + + private: + uint64_t m_b_n; + bool m_p2; + uint64_t m_t_b; + uint64_t m_not_used; + bool m_y_vel_sign; + uint64_t m_y_vel_value; + bool m_y_accel_sign; + uint64_t m_y_accel_value; + bool m_y_sign; + uint64_t m_y_value; + glonass_t* m__root; + glonass_t* m__parent; + + public: + uint64_t b_n() const { return m_b_n; } + bool p2() const { return m_p2; } + uint64_t t_b() const { return m_t_b; } + uint64_t not_used() const { return m_not_used; } + bool y_vel_sign() const { return m_y_vel_sign; } + uint64_t y_vel_value() const { return m_y_vel_value; } + bool y_accel_sign() const { return m_y_accel_sign; } + uint64_t y_accel_value() const { return m_y_accel_value; } + bool y_sign() const { return m_y_sign; } + uint64_t y_value() const { return m_y_value; } + glonass_t* _root() const { return m__root; } + glonass_t* _parent() const { return m__parent; } + }; + + class string_3_t : public kaitai::kstruct { + + public: + + string_3_t(kaitai::kstream* p__io, glonass_t* p__parent = 0, glonass_t* p__root = 0); + + private: + void _read(); + void _clean_up(); + + public: + ~string_3_t(); + + private: + bool f_gamma_n; + int32_t m_gamma_n; + + public: + int32_t gamma_n(); + + private: + bool f_z_vel; + int32_t m_z_vel; + + public: + int32_t z_vel(); + + private: + bool f_z_accel; + int32_t m_z_accel; + + public: + int32_t z_accel(); + + private: + bool f_z; + int32_t m_z; + + public: + int32_t z(); + + private: + bool m_p3; + bool m_gamma_n_sign; + uint64_t m_gamma_n_value; + bool m_not_used; + uint64_t m_p; + bool m_l_n; + bool m_z_vel_sign; + uint64_t m_z_vel_value; + bool m_z_accel_sign; + uint64_t m_z_accel_value; + bool m_z_sign; + uint64_t m_z_value; + glonass_t* m__root; + glonass_t* m__parent; + + public: + bool p3() const { return m_p3; } + bool gamma_n_sign() const { return m_gamma_n_sign; } + uint64_t gamma_n_value() const { return m_gamma_n_value; } + bool not_used() const { return m_not_used; } + uint64_t p() const { return m_p; } + bool l_n() const { return m_l_n; } + bool z_vel_sign() const { return m_z_vel_sign; } + uint64_t z_vel_value() const { return m_z_vel_value; } + bool z_accel_sign() const { return m_z_accel_sign; } + uint64_t z_accel_value() const { return m_z_accel_value; } + bool z_sign() const { return m_z_sign; } + uint64_t z_value() const { return m_z_value; } + glonass_t* _root() const { return m__root; } + glonass_t* _parent() const { return m__parent; } + }; + +private: + bool m_idle_chip; + uint64_t m_string_number; + kaitai::kstruct* m_data; + uint64_t m_hamming_code; + uint64_t m_pad_1; + uint64_t m_superframe_number; + uint64_t m_pad_2; + uint64_t m_frame_number; + glonass_t* m__root; + kaitai::kstruct* m__parent; + +public: + bool idle_chip() const { return m_idle_chip; } + uint64_t string_number() const { return m_string_number; } + kaitai::kstruct* data() const { return m_data; } + uint64_t hamming_code() const { return m_hamming_code; } + uint64_t pad_1() const { return m_pad_1; } + uint64_t superframe_number() const { return m_superframe_number; } + uint64_t pad_2() const { return m_pad_2; } + uint64_t frame_number() const { return m_frame_number; } + glonass_t* _root() const { return m__root; } + kaitai::kstruct* _parent() const { return m__parent; } +}; + +#endif // GLONASS_H_ diff --git a/selfdrive/locationd/generated/gps.cpp b/selfdrive/locationd/generated/gps.cpp index 9b020735b..8e1cb85b9 100644 --- a/selfdrive/locationd/generated/gps.cpp +++ b/selfdrive/locationd/generated/gps.cpp @@ -274,9 +274,9 @@ gps_t::tlm_t::tlm_t(kaitai::kstream* p__io, gps_t* p__parent, gps_t* p__root) : } void gps_t::tlm_t::_read() { - m_magic = m__io->read_bytes(1); - if (!(magic() == std::string("\x8B", 1))) { - throw kaitai::validation_not_equal_error(std::string("\x8B", 1), magic(), _io(), std::string("/types/tlm/seq/0")); + m_preamble = m__io->read_bytes(1); + if (!(preamble() == std::string("\x8B", 1))) { + throw kaitai::validation_not_equal_error(std::string("\x8B", 1), preamble(), _io(), std::string("/types/tlm/seq/0")); } m_tlm = m__io->read_bits_int_be(14); m_integrity_status = m__io->read_bits_int_be(1); diff --git a/selfdrive/locationd/generated/gps.h b/selfdrive/locationd/generated/gps.h index 293e2e4a0..9dfc5031f 100644 --- a/selfdrive/locationd/generated/gps.h +++ b/selfdrive/locationd/generated/gps.h @@ -273,7 +273,7 @@ public: ~tlm_t(); private: - std::string m_magic; + std::string m_preamble; uint64_t m_tlm; bool m_integrity_status; bool m_reserved; @@ -281,7 +281,7 @@ public: gps_t* m__parent; public: - std::string magic() const { return m_magic; } + std::string preamble() const { return m_preamble; } uint64_t tlm() const { return m_tlm; } bool integrity_status() const { return m_integrity_status; } bool reserved() const { return m_reserved; } diff --git a/selfdrive/locationd/generated/ubx.cpp b/selfdrive/locationd/generated/ubx.cpp index 5e743e1ee..34fe1e52c 100644 --- a/selfdrive/locationd/generated/ubx.cpp +++ b/selfdrive/locationd/generated/ubx.cpp @@ -89,13 +89,10 @@ void ubx_t::rxm_rawx_t::_read() { m_num_meas = m__io->read_u1(); m_rec_stat = m__io->read_u1(); m_reserved1 = m__io->read_bytes(3); - int l_measurements = num_meas(); m__raw_measurements = new std::vector(); - m__raw_measurements->reserve(l_measurements); m__io__raw_measurements = new std::vector(); - m__io__raw_measurements->reserve(l_measurements); m_measurements = new std::vector(); - m_measurements->reserve(l_measurements); + const int l_measurements = num_meas(); for (int i = 0; i < l_measurements; i++) { m__raw_measurements->push_back(m__io->read_bytes(32)); kaitai::kstream* io__raw_measurements = new kaitai::kstream(m__raw_measurements->at(m__raw_measurements->size() - 1)); @@ -184,9 +181,8 @@ void ubx_t::rxm_sfrbx_t::_read() { m_reserved2 = m__io->read_bytes(1); m_version = m__io->read_u1(); m_reserved3 = m__io->read_bytes(1); - int l_body = num_words(); m_body = new std::vector(); - m_body->reserve(l_body); + const int l_body = num_words(); for (int i = 0; i < l_body; i++) { m_body->push_back(m__io->read_u4le()); } diff --git a/selfdrive/locationd/laikad.py b/selfdrive/locationd/laikad.py index 6936d88ac..2eb1e9452 100755 --- a/selfdrive/locationd/laikad.py +++ b/selfdrive/locationd/laikad.py @@ -3,6 +3,7 @@ import json import math import os import time +import shutil from collections import defaultdict from concurrent.futures import Future, ProcessPoolExecutor from datetime import datetime @@ -16,30 +17,30 @@ from common.params import Params, put_nonblocking from laika import AstroDog from laika.constants import SECS_IN_HR, SECS_IN_MIN from laika.downloader import DownloadFailed -from laika.ephemeris import Ephemeris, EphemerisType, convert_ublox_ephem, parse_qcom_ephem +from laika.ephemeris import Ephemeris, EphemerisType, convert_ublox_gps_ephem, convert_ublox_glonass_ephem, parse_qcom_ephem from laika.gps_time import GPSTime from laika.helpers import ConstellationId from laika.raw_gnss import GNSSMeasurement, correct_measurements, process_measurements, read_raw_ublox, read_raw_qcom -from selfdrive.locationd.laikad_helpers import calc_pos_fix_gauss_newton, get_posfix_sympy_fun +from laika.opt import calc_pos_fix, get_posfix_sympy_fun, calc_vel_fix, get_velfix_sympy_func from selfdrive.locationd.models.constants import GENERATED_DIR, ObservationKind from selfdrive.locationd.models.gnss_kf import GNSSKalman from selfdrive.locationd.models.gnss_kf import States as GStates from system.swaglog import cloudlog MAX_TIME_GAP = 10 -EPHEMERIS_CACHE = 'LaikadEphemeris' +EPHEMERIS_CACHE = 'LaikadEphemerisV2' DOWNLOADS_CACHE_FOLDER = "/tmp/comma_download_cache/" -CACHE_VERSION = 0.1 +CACHE_VERSION = 0.2 POS_FIX_RESIDUAL_THRESHOLD = 100.0 class Laikad: - def __init__(self, valid_const=("GPS", "GLONASS"), auto_fetch_orbits=True, auto_update=False, - valid_ephem_types=(EphemerisType.ULTRA_RAPID_ORBIT, EphemerisType.NAV), + def __init__(self, valid_const=("GPS", "GLONASS"), auto_fetch_navs=True, auto_update=False, + valid_ephem_types=(EphemerisType.NAV,), save_ephemeris=False, use_qcom=False): """ valid_const: GNSS constellation which can be used - auto_fetch_orbits: If true fetch orbits from internet when needed + auto_fetch_navs: If true fetch navs from internet when needed auto_update: If true download AstroDog will download all files needed. This can be ephemeris or correction data like ionosphere. valid_ephem_types: Valid ephemeris types to be used by AstroDog save_ephemeris: If true saves and loads nav and orbit ephemeris to cache. @@ -47,20 +48,20 @@ class Laikad: self.astro_dog = AstroDog(valid_const=valid_const, auto_update=auto_update, valid_ephem_types=valid_ephem_types, clear_old_ephemeris=True, cache_dir=DOWNLOADS_CACHE_FOLDER) self.gnss_kf = GNSSKalman(GENERATED_DIR, cython=True, erratic_clock=use_qcom) - self.auto_fetch_orbits = auto_fetch_orbits + self.auto_fetch_navs = auto_fetch_navs self.orbit_fetch_executor: Optional[ProcessPoolExecutor] = None self.orbit_fetch_future: Optional[Future] = None - self.last_fetch_orbits_t = None + self.last_fetch_navs_t = None self.got_first_gnss_msg = False self.last_cached_t = None self.save_ephemeris = save_ephemeris self.load_cache() self.posfix_functions = {constellation: get_posfix_sympy_fun(constellation) for constellation in (ConstellationId.GPS, ConstellationId.GLONASS)} - self.last_pos_fix = [] - self.last_pos_residual = [] - self.last_pos_fix_t = None + self.velfix_function = get_velfix_sympy_func() + self.last_fix_pos = None + self.last_fix_t = None self.gps_week = None self.use_qcom = use_qcom @@ -74,38 +75,42 @@ class Laikad: try: cache = json.loads(cache, object_hook=deserialize_hook) - self.astro_dog.add_orbits(cache['orbits']) - self.astro_dog.add_navs(cache['nav']) - self.last_fetch_orbits_t = cache['last_fetch_orbits_t'] + if cache['version'] == CACHE_VERSION: + self.astro_dog.add_navs(cache['navs']) + self.last_fetch_navs_t = cache['last_fetch_navs_t'] + else: + cache['navs'] = {} except json.decoder.JSONDecodeError: cloudlog.exception("Error parsing cache") - timestamp = self.last_fetch_orbits_t.as_datetime() if self.last_fetch_orbits_t is not None else 'Nan' + timestamp = self.last_fetch_navs_t.as_datetime() if self.last_fetch_navs_t is not None else 'Nan' cloudlog.debug( - f"Loaded nav ({sum([len(v) for v in cache['nav']])}) and orbits ({sum([len(v) for v in cache['orbits']])}) cache with timestamp: {timestamp}. Unique orbit and nav sats: {list(cache['orbits'].keys())} {list(cache['nav'].keys())} " + - f"With time range: {[f'{start.as_datetime()}, {end.as_datetime()}' for (start,end) in self.astro_dog.orbit_fetched_times._ranges]}") + f"Loaded navs ({sum([len(v) for v in cache['navs']])}) cache with timestamp: {timestamp}. Unique orbit and nav sats: {list(cache['navs'].keys())} " + + f"With time range: {[f'{start.as_datetime()}, {end.as_datetime()}' for (start,end) in self.astro_dog.navs_fetched_times._ranges]}") def cache_ephemeris(self, t: GPSTime): if self.save_ephemeris and (self.last_cached_t is None or t - self.last_cached_t > SECS_IN_MIN): put_nonblocking(EPHEMERIS_CACHE, json.dumps( - {'version': CACHE_VERSION, 'last_fetch_orbits_t': self.last_fetch_orbits_t, 'orbits': self.astro_dog.orbits, 'nav': self.astro_dog.nav}, + {'version': CACHE_VERSION, 'last_fetch_navs_t': self.last_fetch_navs_t, 'navs': self.astro_dog.navs}, cls=CacheSerializer)) cloudlog.debug("Cache saved") self.last_cached_t = t - def get_est_pos(self, t, processed_measurements): - if self.last_pos_fix_t is None or abs(self.last_pos_fix_t - t) >= 2: - min_measurements = 6 if any(p.constellation_id == ConstellationId.GLONASS for p in processed_measurements) else 5 - pos_fix, pos_fix_residual = calc_pos_fix_gauss_newton(processed_measurements, self.posfix_functions, min_measurements=min_measurements) - if len(pos_fix) > 0: - self.last_pos_fix_t = t - residual_median = np.median(np.abs(pos_fix_residual)) - if np.median(np.abs(pos_fix_residual)) < POS_FIX_RESIDUAL_THRESHOLD: - cloudlog.debug(f"Pos fix is within threshold with median: {residual_median.round()}") - self.last_pos_fix = pos_fix[:3] - self.last_pos_residual = pos_fix_residual - else: - cloudlog.debug(f"Pos fix failed with median: {residual_median.round()}. All residuals: {np.round(pos_fix_residual)}") - return self.last_pos_fix + def get_lsq_fix(self, t, measurements): + if self.last_fix_t is None or abs(self.last_fix_t - t) > 0: + min_measurements = 7 if any(p.constellation_id == ConstellationId.GLONASS for p in measurements) else 6 + position_solution, pr_residuals = calc_pos_fix(measurements, self.posfix_functions, min_measurements=min_measurements) + if len(position_solution) < 3: + return None + position_estimate = position_solution[:3] + #TODO median abs residual is decent estimate of std, can be improved with measurements stds and/or DOP + position_std = np.median(np.abs(pr_residuals)) * np.ones(3) + velocity_solution, prr_residuals = calc_vel_fix(measurements, position_estimate, self.velfix_function, min_measurements=min_measurements) + if len(velocity_solution) < 3: + return None + + velocity_estimate = velocity_solution[:3] + velocity_std = np.median(np.abs(prr_residuals)) * np.ones(3) + return position_estimate, position_std, velocity_estimate, velocity_std def is_good_report(self, gnss_msg): if gnss_msg.which() == 'drMeasurementReport' and self.use_qcom: @@ -134,70 +139,97 @@ class Laikad: if self.use_qcom: return gnss_msg.which() == 'drSvPoly' else: - return gnss_msg.which() == 'ephemeris' + return gnss_msg.which() in ('ephemeris', 'glonassEphemeris') def read_ephemeris(self, gnss_msg): - # TODO this only works on GLONASS if self.use_qcom: # TODO this is not robust to gps week rollover if self.gps_week is None: return ephem = parse_qcom_ephem(gnss_msg.drSvPoly, self.gps_week) else: - ephem = convert_ublox_ephem(gnss_msg.ephemeris) + if gnss_msg.which() == 'ephemeris': + ephem = convert_ublox_gps_ephem(gnss_msg.ephemeris) + elif gnss_msg.which() == 'glonassEphemeris': + ephem = convert_ublox_glonass_ephem(gnss_msg.glonassEphemeris) + else: + cloudlog.error(f"Unsupported ephemeris type: {gnss_msg.which()}") + return self.astro_dog.add_navs({ephem.prn: [ephem]}) self.cache_ephemeris(t=ephem.epoch) - def process_gnss_msg(self, gnss_msg, gnss_mono_time: int, block=False): - if self.is_good_report(gnss_msg): - week, tow, new_meas = self.read_report(gnss_msg) - self.gps_week = week + def process_report(self, new_meas, t): + # Filter measurements with unexpected pseudoranges for GPS and GLONASS satellites + new_meas = [m for m in new_meas if 1e7 < m.observables['C1C'] < 3e7] + processed_measurements = process_measurements(new_meas, self.astro_dog) + if self.last_fix_pos is not None: + corrected_measurements = correct_measurements(processed_measurements, self.last_fix_pos, self.astro_dog) + instant_fix = self.get_lsq_fix(t, corrected_measurements) + #instant_fix = self.get_lsq_fix(t, processed_measurements) + else: + corrected_measurements = [] + instant_fix = self.get_lsq_fix(t, processed_measurements) + if instant_fix is None: + return None + else: + position_estimate, position_std, velocity_estimate, velocity_std = instant_fix + self.last_fix_t = t + self.last_fix_pos = position_estimate + self.lat_fix_pos_std = position_std + if (t*1e9) % 10 == 0: + cloudlog.debug(f"Measurements Incoming/Processed/Corrected: {len(new_meas), len(processed_measurements), len(corrected_measurements)}") + return position_estimate, position_std, velocity_estimate, velocity_std, corrected_measurements, processed_measurements + def process_gnss_msg(self, gnss_msg, gnss_mono_time: int, block=False): + if self.is_ephemeris(gnss_msg): + self.read_ephemeris(gnss_msg) + return None + elif self.is_good_report(gnss_msg): + + week, tow, new_meas = self.read_report(gnss_msg) + if len(new_meas) == 0: + return None + + self.gps_week = week t = gnss_mono_time * 1e-9 if week > 0: self.got_first_gnss_msg = True latest_msg_t = GPSTime(week, tow) - if self.auto_fetch_orbits: - self.fetch_orbits(latest_msg_t, block) + if self.auto_fetch_navs: + self.fetch_navs(latest_msg_t, block) - # Filter measurements with unexpected pseudoranges for GPS and GLONASS satellites - new_meas = [m for m in new_meas if 1e7 < m.observables['C1C'] < 3e7] - - processed_measurements = process_measurements(new_meas, self.astro_dog) - est_pos = self.get_est_pos(t, processed_measurements) - - corrected_measurements = correct_measurements(processed_measurements, est_pos, self.astro_dog) if len(est_pos) > 0 else [] - if gnss_mono_time % 10 == 0: - cloudlog.debug(f"Measurements Incoming/Processed/Corrected: {len(new_meas), len(processed_measurements), len(corrected_measurements)}") - - self.update_localizer(est_pos, t, corrected_measurements) - kf_valid = all(self.kf_valid(t)) - ecef_pos = self.gnss_kf.x[GStates.ECEF_POS] - ecef_vel = self.gnss_kf.x[GStates.ECEF_VELOCITY] - - p = self.gnss_kf.P.diagonal() - pos_std = np.sqrt(p[GStates.ECEF_POS]) - vel_std = np.sqrt(p[GStates.ECEF_VELOCITY]) + output = self.process_report(new_meas, t) + if output is None: + return None + position_estimate, position_std, velocity_estimate, velocity_std, corrected_measurements, _ = output + self.update_localizer(position_estimate, t, corrected_measurements) meas_msgs = [create_measurement_msg(m) for m in corrected_measurements] - dat = messaging.new_message("gnssMeasurements") + msg = messaging.new_message("gnssMeasurements") measurement_msg = log.LiveLocationKalman.Measurement.new_message - dat.gnssMeasurements = { + + P_diag = self.gnss_kf.P.diagonal() + kf_valid = all(self.kf_valid(t)) + msg.gnssMeasurements = { "gpsWeek": week, "gpsTimeOfWeek": tow, - "positionECEF": measurement_msg(value=ecef_pos.tolist(), std=pos_std.tolist(), valid=kf_valid), - "velocityECEF": measurement_msg(value=ecef_vel.tolist(), std=vel_std.tolist(), valid=kf_valid), - # TODO std is incorrectly the dimension of the measurements and not 3D - "positionFixECEF": measurement_msg(value=self.last_pos_fix, std=self.last_pos_residual, valid=self.last_pos_fix_t == t), - "ubloxMonoTime": gnss_mono_time, + "kalmanPositionECEF": measurement_msg(value=self.gnss_kf.x[GStates.ECEF_POS].tolist(), + std=np.sqrt(P_diag[GStates.ECEF_POS]).tolist(), + valid=kf_valid), + "kalmanVelocityECEF": measurement_msg(value=self.gnss_kf.x[GStates.ECEF_VELOCITY].tolist(), + std=np.sqrt(P_diag[GStates.ECEF_VELOCITY]).tolist(), + valid=kf_valid), + "positionECEF": measurement_msg(value=position_estimate, std=position_std.tolist(), valid=bool(self.last_fix_t == t)), + "velocityECEF": measurement_msg(value=velocity_estimate, std=velocity_std.tolist(), valid=bool(self.last_fix_t == t)), + + "measTime": gnss_mono_time, "correctedMeasurements": meas_msgs } - return dat - elif self.is_ephemeris(gnss_msg): - self.read_ephemeris(gnss_msg) + return msg #elif gnss_msg.which() == 'ionoData': - # todo add this. Needed to better correct messages offline. First fix ublox_msg.cc to sent them. + # TODO: add this, Needed to better correct messages offline. First fix ublox_msg.cc to sent them. + def update_localizer(self, est_pos, t: float, measurements: List[GNSSMeasurement]): # Check time and outputs are valid @@ -232,9 +264,9 @@ class Laikad: p_initial_diag[GStates.ECEF_POS] = 1000 ** 2 self.gnss_kf.init_state(x_initial, covs_diag=p_initial_diag) - def fetch_orbits(self, t: GPSTime, block): - # Download new orbits if 1 hour of orbits data left - if t + SECS_IN_HR not in self.astro_dog.orbit_fetched_times and (self.last_fetch_orbits_t is None or abs(t - self.last_fetch_orbits_t) > SECS_IN_MIN): + def fetch_navs(self, t: GPSTime, block): + # Download new navs if 1 hour of navs data left + if t + SECS_IN_HR not in self.astro_dog.navs_fetched_times and (self.last_fetch_navs_t is None or abs(t - self.last_fetch_navs_t) > SECS_IN_MIN): astro_dog_vars = self.astro_dog.valid_const, self.astro_dog.auto_update, self.astro_dog.valid_ephem_types, self.astro_dog.cache_dir ret = None @@ -249,22 +281,22 @@ class Laikad: if ret is not None: if ret[0] is None: - self.last_fetch_orbits_t = ret[2] + self.last_fetch_navs_t = ret[2] else: - self.astro_dog.orbits, self.astro_dog.orbit_fetched_times, self.last_fetch_orbits_t = ret + self.astro_dog.navs, self.astro_dog.navs_fetched_times, self.last_fetch_navs_t = ret self.cache_ephemeris(t=t) def get_orbit_data(t: GPSTime, valid_const, auto_update, valid_ephem_types, cache_dir): astro_dog = AstroDog(valid_const=valid_const, auto_update=auto_update, valid_ephem_types=valid_ephem_types, cache_dir=cache_dir) - cloudlog.info(f"Start to download/parse orbits for time {t.as_datetime()}") + cloudlog.info(f"Start to download/parse navs for time {t.as_datetime()}") start_time = time.monotonic() try: - astro_dog.get_orbit_data(t, only_predictions=True) - cloudlog.info(f"Done parsing orbits. Took {time.monotonic() - start_time:.1f}s") - cloudlog.debug(f"Downloaded orbits ({sum([len(v) for v in astro_dog.orbits])}): {list(astro_dog.orbits.keys())}" + + astro_dog.get_navs(t) + cloudlog.info(f"Done parsing navs. Took {time.monotonic() - start_time:.1f}s") + cloudlog.debug(f"Downloaded navs ({sum([len(v) for v in astro_dog.navs])}): {list(astro_dog.navs.keys())}" + f"With time range: {[f'{start.as_datetime()}, {end.as_datetime()}' for (start,end) in astro_dog.orbit_fetched_times._ranges]}") - return astro_dog.orbits, astro_dog.orbit_fetched_times, t + return astro_dog.navs, astro_dog.navs_fetched_times, t except (DownloadFailed, RuntimeError, ValueError, IOError) as e: cloudlog.warning(f"No orbit data found or parsing failure: {e}") return None, None, t @@ -349,9 +381,31 @@ class EphemerisSourceType(IntEnum): qcom = 3 -def main(sm=None, pm=None): +def process_msg(laikad, gnss_msg, mono_time, block=False): + # TODO: Understand and use remaining unknown constellations + if gnss_msg.which() == "drMeasurementReport": + if getattr(gnss_msg, gnss_msg.which()).source not in ['glonass', 'gps', 'beidou', 'sbas']: + return None + + if getattr(gnss_msg, gnss_msg.which()).gpsWeek > np.iinfo(np.int16).max: + # gpsWeek 65535 is received rarely from quectel, this cannot be + # passed to GnssMeasurements's gpsWeek (Int16) + return None + + return laikad.process_gnss_msg(gnss_msg, mono_time, block=block) + + +def clear_tmp_cache(): + if os.path.exists(DOWNLOADS_CACHE_FOLDER): + shutil.rmtree(DOWNLOADS_CACHE_FOLDER) + os.mkdir(DOWNLOADS_CACHE_FOLDER) + + +def main(sm=None, pm=None, qc=None): + #clear_tmp_cache() + use_qcom = not Params().get_bool("UbloxAvailable", block=True) - if use_qcom: + if use_qcom or (qc is not None and qc): raw_gnss_socket = "qcomGnss" else: raw_gnss_socket = "ubloxGnss" @@ -361,9 +415,14 @@ def main(sm=None, pm=None): if pm is None: pm = messaging.PubMaster(['gnssMeasurements']) + # disable until set as main gps source, to better analyze startup time + use_internet = False #"LAIKAD_NO_INTERNET" not in os.environ + replay = "REPLAY" in os.environ - use_internet = "LAIKAD_NO_INTERNET" not in os.environ - laikad = Laikad(save_ephemeris=not replay, auto_fetch_orbits=use_internet, use_qcom=use_qcom) + if replay or "CI" in os.environ: + use_internet = True + + laikad = Laikad(save_ephemeris=not replay, auto_fetch_navs=use_internet, use_qcom=use_qcom) while True: sm.update() @@ -371,25 +430,17 @@ def main(sm=None, pm=None): if sm.updated[raw_gnss_socket]: gnss_msg = sm[raw_gnss_socket] - # TODO: Understand and use remaining unknown constellations - if gnss_msg.which() == "drMeasurementReport": - if getattr(gnss_msg, gnss_msg.which()).source not in ['glonass', 'gps', 'beidou', 'sbas']: - continue + msg = process_msg(laikad, gnss_msg, sm.logMonoTime[raw_gnss_socket], replay) + if msg is None: + # TODO: beautify this, locationd needs a valid message + msg = messaging.new_message("gnssMeasurements") + pm.send('gnssMeasurements', msg) - if getattr(gnss_msg, gnss_msg.which()).gpsWeek > np.iinfo(np.int16).max: - # gpsWeek 65535 is received rarely from quectel, this cannot be - # passed to GnssMeasurements's gpsWeek (Int16) - continue - - msg = laikad.process_gnss_msg(gnss_msg, sm.logMonoTime[raw_gnss_socket], block=replay) - if msg is not None: - pm.send('gnssMeasurements', msg) if not laikad.got_first_gnss_msg and sm.updated['clocks']: clocks_msg = sm['clocks'] t = GPSTime.from_datetime(datetime.utcfromtimestamp(clocks_msg.wallTimeNanos * 1E-9)) - if laikad.auto_fetch_orbits: - laikad.fetch_orbits(t, block=replay) - + if laikad.auto_fetch_navs: + laikad.fetch_navs(t, block=replay) if __name__ == "__main__": main() diff --git a/selfdrive/locationd/laikad_helpers.py b/selfdrive/locationd/laikad_helpers.py deleted file mode 100644 index f13e8e73b..000000000 --- a/selfdrive/locationd/laikad_helpers.py +++ /dev/null @@ -1,89 +0,0 @@ -import numpy as np -import sympy - -from laika.constants import EARTH_ROTATION_RATE, SPEED_OF_LIGHT -from laika.helpers import ConstellationId - - -def calc_pos_fix_gauss_newton(measurements, posfix_functions, x0=None, signal='C1C', min_measurements=6): - ''' - Calculates gps fix using gauss newton method - To solve the problem a minimal of 4 measurements are required. - If Glonass is included 5 are required to solve for the additional free variable. - returns: - 0 -> list with positions - ''' - if x0 is None: - x0 = [0, 0, 0, 0, 0] - n = len(measurements) - if n < min_measurements: - return [], [] - - Fx_pos = pr_residual(measurements, posfix_functions, signal=signal) - x = gauss_newton(Fx_pos, x0) - residual, _ = Fx_pos(x, weight=1.0) - return x.tolist(), residual.tolist() - - -def pr_residual(measurements, posfix_functions, signal='C1C'): - def Fx_pos(inp, weight=None): - vals, gradients = [], [] - - for meas in measurements: - pr = meas.observables[signal] - pr += meas.sat_clock_err * SPEED_OF_LIGHT - - w = (1 / meas.observables_std[signal]) if weight is None else weight - - val, *gradient = posfix_functions[meas.constellation_id](*inp, pr, *meas.sat_pos, w) - vals.append(val) - gradients.append(gradient) - return np.asarray(vals), np.asarray(gradients) - - return Fx_pos - - -def gauss_newton(fun, b, xtol=1e-8, max_n=25): - for _ in range(max_n): - # Compute function and jacobian on current estimate - r, J = fun(b) - - # Update estimate - delta = np.linalg.pinv(J) @ r - b -= delta - - # Check step size for stopping condition - if np.linalg.norm(delta) < xtol: - break - return b - - -def get_posfix_sympy_fun(constellation): - # Unknowns - x, y, z = sympy.Symbol('x'), sympy.Symbol('y'), sympy.Symbol('z') - bc = sympy.Symbol('bc') - bg = sympy.Symbol('bg') - var = [x, y, z, bc, bg] - - # Knowns - pr = sympy.Symbol('pr') - sat_x, sat_y, sat_z = sympy.Symbol('sat_x'), sympy.Symbol('sat_y'), sympy.Symbol('sat_z') - weight = sympy.Symbol('weight') - - theta = EARTH_ROTATION_RATE * (pr - bc) / SPEED_OF_LIGHT - val = sympy.sqrt( - (sat_x * sympy.cos(theta) + sat_y * sympy.sin(theta) - x) ** 2 + - (sat_y * sympy.cos(theta) - sat_x * sympy.sin(theta) - y) ** 2 + - (sat_z - z) ** 2 - ) - - if constellation == ConstellationId.GLONASS: - res = weight * (val - (pr - bc - bg)) - elif constellation == ConstellationId.GPS: - res = weight * (val - (pr - bc)) - else: - raise NotImplementedError(f"Constellation {constellation} not supported") - - res = [res] + [sympy.diff(res, v) for v in var] - - return sympy.lambdify([x, y, z, bc, bg, pr, sat_x, sat_y, sat_z, weight], res, modules=["numpy"]) diff --git a/selfdrive/locationd/locationd.cc b/selfdrive/locationd/locationd.cc index 4325900c0..307626506 100755 --- a/selfdrive/locationd/locationd.cc +++ b/selfdrive/locationd/locationd.cc @@ -25,8 +25,16 @@ const double MAX_FILTER_REWIND_TIME = 0.8; // s // TODO: GPS sensor time offsets are empirically calculated // They should be replaced with synced time from a real clock -const double GPS_LOCATION_SENSOR_TIME_OFFSET = 0.630; // s -const double GPS_LOCATION_EXTERNAL_SENSOR_TIME_OFFSET = 0.095; // s +const double GPS_QUECTEL_SENSOR_TIME_OFFSET = 0.630; // s +const double GPS_UBLOX_SENSOR_TIME_OFFSET = 0.095; // s +const float GPS_MUL_FACTOR = 10.0; +const float GPS_POS_STD_THRESHOLD = 50.0; +const float GPS_VEL_STD_THRESHOLD = 5.0; +const float GPS_POS_ERROR_RESET_THRESHOLD = 300.0; +const float GPS_POS_STD_RESET_THRESHOLD = 2.0; +const float GPS_VEL_STD_RESET_THRESHOLD = 0.5; +const float GPS_ORIENTATION_ERROR_RESET_THRESHOLD = 1.0; +const int GPS_ORIENTATION_ERROR_RESET_CNT = 3; static VectorXd floatlist2vector(const capnp::List::Reader& floatlist) { VectorXd res(floatlist.size()); @@ -77,6 +85,8 @@ Localizer::Localizer() { this->converter = std::make_unique((ECEF) { .x = ecef_pos[0], .y = ecef_pos[1], .z = ecef_pos[2] }); } +Localizer::Localizer(bool has_ublox) : Localizer() { ublox_available = has_ublox; } + void Localizer::build_live_location(cereal::LiveLocationKalman::Builder& fix) { VectorXd predicted_state = this->kf->get_x(); MatrixXdr predicted_cov = this->kf->get_P(); @@ -220,7 +230,7 @@ void Localizer::handle_sensor(double current_time, const cereal::SensorEventData } double sensor_time = 1e-9 * log.getTimestamp(); - + // sensor time and log time should be close if (std::abs(current_time - sensor_time) > 0.1) { LOGE("Sensor reading ignored, sensor timestamp more than 100ms off from log time"); @@ -301,7 +311,7 @@ void Localizer::handle_gps(double current_time, const cereal::GpsLocationData::R } if (gps_invalid_flag || gps_unreasonable || gps_accuracy_insane || gps_lat_lng_alt_insane || gps_vel_insane || gps_accuracy_insane_quectel) { - this->gps_valid = false; + //this->gps_valid = false; this->determine_gps_mode(current_time); return; } @@ -309,15 +319,15 @@ void Localizer::handle_gps(double current_time, const cereal::GpsLocationData::R double sensor_time = current_time - sensor_time_offset; // Process message - this->gps_valid = true; + //this->gps_valid = true; this->gps_mode = true; Geodetic geodetic = { log.getLatitude(), log.getLongitude(), log.getAltitude() }; this->converter = std::make_unique(geodetic); VectorXd ecef_pos = this->converter->ned2ecef({ 0.0, 0.0, 0.0 }).to_vector(); VectorXd ecef_vel = this->converter->ned2ecef({ log.getVNED()[0], log.getVNED()[1], log.getVNED()[2] }).to_vector() - ecef_pos; - MatrixXdr ecef_pos_R = Vector3d::Constant(std::pow(10.0 * log.getAccuracy(),2) + std::pow(10.0 * log.getVerticalAccuracy(),2)).asDiagonal(); - MatrixXdr ecef_vel_R = Vector3d::Constant(std::pow(log.getSpeedAccuracy() * 10.0, 2)).asDiagonal(); + MatrixXdr ecef_pos_R = Vector3d::Constant(std::pow(GPS_MUL_FACTOR * log.getAccuracy(),2) + std::pow(GPS_MUL_FACTOR * log.getVerticalAccuracy(),2)).asDiagonal(); + MatrixXdr ecef_vel_R = Vector3d::Constant(std::pow(GPS_MUL_FACTOR * log.getSpeedAccuracy(), 2)).asDiagonal(); this->unix_timestamp_millis = log.getUnixTimestampMillis(); double gps_est_error = (this->kf->get_x().segment(STATE_ECEF_POS_START) - ecef_pos).norm(); @@ -344,13 +354,96 @@ void Localizer::handle_gps(double current_time, const cereal::GpsLocationData::R this->reset_kalman(NAN, initial_pose_ecef_quat, ecef_pos, ecef_vel, ecef_pos_R, ecef_vel_R); } + this->last_gps_msg = sensor_time; + this->kf->predict_and_observe(sensor_time, OBSERVATION_ECEF_POS, { ecef_pos }, { ecef_pos_R }); + this->kf->predict_and_observe(sensor_time, OBSERVATION_ECEF_VEL, { ecef_vel }, { ecef_vel_R }); +} + +void Localizer::handle_gnss(double current_time, const cereal::GnssMeasurements::Reader& log) { + + if(!log.getPositionECEF().getValid() || !log.getVelocityECEF().getValid()) { + this->determine_gps_mode(current_time); + return; + } + + double sensor_time = log.getMeasTime() * 1e-9; + if (ublox_available) + sensor_time -= GPS_UBLOX_SENSOR_TIME_OFFSET; + else + sensor_time -= GPS_QUECTEL_SENSOR_TIME_OFFSET; + + auto ecef_pos_v = log.getPositionECEF().getValue(); + VectorXd ecef_pos = Vector3d(ecef_pos_v[0], ecef_pos_v[1], ecef_pos_v[2]); + + // indexed at 0 cause all std values are the same MAE + auto ecef_pos_std = log.getPositionECEF().getStd()[0]; + MatrixXdr ecef_pos_R = Vector3d::Constant(pow(GPS_MUL_FACTOR*ecef_pos_std, 2)).asDiagonal(); + + auto ecef_vel_v = log.getVelocityECEF().getValue(); + VectorXd ecef_vel = Vector3d(ecef_vel_v[0], ecef_vel_v[1], ecef_vel_v[2]); + + // indexed at 0 cause all std values are the same MAE + auto ecef_vel_std = log.getVelocityECEF().getStd()[0]; + MatrixXdr ecef_vel_R = Vector3d::Constant(pow(GPS_MUL_FACTOR*ecef_vel_std, 2)).asDiagonal(); + + double gps_est_error = (this->kf->get_x().segment(STATE_ECEF_POS_START) - ecef_pos).norm(); + + VectorXd orientation_ecef = quat2euler(vector2quat(this->kf->get_x().segment(STATE_ECEF_ORIENTATION_START))); + VectorXd orientation_ned = ned_euler_from_ecef({ ecef_pos[0], ecef_pos[1], ecef_pos[2] }, orientation_ecef); + + LocalCoord convs((ECEF){ .x = ecef_pos[0], .y = ecef_pos[1], .z = ecef_pos[2] }); + ECEF next_ecef = {.x = ecef_pos[0] + ecef_vel[0], .y = ecef_pos[1] + ecef_vel[1], .z = ecef_pos[2] + ecef_vel[2]}; + VectorXd ned_vel = convs.ecef2ned(next_ecef).to_vector(); + double bearing_rad = atan2(ned_vel[1], ned_vel[0]); + + VectorXd orientation_ned_gps = Vector3d(0.0, 0.0, bearing_rad); + VectorXd orientation_error = (orientation_ned - orientation_ned_gps).array() - M_PI; + for (int i = 0; i < orientation_error.size(); i++) { + orientation_error(i) = std::fmod(orientation_error(i), 2.0 * M_PI); + if (orientation_error(i) < 0.0) { + orientation_error(i) += 2.0 * M_PI; + } + orientation_error(i) -= M_PI; + } + VectorXd initial_pose_ecef_quat = quat2vector(euler2quat(ecef_euler_from_ned({ ecef_pos(0), ecef_pos(1), ecef_pos(2) }, orientation_ned_gps))); + + if (ecef_pos_std > GPS_POS_STD_THRESHOLD || ecef_vel_std > GPS_VEL_STD_THRESHOLD) { + this->determine_gps_mode(current_time); + return; + } + + // prevent jumping gnss measurements (covered lots, standstill...) + bool orientation_reset = ecef_vel_std < GPS_VEL_STD_RESET_THRESHOLD; + orientation_reset &= orientation_error.norm() > GPS_ORIENTATION_ERROR_RESET_THRESHOLD; + orientation_reset &= !this->standstill; + if (orientation_reset) { + this->orientation_reset_count++; + } + else { + this->orientation_reset_count = 0; + } + + if ((gps_est_error > GPS_POS_ERROR_RESET_THRESHOLD && ecef_pos_std < GPS_POS_STD_RESET_THRESHOLD) || this->last_gps_msg == 0) { + // always reset on first gps message and if the location is off but the accuracy is high + LOGE("Locationd vs gnssMeasurement position difference too large, kalman reset"); + this->reset_kalman(NAN, initial_pose_ecef_quat, ecef_pos, ecef_vel, ecef_pos_R, ecef_vel_R); + } else if (orientation_reset_count > GPS_ORIENTATION_ERROR_RESET_CNT) { + LOGE("Locationd vs gnssMeasurement orientation difference too large, kalman reset"); + this->reset_kalman(NAN, initial_pose_ecef_quat, ecef_pos, ecef_vel, ecef_pos_R, ecef_vel_R); + this->kf->predict_and_observe(sensor_time, OBSERVATION_ECEF_ORIENTATION_FROM_GPS, { initial_pose_ecef_quat }); + this->orientation_reset_count = 0; + } + + this->gps_mode = true; + this->last_gps_msg = sensor_time; this->kf->predict_and_observe(sensor_time, OBSERVATION_ECEF_POS, { ecef_pos }, { ecef_pos_R }); this->kf->predict_and_observe(sensor_time, OBSERVATION_ECEF_VEL, { ecef_vel }, { ecef_vel_R }); } void Localizer::handle_car_state(double current_time, const cereal::CarState::Reader& log) { this->car_speed = std::abs(log.getVEgo()); - if (log.getStandstill()) { + this->standstill = log.getStandstill(); + if (this->standstill) { this->kf->predict_and_observe(current_time, OBSERVATION_NO_ROT, { Vector3d(0.0, 0.0, 0.0) }); this->kf->predict_and_observe(current_time, OBSERVATION_NO_ACCEL, { Vector3d(0.0, 0.0, 0.0) }); } @@ -359,7 +452,7 @@ void Localizer::handle_car_state(double current_time, const cereal::CarState::Re void Localizer::handle_cam_odo(double current_time, const cereal::CameraOdometry::Reader& log) { VectorXd rot_device = this->device_from_calib * floatlist2vector(log.getRot()); VectorXd trans_device = this->device_from_calib * floatlist2vector(log.getTrans()); - + if (!this->is_timestamp_valid(current_time)) { this->observation_timings_invalid = true; return; @@ -497,9 +590,11 @@ void Localizer::handle_msg(const cereal::Event::Reader& log) { } else if (log.isGyroscope()) { this->handle_sensor(t, log.getGyroscope()); } else if (log.isGpsLocation()) { - this->handle_gps(t, log.getGpsLocation(), GPS_LOCATION_SENSOR_TIME_OFFSET); + this->handle_gps(t, log.getGpsLocation(), GPS_QUECTEL_SENSOR_TIME_OFFSET); } else if (log.isGpsLocationExternal()) { - this->handle_gps(t, log.getGpsLocationExternal(), GPS_LOCATION_EXTERNAL_SENSOR_TIME_OFFSET); + this->handle_gps(t, log.getGpsLocationExternal(), GPS_UBLOX_SENSOR_TIME_OFFSET); + //} else if (log.isGnssMeasurements()) { + // this->handle_gnss(t, log.getGnssMeasurements()); } else if (log.isCarState()) { this->handle_car_state(t, log.getCarState()); } else if (log.isCameraOdometry()) { @@ -524,7 +619,7 @@ kj::ArrayPtr Localizer::get_message_bytes(MessageBuilder& msg_build } bool Localizer::is_gps_ok() { - return this->gps_valid; + return (this->kf->get_filter_time() - this->last_gps_msg) < 2.0; } bool Localizer::critical_services_valid(std::map critical_services) { @@ -536,7 +631,7 @@ bool Localizer::critical_services_valid(std::map critical_s return true; } -bool Localizer::is_timestamp_valid(double current_time) { +bool Localizer::is_timestamp_valid(double current_time) { double filter_time = this->kf->get_filter_time(); if (!std::isnan(filter_time) && ((filter_time - current_time) > MAX_FILTER_REWIND_TIME)) { LOGE("Observation timestamp is older than the max rewind threshold of the filter"); @@ -563,19 +658,18 @@ void Localizer::determine_gps_mode(double current_time) { int Localizer::locationd_thread() { ublox_available = Params().getBool("UbloxAvailable", true); - const char* gps_location_socket; if (ublox_available) { gps_location_socket = "gpsLocationExternal"; } else { gps_location_socket = "gpsLocation"; } - const std::initializer_list service_list = {gps_location_socket, "cameraOdometry", "liveCalibration", + const std::initializer_list service_list = {gps_location_socket, "cameraOdometry", "liveCalibration", "carState", "carParams", "accelerometer", "gyroscope"}; - PubMaster pm({"liveLocationKalman"}); // TODO: remove carParams once we're always sending at 100Hz SubMaster sm(service_list, {}, nullptr, {gps_location_socket, "carParams"}); + PubMaster pm({"liveLocationKalman"}); uint64_t cnt = 0; bool filterInitialized = false; diff --git a/selfdrive/locationd/locationd.h b/selfdrive/locationd/locationd.h index f0872d9f5..a292a3c93 100755 --- a/selfdrive/locationd/locationd.h +++ b/selfdrive/locationd/locationd.h @@ -24,6 +24,7 @@ class Localizer { public: Localizer(); + Localizer(bool has_ublox); int locationd_thread(); @@ -52,6 +53,7 @@ public: void handle_msg(const cereal::Event::Reader& log); void handle_sensor(double current_time, const cereal::SensorEventData::Reader& log); void handle_gps(double current_time, const cereal::GpsLocationData::Reader& log, const double sensor_time_offset); + void handle_gnss(double current_time, const cereal::GnssMeasurements::Reader& log); void handle_car_state(double current_time, const cereal::CarState::Reader& log); void handle_cam_odo(double current_time, const cereal::CameraOdometry::Reader& log); void handle_live_calib(double current_time, const cereal::LiveCalibrationData::Reader& log); @@ -76,8 +78,10 @@ private: double reset_tracker = 0.0; bool device_fell = false; bool gps_mode = false; - bool gps_valid = false; + double last_gps_msg = 0; bool ublox_available = true; bool observation_timings_invalid = false; - std::map observation_values_invalid; + std::map observation_values_invalid; + bool standstill = true; + int32_t orientation_reset_count = 0; }; diff --git a/selfdrive/locationd/paramsd.py b/selfdrive/locationd/paramsd.py index 9bd4ed083..7e30b1e3a 100755 --- a/selfdrive/locationd/paramsd.py +++ b/selfdrive/locationd/paramsd.py @@ -14,8 +14,9 @@ from system.swaglog import cloudlog MAX_ANGLE_OFFSET_DELTA = 20 * DT_MDL # Max 20 deg/s -ROLL_MAX_DELTA = np.radians(20.0) * DT_MDL # 20deg in 1 second is well within curvature limits +ROLL_MAX_DELTA = math.radians(20.0) * DT_MDL # 20deg in 1 second is well within curvature limits ROLL_MIN, ROLL_MAX = math.radians(-10), math.radians(10) +ROLL_STD_MAX = math.radians(1.5) LATERAL_ACC_SENSOR_THRESHOLD = 4.0 @@ -36,10 +37,8 @@ class ParamsLearner: self.yaw_rate = 0.0 self.yaw_rate_std = 0.0 self.roll = 0.0 - self.steering_pressed = False self.steering_angle = 0.0 - - self.valid = True + self.roll_valid = False def handle_log(self, t, which, msg): if which == 'liveLocationKalman': @@ -48,8 +47,8 @@ class ParamsLearner: localizer_roll = msg.orientationNED.value[0] localizer_roll_std = np.radians(1) if np.isnan(msg.orientationNED.std[0]) else msg.orientationNED.std[0] - roll_valid = msg.orientationNED.valid and ROLL_MIN < localizer_roll < ROLL_MAX - if roll_valid: + self.roll_valid = (localizer_roll_std < ROLL_STD_MAX) and (ROLL_MIN < localizer_roll < ROLL_MAX) and msg.sensorsOK + if self.roll_valid: roll = localizer_roll # Experimentally found multiplier of 2 to be best trade-off between stability and accuracy or similar? roll_std = 2 * localizer_roll_std @@ -88,10 +87,9 @@ class ParamsLearner: elif which == 'carState': self.steering_angle = msg.steeringAngleDeg - self.steering_pressed = msg.steeringPressed self.speed = msg.vEgo - in_linear_region = abs(self.steering_angle) < 45 or not self.steering_pressed + in_linear_region = abs(self.steering_angle) < 45 self.active = self.speed > 1 and in_linear_region if self.active: @@ -158,6 +156,7 @@ def main(sm=None, pm=None): learner = ParamsLearner(CP, params['steerRatio'], params['stiffnessFactor'], math.radians(params['angleOffsetAverageDeg'])) angle_offset_average = params['angleOffsetAverageDeg'] angle_offset = angle_offset_average + roll = 0.0 while True: sm.update() @@ -177,6 +176,8 @@ def main(sm=None, pm=None): angle_offset_average = clip(math.degrees(x[States.ANGLE_OFFSET]), angle_offset_average - MAX_ANGLE_OFFSET_DELTA, angle_offset_average + MAX_ANGLE_OFFSET_DELTA) angle_offset = clip(math.degrees(x[States.ANGLE_OFFSET] + x[States.ANGLE_OFFSET_FAST]), angle_offset - MAX_ANGLE_OFFSET_DELTA, angle_offset + MAX_ANGLE_OFFSET_DELTA) + roll = clip(float(x[States.ROAD_ROLL]), roll - ROLL_MAX_DELTA, roll + ROLL_MAX_DELTA) + roll_std = float(P[States.ROAD_ROLL]) # Account for the opposite signs of the yaw rates sensors_valid = bool(abs(learner.speed * (x[States.YAW_RATE] + learner.yaw_rate)) < LATERAL_ACC_SENSOR_THRESHOLD) @@ -187,12 +188,14 @@ def main(sm=None, pm=None): liveParameters.sensorValid = sensors_valid liveParameters.steerRatio = float(x[States.STEER_RATIO]) liveParameters.stiffnessFactor = float(x[States.STIFFNESS]) - liveParameters.roll = float(x[States.ROAD_ROLL]) + liveParameters.roll = roll liveParameters.angleOffsetAverageDeg = angle_offset_average liveParameters.angleOffsetDeg = angle_offset liveParameters.valid = all(( abs(liveParameters.angleOffsetAverageDeg) < 10.0, abs(liveParameters.angleOffsetDeg) < 10.0, + abs(liveParameters.roll) < ROLL_MAX, + roll_std < ROLL_STD_MAX, 0.2 <= liveParameters.stiffnessFactor <= 5.0, min_sr <= liveParameters.steerRatio <= max_sr, )) diff --git a/selfdrive/locationd/torqued.py b/selfdrive/locationd/torqued.py index 588bca157..fcc068d34 100755 --- a/selfdrive/locationd/torqued.py +++ b/selfdrive/locationd/torqued.py @@ -22,15 +22,15 @@ FIT_POINTS_TOTAL_QLOG = 600 MIN_VEL = 15 # m/s FRICTION_FACTOR = 1.5 # ~85% of data coverage FACTOR_SANITY = 0.3 +FACTOR_SANITY_QLOG = 0.5 FRICTION_SANITY = 0.5 +FRICTION_SANITY_QLOG = 0.8 STEER_MIN_THRESHOLD = 0.02 MIN_FILTER_DECAY = 50 MAX_FILTER_DECAY = 250 LAT_ACC_THRESHOLD = 1 STEER_BUCKET_BOUNDS = [(-0.5, -0.3), (-0.3, -0.2), (-0.2, -0.1), (-0.1, 0), (0, 0.1), (0.1, 0.2), (0.2, 0.3), (0.3, 0.5)] MIN_BUCKET_POINTS = np.array([100, 300, 500, 500, 500, 500, 300, 100]) -MAX_RESETS = 5.0 -MAX_INVALID_THRESHOLD = 10 MIN_ENGAGE_BUFFER = 2 # secs VERSION = 1 # bump this to invalidate old parameter caches @@ -100,10 +100,15 @@ class TorqueEstimator: self.min_bucket_points = MIN_BUCKET_POINTS / 10 self.min_points_total = MIN_POINTS_TOTAL_QLOG self.fit_points = FIT_POINTS_TOTAL_QLOG + self.factor_sanity = FACTOR_SANITY_QLOG + self.friction_sanity = FRICTION_SANITY_QLOG + else: self.min_bucket_points = MIN_BUCKET_POINTS self.min_points_total = MIN_POINTS_TOTAL self.fit_points = FIT_POINTS_TOTAL + self.factor_sanity = FACTOR_SANITY + self.friction_sanity = FRICTION_SANITY self.offline_friction = 0.0 self.offline_latAccelFactor = 0.0 @@ -123,10 +128,10 @@ class TorqueEstimator: 'points': [] } self.decay = MIN_FILTER_DECAY - self.min_lataccel_factor = (1.0 - FACTOR_SANITY) * self.offline_latAccelFactor - self.max_lataccel_factor = (1.0 + FACTOR_SANITY) * self.offline_latAccelFactor - self.min_friction = (1.0 - FRICTION_SANITY) * self.offline_friction - self.max_friction = (1.0 + FRICTION_SANITY) * self.offline_friction + self.min_lataccel_factor = (1.0 - self.factor_sanity) * self.offline_latAccelFactor + self.max_lataccel_factor = (1.0 + self.factor_sanity) * self.offline_latAccelFactor + self.min_friction = (1.0 - self.friction_sanity) * self.offline_friction + self.max_friction = (1.0 + self.friction_sanity) * self.offline_friction # try to restore cached params params = Params() @@ -165,7 +170,6 @@ class TorqueEstimator: def reset(self): self.resets += 1.0 - self.invalid_values_tracker = 0.0 self.decay = MIN_FILTER_DECAY self.raw_points = defaultdict(lambda: deque(maxlen=self.hist_len)) self.filtered_points = PointBuckets(x_bounds=STEER_BUCKET_BOUNDS, min_points=self.min_bucket_points, min_points_total=self.min_points_total) @@ -190,12 +194,6 @@ class TorqueEstimator: self.filtered_params[param].update(value) self.filtered_params[param].update_alpha(self.decay) - def is_sane(self, latAccelFactor, latAccelOffset, friction): - if any([val is None or np.isnan(val) for val in [latAccelFactor, latAccelOffset, friction]]): - return False - return (self.max_friction >= friction >= self.min_friction) and\ - (self.max_lataccel_factor >= latAccelFactor >= self.min_lataccel_factor) - def handle_log(self, t, which, msg): if which == "carControl": self.raw_points["carControl_t"].append(t + self.lag) @@ -225,23 +223,20 @@ class TorqueEstimator: liveTorqueParameters.useParams = self.use_params if self.filtered_points.is_valid(): - latAccelFactor, latAccelOffset, friction_coeff = self.estimate_params() + latAccelFactor, latAccelOffset, frictionCoeff = self.estimate_params() liveTorqueParameters.latAccelFactorRaw = float(latAccelFactor) liveTorqueParameters.latAccelOffsetRaw = float(latAccelOffset) - liveTorqueParameters.frictionCoefficientRaw = float(friction_coeff) + liveTorqueParameters.frictionCoefficientRaw = float(frictionCoeff) - if self.is_sane(latAccelFactor, latAccelOffset, friction_coeff): - liveTorqueParameters.liveValid = True - self.update_params({'latAccelFactor': latAccelFactor, 'latAccelOffset': latAccelOffset, 'frictionCoefficient': friction_coeff}) - self.invalid_values_tracker = max(0.0, self.invalid_values_tracker - 0.5) - else: - cloudlog.exception("Live torque parameters are outside acceptable bounds.") + if any([val is None or np.isnan(val) for val in [latAccelFactor, latAccelOffset, frictionCoeff]]): + cloudlog.exception("Live torque parameters are invalid.") liveTorqueParameters.liveValid = False - self.invalid_values_tracker += 1.0 - # Reset when ~10 invalid over 5 secs - if self.invalid_values_tracker > MAX_INVALID_THRESHOLD: - # Do not reset the filter as it may cause a drastic jump, just reset points - self.reset() + self.reset() + else: + liveTorqueParameters.liveValid = True + latAccelFactor = np.clip(latAccelFactor, self.min_lataccel_factor, self.max_lataccel_factor) + frictionCoeff = np.clip(frictionCoeff, self.min_friction, self.max_friction) + self.update_params({'latAccelFactor': latAccelFactor, 'latAccelOffset': latAccelOffset, 'frictionCoefficient': frictionCoeff}) else: liveTorqueParameters.liveValid = False diff --git a/selfdrive/locationd/ublox_msg.cc b/selfdrive/locationd/ublox_msg.cc index b45dffae3..b74698946 100644 --- a/selfdrive/locationd/ublox_msg.cc +++ b/selfdrive/locationd/ublox_msg.cc @@ -65,6 +65,21 @@ inline bool UbloxMsgParser::valid_so_far() { return true; } +inline uint16_t UbloxMsgParser::get_glonass_year(uint8_t N4, uint16_t Nt) { + // convert time to year (conversion from A3.1.3) + int J = 0; + if (1 <= Nt && Nt <= 366) { + J = 1; + } else if (367 <= Nt && Nt <= 731) { + J = 2; + } else if (732 <= Nt && Nt <= 1096) { + J = 3; + } else if (1097 <= Nt && Nt <= 1461) { + J = 4; + } + uint16_t year = 1996 + 4*(N4 -1) + (J - 1); + return year; +} bool UbloxMsgParser::add_data(const uint8_t *incoming_data, uint32_t incoming_data_len, size_t &bytes_consumed) { int needed = needed_bytes(); @@ -103,9 +118,9 @@ std::pair> UbloxMsgParser::gen_msg() { switch (ubx_message.msg_type()) { case 0x0107: return {"gpsLocationExternal", gen_nav_pvt(static_cast(body))}; - case 0x0213: + case 0x0213: // UBX-RXM-SFRB (Broadcast Navigation Data Subframe) return {"ubloxGnss", gen_rxm_sfrbx(static_cast(body))}; - case 0x0215: + case 0x0215: // UBX-RXM-RAW (Multi-GNSS Raw Measurement Data) return {"ubloxGnss", gen_rxm_rawx(static_cast(body))}; case 0x0a09: return {"ubloxGnss", gen_mon_hw(static_cast(body))}; @@ -147,115 +162,256 @@ kj::Array UbloxMsgParser::gen_nav_pvt(ubx_t::nav_pvt_t *msg) { return capnp::messageToFlatArray(msg_builder); } - -kj::Array UbloxMsgParser::gen_rxm_sfrbx(ubx_t::rxm_sfrbx_t *msg) { +kj::Array UbloxMsgParser::parse_gps_ephemeris(ubx_t::rxm_sfrbx_t *msg) { + // GPS subframes are packed into 10x 4 bytes, each containing 3 actual bytes + // We will first need to separate the data from the padding and parity auto body = *msg->body(); + assert(body.size() == 10); - if (msg->gnss_id() == ubx_t::gnss_type_t::GNSS_TYPE_GPS) { - // GPS subframes are packed into 10x 4 bytes, each containing 3 actual bytes - // We will first need to separate the data from the padding and parity - assert(body.size() == 10); + std::string subframe_data; + subframe_data.reserve(30); + for (uint32_t word : body) { + word = word >> 6; // TODO: Verify parity + subframe_data.push_back(word >> 16); + subframe_data.push_back(word >> 8); + subframe_data.push_back(word >> 0); + } - std::string subframe_data; - subframe_data.reserve(30); - for (uint32_t word : body) { - word = word >> 6; // TODO: Verify parity - subframe_data.push_back(word >> 16); - subframe_data.push_back(word >> 8); - subframe_data.push_back(word >> 0); + // Collect subframes in map and parse when we have all the parts + { + kaitai::kstream stream(subframe_data); + gps_t subframe(&stream); + + int subframe_id = subframe.how()->subframe_id(); + if (subframe_id > 3) { + // dont parse almanac subframes + return kj::Array(); } + gps_subframes[msg->sv_id()][subframe_id] = subframe_data; + } - // Collect subframes in map and parse when we have all the parts + // publish if subframes 1-3 have been collected + if (gps_subframes[msg->sv_id()].size() == 3) { + MessageBuilder msg_builder; + auto eph = msg_builder.initEvent().initUbloxGnss().initEphemeris(); + eph.setSvId(msg->sv_id()); + + int iode_s2 = 0; + int iode_s3 = 0; + int iodc_lsb = 0; + + // Subframe 1 { - kaitai::kstream stream(subframe_data); + kaitai::kstream stream(gps_subframes[msg->sv_id()][1]); gps_t subframe(&stream); - int subframe_id = subframe.how()->subframe_id(); + gps_t::subframe_1_t* subframe_1 = static_cast(subframe.body()); - if (subframe_id == 1) gps_subframes[msg->sv_id()].clear(); - gps_subframes[msg->sv_id()][subframe_id] = subframe_data; + eph.setGpsWeek(subframe_1->week_no()); + eph.setTgd(subframe_1->t_gd() * pow(2, -31)); + eph.setToc(subframe_1->t_oc() * pow(2, 4)); + eph.setAf2(subframe_1->af_2() * pow(2, -55)); + eph.setAf1(subframe_1->af_1() * pow(2, -43)); + eph.setAf0(subframe_1->af_0() * pow(2, -31)); + eph.setSvHealth(subframe_1->sv_health()); + eph.setTowCount(subframe.how()->tow_count()); + iodc_lsb = subframe_1->iodc_lsb(); } - if (gps_subframes[msg->sv_id()].size() == 5) { - MessageBuilder msg_builder; - auto eph = msg_builder.initEvent().initUbloxGnss().initEphemeris(); - eph.setSvId(msg->sv_id()); + // Subframe 2 + { + kaitai::kstream stream(gps_subframes[msg->sv_id()][2]); + gps_t subframe(&stream); + gps_t::subframe_2_t* subframe_2 = static_cast(subframe.body()); - // Subframe 1 - { - kaitai::kstream stream(gps_subframes[msg->sv_id()][1]); - gps_t subframe(&stream); - gps_t::subframe_1_t* subframe_1 = static_cast(subframe.body()); - - eph.setGpsWeek(subframe_1->week_no()); - eph.setTgd(subframe_1->t_gd() * pow(2, -31)); - eph.setToc(subframe_1->t_oc() * pow(2, 4)); - eph.setAf2(subframe_1->af_2() * pow(2, -55)); - eph.setAf1(subframe_1->af_1() * pow(2, -43)); - eph.setAf0(subframe_1->af_0() * pow(2, -31)); - } - - // Subframe 2 - { - kaitai::kstream stream(gps_subframes[msg->sv_id()][2]); - gps_t subframe(&stream); - gps_t::subframe_2_t* subframe_2 = static_cast(subframe.body()); - - eph.setCrs(subframe_2->c_rs() * pow(2, -5)); - eph.setDeltaN(subframe_2->delta_n() * pow(2, -43) * gpsPi); - eph.setM0(subframe_2->m_0() * pow(2, -31) * gpsPi); - eph.setCuc(subframe_2->c_uc() * pow(2, -29)); - eph.setEcc(subframe_2->e() * pow(2, -33)); - eph.setCus(subframe_2->c_us() * pow(2, -29)); - eph.setA(pow(subframe_2->sqrt_a() * pow(2, -19), 2.0)); - eph.setToe(subframe_2->t_oe() * pow(2, 4)); - } - - // Subframe 3 - { - kaitai::kstream stream(gps_subframes[msg->sv_id()][3]); - gps_t subframe(&stream); - gps_t::subframe_3_t* subframe_3 = static_cast(subframe.body()); - - eph.setCic(subframe_3->c_ic() * pow(2, -29)); - eph.setOmega0(subframe_3->omega_0() * pow(2, -31) * gpsPi); - eph.setCis(subframe_3->c_is() * pow(2, -29)); - eph.setI0(subframe_3->i_0() * pow(2, -31) * gpsPi); - eph.setCrc(subframe_3->c_rc() * pow(2, -5)); - eph.setOmega(subframe_3->omega() * pow(2, -31) * gpsPi); - eph.setOmegaDot(subframe_3->omega_dot() * pow(2, -43) * gpsPi); - eph.setIode(subframe_3->iode()); - eph.setIDot(subframe_3->idot() * pow(2, -43) * gpsPi); - } - - // Subframe 4 - { - kaitai::kstream stream(gps_subframes[msg->sv_id()][4]); - gps_t subframe(&stream); - gps_t::subframe_4_t* subframe_4 = static_cast(subframe.body()); - - // This is page 18, why is the page id 56? - if (subframe_4->data_id() == 1 && subframe_4->page_id() == 56) { - auto iono = static_cast(subframe_4->body()); - double a0 = iono->a0() * pow(2, -30); - double a1 = iono->a1() * pow(2, -27); - double a2 = iono->a2() * pow(2, -24); - double a3 = iono->a3() * pow(2, -24); - eph.setIonoAlpha({a0, a1, a2, a3}); - - double b0 = iono->b0() * pow(2, 11); - double b1 = iono->b1() * pow(2, 14); - double b2 = iono->b2() * pow(2, 16); - double b3 = iono->b3() * pow(2, 16); - eph.setIonoBeta({b0, b1, b2, b3}); - } - } - - return capnp::messageToFlatArray(msg_builder); + eph.setCrs(subframe_2->c_rs() * pow(2, -5)); + eph.setDeltaN(subframe_2->delta_n() * pow(2, -43) * gpsPi); + eph.setM0(subframe_2->m_0() * pow(2, -31) * gpsPi); + eph.setCuc(subframe_2->c_uc() * pow(2, -29)); + eph.setEcc(subframe_2->e() * pow(2, -33)); + eph.setCus(subframe_2->c_us() * pow(2, -29)); + eph.setA(pow(subframe_2->sqrt_a() * pow(2, -19), 2.0)); + eph.setToe(subframe_2->t_oe() * pow(2, 4)); + iode_s2 = subframe_2->iode(); } + + // Subframe 3 + { + kaitai::kstream stream(gps_subframes[msg->sv_id()][3]); + gps_t subframe(&stream); + gps_t::subframe_3_t* subframe_3 = static_cast(subframe.body()); + + eph.setCic(subframe_3->c_ic() * pow(2, -29)); + eph.setOmega0(subframe_3->omega_0() * pow(2, -31) * gpsPi); + eph.setCis(subframe_3->c_is() * pow(2, -29)); + eph.setI0(subframe_3->i_0() * pow(2, -31) * gpsPi); + eph.setCrc(subframe_3->c_rc() * pow(2, -5)); + eph.setOmega(subframe_3->omega() * pow(2, -31) * gpsPi); + eph.setOmegaDot(subframe_3->omega_dot() * pow(2, -43) * gpsPi); + eph.setIode(subframe_3->iode()); + eph.setIDot(subframe_3->idot() * pow(2, -43) * gpsPi); + iode_s3 = subframe_3->iode(); + } + + gps_subframes[msg->sv_id()].clear(); + if (iodc_lsb != iode_s2 || iodc_lsb != iode_s3) { + // data set cutover, reject ephemeris + return kj::Array(); + } + return capnp::messageToFlatArray(msg_builder); } return kj::Array(); } +kj::Array UbloxMsgParser::parse_glonass_ephemeris(ubx_t::rxm_sfrbx_t *msg) { + if (msg->sv_id() == 255) { + // data can be decoded before identifying the SV number, in this case 255 + // is returned, which means "unknown" (ublox p32) + return kj::Array(); + } + + auto body = *msg->body(); + assert(body.size() == 4); + { + std::string string_data; + string_data.reserve(16); + for (uint32_t word : body) { + for (int i = 3; i >= 0; i--) + string_data.push_back(word >> 8*i); + } + + kaitai::kstream stream(string_data); + glonass_t gl_string(&stream); + + int string_number = gl_string.string_number(); + if (string_number > 5 || gl_string.idle_chip()) { + // dont parse non immediate data, idle_chip == 0 + return kj::Array(); + } + + // immediate data is the same within one superframe + if (glonass_superframes[msg->sv_id()] != gl_string.superframe_number()) { + glonass_strings[msg->sv_id()].clear(); + glonass_superframes[msg->sv_id()] = gl_string.superframe_number(); + } + glonass_strings[msg->sv_id()][string_number] = string_data; + } + + // publish if strings 1-5 have been collected + if (glonass_strings[msg->sv_id()].size() != 5) { + return kj::Array(); + } + + MessageBuilder msg_builder; + auto eph = msg_builder.initEvent().initUbloxGnss().initGlonassEphemeris(); + eph.setSvId(msg->sv_id()); + uint16_t current_day = 0; + + // string number 1 + { + kaitai::kstream stream(glonass_strings[msg->sv_id()][1]); + glonass_t gl_stream(&stream); + glonass_t::string_1_t* data = static_cast(gl_stream.data()); + + eph.setP1(data->p1()); + eph.setTk(data->t_k()); + eph.setXVel(data->x_vel() * pow(2, -20)); + eph.setXAccel(data->x_accel() * pow(2, -30)); + eph.setX(data->x() * pow(2, -11)); + } + + // string number 2 + { + kaitai::kstream stream(glonass_strings[msg->sv_id()][2]); + glonass_t gl_stream(&stream); + glonass_t::string_2_t* data = static_cast(gl_stream.data()); + + eph.setSvHealth(data->b_n()>>2); // MSB indicates health + eph.setP2(data->p2()); + eph.setTb(data->t_b()); + eph.setYVel(data->y_vel() * pow(2, -20)); + eph.setYAccel(data->y_accel() * pow(2, -30)); + eph.setY(data->y() * pow(2, -11)); + } + + // string number 3 + { + kaitai::kstream stream(glonass_strings[msg->sv_id()][3]); + glonass_t gl_stream(&stream); + glonass_t::string_3_t* data = static_cast(gl_stream.data()); + + eph.setP3(data->p3()); + eph.setGammaN(data->gamma_n() * pow(2, -40)); + eph.setSvHealth(eph.getSvHealth() | data->l_n()); + eph.setZVel(data->z_vel() * pow(2, -20)); + eph.setZAccel(data->z_accel() * pow(2, -30)); + eph.setZ(data->z() * pow(2, -11)); + } + + // string number 4 + { + kaitai::kstream stream(glonass_strings[msg->sv_id()][4]); + glonass_t gl_stream(&stream); + glonass_t::string_4_t* data = static_cast(gl_stream.data()); + + current_day = data->n_t(); + eph.setTauN(data->tau_n() * pow(2, -30)); + eph.setDeltaTauN(data->delta_tau_n() * pow(2, -30)); + eph.setAge(data->e_n()); + eph.setP4(data->p4()); + eph.setSvURA(glonass_URA_lookup.at(data->f_t())); + if (msg->sv_id() != data->n()) { + LOGE("SV_ID != SLOT_NUMBER: %d %d", msg->sv_id(), data->n()) + } + eph.setSvType(data->m()); + } + + // string number 5 + { + kaitai::kstream stream(glonass_strings[msg->sv_id()][5]); + glonass_t gl_stream(&stream); + glonass_t::string_5_t* data = static_cast(gl_stream.data()); + + // string5 parsing is only needed to get the year, this can be removed and + // the year can be fetched later in laika (note rollovers and leap year) + uint8_t n_4 = data->n_4(); + uint16_t year = get_glonass_year(n_4, current_day); + if (current_day > 1461) { + // impossible day within last 4 year, reject ephemeris + // TODO: check if this can be detected via hamming code + LOGE("INVALID DATA: current day out of range: %d, %d", current_day, n_4); + glonass_strings[msg->sv_id()].clear(); + return kj::Array(); + } + + uint16_t last_leap_year = 1996 + 4*(n_4-1); + uint16_t days_till_this_year = (year - last_leap_year)*365; + if (days_till_this_year != 0) { + days_till_this_year++; + } + + eph.setYear(year); + eph.setDayInYear(current_day - days_till_this_year); + eph.setHour((eph.getTk()>>7) & 0x1F); + eph.setMinute((eph.getTk()>>1) & 0x3F); + eph.setSecond((eph.getTk() & 0x1) * 30); + } + + glonass_strings[msg->sv_id()].clear(); + return capnp::messageToFlatArray(msg_builder); +} + + +kj::Array UbloxMsgParser::gen_rxm_sfrbx(ubx_t::rxm_sfrbx_t *msg) { + switch (msg->gnss_id()) { + case ubx_t::gnss_type_t::GNSS_TYPE_GPS: + return parse_gps_ephemeris(msg); + case ubx_t::gnss_type_t::GNSS_TYPE_GLONASS: + return parse_glonass_ephemeris(msg); + default: + return kj::Array(); + } +} + kj::Array UbloxMsgParser::gen_rxm_rawx(ubx_t::rxm_rawx_t *msg) { MessageBuilder msg_builder; auto mr = msg_builder.initEvent().initUbloxGnss().initMeasurementReport(); diff --git a/selfdrive/locationd/ublox_msg.h b/selfdrive/locationd/ublox_msg.h index 542e72816..6988f20b7 100644 --- a/selfdrive/locationd/ublox_msg.h +++ b/selfdrive/locationd/ublox_msg.h @@ -10,6 +10,7 @@ #include "cereal/messaging/messaging.h" #include "common/util.h" #include "selfdrive/locationd/generated/gps.h" +#include "selfdrive/locationd/generated/glonass.h" #include "selfdrive/locationd/generated/ubx.h" using namespace std::string_literals; @@ -101,11 +102,22 @@ class UbloxMsgParser { inline bool valid_cheksum(); inline bool valid(); inline bool valid_so_far(); + inline uint16_t get_glonass_year(uint8_t N4, uint16_t Nt); + + kj::Array parse_gps_ephemeris(ubx_t::rxm_sfrbx_t *msg); + kj::Array parse_glonass_ephemeris(ubx_t::rxm_sfrbx_t *msg); std::unordered_map> gps_subframes; size_t bytes_in_parse_buf = 0; uint8_t msg_parse_buf[ublox::UBLOX_HEADER_SIZE + ublox::UBLOX_MAX_MSG_SIZE]; -}; + // user range accuracy in meters + const std::unordered_map glonass_URA_lookup = + {{ 0, 1}, { 1, 2}, { 2, 2.5}, { 3, 4}, { 4, 5}, {5, 7}, + { 6, 10}, { 7, 12}, { 8, 14}, { 9, 16}, {10, 32}, + {11, 64}, {12, 128}, {13, 256}, {14, 512}, {15, 1024}}; + std::unordered_map> glonass_strings; + std::unordered_map glonass_superframes; +}; diff --git a/selfdrive/loggerd/SConscript b/selfdrive/loggerd/SConscript index 92706c53e..3b961bce6 100644 --- a/selfdrive/loggerd/SConscript +++ b/selfdrive/loggerd/SConscript @@ -13,6 +13,8 @@ if arch == "Darwin": # fix OpenCL del libs[libs.index('OpenCL')] env['FRAMEWORKS'] = ['OpenCL'] + # exclude v4l + del src[src.index('encoder/v4l_encoder.cc')] logger_lib = env.Library('logger', src) libs.insert(0, logger_lib) diff --git a/selfdrive/loggerd/loggerd.cc b/selfdrive/loggerd/loggerd.cc index 9beb3c3bf..e09cdfaa9 100644 --- a/selfdrive/loggerd/loggerd.cc +++ b/selfdrive/loggerd/loggerd.cc @@ -24,15 +24,25 @@ void logger_rotate(LoggerdState *s) { } void rotate_if_needed(LoggerdState *s) { - if (s->ready_to_rotate == s->max_waiting) { - logger_rotate(s); + // all encoders ready, trigger rotation + bool all_ready = s->ready_to_rotate == s->max_waiting; + + // fallback logic to prevent extremely long segments in the case of camera, encoder, etc. malfunctions + bool timed_out = false; + double tms = millis_since_boot(); + double seg_length_secs = (tms - s->last_rotate_tms) / 1000.; + if ((seg_length_secs > SEGMENT_LENGTH) && !LOGGERD_TEST) { + // TODO: might be nice to put these reasons in the sentinel + if ((tms - s->last_camera_seen_tms) > NO_CAMERA_PATIENCE) { + timed_out = true; + LOGE("no camera packets seen. auto rotating"); + } else if (seg_length_secs > SEGMENT_LENGTH*1.2) { + timed_out = true; + LOGE("segment too long. auto rotating"); + } } - double tms = millis_since_boot(); - if ((tms - s->last_rotate_tms) > SEGMENT_LENGTH * 1000 && - (tms - s->last_camera_seen_tms) > NO_CAMERA_PATIENCE && - !LOGGERD_TEST) { - LOGW("no camera packet seen. auto rotating"); + if (all_ready || timed_out) { logger_rotate(s); } } diff --git a/selfdrive/manager/build.py b/selfdrive/manager/build.py index c8a7d4153..5b69e3dca 100755 --- a/selfdrive/manager/build.py +++ b/selfdrive/manager/build.py @@ -15,7 +15,7 @@ from system.version import is_dirty MAX_CACHE_SIZE = 4e9 if "CI" in os.environ else 2e9 CACHE_DIR = Path("/data/scons_cache" if AGNOS else "/tmp/scons_cache") -TOTAL_SCONS_NODES = 2395 +TOTAL_SCONS_NODES = 2460 MAX_BUILD_PROGRESS = 100 PREBUILT = os.path.exists(os.path.join(BASEDIR, 'prebuilt')) diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index 928507f65..865966d6c 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -20,11 +20,9 @@ from selfdrive.manager.process_config import managed_processes from selfdrive.athena.registration import register, UNREGISTERED_DONGLE_ID from system.swaglog import cloudlog, add_file_handler from system.version import is_dirty, get_commit, get_version, get_origin, get_short_branch, \ - terms_version, training_version, is_tested_branch + terms_version, training_version, is_tested_branch, is_release_branch -sys.path.append(os.path.join(BASEDIR, "pyextra")) - def manager_init() -> None: # update system time from panda @@ -78,6 +76,7 @@ def manager_init() -> None: params.put("GitBranch", get_short_branch(default="")) params.put("GitRemote", get_origin(default="")) params.put_bool("IsTestedBranch", is_tested_branch()) + params.put_bool("IsReleaseBranch", is_release_branch()) # set dongle id reg_res = register(show_spinner=True) diff --git a/selfdrive/manager/process.py b/selfdrive/manager/process.py index dabfbe4ee..ce18073d9 100644 --- a/selfdrive/manager/process.py +++ b/selfdrive/manager/process.py @@ -290,10 +290,11 @@ class DaemonProcess(ManagerProcess): def ensure_running(procs: ValuesView[ManagerProcess], started: bool, params=None, CP: car.CarParams=None, - not_run: Optional[List[str]]=None) -> None: + not_run: Optional[List[str]]=None) -> List[ManagerProcess]: if not_run is None: not_run = [] + running = [] for p in procs: # Conditions that make a process run run = any(( @@ -311,7 +312,10 @@ def ensure_running(procs: ValuesView[ManagerProcess], started: bool, params=None if run: p.start() + running.append(p) else: p.stop(block=False) p.check_watchdog(started) + + return running diff --git a/selfdrive/manager/process_config.py b/selfdrive/manager/process_config.py index dbccb8d4a..8fc4d94e5 100644 --- a/selfdrive/manager/process_config.py +++ b/selfdrive/manager/process_config.py @@ -17,6 +17,11 @@ def logging(started, params, CP: car.CarParams) -> bool: run = (not CP.notCar) or not params.get_bool("DisableLogging") return started and run +def ublox(started, params, CP: car.CarParams) -> bool: + use_ublox = os.path.exists('/dev/ttyHS0') and not os.path.exists('/persist/comma/use-quectel-gps') + params.put_bool("UbloxAvailable", use_ublox) + return started and use_ublox + procs = [ # due to qualcomm kernel bugs SIGKILLing camerad sometimes causes page table corruption NativeProcess("camerad", "system/camerad", ["./camerad"], unkillable=True, callback=driverview), @@ -24,6 +29,7 @@ procs = [ NativeProcess("logcatd", "system/logcatd", ["./logcatd"]), NativeProcess("proclogd", "system/proclogd", ["./proclogd"]), PythonProcess("logmessaged", "system.logmessaged", offroad=True), + PythonProcess("micd", "system.micd"), PythonProcess("timezoned", "system.timezoned", enabled=not PC, offroad=True), DaemonProcess("manage_athenad", "selfdrive.athena.manage_athenad", "AthenadPid"), @@ -31,9 +37,9 @@ procs = [ NativeProcess("encoderd", "selfdrive/loggerd", ["./encoderd"]), NativeProcess("loggerd", "selfdrive/loggerd", ["./loggerd"], onroad=False, callback=logging), NativeProcess("modeld", "selfdrive/modeld", ["./modeld"]), - # NativeProcess("mapsd", "selfdrive/navd", ["./map_renderer"]), + NativeProcess("mapsd", "selfdrive/navd", ["./map_renderer"], enabled=False), + NativeProcess("navmodeld", "selfdrive/modeld", ["./navmodeld"], enabled=False), NativeProcess("sensord", "selfdrive/sensord", ["./sensord"], enabled=not PC), - NativeProcess("ubloxd", "selfdrive/locationd", ["./ubloxd"], enabled=(not PC or WEBCAM)), NativeProcess("ui", "selfdrive/ui", ["./ui"], offroad=True, watchdog_max_dt=(5 if not PC else None)), NativeProcess("soundd", "selfdrive/ui/soundd", ["./soundd"], offroad=True), NativeProcess("locationd", "selfdrive/locationd", ["./locationd"]), @@ -48,7 +54,8 @@ procs = [ PythonProcess("navd", "selfdrive.navd.navd"), PythonProcess("pandad", "selfdrive.boardd.pandad", offroad=True), PythonProcess("paramsd", "selfdrive.locationd.paramsd"), - PythonProcess("pigeond", "selfdrive.sensord.pigeond", enabled=TICI), + NativeProcess("ubloxd", "selfdrive/locationd", ["./ubloxd"], enabled=TICI, onroad=False, callback=ublox), + PythonProcess("pigeond", "selfdrive.sensord.pigeond", enabled=TICI, onroad=False, callback=ublox), PythonProcess("plannerd", "selfdrive.controls.plannerd"), PythonProcess("radard", "selfdrive.controls.radard"), PythonProcess("thermald", "selfdrive.thermald.thermald", offroad=True), diff --git a/selfdrive/manager/test/test_manager.py b/selfdrive/manager/test/test_manager.py index 6d4df0423..889ab3d27 100755 --- a/selfdrive/manager/test/test_manager.py +++ b/selfdrive/manager/test/test_manager.py @@ -4,17 +4,17 @@ import signal import time import unittest +from cereal import car from common.params import Params import selfdrive.manager.manager as manager -from selfdrive.manager.process import DaemonProcess +from selfdrive.manager.process import ensure_running from selfdrive.manager.process_config import managed_processes from system.hardware import HARDWARE os.environ['FAKEUPLOAD'] = "1" MAX_STARTUP_TIME = 3 -ALL_PROCESSES = [p.name for p in managed_processes.values() if (type(p) is not DaemonProcess) and p.enabled and (p.name not in ['pandad', ])] - +BLACKLIST_PROCS = ['manage_athenad', 'pandad', 'pigeond'] class TestManager(unittest.TestCase): def setUp(self): @@ -47,24 +47,25 @@ class TestManager(unittest.TestCase): HARDWARE.set_power_save(False) manager.manager_init() manager.manager_prepare() - for p in ALL_PROCESSES: - managed_processes[p].start() + + CP = car.CarParams.new_message() + procs = ensure_running(managed_processes.values(), True, Params(), CP, not_run=BLACKLIST_PROCS) time.sleep(10) - for p in reversed(ALL_PROCESSES): - with self.subTest(proc=p): - state = managed_processes[p].get_process_state_msg() - self.assertTrue(state.running, f"{p} not running") - exit_code = managed_processes[p].stop(retry=False) + for p in procs: + with self.subTest(proc=p.name): + state = p.get_process_state_msg() + self.assertTrue(state.running, f"{p.name} not running") + exit_code = p.stop(retry=False) - self.assertTrue(exit_code is not None, f"{p} failed to exit") + self.assertTrue(exit_code is not None, f"{p.name} failed to exit") # TODO: interrupted blocking read exits with 1 in cereal. use a more unique return code exit_codes = [0, 1] - if managed_processes[p].sigkill: + if p.sigkill: exit_codes = [-signal.SIGKILL] - self.assertIn(exit_code, exit_codes, f"{p} died with {exit_code}") + self.assertIn(exit_code, exit_codes, f"{p.name} died with {exit_code}") if __name__ == "__main__": diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index f1a8d7188..7bbc1b347 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -70,30 +70,14 @@ lenv.Program('_dmonitoringmodeld', [ if use_thneed and arch == "larch64" or GetOption('pc_thneed'): fn = File("models/supercombo").abspath - if GetOption('pc_thneed'): - cmd = f"cd {Dir('#').abspath}/tinygrad_repo && GPU=1 NATIVE_EXPLOG=1 OPTWG=1 UNSAFE_FLOAT4=1 DEBUGCL=1 python3 openpilot/compile.py {fn}.onnx {fn}.thneed" - else: - cmd = f"cd {Dir('#').abspath}/tinygrad_repo && FLOAT16=1 MATMUL=1 PYOPENCL_NO_CACHE=1 NATIVE_EXPLOG=1 OPTWG=1 UNSAFE_FLOAT4=1 DEBUGCL=1 python3 openpilot/compile.py {fn}.onnx {fn}.thneed" + tinygrad_opts = ["NATIVE_EXPLOG=1", "VALIDHACKS=1", "OPTWG=1", "IMAGE=2", "GPU=1", "CLCACHE=0"] + if not GetOption('pc_thneed'): + # use FLOAT16 on device for speed + don't cache the CL kernels for space + tinygrad_opts += ["FLOAT16=1", "PYOPENCL_NO_CACHE=1"] + cmd = f"cd {Dir('#').abspath}/tinygrad_repo && " + ' '.join(tinygrad_opts) + f" python3 openpilot/compile.py {fn}.onnx {fn}.thneed" - # is there a better way then listing all of tinygrad? - lenv.Command(fn + ".thneed", [fn + ".onnx", - "#tinygrad_repo/openpilot/compile.py", - "#tinygrad_repo/accel/opencl/conv.cl", - "#tinygrad_repo/accel/opencl/matmul.cl", - "#tinygrad_repo/accel/opencl/ops_opencl.py", - "#tinygrad_repo/accel/opencl/preprocessing.py", - "#tinygrad_repo/extra/onnx.py", - "#tinygrad_repo/extra/thneed.py", - "#tinygrad_repo/extra/utils.py", - "#tinygrad_repo/tinygrad/llops/ops_gpu.py", - "#tinygrad_repo/tinygrad/llops/ops_opencl.py", - "#tinygrad_repo/tinygrad/helpers.py", - "#tinygrad_repo/tinygrad/mlops.py", - "#tinygrad_repo/tinygrad/ops.py", - "#tinygrad_repo/tinygrad/shapetracker.py", - "#tinygrad_repo/tinygrad/tensor.py", - "#tinygrad_repo/tinygrad/nn/__init__.py" - ], cmd) + tinygrad_files = sum([lenv.Glob("#"+x) for x in open(File("#release/files_common").abspath).read().split("\n") if x.startswith("tinygrad_repo/")], []) + lenv.Command(fn + ".thneed", [fn + ".onnx"] + tinygrad_files, cmd) llenv = lenv.Clone() if GetOption('pc_thneed'): @@ -112,3 +96,8 @@ llenv.Program('_modeld', [ "modeld.cc", "models/driving.cc", ]+common_model, LIBS=libs + transformations) + +lenv.Program('_navmodeld', [ + "navmodeld.cc", + "models/nav.cc", + ]+common_model, LIBS=libs + transformations) diff --git a/selfdrive/modeld/modeld.cc b/selfdrive/modeld/modeld.cc index cfc71a0e2..0a8a8d635 100644 --- a/selfdrive/modeld/modeld.cc +++ b/selfdrive/modeld/modeld.cc @@ -15,6 +15,7 @@ #include "common/util.h" #include "system/hardware/hw.h" #include "selfdrive/modeld/models/driving.h" +#include "selfdrive/modeld/models/nav.h" ExitHandler do_exit; @@ -72,6 +73,8 @@ void run_model(ModelState &model, VisionIpcClient &vipc_client_main, VisionIpcCl mat3 model_transform_main = {}; mat3 model_transform_extra = {}; bool live_calib_seen = false; + float driving_style[DRIVING_STYLE_LEN] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}; + float nav_features[NAV_FEATURE_LEN] = {0}; VisionBuf *buf_main = nullptr; VisionBuf *buf_extra = nullptr; @@ -151,7 +154,7 @@ void run_model(ModelState &model, VisionIpcClient &vipc_client_main, VisionIpcCl } double mt1 = millis_since_boot(); - ModelOutput *model_output = model_eval_frame(&model, buf_main, buf_extra, model_transform_main, model_transform_extra, vec_desire, is_rhd, prepare_only); + ModelOutput *model_output = model_eval_frame(&model, buf_main, buf_extra, model_transform_main, model_transform_extra, vec_desire, is_rhd, driving_style, nav_features, prepare_only); double mt2 = millis_since_boot(); float model_execution_time = (mt2 - mt1) / 1000.0; diff --git a/selfdrive/modeld/models/commonmodel.h b/selfdrive/modeld/models/commonmodel.h index 40c82a8c2..1a079da05 100644 --- a/selfdrive/modeld/models/commonmodel.h +++ b/selfdrive/modeld/models/commonmodel.h @@ -13,6 +13,7 @@ #endif #include "common/mat.h" +#include "cereal/messaging/messaging.h" #include "selfdrive/modeld/transforms/loadyuv.h" #include "selfdrive/modeld/transforms/transform.h" @@ -21,6 +22,11 @@ const bool send_raw_pred = getenv("SEND_RAW_PRED") != NULL; void softmax(const float* input, float* output, size_t len); float sigmoid(float input); +template +constexpr const kj::ArrayPtr to_kj_array_ptr(const std::array &arr) { + return kj::ArrayPtr(arr.data(), arr.size()); +} + class ModelFrame { public: ModelFrame(cl_device_id device_id, cl_context context); diff --git a/selfdrive/modeld/models/driving.cc b/selfdrive/modeld/models/driving.cc index 4015731c4..ac101bfee 100644 --- a/selfdrive/modeld/models/driving.cc +++ b/selfdrive/modeld/models/driving.cc @@ -22,11 +22,6 @@ std::array prev_brake_3ms2_probs = {0,0,0}; // #define DUMP_YUV -template -constexpr const kj::ArrayPtr to_kj_array_ptr(const std::array &arr) { - return kj::ArrayPtr(arr.data(), arr.size()); -} - void model_init(ModelState* s, cl_device_id device_id, cl_context context) { s->frame = new ModelFrame(device_id, context); s->wide_frame = new ModelFrame(device_id, context); @@ -51,10 +46,19 @@ void model_init(ModelState* s, cl_device_id device_id, cl_context context) { #ifdef TRAFFIC_CONVENTION s->m->addTrafficConvention(s->traffic_convention, TRAFFIC_CONVENTION_LEN); #endif + +#ifdef DRIVING_STYLE + s->m->addDrivingStyle(s->driving_style, DRIVING_STYLE_LEN); +#endif + +#ifdef NAV + s->m->addNavFeatures(s->nav_features, NAV_FEATURE_LEN); +#endif + } ModelOutput* model_eval_frame(ModelState* s, VisionBuf* buf, VisionBuf* wbuf, - const mat3 &transform, const mat3 &transform_wide, float *desire_in, bool is_rhd, bool prepare_only) { + const mat3 &transform, const mat3 &transform_wide, float *desire_in, bool is_rhd, float *driving_style, float *nav_features, bool prepare_only) { #ifdef DESIRE std::memmove(&s->pulse_desire[0], &s->pulse_desire[DESIRE_LEN], sizeof(float) * DESIRE_LEN*HISTORY_BUFFER_LEN); if (desire_in != NULL) { @@ -72,6 +76,14 @@ ModelOutput* model_eval_frame(ModelState* s, VisionBuf* buf, VisionBuf* wbuf, LOGT("Desire enqueued"); #endif +#ifdef NAV + std::memcpy(s->nav_features, nav_features, sizeof(float)*NAV_FEATURE_LEN); +#endif + +#ifdef DRIVING_STYLE + std::memcpy(s->driving_style, driving_style, sizeof(float)*DRIVING_STYLE_LEN); +#endif + int rhd_idx = is_rhd; s->traffic_convention[rhd_idx] = 1.0; s->traffic_convention[1-rhd_idx] = 0.0; diff --git a/selfdrive/modeld/models/driving.h b/selfdrive/modeld/models/driving.h index b23691a56..5734c68cb 100644 --- a/selfdrive/modeld/models/driving.h +++ b/selfdrive/modeld/models/driving.h @@ -14,6 +14,7 @@ #include "common/modeldata.h" #include "common/util.h" #include "selfdrive/modeld/models/commonmodel.h" +#include "selfdrive/modeld/models/nav.h" #include "selfdrive/modeld/runners/run.h" constexpr int FEATURE_LEN = 128; @@ -21,6 +22,7 @@ constexpr int HISTORY_BUFFER_LEN = 99; constexpr int DESIRE_LEN = 8; constexpr int DESIRE_PRED_LEN = 4; constexpr int TRAFFIC_CONVENTION_LEN = 2; +constexpr int DRIVING_STYLE_LEN = 12; constexpr int MODEL_FREQ = 20; constexpr int DISENGAGE_LEN = 5; @@ -259,11 +261,17 @@ struct ModelState { #ifdef TRAFFIC_CONVENTION float traffic_convention[TRAFFIC_CONVENTION_LEN] = {}; #endif +#ifdef DRIVING_STYLE + float driving_style[DRIVING_STYLE_LEN] = {}; +#endif +#ifdef NAV + float nav_features[NAV_FEATURE_LEN] = {}; +#endif }; void model_init(ModelState* s, cl_device_id device_id, cl_context context); ModelOutput *model_eval_frame(ModelState* s, VisionBuf* buf, VisionBuf* buf_wide, - const mat3 &transform, const mat3 &transform_wide, float *desire_in, bool is_rhd, bool prepare_only); + const mat3 &transform, const mat3 &transform_wide, float *desire_in, bool is_rhd, float *driving_style, float *nav_features, bool prepare_only); void model_free(ModelState* s); void model_publish(PubMaster &pm, uint32_t vipc_frame_id, uint32_t vipc_frame_id_extra, uint32_t frame_id, float frame_drop, const ModelOutput &net_outputs, uint64_t timestamp_eof, diff --git a/selfdrive/modeld/models/nav.cc b/selfdrive/modeld/models/nav.cc new file mode 100644 index 000000000..861795e17 --- /dev/null +++ b/selfdrive/modeld/models/nav.cc @@ -0,0 +1,66 @@ +#include "selfdrive/modeld/models/nav.h" + +#include +#include + +#include "common/mat.h" +#include "common/modeldata.h" +#include "common/timing.h" + + +void navmodel_init(NavModelState* s) { + #ifdef USE_ONNX_MODEL + s->m = new ONNXModel("models/navmodel.onnx", &s->output[0], NAV_NET_OUTPUT_SIZE, USE_DSP_RUNTIME, false, true); + #else + s->m = new SNPEModel("models/navmodel_q.dlc", &s->output[0], NAV_NET_OUTPUT_SIZE, USE_DSP_RUNTIME, false, true); + #endif +} + +NavModelResult* navmodel_eval_frame(NavModelState* s, VisionBuf* buf) { + memcpy(s->net_input_buf, buf->addr, NAV_INPUT_SIZE); + + double t1 = millis_since_boot(); + s->m->addImage((float*)s->net_input_buf, NAV_INPUT_SIZE/sizeof(float)); + s->m->execute(); + double t2 = millis_since_boot(); + + NavModelResult *model_res = (NavModelResult*)&s->output; + model_res->dsp_execution_time = (t2 - t1) / 1000.; + return model_res; +} + +void fill_plan(cereal::NavModelData::Builder &framed, const NavModelOutputPlan &plan) { + std::array pos_x, pos_y; + std::array pos_x_std, pos_y_std; + + for (int i=0; im; +} diff --git a/selfdrive/modeld/models/nav.h b/selfdrive/modeld/models/nav.h new file mode 100644 index 000000000..b2955ad2c --- /dev/null +++ b/selfdrive/modeld/models/nav.h @@ -0,0 +1,56 @@ +#pragma once + +#include "cereal/messaging/messaging.h" +#include "cereal/visionipc/visionipc_client.h" +#include "common/util.h" +#include "common/modeldata.h" +#include "selfdrive/modeld/models/commonmodel.h" +#include "selfdrive/modeld/runners/run.h" + +constexpr int NAV_INPUT_SIZE = 256*256; +constexpr int NAV_FEATURE_LEN = 64; +constexpr int NAV_DESIRE_LEN = 32; + +struct NavModelOutputXY { + float x; + float y; +}; +static_assert(sizeof(NavModelOutputXY) == sizeof(float)*2); + +struct NavModelOutputPlan { + std::array mean; + std::array std; +}; +static_assert(sizeof(NavModelOutputPlan) == sizeof(NavModelOutputXY)*TRAJECTORY_SIZE*2); + +struct NavModelOutputDesirePrediction { + std::array values; +}; +static_assert(sizeof(NavModelOutputDesirePrediction) == sizeof(float)*NAV_DESIRE_LEN); + +struct NavModelOutputFeatures { + std::array values; +}; +static_assert(sizeof(NavModelOutputFeatures) == sizeof(float)*NAV_FEATURE_LEN); + +struct NavModelResult { + const NavModelOutputPlan plan; + const NavModelOutputDesirePrediction desire_pred; + const NavModelOutputFeatures features; + float dsp_execution_time; +}; +static_assert(sizeof(NavModelResult) == sizeof(NavModelOutputPlan) + sizeof(NavModelOutputDesirePrediction) + sizeof(NavModelOutputFeatures) + sizeof(float)); + +constexpr int NAV_OUTPUT_SIZE = sizeof(NavModelResult) / sizeof(float); +constexpr int NAV_NET_OUTPUT_SIZE = NAV_OUTPUT_SIZE - 1; + +struct NavModelState { + RunModel *m; + uint8_t net_input_buf[NAV_INPUT_SIZE]; + float output[NAV_OUTPUT_SIZE]; +}; + +void navmodel_init(NavModelState* s); +NavModelResult* navmodel_eval_frame(NavModelState* s, VisionBuf* buf); +void navmodel_publish(PubMaster &pm, uint32_t frame_id, const NavModelResult &model_res, float execution_time); +void navmodel_free(NavModelState* s); diff --git a/selfdrive/modeld/models/navmodel_q.dlc b/selfdrive/modeld/models/navmodel_q.dlc new file mode 100644 index 000000000..21d40c2cd Binary files /dev/null and b/selfdrive/modeld/models/navmodel_q.dlc differ diff --git a/selfdrive/modeld/models/supercombo.onnx b/selfdrive/modeld/models/supercombo.onnx index e16a69999..2f2dbc476 100644 Binary files a/selfdrive/modeld/models/supercombo.onnx and b/selfdrive/modeld/models/supercombo.onnx differ diff --git a/selfdrive/modeld/navmodeld b/selfdrive/modeld/navmodeld new file mode 100755 index 000000000..079afd967 --- /dev/null +++ b/selfdrive/modeld/navmodeld @@ -0,0 +1,12 @@ +#!/bin/sh + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" +cd $DIR + +if [ -f /TICI ]; then + export LD_LIBRARY_PATH="/usr/lib/aarch64-linux-gnu:/data/pythonpath/third_party/snpe/larch64:$LD_LIBRARY_PATH" + export ADSP_LIBRARY_PATH="/data/pythonpath/third_party/snpe/dsp/" +else + export LD_LIBRARY_PATH="$DIR/../../third_party/snpe/x86_64-linux-clang:$DIR/../../openpilot/third_party/snpe/x86_64:$LD_LIBRARY_PATH" +fi +exec ./_navmodeld diff --git a/selfdrive/modeld/navmodeld.cc b/selfdrive/modeld/navmodeld.cc new file mode 100644 index 000000000..75f2c62ab --- /dev/null +++ b/selfdrive/modeld/navmodeld.cc @@ -0,0 +1,59 @@ +#include +#include + +#include +#include + +#include "cereal/visionipc/visionipc_client.h" +#include "common/swaglog.h" +#include "common/util.h" +#include "selfdrive/modeld/models/nav.h" + +ExitHandler do_exit; + +void run_model(NavModelState &model, VisionIpcClient &vipc_client) { + PubMaster pm({"navModel"}); + + double last_ts = 0; + uint32_t last_frame_id = 0; + VisionIpcBufExtra extra = {}; + + while (!do_exit) { + VisionBuf *buf = vipc_client.recv(&extra); + if (buf == nullptr) continue; + + double t1 = millis_since_boot(); + NavModelResult *model_res = navmodel_eval_frame(&model, buf); + double t2 = millis_since_boot(); + + // send navmodel packet + navmodel_publish(pm, extra.frame_id, *model_res, (t2 - t1) / 1000.0); + + //printf("navmodel process: %.2fms, from last %.2fms\n", t2 - t1, t1 - last_ts); + last_ts = t1; + last_frame_id = extra.frame_id; + } +} + +int main(int argc, char **argv) { + setpriority(PRIO_PROCESS, 0, -15); + + // init the models + NavModelState model; + navmodel_init(&model); + LOGW("models loaded, navmodeld starting"); + + VisionIpcClient vipc_client = VisionIpcClient("navd", VISION_STREAM_MAP, true); + while (!do_exit && !vipc_client.connect(false)) { + util::sleep_for(100); + } + + // run the models + if (vipc_client.connected) { + LOGW("connected with buffer size: %d", vipc_client.buffers[0].len); + run_model(model, vipc_client); + } + + navmodel_free(&model); + return 0; +} diff --git a/selfdrive/modeld/runners/onnxmodel.cc b/selfdrive/modeld/runners/onnxmodel.cc index 447d90fd7..5616a6c9c 100644 --- a/selfdrive/modeld/runners/onnxmodel.cc +++ b/selfdrive/modeld/runners/onnxmodel.cc @@ -97,6 +97,16 @@ void ONNXModel::addDesire(float *state, int state_size) { desire_state_size = state_size; } +void ONNXModel::addNavFeatures(float *state, int state_size) { + nav_features_input_buf = state; + nav_features_size = state_size; +} + +void ONNXModel::addDrivingStyle(float *state, int state_size) { + driving_style_input_buf = state; + driving_style_size = state_size; +} + void ONNXModel::addTrafficConvention(float *state, int state_size) { traffic_convention_input_buf = state; traffic_convention_size = state_size; @@ -128,7 +138,13 @@ void ONNXModel::execute() { if (desire_input_buf != NULL) { pwrite(desire_input_buf, desire_state_size); } - if (traffic_convention_input_buf != NULL) { + if (nav_features_input_buf != NULL) { + pwrite(nav_features_input_buf, nav_features_size); + } + if (driving_style_input_buf != NULL) { + pwrite(driving_style_input_buf, driving_style_size); + } + if (traffic_convention_input_buf != NULL) { pwrite(traffic_convention_input_buf, traffic_convention_size); } if (calib_input_buf != NULL) { diff --git a/selfdrive/modeld/runners/onnxmodel.h b/selfdrive/modeld/runners/onnxmodel.h index d5b7bfecf..9990bf1b4 100644 --- a/selfdrive/modeld/runners/onnxmodel.h +++ b/selfdrive/modeld/runners/onnxmodel.h @@ -10,6 +10,8 @@ public: ~ONNXModel(); void addRecurrent(float *state, int state_size); void addDesire(float *state, int state_size); + void addNavFeatures(float *state, int state_size); + void addDrivingStyle(float *state, int state_size); void addTrafficConvention(float *state, int state_size); void addCalib(float *state, int state_size); void addImage(float *image_buf, int buf_size); @@ -25,6 +27,10 @@ private: int rnn_state_size; float *desire_input_buf = NULL; int desire_state_size; + float *nav_features_input_buf = NULL; + int nav_features_size; + float *driving_style_input_buf = NULL; + int driving_style_size; float *traffic_convention_input_buf = NULL; int traffic_convention_size; float *calib_input_buf = NULL; diff --git a/selfdrive/modeld/runners/runmodel.h b/selfdrive/modeld/runners/runmodel.h index c60781140..673ddb50b 100644 --- a/selfdrive/modeld/runners/runmodel.h +++ b/selfdrive/modeld/runners/runmodel.h @@ -5,6 +5,8 @@ public: virtual ~RunModel() {} virtual void addRecurrent(float *state, int state_size) {} virtual void addDesire(float *state, int state_size) {} + virtual void addNavFeatures(float *state, int state_size) {} + virtual void addDrivingStyle(float *state, int state_size) {} virtual void addTrafficConvention(float *state, int state_size) {} virtual void addCalib(float *state, int state_size) {} virtual void addImage(float *image_buf, int buf_size) {} diff --git a/selfdrive/modeld/runners/snpemodel.cc b/selfdrive/modeld/runners/snpemodel.cc index ff4adcd8d..609a7a665 100644 --- a/selfdrive/modeld/runners/snpemodel.cc +++ b/selfdrive/modeld/runners/snpemodel.cc @@ -153,6 +153,16 @@ void SNPEModel::addDesire(float *state, int state_size) { desireBuffer = this->addExtra(state, state_size, 1); } +void SNPEModel::addNavFeatures(float *state, int state_size) { + navFeatures = state; + navFeaturesBuffer = this->addExtra(state, state_size, 1); +} + +void SNPEModel::addDrivingStyle(float *state, int state_size) { + drivingStyle = state; + drivingStyleBuffer = this->addExtra(state, state_size, 2); +} + void SNPEModel::addCalib(float *state, int state_size) { calib = state; calibBuffer = this->addExtra(state, state_size, 1); diff --git a/selfdrive/modeld/runners/snpemodel.h b/selfdrive/modeld/runners/snpemodel.h index 08ae16c2b..0d84d1d48 100644 --- a/selfdrive/modeld/runners/snpemodel.h +++ b/selfdrive/modeld/runners/snpemodel.h @@ -28,6 +28,8 @@ public: void addTrafficConvention(float *state, int state_size); void addCalib(float *state, int state_size); void addDesire(float *state, int state_size); + void addDrivingStyle(float *state, int state_size); + void addNavFeatures(float *state, int state_size); void addImage(float *image_buf, int buf_size); void addExtra(float *image_buf, int buf_size); void execute(); @@ -75,6 +77,10 @@ private: std::unique_ptr trafficConventionBuffer; float *desire; std::unique_ptr desireBuffer; + float *navFeatures; + std::unique_ptr navFeaturesBuffer; + float *drivingStyle; + std::unique_ptr drivingStyleBuffer; float *calib; std::unique_ptr calibBuffer; }; diff --git a/selfdrive/modeld/runners/thneedmodel.cc b/selfdrive/modeld/runners/thneedmodel.cc index 67db01bb9..4fdd7ca46 100644 --- a/selfdrive/modeld/runners/thneedmodel.cc +++ b/selfdrive/modeld/runners/thneedmodel.cc @@ -24,6 +24,14 @@ void ThneedModel::addDesire(float *state, int state_size) { desire = state; } +void ThneedModel::addDrivingStyle(float *state, int state_size) { + drivingStyle = state; +} + +void ThneedModel::addNavFeatures(float *state, int state_size) { + navFeatures = state; +} + void ThneedModel::addImage(float *image_input_buf, int buf_size) { input = image_input_buf; } diff --git a/selfdrive/modeld/runners/thneedmodel.h b/selfdrive/modeld/runners/thneedmodel.h index f3f34dc7f..63712f1d0 100644 --- a/selfdrive/modeld/runners/thneedmodel.h +++ b/selfdrive/modeld/runners/thneedmodel.h @@ -9,6 +9,8 @@ public: void addRecurrent(float *state, int state_size); void addTrafficConvention(float *state, int state_size); void addDesire(float *state, int state_size); + void addNavFeatures(float *state, int state_size); + void addDrivingStyle(float *state, int state_size); void addImage(float *image_buf, int buf_size); void addExtra(float *image_buf, int buf_size); void execute(); @@ -26,6 +28,8 @@ private: // recurrent and desire float *recurrent; float *trafficConvention; + float *drivingStyle; float *desire; + float *navFeatures; }; diff --git a/selfdrive/modeld/thneed/thneed.h b/selfdrive/modeld/thneed/thneed.h index 65475ccf7..647557773 100644 --- a/selfdrive/modeld/thneed/thneed.h +++ b/selfdrive/modeld/thneed/thneed.h @@ -12,7 +12,7 @@ #include -#include "selfdrive/modeld/thneed/include/msm_kgsl.h" +#include "msm_kgsl.h" using namespace std; diff --git a/selfdrive/monitoring/driver_monitor.py b/selfdrive/monitoring/driver_monitor.py index e3f6a5094..a2cddc246 100644 --- a/selfdrive/monitoring/driver_monitor.py +++ b/selfdrive/monitoring/driver_monitor.py @@ -156,6 +156,11 @@ class DriverStatus(): self._set_timers(active_monitoring=True) + def _reset_awareness(self): + self.awareness = 1. + self.awareness_active = 1. + self.awareness_passive = 1. + def _set_timers(self, active_monitoring): if self.active_monitoring_mode and self.awareness <= self.threshold_prompt: if active_monitoring: @@ -289,17 +294,17 @@ class DriverStatus(): self.hi_stds = 0 def update_events(self, events, driver_engaged, ctrl_active, standstill): - if (driver_engaged and self.awareness > 0) or not ctrl_active: - # reset only when on disengagement if red reached - self.awareness = 1. - self.awareness_active = 1. - self.awareness_passive = 1. + if (driver_engaged and self.awareness > 0 and not self.active_monitoring_mode) or not ctrl_active: # reset only when on disengagement if red reached + self._reset_awareness() return driver_attentive = self.driver_distraction_filter.x < 0.37 awareness_prev = self.awareness if (driver_attentive and self.face_detected and self.pose.low_std and self.awareness > 0): + if driver_engaged: + self._reset_awareness() + return # only restore awareness when paying attention and alert is not red self.awareness = min(self.awareness + ((self.settings._RECOVERY_FACTOR_MAX-self.settings._RECOVERY_FACTOR_MIN)*(1.-self.awareness)+self.settings._RECOVERY_FACTOR_MIN)*self.step_change, 1.) if self.awareness == 1.: diff --git a/selfdrive/navd/navd.py b/selfdrive/navd/navd.py index 4855b6359..7af911ab2 100755 --- a/selfdrive/navd/navd.py +++ b/selfdrive/navd/navd.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import json import math import os import threading @@ -21,6 +22,7 @@ from system.swaglog import cloudlog REROUTE_DISTANCE = 25 MANEUVER_TRANSITION_THRESHOLD = 10 VALID_POS_STD = 50.0 +REROUTE_COUNTER_MIN = 3 class RouteEngine: @@ -47,6 +49,8 @@ class RouteEngine: self.ui_pid = None + self.reroute_counter = 0 + if "MAPBOX_TOKEN" in os.environ: self.mapbox_token = os.environ["MAPBOX_TOKEN"] self.mapbox_host = "https://api.mapbox.com" @@ -113,6 +117,7 @@ class RouteEngine: self.recompute_countdown = 2**self.recompute_backoff self.recompute_backoff = min(6, self.recompute_backoff + 1) self.calculate_route(new_destination) + self.reroute_counter = 0 else: self.recompute_countdown = max(0, self.recompute_countdown - 1) @@ -135,12 +140,27 @@ class RouteEngine: 'language': lang, } - if self.last_bearing is not None: - params['bearings'] = f"{(self.last_bearing + 360) % 360:.0f},90;" + # TODO: move waypoints into NavDestination param? + waypoints = self.params.get('NavDestinationWaypoints', encoding='utf8') + waypoint_coords = [] + if waypoints is not None and len(waypoints) > 0: + waypoint_coords = json.loads(waypoints) - url = self.mapbox_host + f'/directions/v5/mapbox/driving-traffic/{self.last_position.longitude},{self.last_position.latitude};{destination.longitude},{destination.latitude}' + coords = [ + (self.last_position.longitude, self.last_position.latitude), + *waypoint_coords, + (destination.longitude, destination.latitude) + ] + params['waypoints'] = f'0;{len(coords)-1}' + if self.last_bearing is not None: + params['bearings'] = f"{(self.last_bearing + 360) % 360:.0f},90" + (';'*(len(coords)-1)) + + coords_str = ';'.join([f'{lon},{lat}' for lon, lat in coords]) + url = self.mapbox_host + '/directions/v5/mapbox/driving-traffic/' + coords_str try: resp = requests.get(url, params=params, timeout=10) + if resp.status_code != 200: + cloudlog.event("API request failed", status_code=resp.status_code, text=resp.text, error=True) resp.raise_for_status() r = resp.json() @@ -175,6 +195,10 @@ class RouteEngine: cloudlog.warning("Got empty route response") self.clear_route() + # clear waypoints to avoid a re-route including past waypoints + # TODO: only clear once we're past a waypoint + self.params.remove('NavDestinationWaypoints') + except requests.exceptions.RequestException: cloudlog.exception("failed to get route") self.clear_route() @@ -202,7 +226,11 @@ class RouteEngine: remaining = 1.0 - along_geometry / max(step['distance'], 1) total_distance = step['distance'] * remaining total_time = step['duration'] * remaining - total_time_typical = step['duration_typical'] * remaining + + if step['duration_typical'] is None: + total_time_typical = total_time + else: + total_time_typical = step['duration_typical'] * remaining # Add up totals for future steps for i in range(self.step_idx + 1, len(self.route)): @@ -285,8 +313,11 @@ class RouteEngine: min_d = min(min_d, minimum_distance(a, b, self.last_position)) - return min_d > REROUTE_DISTANCE - + if min_d > REROUTE_DISTANCE: + self.reroute_counter += 1 + else: + self.reroute_counter = 0 + return self.reroute_counter > REROUTE_COUNTER_MIN # TODO: Check for going wrong way in segment diff --git a/selfdrive/sensord/libdiag.h b/selfdrive/sensord/libdiag.h deleted file mode 100644 index 03a59464e..000000000 --- a/selfdrive/sensord/libdiag.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#define DIAG_MAX_RX_PKT_SIZ 4096 - -bool Diag_LSM_Init(uint8_t* pIEnv); -bool Diag_LSM_DeInit(void); - -// DCI - -#define DIAG_CON_APSS 0x001 -#define DIAG_CON_MPSS 0x002 -#define DIAG_CON_LPASS 0x004 -#define DIAG_CON_WCNSS 0x008 - -enum { - DIAG_DCI_NO_ERROR = 1001, -} diag_dci_error_type; - -int diag_register_dci_client(int*, uint16_t*, int, void*); -int diag_log_stream_config(int client_id, int set_mask, uint16_t log_codes_array[], int num_codes); -int diag_register_dci_stream(void (*func_ptr_logs)(unsigned char *ptr, int len), void (*func_ptr_events)(unsigned char *ptr, int len)); -int diag_release_dci_client(int*); - -int diag_send_dci_async_req(int client_id, unsigned char buf[], int bytes, unsigned char *rsp_ptr, int rsp_len, - void (*func_ptr)(unsigned char *ptr, int len, void *data_ptr), void *data_ptr); - - -#ifdef __cplusplus -} -#endif diff --git a/selfdrive/sensord/pigeond.py b/selfdrive/sensord/pigeond.py index f56af1c70..9d0a62bd3 100755 --- a/selfdrive/sensord/pigeond.py +++ b/selfdrive/sensord/pigeond.py @@ -123,7 +123,7 @@ class TTYPigeon(): init_baudrate(self) # clear configuration - self.send_with_ack(b"\xb5\x62\x06\x09\x0d\x00\x00\x00\x1f\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x17\x71\x5b") + self.send_with_ack(b"\xb5\x62\x06\x09\x0d\x00\x1f\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17\x71\xd7") # clear flash memory (almanac backup) self.send_with_ack(b"\xB5\x62\x09\x14\x04\x00\x01\x00\x00\x00\x22\xf0") @@ -303,8 +303,7 @@ def main(): pigeon, pm = create_pigeon() init_baudrate(pigeon) - r = initialize_pigeon(pigeon) - Params().put_bool("UbloxAvailable", r) + initialize_pigeon(pigeon) # start receiving data run_receiving(pigeon, pm) diff --git a/selfdrive/sensord/rawgps/rawgpsd.py b/selfdrive/sensord/rawgps/rawgpsd.py index 1c6505166..3fa5e927a 100755 --- a/selfdrive/sensord/rawgps/rawgpsd.py +++ b/selfdrive/sensord/rawgps/rawgpsd.py @@ -11,7 +11,9 @@ from struct import unpack_from, calcsize, pack from cereal import log import cereal.messaging as messaging +from common.gpio import gpio_init, gpio_set from laika.gps_time import GPSTime +from system.hardware.tici.pins import GPIO from system.swaglog import cloudlog from selfdrive.sensord.rawgps.modemdiag import ModemDiag, DIAG_LOG_F, setup_logs, send_recv from selfdrive.sensord.rawgps.structs import (dict_unpacker, position_report, relist, @@ -182,6 +184,7 @@ def main() -> NoReturn: def cleanup(sig, frame): cloudlog.warning(f"caught sig {sig}, disabling quectel gps") + gpio_set(GPIO.UBLOX_PWR_EN, False) teardown_quectel(diag) cloudlog.warning("quectel cleanup done") sys.exit(0) @@ -190,6 +193,8 @@ def main() -> NoReturn: setup_quectel(diag) cloudlog.warning("quectel setup done") + gpio_init(GPIO.UBLOX_PWR_EN, True) + gpio_set(GPIO.UBLOX_PWR_EN, True) pm = messaging.PubMaster(['qcomGnss', 'gpsLocation']) diff --git a/selfdrive/sensord/sensors/bmx055_accel.cc b/selfdrive/sensord/sensors/bmx055_accel.cc index 78b3ac526..78f5d64e7 100644 --- a/selfdrive/sensord/sensors/bmx055_accel.cc +++ b/selfdrive/sensord/sensors/bmx055_accel.cc @@ -9,20 +9,8 @@ BMX055_Accel::BMX055_Accel(I2CBus *bus) : I2CSensor(bus) {} int BMX055_Accel::init() { - int ret = 0; - uint8_t buffer[1]; - - ret = read_register(BMX055_ACCEL_I2C_REG_ID, buffer, 1); - if(ret < 0) { - LOGE("Reading chip ID failed: %d", ret); - goto fail; - } - - if(buffer[0] != BMX055_ACCEL_CHIP_ID) { - LOGE("Chip ID wrong. Got: %d, Expected %d", buffer[0], BMX055_ACCEL_CHIP_ID); - ret = -1; - goto fail; - } + int ret = verify_chip_id(BMX055_ACCEL_I2C_REG_ID, {BMX055_ACCEL_CHIP_ID}); + if (ret == -1) return -1; ret = set_register(BMX055_ACCEL_I2C_REG_PMU, BMX055_ACCEL_NORMAL_MODE); if (ret < 0) { diff --git a/selfdrive/sensord/sensors/bmx055_gyro.cc b/selfdrive/sensord/sensors/bmx055_gyro.cc index 9d70b9e43..2a938044f 100644 --- a/selfdrive/sensord/sensors/bmx055_gyro.cc +++ b/selfdrive/sensord/sensors/bmx055_gyro.cc @@ -12,20 +12,8 @@ BMX055_Gyro::BMX055_Gyro(I2CBus *bus) : I2CSensor(bus) {} int BMX055_Gyro::init() { - int ret = 0; - uint8_t buffer[1]; - - ret =read_register(BMX055_GYRO_I2C_REG_ID, buffer, 1); - if(ret < 0) { - LOGE("Reading chip ID failed: %d", ret); - goto fail; - } - - if(buffer[0] != BMX055_GYRO_CHIP_ID) { - LOGE("Chip ID wrong. Got: %d, Expected %d", buffer[0], BMX055_GYRO_CHIP_ID); - ret = -1; - goto fail; - } + int ret = verify_chip_id(BMX055_GYRO_I2C_REG_ID, {BMX055_GYRO_CHIP_ID}); + if (ret == -1) return -1; ret = set_register(BMX055_GYRO_I2C_REG_LPM1, BMX055_GYRO_NORMAL_MODE); if (ret < 0) { diff --git a/selfdrive/sensord/sensors/bmx055_magn.cc b/selfdrive/sensord/sensors/bmx055_magn.cc index 394b1e83d..2f0d10412 100644 --- a/selfdrive/sensord/sensors/bmx055_magn.cc +++ b/selfdrive/sensord/sensors/bmx055_magn.cc @@ -66,8 +66,6 @@ static int16_t compensate_z(trim_data_t trim_data, int16_t mag_data_z, uint16_t BMX055_Magn::BMX055_Magn(I2CBus *bus) : I2CSensor(bus) {} int BMX055_Magn::init() { - int ret; - uint8_t buffer[1]; uint8_t trim_x1y1[2] = {0}; uint8_t trim_x2y2[2] = {0}; uint8_t trim_xy1xy2[2] = {0}; @@ -78,25 +76,18 @@ int BMX055_Magn::init() { uint8_t trim_xyz1[2] = {0}; // suspend -> sleep - ret = set_register(BMX055_MAGN_I2C_REG_PWR_0, 0x01); + int ret = set_register(BMX055_MAGN_I2C_REG_PWR_0, 0x01); if(ret < 0) { LOGE("Enabling power failed: %d", ret); goto fail; } util::sleep_for(5); // wait until the chip is powered on - // read chip ID - ret = read_register(BMX055_MAGN_I2C_REG_ID, buffer, 1); - if(ret < 0) { - LOGE("Reading chip ID failed: %d", ret); + ret = verify_chip_id(BMX055_MAGN_I2C_REG_ID, {BMX055_MAGN_CHIP_ID}); + if (ret == -1) { goto fail; } - if(buffer[0] != BMX055_MAGN_CHIP_ID) { - LOGE("Chip ID wrong. Got: %d, Expected %d", buffer[0], BMX055_MAGN_CHIP_ID); - return -1; - } - // Load magnetometer trim ret = read_register(BMX055_MAGN_I2C_REG_DIG_X1, trim_x1y1, 2); if(ret < 0) goto fail; diff --git a/selfdrive/sensord/sensors/bmx055_temp.cc b/selfdrive/sensord/sensors/bmx055_temp.cc index bdb34f150..95b8068ac 100644 --- a/selfdrive/sensord/sensors/bmx055_temp.cc +++ b/selfdrive/sensord/sensors/bmx055_temp.cc @@ -9,23 +9,7 @@ BMX055_Temp::BMX055_Temp(I2CBus *bus) : I2CSensor(bus) {} int BMX055_Temp::init() { - int ret = 0; - uint8_t buffer[1]; - - ret = read_register(BMX055_ACCEL_I2C_REG_ID, buffer, 1); - if(ret < 0) { - LOGE("Reading chip ID failed: %d", ret); - goto fail; - } - - if(buffer[0] != BMX055_ACCEL_CHIP_ID) { - LOGE("Chip ID wrong. Got: %d, Expected %d", buffer[0], BMX055_ACCEL_CHIP_ID); - ret = -1; - goto fail; - } - -fail: - return ret; + return verify_chip_id(BMX055_ACCEL_I2C_REG_ID, {BMX055_ACCEL_CHIP_ID}) == -1 ? -1 : 0; } bool BMX055_Temp::get_event(MessageBuilder &msg, uint64_t ts) { diff --git a/selfdrive/sensord/sensors/i2c_sensor.h b/selfdrive/sensord/sensors/i2c_sensor.h index 0de2a9873..08ca6f09c 100644 --- a/selfdrive/sensord/sensors/i2c_sensor.h +++ b/selfdrive/sensord/sensors/i2c_sensor.h @@ -2,12 +2,12 @@ #include #include - #include "cereal/gen/cpp/log.capnp.h" #include "common/i2c.h" #include "common/gpio.h" +#include "common/swaglog.h" #include "selfdrive/sensord/sensors/constants.h" #include "selfdrive/sensord/sensors/sensor.h" @@ -33,4 +33,18 @@ public: virtual int init() = 0; virtual bool get_event(MessageBuilder &msg, uint64_t ts = 0) = 0; virtual int shutdown() = 0; + + int verify_chip_id(uint8_t address, const std::vector &expected_ids) { + uint8_t chip_id = 0; + int ret = read_register(address, &chip_id, 1); + if (ret < 0) { + LOGE("Reading chip ID failed: %d", ret); + return -1; + } + for (int i = 0; i < expected_ids.size(); ++i) { + if (chip_id == expected_ids[i]) return chip_id; + } + LOGE("Chip ID wrong. Got: %d, Expected %d", chip_id, expected_ids[0]); + return -1; + } }; diff --git a/selfdrive/sensord/sensors/lsm6ds3_accel.cc b/selfdrive/sensord/sensors/lsm6ds3_accel.cc index c19e3de7e..c8eeb6e5d 100644 --- a/selfdrive/sensord/sensors/lsm6ds3_accel.cc +++ b/selfdrive/sensord/sensors/lsm6ds3_accel.cc @@ -118,8 +118,6 @@ int LSM6DS3_Accel::self_test(int test_type) { } int LSM6DS3_Accel::init() { - int ret = 0; - uint8_t buffer[1]; uint8_t value = 0; bool do_self_test = false; @@ -128,19 +126,10 @@ int LSM6DS3_Accel::init() { do_self_test = true; } - ret = read_register(LSM6DS3_ACCEL_I2C_REG_ID, buffer, 1); - if(ret < 0) { - LOGE("Reading chip ID failed: %d", ret); - goto fail; - } + int ret = verify_chip_id(LSM6DS3_ACCEL_I2C_REG_ID, {LSM6DS3_ACCEL_CHIP_ID, LSM6DS3TRC_ACCEL_CHIP_ID}); + if (ret == -1) return -1; - if(buffer[0] != LSM6DS3_ACCEL_CHIP_ID && buffer[0] != LSM6DS3TRC_ACCEL_CHIP_ID) { - LOGE("Chip ID wrong. Got: %d, Expected %d", buffer[0], LSM6DS3_ACCEL_CHIP_ID); - ret = -1; - goto fail; - } - - if (buffer[0] == LSM6DS3TRC_ACCEL_CHIP_ID) { + if (ret == LSM6DS3TRC_ACCEL_CHIP_ID) { source = cereal::SensorEventData::SensorSource::LSM6DS3TRC; } diff --git a/selfdrive/sensord/sensors/lsm6ds3_gyro.cc b/selfdrive/sensord/sensors/lsm6ds3_gyro.cc index f306be0fe..d9f5b4cb6 100644 --- a/selfdrive/sensord/sensors/lsm6ds3_gyro.cc +++ b/selfdrive/sensord/sensors/lsm6ds3_gyro.cc @@ -107,8 +107,6 @@ int LSM6DS3_Gyro::self_test(int test_type) { } int LSM6DS3_Gyro::init() { - int ret = 0; - uint8_t buffer[1]; uint8_t value = 0; bool do_self_test = false; @@ -117,19 +115,10 @@ int LSM6DS3_Gyro::init() { do_self_test = true; } - ret = read_register(LSM6DS3_GYRO_I2C_REG_ID, buffer, 1); - if(ret < 0) { - LOGE("Reading chip ID failed: %d", ret); - goto fail; - } + int ret = verify_chip_id(LSM6DS3_GYRO_I2C_REG_ID, {LSM6DS3_GYRO_CHIP_ID, LSM6DS3TRC_GYRO_CHIP_ID}); + if (ret == -1) return -1; - if(buffer[0] != LSM6DS3_GYRO_CHIP_ID && buffer[0] != LSM6DS3TRC_GYRO_CHIP_ID) { - LOGE("Chip ID wrong. Got: %d, Expected %d", buffer[0], LSM6DS3_GYRO_CHIP_ID); - ret = -1; - goto fail; - } - - if (buffer[0] == LSM6DS3TRC_GYRO_CHIP_ID) { + if (ret == LSM6DS3TRC_GYRO_CHIP_ID) { source = cereal::SensorEventData::SensorSource::LSM6DS3TRC; } diff --git a/selfdrive/sensord/sensors/lsm6ds3_temp.cc b/selfdrive/sensord/sensors/lsm6ds3_temp.cc index 7e1c4d4c8..c2e2c83c1 100644 --- a/selfdrive/sensord/sensors/lsm6ds3_temp.cc +++ b/selfdrive/sensord/sensors/lsm6ds3_temp.cc @@ -8,27 +8,13 @@ LSM6DS3_Temp::LSM6DS3_Temp(I2CBus *bus) : I2CSensor(bus) {} int LSM6DS3_Temp::init() { - int ret = 0; - uint8_t buffer[1]; + int ret = verify_chip_id(LSM6DS3_TEMP_I2C_REG_ID, {LSM6DS3_TEMP_CHIP_ID, LSM6DS3TRC_TEMP_CHIP_ID}); + if (ret == -1) return -1; - ret = read_register(LSM6DS3_TEMP_I2C_REG_ID, buffer, 1); - if(ret < 0) { - LOGE("Reading chip ID failed: %d", ret); - goto fail; - } - - if(buffer[0] != LSM6DS3_TEMP_CHIP_ID && buffer[0] != LSM6DS3TRC_TEMP_CHIP_ID) { - LOGE("Chip ID wrong. Got: %d, Expected %d", buffer[0], LSM6DS3_TEMP_CHIP_ID); - ret = -1; - goto fail; - } - - if (buffer[0] == LSM6DS3TRC_TEMP_CHIP_ID) { + if (ret == LSM6DS3TRC_TEMP_CHIP_ID) { source = cereal::SensorEventData::SensorSource::LSM6DS3TRC; } - -fail: - return ret; + return 0; } bool LSM6DS3_Temp::get_event(MessageBuilder &msg, uint64_t ts) { diff --git a/selfdrive/sensord/sensors/mmc5603nj_magn.cc b/selfdrive/sensord/sensors/mmc5603nj_magn.cc index 7a9b7a298..cdb82a4a1 100644 --- a/selfdrive/sensord/sensors/mmc5603nj_magn.cc +++ b/selfdrive/sensord/sensors/mmc5603nj_magn.cc @@ -8,20 +8,8 @@ MMC5603NJ_Magn::MMC5603NJ_Magn(I2CBus *bus) : I2CSensor(bus) {} int MMC5603NJ_Magn::init() { - int ret = 0; - uint8_t buffer[1]; - - ret = read_register(MMC5603NJ_I2C_REG_ID, buffer, 1); - if(ret < 0) { - LOGE("Reading chip ID failed: %d", ret); - goto fail; - } - - if(buffer[0] != MMC5603NJ_CHIP_ID) { - LOGE("Chip ID wrong. Got: %d, Expected %d", buffer[0], MMC5603NJ_CHIP_ID); - ret = -1; - goto fail; - } + int ret = verify_chip_id(MMC5603NJ_I2C_REG_ID, {MMC5603NJ_CHIP_ID}); + if (ret == -1) return -1; // Set 100 Hz ret = set_register(MMC5603NJ_I2C_REG_ODR, 100); diff --git a/selfdrive/sensord/sensors_qcom2.cc b/selfdrive/sensord/sensors_qcom2.cc index 2279cf253..fc8dc8620 100644 --- a/selfdrive/sensord/sensors_qcom2.cc +++ b/selfdrive/sensord/sensors_qcom2.cc @@ -27,7 +27,6 @@ #define I2C_BUS_IMU 1 ExitHandler do_exit; -std::mutex pm_mutex; uint64_t init_ts = 0; void interrupt_loop(std::vector& sensors, diff --git a/selfdrive/test/setup_device_ci.sh b/selfdrive/test/setup_device_ci.sh index 02c7d7663..b7d586a83 100755 --- a/selfdrive/test/setup_device_ci.sh +++ b/selfdrive/test/setup_device_ci.sh @@ -29,6 +29,7 @@ sudo abctl --set_success # patch sshd config sudo mount -o rw,remount / +echo tici-$(cat /proc/cmdline | sed -e 's/^.*androidboot.serialno=//' -e 's/ .*$//') | sudo tee /etc/hostname sudo sed -i "s,/data/params/d/GithubSshKeys,/usr/comma/setup_keys," /etc/ssh/sshd_config sudo systemctl daemon-reload sudo systemctl restart ssh @@ -55,7 +56,7 @@ cd $SOURCE_DIR rm -f .git/index.lock git reset --hard -git fetch --verbose origin $GIT_COMMIT +git fetch --no-tags --no-recurse-submodules -j4 --verbose --depth 1 origin $GIT_COMMIT find . -maxdepth 1 -not -path './.git' -not -name '.' -not -name '..' -exec rm -rf '{}' \; git reset --hard $GIT_COMMIT git checkout $GIT_COMMIT @@ -68,6 +69,7 @@ git lfs pull (ulimit -n 65535 && git lfs prune) echo "git checkout done, t=$SECONDS" +du -hs $SOURCE_DIR $SOURCE_DIR/.git rsync -a --delete $SOURCE_DIR $TEST_DIR diff --git a/selfdrive/thermald/power_monitoring.py b/selfdrive/thermald/power_monitoring.py index 783456908..85e9510eb 100644 --- a/selfdrive/thermald/power_monitoring.py +++ b/selfdrive/thermald/power_monitoring.py @@ -1,24 +1,23 @@ import threading from typing import Optional -from cereal import log from common.params import Params, put_nonblocking from common.realtime import sec_since_boot from system.hardware import HARDWARE from system.swaglog import cloudlog from selfdrive.statsd import statlog -CAR_VOLTAGE_LOW_PASS_K = 0.091 # LPF gain for 5s tau (dt/tau / (dt/tau + 1)) +CAR_VOLTAGE_LOW_PASS_K = 0.011 # LPF gain for 45s tau (dt/tau / (dt/tau + 1)) -# A C2 uses about 1W while idling, and 30h seens like a good shutoff for most cars # While driving, a battery charges completely in about 30-60 minutes CAR_BATTERY_CAPACITY_uWh = 30e6 CAR_CHARGING_RATE_W = 45 -VBATT_PAUSE_CHARGING = 11.0 # Lower limit on the LPF car battery voltage +VBATT_PAUSE_CHARGING = 11.8 # Lower limit on the LPF car battery voltage VBATT_INSTANT_PAUSE_CHARGING = 7.0 # Lower limit on the instant car battery voltage measurements to avoid triggering on instant power loss MAX_TIME_OFFROAD_S = 30*3600 MIN_ON_TIME_S = 3600 +VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S = 60 class PowerMonitoring: def __init__(self): @@ -39,12 +38,12 @@ class PowerMonitoring: self.car_battery_capacity_uWh = max((CAR_BATTERY_CAPACITY_uWh / 10), int(car_battery_capacity_uWh)) # Calculation tick - def calculate(self, peripheralState, ignition): + def calculate(self, voltage: Optional[int], ignition: bool): try: now = sec_since_boot() # If peripheralState is None, we're probably not in a car, so we don't care - if peripheralState is None or peripheralState.pandaType == log.PandaState.PandaType.unknown: + if voltage is None: with self.integration_lock: self.last_measurement_time = None self.next_pulsed_measurement_time = None @@ -52,8 +51,8 @@ class PowerMonitoring: return # Low-pass battery voltage - self.car_voltage_instant_mV = peripheralState.voltage - self.car_voltage_mV = ((peripheralState.voltage * CAR_VOLTAGE_LOW_PASS_K) + (self.car_voltage_mV * (1 - CAR_VOLTAGE_LOW_PASS_K))) + self.car_voltage_instant_mV = voltage + self.car_voltage_mV = ((voltage * CAR_VOLTAGE_LOW_PASS_K) + (self.car_voltage_mV * (1 - CAR_VOLTAGE_LOW_PASS_K))) statlog.gauge("car_voltage", self.car_voltage_mV / 1e3) # Cap the car battery power and save it in a param every 10-ish seconds @@ -115,8 +114,12 @@ class PowerMonitoring: now = sec_since_boot() should_shutdown = False - should_shutdown |= (now - offroad_timestamp) > MAX_TIME_OFFROAD_S - should_shutdown |= (self.car_voltage_mV < (VBATT_PAUSE_CHARGING * 1e3)) and (self.car_voltage_instant_mV > (VBATT_INSTANT_PAUSE_CHARGING * 1e3)) + offroad_time = (now - offroad_timestamp) + low_voltage_shutdown = (self.car_voltage_mV < (VBATT_PAUSE_CHARGING * 1e3) and + self.car_voltage_instant_mV > (VBATT_INSTANT_PAUSE_CHARGING * 1e3) and + offroad_time > VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S) + should_shutdown |= offroad_time > MAX_TIME_OFFROAD_S + should_shutdown |= low_voltage_shutdown should_shutdown |= (self.car_battery_capacity_uWh <= 0) should_shutdown &= not ignition should_shutdown &= (not self.params.get_bool("DisablePowerDown")) diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index 89b81f06e..eedeff31f 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -347,7 +347,8 @@ def thermald_thread(end_event, hw_queue): off_ts = sec_since_boot() # Offroad power monitoring - power_monitor.calculate(peripheralState, onroad_conditions["ignition"]) + voltage = None if peripheralState.pandaType == log.PandaState.PandaType.unknown else peripheralState.voltage + power_monitor.calculate(voltage, onroad_conditions["ignition"]) msg.deviceState.offroadPowerUsageUwh = power_monitor.get_power_used() msg.deviceState.carBatteryCapacityUwh = max(0, power_monitor.get_car_battery_capacity()) current_power_draw = HARDWARE.get_current_power_draw() diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 669c21474..0f28f5ccc 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -1,4 +1,5 @@ import os +import json Import('qt_env', 'arch', 'common', 'messaging', 'visionipc', 'cereal', 'transformations') @@ -41,6 +42,7 @@ assets_src = "#selfdrive/assets/assets.qrc" qt_env.Command(assets, assets_src, f"rcc $SOURCES -o $TARGET") qt_env.Depends(assets, Glob('#selfdrive/assets/*', exclude=[assets, assets_src, "#selfdrive/assets/assets.o"])) asset_obj = qt_env.Object("assets", assets) +Export('asset_obj') # build soundd qt_env.Program("soundd/_soundd", ["soundd/main.cc", "soundd/sound.cc"], LIBS=qt_libs) @@ -66,11 +68,18 @@ if GetOption('test'): # build translation files -translation_sources = Glob("#selfdrive/ui/translations/*.ts", strings=True) +with open(File("translations/languages.json").abspath) as f: + languages = json.loads(f.read()) +translation_sources = [f"#selfdrive/ui/translations/{l}.ts" for l in languages.values()] translation_targets = [src.replace(".ts", ".qm") for src in translation_sources] -lrelease = 'third_party/qt5/larch64/bin/lrelease' if arch == 'larch64' else 'lrelease' -qt_env.Command(translation_targets, translation_sources, f"{lrelease} $SOURCES") +lrelease_bin = 'third_party/qt5/larch64/bin/lrelease' if arch == 'larch64' else 'lrelease' +lupdate = qt_env.Command(translation_sources, qt_src, "selfdrive/ui/update_translations.py") +lrelease = qt_env.Command(translation_targets, translation_sources, f"{lrelease_bin} $SOURCES") +qt_env.Depends(lrelease, lupdate) +qt_env.NoClean(translation_sources) +qt_env.Precious(translation_sources) +qt_env.NoCache(lupdate) # setup and factory resetter if GetOption('extras'): diff --git a/selfdrive/ui/installer/installer.cc b/selfdrive/ui/installer/installer.cc new file mode 100644 index 000000000..1af72c04d --- /dev/null +++ b/selfdrive/ui/installer/installer.cc @@ -0,0 +1,220 @@ +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include "selfdrive/ui/installer/installer.h" +#include "selfdrive/ui/qt/util.h" +#include "selfdrive/ui/qt/qt_window.h" + +std::string get_str(std::string const s) { + std::string::size_type pos = s.find('?'); + assert(pos != std::string::npos); + return s.substr(0, pos); +} + +// Leave some extra space for the fork installer +const std::string GIT_URL = get_str("https://github.com/commaai/openpilot.git" "? "); +const std::string BRANCH_STR = get_str(BRANCH "? "); + +#define GIT_SSH_URL "git@github.com:commaai/openpilot.git" +#define CONTINUE_PATH "/data/continue.sh" + +const QString CACHE_PATH = "/data/openpilot.cache"; + +#define INSTALL_PATH "/data/openpilot" +#define TMP_INSTALL_PATH "/data/tmppilot" + +extern const uint8_t str_continue[] asm("_binary_selfdrive_ui_installer_continue_" BRAND "_sh_start"); +extern const uint8_t str_continue_end[] asm("_binary_selfdrive_ui_installer_continue_" BRAND "_sh_end"); + +bool time_valid() { + time_t rawtime; + time(&rawtime); + + struct tm * sys_time = gmtime(&rawtime); + return (1900 + sys_time->tm_year) >= 2020; +} + +void run(const char* cmd) { + int err = std::system(cmd); + assert(err == 0); +} + +Installer::Installer(QWidget *parent) : QWidget(parent) { + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setContentsMargins(150, 290, 150, 150); + layout->setSpacing(0); + + QLabel *title = new QLabel(tr("Installing...")); + title->setStyleSheet("font-size: 90px; font-weight: 600;"); + layout->addWidget(title, 0, Qt::AlignTop); + + layout->addSpacing(170); + + bar = new QProgressBar(); + bar->setRange(0, 100); + bar->setTextVisible(false); + bar->setFixedHeight(72); + layout->addWidget(bar, 0, Qt::AlignTop); + + layout->addSpacing(30); + + val = new QLabel("0%"); + val->setStyleSheet("font-size: 70px; font-weight: 300;"); + layout->addWidget(val, 0, Qt::AlignTop); + + layout->addStretch(); + + QObject::connect(&proc, QOverload::of(&QProcess::finished), this, &Installer::cloneFinished); + QObject::connect(&proc, &QProcess::readyReadStandardError, this, &Installer::readProgress); + + QTimer::singleShot(100, this, &Installer::doInstall); + + setStyleSheet(R"( + * { + font-family: Inter; + color: white; + background-color: black; + } + QProgressBar { + border: none; + background-color: #292929; + } + QProgressBar::chunk { + background-color: #364DEF; + } + )"); +} + +void Installer::updateProgress(int percent) { + bar->setValue(percent); + val->setText(QString("%1%").arg(percent)); + update(); +} + +void Installer::doInstall() { + // wait for valid time + while (!time_valid()) { + usleep(500 * 1000); + qDebug() << "Waiting for valid time"; + } + + // cleanup previous install attempts + run("rm -rf " TMP_INSTALL_PATH " " INSTALL_PATH); + + // do the install + if (QDir(CACHE_PATH).exists()) { + cachedFetch(CACHE_PATH); + } else { + freshClone(); + } +} + +void Installer::freshClone() { + qDebug() << "Doing fresh clone"; + proc.start("git", {"clone", "--progress", GIT_URL.c_str(), "-b", BRANCH_STR.c_str(), + "--depth=1", "--recurse-submodules", TMP_INSTALL_PATH}); +} + +void Installer::cachedFetch(const QString &cache) { + qDebug() << "Fetching with cache: " << cache; + + run(QString("cp -rp %1 %2").arg(cache, TMP_INSTALL_PATH).toStdString().c_str()); + int err = chdir(TMP_INSTALL_PATH); + assert(err == 0); + run(("git remote set-branches --add origin " + BRANCH_STR).c_str()); + + updateProgress(10); + + proc.setWorkingDirectory(TMP_INSTALL_PATH); + proc.start("git", {"fetch", "--progress", "origin", BRANCH_STR.c_str()}); +} + +void Installer::readProgress() { + const QVector> stages = { + // prefix, weight in percentage + {"Receiving objects: ", 91}, + {"Resolving deltas: ", 2}, + {"Updating files: ", 7}, + }; + + auto line = QString(proc.readAllStandardError()); + + int base = 0; + for (const QPair kv : stages) { + if (line.startsWith(kv.first)) { + auto perc = line.split(kv.first)[1].split("%")[0]; + int p = base + int(perc.toFloat() / 100. * kv.second); + updateProgress(p); + break; + } + base += kv.second; + } +} + +void Installer::cloneFinished(int exitCode, QProcess::ExitStatus exitStatus) { + qDebug() << "git finished with " << exitCode; + assert(exitCode == 0); + + updateProgress(100); + + // ensure correct branch is checked out + int err = chdir(TMP_INSTALL_PATH); + assert(err == 0); + run(("git checkout " + BRANCH_STR).c_str()); + run(("git reset --hard origin/" + BRANCH_STR).c_str()); + run("git submodule update --init"); + + // move into place + run("mv " TMP_INSTALL_PATH " " INSTALL_PATH); + +#ifdef INTERNAL + run("mkdir -p /data/params/d/"); + + std::map params = { + {"SshEnabled", "1"}, + {"RecordFrontLock", "1"}, + {"GithubSshKeys", SSH_KEYS}, + }; + for (const auto& [key, value] : params) { + std::ofstream param; + param.open("/data/params/d/" + key); + param << value; + param.close(); + } + run("cd " INSTALL_PATH " && " + "git remote set-url origin --push " GIT_SSH_URL " && " + "git config --replace-all remote.origin.fetch \"+refs/heads/*:refs/remotes/origin/*\""); +#endif + + // write continue.sh + FILE *of = fopen("/data/continue.sh.new", "wb"); + assert(of != NULL); + + size_t num = str_continue_end - str_continue; + size_t num_written = fwrite(str_continue, 1, num, of); + assert(num == num_written); + fclose(of); + + run("chmod +x /data/continue.sh.new"); + run("mv /data/continue.sh.new " CONTINUE_PATH); + + // wait for the installed software's UI to take over + QTimer::singleShot(60 * 1000, &QCoreApplication::quit); +} + +int main(int argc, char *argv[]) { + initApp(argc, argv); + QApplication a(argc, argv); + Installer installer; + setMainWindow(&installer); + return a.exec(); +} diff --git a/selfdrive/ui/installer/installer.h b/selfdrive/ui/installer/installer.h new file mode 100644 index 000000000..de3af0ff3 --- /dev/null +++ b/selfdrive/ui/installer/installer.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include +#include + +class Installer : public QWidget { + Q_OBJECT + +public: + explicit Installer(QWidget *parent = 0); + +private slots: + void updateProgress(int percent); + + void readProgress(); + void cloneFinished(int exitCode, QProcess::ExitStatus exitStatus); + +private: + QLabel *val; + QProgressBar *bar; + QProcess proc; + + void doInstall(); + void freshClone(); + void cachedFetch(const QString &cache); +}; diff --git a/selfdrive/ui/qt/maps/map.cc b/selfdrive/ui/qt/maps/map.cc index b5519dacc..dd30b5fef 100644 --- a/selfdrive/ui/qt/maps/map.cc +++ b/selfdrive/ui/qt/maps/map.cc @@ -47,7 +47,7 @@ MapWindow::MapWindow(const QMapboxGLSettings &settings) : m_settings(settings), map_eta->setVisible(false); auto last_gps_position = coordinate_from_param("LastGPSPosition"); - if (last_gps_position) { + if (last_gps_position.has_value()) { last_position = *last_gps_position; } @@ -82,6 +82,7 @@ void MapWindow::initLayers() { m_map->setPaintProperty("navLayer", "line-color", QColor("#31a1ee")); m_map->setPaintProperty("navLayer", "line-width", 7.5); m_map->setLayoutProperty("navLayer", "line-cap", "round"); + m_map->addAnnotationIcon("default_marker", QImage("../assets/navigation/default_marker.svg")); } if (!m_map->layerExists("carPosLayer")) { qDebug() << "Initializing carPosLayer"; @@ -124,7 +125,8 @@ void MapWindow::updateState(const UIState &s) { } } - if (sm.updated("gnssMeasurements")) { + // TODO should check a valid/status flag + if (sm.updated("gnssMeasurements") && sm["gnssMeasurements"].getGnssMeasurements().getGpsWeek() > 0){ auto laikad_location = sm["gnssMeasurements"].getGnssMeasurements(); auto laikad_pos = laikad_location.getPositionECEF(); auto laikad_pos_ecef = laikad_pos.getValue(); @@ -220,7 +222,6 @@ void MapWindow::updateState(const UIState &s) { emit instructionsChanged(i); } } else { - m_map->setPitch(MIN_PITCH); clearRoute(); } } @@ -237,6 +238,7 @@ void MapWindow::updateState(const UIState &s) { m_map->setLayoutProperty("navLayer", "visibility", "visible"); route_rcv_frame = sm.rcv_frame("navRoute"); + updateDestinationMarker(); } } @@ -274,6 +276,7 @@ void MapWindow::clearRoute() { if (!m_map.isNull()) { m_map->setLayoutProperty("navLayer", "visibility", "none"); m_map->setPitch(MIN_PITCH); + updateDestinationMarker(); } map_instructions->hideIfNoError(); @@ -361,6 +364,19 @@ void MapWindow::offroadTransition(bool offroad) { last_bearing = {}; } +void MapWindow::updateDestinationMarker() { + if (marker_id != -1) { + m_map->removeAnnotation(marker_id); + marker_id = -1; + } + + auto nav_dest = coordinate_from_param("NavDestination"); + if (nav_dest.has_value()) { + auto ano = QMapbox::SymbolAnnotation {*nav_dest, "default_marker"}; + marker_id = m_map->addAnnotation(QVariant::fromValue(ano)); + } +} + MapInstructions::MapInstructions(QWidget * parent) : QWidget(parent) { is_rhd = Params().getBool("IsRhdDetected"); QHBoxLayout *main_layout = new QHBoxLayout(this); diff --git a/selfdrive/ui/qt/maps/map.h b/selfdrive/ui/qt/maps/map.h index c3d5e9253..0d8b93a5f 100644 --- a/selfdrive/ui/qt/maps/map.h +++ b/selfdrive/ui/qt/maps/map.h @@ -79,6 +79,7 @@ private: QMapboxGLSettings m_settings; QScopedPointer m_map; + QMapbox::AnnotationID marker_id = -1; void initLayers(); @@ -111,6 +112,7 @@ private: MapETA* map_eta; void clearRoute(); + void updateDestinationMarker(); uint64_t route_rcv_frame = 0; private slots: diff --git a/selfdrive/ui/qt/offroad/driverview.cc b/selfdrive/ui/qt/offroad/driverview.cc index 1377bb3b2..2f366a9b3 100644 --- a/selfdrive/ui/qt/offroad/driverview.cc +++ b/selfdrive/ui/qt/offroad/driverview.cc @@ -22,6 +22,7 @@ DriverViewWindow::DriverViewWindow(QWidget* parent) : QWidget(parent) { } void DriverViewWindow::mouseReleaseEvent(QMouseEvent* e) { + cameraView->stopVipcThread(); emit done(); } @@ -35,7 +36,6 @@ void DriverViewScene::showEvent(QShowEvent* event) { } void DriverViewScene::hideEvent(QHideEvent* event) { - // TODO: stop vipc thread ? params.putBool("IsDriverViewEnabled", false); } diff --git a/selfdrive/ui/qt/offroad/experimental_mode.cc b/selfdrive/ui/qt/offroad/experimental_mode.cc index f73149cdf..b99220c1d 100644 --- a/selfdrive/ui/qt/offroad/experimental_mode.cc +++ b/selfdrive/ui/qt/offroad/experimental_mode.cc @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "selfdrive/ui/ui.h" diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 01cb0ea72..63b87149d 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -120,29 +120,29 @@ void TogglesPanel::showEvent(QShowEvent *event) { void TogglesPanel::updateToggles() { auto e2e_toggle = toggles["ExperimentalMode"]; auto op_long_toggle = toggles["ExperimentalLongitudinalEnabled"]; - const QString e2e_description = tr("\ - openpilot defaults to driving in chill mode.\ - Experimental mode enables alpha-level features that aren't ready for chill mode. \ - Experimental features are listed below: \ -
\ -

🌮 End-to-End Longitudinal Control 🌮

\ - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. \ - Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. \ -
\ -

New Driving Visualization

\ - The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner.\ - "); + const QString e2e_description = QString("%1
" + "

%2


" + "%3
" + "

%4


" + "%5") + .arg(tr("openpilot defaults to driving in chill mode. Experimental mode enables alpha-level features that aren't ready for chill mode. Experimental features are listed below:")) + .arg(tr("🌮 End-to-End Longitudinal Control 🌮")) + .arg(tr("Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. " + "Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected.")) + .arg(tr("New Driving Visualization")) + .arg(tr("The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner.")); + const bool is_release = params.getBool("IsReleaseBranch"); auto cp_bytes = params.get("CarParamsPersistent"); if (!cp_bytes.empty()) { AlignedBuffer aligned_buf; capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size())); cereal::CarParams::Reader CP = cmsg.getRoot(); - if (!CP.getExperimentalLongitudinalAvailable()) { + if (!CP.getExperimentalLongitudinalAvailable() || is_release) { params.remove("ExperimentalLongitudinalEnabled"); } - op_long_toggle->setVisible(CP.getExperimentalLongitudinalAvailable()); + op_long_toggle->setVisible(CP.getExperimentalLongitudinalAvailable() && !is_release); const bool op_long = CP.getOpenpilotLongitudinalControl() && !CP.getExperimentalLongitudinalAvailable(); const bool exp_long_enabled = CP.getExperimentalLongitudinalAvailable() && params.getBool("ExperimentalLongitudinalEnabled"); @@ -155,9 +155,18 @@ void TogglesPanel::updateToggles() { e2e_toggle->setEnabled(false); params.remove("ExperimentalMode"); - const QString no_long = tr("Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control."); - const QString exp_long = tr("Enable experimental longitudinal control to allow experimental mode."); - e2e_toggle->setDescription("" + (CP.getExperimentalLongitudinalAvailable() ? exp_long : no_long) + "

" + e2e_description); + const QString unavailable = tr("Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control."); + + QString long_desc = unavailable + " " + \ + tr("openpilot longitudinal control may come in a future update."); + if (CP.getExperimentalLongitudinalAvailable()) { + if (is_release) { + long_desc = unavailable + " " + tr("An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches."); + } else { + long_desc = tr("Enable experimental longitudinal control to allow Experimental mode."); + } + } + e2e_toggle->setDescription("" + long_desc + "

" + e2e_description); } e2e_toggle->refresh(); diff --git a/selfdrive/ui/qt/offroad/software_settings.cc b/selfdrive/ui/qt/offroad/software_settings.cc index 12d62e63f..6db6a6cdf 100644 --- a/selfdrive/ui/qt/offroad/software_settings.cc +++ b/selfdrive/ui/qt/offroad/software_settings.cc @@ -54,7 +54,7 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) { connect(targetBranchBtn, &ButtonControl::clicked, [=]() { auto current = params.get("GitBranch"); QStringList branches = QString::fromStdString(params.get("UpdaterAvailableBranches")).split(","); - for (QString b : {current.c_str(), "devel-staging", "devel", "master-ci", "master"}) { + for (QString b : {current.c_str(), "devel-staging", "devel", "nightly", "master-ci", "master"}) { auto i = branches.indexOf(b); if (i >= 0) { branches.removeAt(i); diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index 50f891dd5..33b1ea8e2 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -31,6 +31,11 @@ OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) { split->insertWidget(0, arCam); } + if (getenv("MAP_RENDER_VIEW")) { + CameraWidget *map_render = new CameraWidget("navd", VISION_STREAM_MAP, false, this); + split->insertWidget(0, map_render); + } + stacked_layout->addWidget(split_wrapper); alerts = new OnroadAlerts(this); @@ -170,12 +175,63 @@ void OnroadAlerts::paintEvent(QPaintEvent *event) { } +ExperimentalButton::ExperimentalButton(QWidget *parent) : QPushButton(parent) { + setVisible(false); + setFixedSize(btn_size, btn_size); + setCheckable(true); + + params = Params(); + engage_img = loadPixmap("../assets/img_chffr_wheel.png", {img_size, img_size}); + experimental_img = loadPixmap("../assets/img_experimental.svg", {img_size, img_size}); + + QObject::connect(this, &QPushButton::toggled, [=](bool checked) { + params.putBool("ExperimentalMode", checked); + }); +} + +void ExperimentalButton::updateState(const UIState &s) { + const SubMaster &sm = *(s.sm); + + // button is "visible" if engageable or enabled + const auto cs = sm["controlsState"].getControlsState(); + setVisible(cs.getEngageable() || cs.getEnabled()); + + // button is "checked" if experimental mode is enabled + setChecked(sm["controlsState"].getControlsState().getExperimentalMode()); + + // disable button when experimental mode is not available, or has not been confirmed for the first time + const auto cp = sm["carParams"].getCarParams(); + const bool experimental_mode_available = cp.getExperimentalLongitudinalAvailable() ? params.getBool("ExperimentalLongitudinalEnabled") : cp.getOpenpilotLongitudinalControl(); + setEnabled(params.getBool("ExperimentalModeConfirmed") && experimental_mode_available); +} + +void ExperimentalButton::paintEvent(QPaintEvent *event) { + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing); + + QPoint center(btn_size / 2, btn_size / 2); + QPixmap img = isChecked() ? experimental_img : engage_img; + + p.setOpacity(1.0); + p.setPen(Qt::NoPen); + p.setBrush(QColor(0, 0, 0, 166)); + p.drawEllipse(center, btn_size / 2, btn_size / 2); + p.setOpacity(isDown() ? 0.8 : 1.0); + p.drawPixmap((btn_size - img_size) / 2, (btn_size - img_size) / 2, img); +} + + AnnotatedCameraWidget::AnnotatedCameraWidget(VisionStreamType type, QWidget* parent) : fps_filter(UI_FREQ, 3, 1. / UI_FREQ), CameraWidget("camerad", type, true, parent) { pm = std::make_unique>({"uiDebug"}); - engage_img = loadPixmap("../assets/img_chffr_wheel.png", {img_size, img_size}); - experimental_img = loadPixmap("../assets/img_experimental.svg", {img_size - 5, img_size - 5}); - dm_img = loadPixmap("../assets/img_driver_face.png", {img_size, img_size}); + QVBoxLayout *main_layout = new QVBoxLayout(this); + main_layout->setMargin(bdr_s); + main_layout->setSpacing(0); + + experimental_btn = new ExperimentalButton(this); + main_layout->addWidget(experimental_btn, 0, Qt::AlignTop | Qt::AlignRight); + + dm_img = loadPixmap("../assets/img_driver_face.png", {img_size + 5, img_size + 5}); } void AnnotatedCameraWidget::updateState(const UIState &s) { @@ -204,7 +260,7 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { v_ego_cluster_seen = true; } float cur_speed = cs_alive ? std::max(0.0, v_ego) : 0.0; - cur_speed *= s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH; + cur_speed *= s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH; auto speed_limit_sign = sm["navInstruction"].getNavInstruction().getSpeedLimitSign(); float speed_limit = nav_alive ? sm["navInstruction"].getNavInstruction().getSpeedLimit() : 0.0; @@ -219,15 +275,20 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { setProperty("speed", cur_speed); setProperty("setSpeed", set_speed); setProperty("speedUnit", s.scene.is_metric ? tr("km/h") : tr("mph")); - setProperty("hideDM", cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE); + setProperty("hideDM", (cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE)); setProperty("status", s.status); - // update engageability and DM icons at 2Hz + // update engageability/experimental mode button + experimental_btn->updateState(s); + + // update DM icons at 2Hz if (sm.frame % (UI_FREQ / 2) == 0) { - setProperty("engageable", cs.getEngageable() || cs.getEnabled()); setProperty("dmActive", sm["driverMonitoringState"].getDriverMonitoringState().getIsActiveMode()); setProperty("rightHandDM", sm["driverMonitoringState"].getDriverMonitoringState().getIsRHD()); } + + // DM icon transition + dm_fade_state = fmax(0.0, fmin(1.0, dm_fade_state+0.2*(0.5-(float)(dmActive)))); } void AnnotatedCameraWidget::drawHud(QPainter &p) { @@ -377,19 +438,6 @@ void AnnotatedCameraWidget::drawHud(QPainter &p) { configFont(p, "Inter", 66, "Regular"); drawText(p, rect().center().x(), 290, speedUnit, 200); - // engage-ability icon - if (engageable) { - SubMaster &sm = *(uiState()->sm); - drawIcon(p, rect().right() - radius / 2 - bdr_s * 2, radius / 2 + int(bdr_s * 1.5), - sm["controlsState"].getControlsState().getExperimentalMode() ? experimental_img : engage_img, blackColor(166), 1.0); - } - - // dm icon - if (!hideDM) { - int dm_icon_x = rightHandDM ? rect().right() - radius / 2 - (bdr_s * 2) : radius / 2 + (bdr_s * 2); - drawIcon(p, dm_icon_x, rect().bottom() - footer_h / 2, - dm_img, blackColor(70), dmActive ? 1.0 : 0.2); - } p.restore(); } @@ -409,7 +457,7 @@ void AnnotatedCameraWidget::drawIcon(QPainter &p, int x, int y, QPixmap &img, QB p.setOpacity(1.0); // bg dictates opacity of ellipse p.setPen(Qt::NoPen); p.setBrush(bg); - p.drawEllipse(x - radius / 2, y - radius / 2, radius, radius); + p.drawEllipse(x - btn_size / 2, y - btn_size / 2, btn_size, btn_size); p.setOpacity(opacity); p.drawPixmap(x - img.size().width() / 2, y - img.size().height() / 2, img); } @@ -493,13 +541,56 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) { painter.restore(); } -void AnnotatedCameraWidget::drawLead(QPainter &painter, const cereal::ModelDataV2::LeadDataV3::Reader &lead_data, const QPointF &vd) { +void AnnotatedCameraWidget::drawDriverState(QPainter &painter, const UIState *s) { + const UIScene &scene = s->scene; + + painter.save(); + + // base icon + int x = rightHandDM ? rect().right() - (btn_size - 24) / 2 - (bdr_s * 2) : (btn_size - 24) / 2 + (bdr_s * 2); + int y = rect().bottom() - footer_h / 2; + float opacity = dmActive ? 0.65 : 0.2; + drawIcon(painter, x, y, dm_img, blackColor(0), opacity); + + // circle background + painter.setOpacity(1.0); + painter.setPen(Qt::NoPen); + painter.setBrush(blackColor(70)); + painter.drawEllipse(x - btn_size / 2, y - btn_size / 2, btn_size, btn_size); + + // face + QPointF face_kpts_draw[std::size(default_face_kpts_3d)]; + float kp; + for (int i = 0; i < std::size(default_face_kpts_3d); ++i) { + kp = (scene.face_kpts_draw[i].v[2] - 8) / 120 + 1.0; + face_kpts_draw[i] = QPointF(scene.face_kpts_draw[i].v[0] * kp + x, scene.face_kpts_draw[i].v[1] * kp + y); + } + + painter.setPen(QPen(QColor::fromRgbF(1.0, 1.0, 1.0, opacity), 5.2, Qt::SolidLine, Qt::RoundCap)); + painter.drawPolyline(face_kpts_draw, std::size(default_face_kpts_3d)); + + // tracking arcs + const int arc_l = 133; + const float arc_t_default = 6.7; + const float arc_t_extend = 12.0; + QColor arc_color = QColor::fromRgbF(0.09, 0.945, 0.26, 0.4*(1.0-dm_fade_state)*(s->engaged())); + float delta_x = -scene.driver_pose_sins[1] * arc_l / 2; + float delta_y = -scene.driver_pose_sins[0] * arc_l / 2; + painter.setPen(QPen(arc_color, arc_t_default+arc_t_extend*fmin(1.0, scene.driver_pose_diff[1] * 5.0), Qt::SolidLine, Qt::RoundCap)); + painter.drawArc(QRectF(std::fmin(x + delta_x, x), y - arc_l / 2, fabs(delta_x), arc_l), (scene.driver_pose_sins[1]>0 ? 90 : -90) * 16, 180 * 16); + painter.setPen(QPen(arc_color, arc_t_default+arc_t_extend*fmin(1.0, scene.driver_pose_diff[0] * 5.0), Qt::SolidLine, Qt::RoundCap)); + painter.drawArc(QRectF(x - arc_l / 2, std::fmin(y + delta_y, y), arc_l, fabs(delta_y)), (scene.driver_pose_sins[0]>0 ? 0 : 180) * 16, 180 * 16); + + painter.restore(); +} + +void AnnotatedCameraWidget::drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd) { painter.save(); const float speedBuff = 10.; const float leadBuff = 40.; - const float d_rel = lead_data.getX()[0]; - const float v_rel = lead_data.getV()[0]; + const float d_rel = lead_data.getDRel(); + const float v_rel = lead_data.getVRel(); float fillAlpha = 0; if (d_rel < leadBuff) { @@ -534,6 +625,7 @@ void AnnotatedCameraWidget::paintGL() { SubMaster &sm = *(s->sm); const double start_draw_t = millis_since_boot(); const cereal::ModelDataV2::Reader &model = sm["modelV2"].getModelV2(); + const cereal::RadarState::Reader &radar_state = sm["radarState"].getRadarState(); // draw camera frame { @@ -584,23 +676,30 @@ void AnnotatedCameraWidget::paintGL() { if (sm.rcv_frame("modelV2") > s->scene.started_frame) { update_model(s, sm["modelV2"].getModelV2()); if (sm.rcv_frame("radarState") > s->scene.started_frame) { - update_leads(s, sm["radarState"].getRadarState(), sm["modelV2"].getModelV2().getPosition()); + update_leads(s, radar_state, sm["modelV2"].getModelV2().getPosition()); } } drawLaneLines(painter, s); if (s->scene.longitudinal_control) { - const auto leads = model.getLeadsV3(); - if (leads[0].getProb() > .5) { - drawLead(painter, leads[0], s->scene.lead_vertices[0]); + auto lead_one = radar_state.getLeadOne(); + auto lead_two = radar_state.getLeadTwo(); + if (lead_one.getStatus()) { + drawLead(painter, lead_one, s->scene.lead_vertices[0]); } - if (leads[1].getProb() > .5 && (std::abs(leads[1].getX()[0] - leads[0].getX()[0]) > 3.0)) { - drawLead(painter, leads[1], s->scene.lead_vertices[1]); + if (lead_two.getStatus() && (std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0)) { + drawLead(painter, lead_two, s->scene.lead_vertices[1]); } } } + // DMoji + if (!hideDM && (sm.rcv_frame("driverStateV2") > s->scene.started_frame)) { + update_dmonitoring(s, sm["driverStateV2"].getDriverStateV2(), dm_fade_state, rightHandDM); + drawDriverState(painter, s); + } + drawHud(painter); double cur_draw_t = millis_since_boot(); diff --git a/selfdrive/ui/qt/onroad.h b/selfdrive/ui/qt/onroad.h index 9e1835597..73c2a3789 100644 --- a/selfdrive/ui/qt/onroad.h +++ b/selfdrive/ui/qt/onroad.h @@ -1,11 +1,16 @@ #pragma once +#include #include #include #include "common/util.h" -#include "selfdrive/ui/qt/widgets/cameraview.h" #include "selfdrive/ui/ui.h" +#include "selfdrive/ui/qt/widgets/cameraview.h" + + +const int btn_size = 192; +const int img_size = (btn_size / 4) * 3; // ***** onroad widgets ***** @@ -24,6 +29,21 @@ private: Alert alert = {}; }; +class ExperimentalButton : public QPushButton { + Q_OBJECT + +public: + explicit ExperimentalButton(QWidget *parent = 0); + void updateState(const UIState &s); + +private: + void paintEvent(QPaintEvent *event) override; + + Params params; + QPixmap engage_img; + QPixmap experimental_img; +}; + // container window for the NVG UI class AnnotatedCameraWidget : public CameraWidget { Q_OBJECT @@ -36,7 +56,6 @@ class AnnotatedCameraWidget : public CameraWidget { Q_PROPERTY(bool has_us_speed_limit MEMBER has_us_speed_limit); Q_PROPERTY(bool is_metric MEMBER is_metric); - Q_PROPERTY(bool engageable MEMBER engageable); Q_PROPERTY(bool dmActive MEMBER dmActive); Q_PROPERTY(bool hideDM MEMBER hideDM); Q_PROPERTY(bool rightHandDM MEMBER rightHandDM); @@ -50,21 +69,18 @@ private: void drawIcon(QPainter &p, int x, int y, QPixmap &img, QBrush bg, float opacity); void drawText(QPainter &p, int x, int y, const QString &text, int alpha = 255); - QPixmap engage_img; - QPixmap experimental_img; + ExperimentalButton *experimental_btn; QPixmap dm_img; - const int radius = 192; - const int img_size = (radius / 2) * 1.5; float speed; QString speedUnit; float setSpeed; float speedLimit; bool is_cruise_set = false; bool is_metric = false; - bool engageable = false; bool dmActive = false; bool hideDM = false; bool rightHandDM = false; + float dm_fade_state = 1.0; bool has_us_speed_limit = false; bool has_eu_speed_limit = false; bool v_ego_cluster_seen = false; @@ -80,8 +96,9 @@ protected: void showEvent(QShowEvent *event) override; void updateFrameMat() override; void drawLaneLines(QPainter &painter, const UIState *s); - void drawLead(QPainter &painter, const cereal::ModelDataV2::LeadDataV3::Reader &lead_data, const QPointF &vd); + void drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd); void drawHud(QPainter &p); + void drawDriverState(QPainter &painter, const UIState *s); inline QColor redColor(int alpha = 255) { return QColor(201, 34, 49, alpha); } inline QColor whiteColor(int alpha = 255) { return QColor(255, 255, 255, alpha); } inline QColor blackColor(int alpha = 255) { return QColor(0, 0, 0, alpha); } diff --git a/selfdrive/ui/qt/setup/reset.cc b/selfdrive/ui/qt/setup/reset.cc new file mode 100644 index 000000000..582217c1d --- /dev/null +++ b/selfdrive/ui/qt/setup/reset.cc @@ -0,0 +1,116 @@ +#include +#include +#include +#include +#include +#include + +#include "selfdrive/ui/qt/qt_window.h" +#include "selfdrive/ui/qt/setup/reset.h" + +#define NVME "/dev/nvme0n1" +#define USERDATA "/dev/disk/by-partlabel/userdata" + +void Reset::doReset() { + // best effort to wipe nvme and sd card + std::system("sudo umount " NVME); + std::system("yes | sudo mkfs.ext4 " NVME); + + // we handle two cases here + // * user-prompted factory reset + // * recovering from a corrupt userdata by formatting + int rm = std::system("sudo rm -rf /data/*"); + std::system("sudo umount " USERDATA); + int fmt = std::system("yes | sudo mkfs.ext4 " USERDATA); + + if (rm == 0 || fmt == 0) { + std::system("sudo reboot"); + } + body->setText(tr("Reset failed. Reboot to try again.")); + rebootBtn->show(); +} + +void Reset::confirm() { + const QString confirm_txt = tr("Are you sure you want to reset your device?"); + if (body->text() != confirm_txt) { + body->setText(confirm_txt); + } else { + body->setText(tr("Resetting device...")); + rejectBtn->hide(); + rebootBtn->hide(); + confirmBtn->hide(); +#ifdef __aarch64__ + QTimer::singleShot(100, this, &Reset::doReset); +#endif + } +} + +Reset::Reset(bool recover, QWidget *parent) : QWidget(parent) { + QVBoxLayout *main_layout = new QVBoxLayout(this); + main_layout->setContentsMargins(45, 220, 45, 45); + main_layout->setSpacing(0); + + QLabel *title = new QLabel(tr("System Reset")); + title->setStyleSheet("font-size: 90px; font-weight: 600;"); + main_layout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft); + + main_layout->addSpacing(60); + + body = new QLabel(tr("System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot.")); + body->setWordWrap(true); + body->setStyleSheet("font-size: 80px; font-weight: light;"); + main_layout->addWidget(body, 1, Qt::AlignTop | Qt::AlignLeft); + + QHBoxLayout *blayout = new QHBoxLayout(); + main_layout->addLayout(blayout); + blayout->setSpacing(50); + + rejectBtn = new QPushButton(tr("Cancel")); + blayout->addWidget(rejectBtn); + QObject::connect(rejectBtn, &QPushButton::clicked, QCoreApplication::instance(), &QCoreApplication::quit); + + rebootBtn = new QPushButton(tr("Reboot")); + blayout->addWidget(rebootBtn); +#ifdef __aarch64__ + QObject::connect(rebootBtn, &QPushButton::clicked, [=]{ + std::system("sudo reboot"); + }); +#endif + + confirmBtn = new QPushButton(tr("Confirm")); + confirmBtn->setStyleSheet("background-color: #465BEA;"); + blayout->addWidget(confirmBtn); + QObject::connect(confirmBtn, &QPushButton::clicked, this, &Reset::confirm); + + rejectBtn->setVisible(!recover); + rebootBtn->setVisible(recover); + if (recover) { + body->setText(tr("Unable to mount data partition. Press confirm to reset your device.")); + } + + setStyleSheet(R"( + * { + font-family: Inter; + color: white; + background-color: black; + } + QLabel { + margin-left: 140; + } + QPushButton { + height: 160; + font-size: 55px; + font-weight: 400; + border-radius: 10px; + background-color: #333333; + } + )"); +} + +int main(int argc, char *argv[]) { + bool recover = argc > 1 && strcmp(argv[1], "--recover") == 0; + QApplication a(argc, argv); + Reset reset(recover); + setMainWindow(&reset); + return a.exec(); +} diff --git a/selfdrive/ui/qt/setup/reset.h b/selfdrive/ui/qt/setup/reset.h new file mode 100644 index 000000000..3a4994077 --- /dev/null +++ b/selfdrive/ui/qt/setup/reset.h @@ -0,0 +1,20 @@ +#include +#include +#include + +class Reset : public QWidget { + Q_OBJECT + +public: + explicit Reset(bool recover = false, QWidget *parent = 0); + +private: + QLabel *body; + QPushButton *rejectBtn; + QPushButton *rebootBtn; + QPushButton *confirmBtn; + void doReset(); + +private slots: + void confirm(); +}; diff --git a/selfdrive/ui/qt/setup/setup.cc b/selfdrive/ui/qt/setup/setup.cc new file mode 100644 index 000000000..392be68a1 --- /dev/null +++ b/selfdrive/ui/qt/setup/setup.cc @@ -0,0 +1,357 @@ +#include "selfdrive/ui/qt/setup/setup.h" + +#include +#include +#include + +#include +#include +#include + +#include + +#include "common/util.h" +#include "system/hardware/hw.h" +#include "selfdrive/ui/qt/api.h" +#include "selfdrive/ui/qt/qt_window.h" +#include "selfdrive/ui/qt/offroad/networking.h" +#include "selfdrive/ui/qt/widgets/input.h" + +const std::string USER_AGENT = "AGNOSSetup-"; +const QString DASHCAM_URL = "https://dashcam.comma.ai"; + +bool is_elf(char *fname) { + FILE *fp = fopen(fname, "rb"); + if (fp == NULL) { + return false; + } + char buf[4]; + size_t n = fread(buf, 1, 4, fp); + fclose(fp); + return n == 4 && buf[0] == 0x7f && buf[1] == 'E' && buf[2] == 'L' && buf[3] == 'F'; +} + +void Setup::download(QString url) { + CURL *curl = curl_easy_init(); + if (!curl) { + emit finished(false); + return; + } + + auto version = util::read_file("/VERSION"); + + struct curl_slist *list = NULL; + list = curl_slist_append(list, ("X-openpilot-serial: " + Hardware::get_serial()).c_str()); + + char tmpfile[] = "/tmp/installer_XXXXXX"; + FILE *fp = fdopen(mkstemp(tmpfile), "w"); + + curl_easy_setopt(curl, CURLOPT_URL, url.toStdString().c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NULL); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp); + curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(curl, CURLOPT_USERAGENT, (USER_AGENT + version).c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); + + int ret = curl_easy_perform(curl); + long res_status = 0; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &res_status); + if (ret == CURLE_OK && res_status == 200 && is_elf(tmpfile)) { + rename(tmpfile, "/tmp/installer"); + emit finished(true); + } else { + emit finished(false); + } + + curl_slist_free_all(list); + curl_easy_cleanup(curl); + fclose(fp); +} + +QWidget * Setup::low_voltage() { + QWidget *widget = new QWidget(); + QVBoxLayout *main_layout = new QVBoxLayout(widget); + main_layout->setContentsMargins(55, 0, 55, 55); + main_layout->setSpacing(0); + + // inner text layout: warning icon, title, and body + QVBoxLayout *inner_layout = new QVBoxLayout(); + inner_layout->setContentsMargins(110, 144, 365, 0); + main_layout->addLayout(inner_layout); + + QLabel *triangle = new QLabel(); + triangle->setPixmap(QPixmap(ASSET_PATH + "offroad/icon_warning.png")); + inner_layout->addWidget(triangle, 0, Qt::AlignTop | Qt::AlignLeft); + inner_layout->addSpacing(80); + + QLabel *title = new QLabel(tr("WARNING: Low Voltage")); + title->setStyleSheet("font-size: 90px; font-weight: 500; color: #FF594F;"); + inner_layout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft); + + inner_layout->addSpacing(25); + + QLabel *body = new QLabel(tr("Power your device in a car with a harness or proceed at your own risk.")); + body->setWordWrap(true); + body->setAlignment(Qt::AlignTop | Qt::AlignLeft); + body->setStyleSheet("font-size: 80px; font-weight: 300;"); + inner_layout->addWidget(body); + + inner_layout->addStretch(); + + // power off + continue buttons + QHBoxLayout *blayout = new QHBoxLayout(); + blayout->setSpacing(50); + main_layout->addLayout(blayout, 0); + + QPushButton *poweroff = new QPushButton(tr("Power off")); + poweroff->setObjectName("navBtn"); + blayout->addWidget(poweroff); + QObject::connect(poweroff, &QPushButton::clicked, this, [=]() { + Hardware::poweroff(); + }); + + QPushButton *cont = new QPushButton(tr("Continue")); + cont->setObjectName("navBtn"); + blayout->addWidget(cont); + QObject::connect(cont, &QPushButton::clicked, this, &Setup::nextPage); + + return widget; +} + +QWidget * Setup::getting_started() { + QWidget *widget = new QWidget(); + + QHBoxLayout *main_layout = new QHBoxLayout(widget); + main_layout->setMargin(0); + + QVBoxLayout *vlayout = new QVBoxLayout(); + vlayout->setContentsMargins(165, 280, 100, 0); + main_layout->addLayout(vlayout); + + QLabel *title = new QLabel(tr("Getting Started")); + title->setStyleSheet("font-size: 90px; font-weight: 500;"); + vlayout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft); + + vlayout->addSpacing(90); + QLabel *desc = new QLabel(tr("Before we get on the road, let’s finish installation and cover some details.")); + desc->setWordWrap(true); + desc->setStyleSheet("font-size: 80px; font-weight: 300;"); + vlayout->addWidget(desc, 0, Qt::AlignTop | Qt::AlignLeft); + + vlayout->addStretch(); + + QPushButton *btn = new QPushButton(); + btn->setIcon(QIcon(":/img_continue_triangle.svg")); + btn->setIconSize(QSize(54, 106)); + btn->setFixedSize(310, 1080); + btn->setProperty("primary", true); + btn->setStyleSheet("border: none;"); + main_layout->addWidget(btn, 0, Qt::AlignRight); + QObject::connect(btn, &QPushButton::clicked, this, &Setup::nextPage); + + return widget; +} + +QWidget * Setup::network_setup() { + QWidget *widget = new QWidget(); + QVBoxLayout *main_layout = new QVBoxLayout(widget); + main_layout->setContentsMargins(55, 50, 55, 50); + + // title + QLabel *title = new QLabel(tr("Connect to Wi-Fi")); + title->setStyleSheet("font-size: 90px; font-weight: 500;"); + main_layout->addWidget(title, 0, Qt::AlignLeft | Qt::AlignTop); + + main_layout->addSpacing(25); + + // wifi widget + Networking *networking = new Networking(this, false); + networking->setStyleSheet("Networking {background-color: #292929; border-radius: 13px;}"); + main_layout->addWidget(networking, 1); + + main_layout->addSpacing(35); + + // back + continue buttons + QHBoxLayout *blayout = new QHBoxLayout; + main_layout->addLayout(blayout); + blayout->setSpacing(50); + + QPushButton *back = new QPushButton(tr("Back")); + back->setObjectName("navBtn"); + QObject::connect(back, &QPushButton::clicked, this, &Setup::prevPage); + blayout->addWidget(back); + + QPushButton *cont = new QPushButton(); + cont->setObjectName("navBtn"); + cont->setProperty("primary", true); + QObject::connect(cont, &QPushButton::clicked, [=]() { + auto w = currentWidget(); + QTimer::singleShot(0, [=]() { + setCurrentWidget(downloading_widget); + }); + QString url = InputDialog::getText(tr("Enter URL"), this, tr("for Custom Software")); + if (!url.isEmpty()) { + QTimer::singleShot(1000, this, [=]() { + download(url); + }); + } else { + setCurrentWidget(w); + } + }); + blayout->addWidget(cont); + + // setup timer for testing internet connection + HttpRequest *request = new HttpRequest(this, false, 2500); + QObject::connect(request, &HttpRequest::requestDone, [=](const QString &, bool success) { + cont->setEnabled(success); + if (success) { + const bool wifi = networking->wifi->currentNetworkType() == NetworkType::WIFI; + cont->setText(wifi ? tr("Continue") : tr("Continue without Wi-Fi")); + } else { + cont->setText(tr("Waiting for internet")); + } + repaint(); + }); + request->sendRequest(DASHCAM_URL); + QTimer *timer = new QTimer(this); + QObject::connect(timer, &QTimer::timeout, [=]() { + if (!request->active() && cont->isVisible()) { + request->sendRequest(DASHCAM_URL); + } + }); + timer->start(1000); + + return widget; +} + +QWidget * Setup::downloading() { + QWidget *widget = new QWidget(); + QVBoxLayout *main_layout = new QVBoxLayout(widget); + QLabel *txt = new QLabel(tr("Downloading...")); + txt->setStyleSheet("font-size: 90px; font-weight: 500;"); + main_layout->addWidget(txt, 0, Qt::AlignCenter); + return widget; +} + +QWidget * Setup::download_failed() { + QWidget *widget = new QWidget(); + QVBoxLayout *main_layout = new QVBoxLayout(widget); + main_layout->setContentsMargins(55, 225, 55, 55); + main_layout->setSpacing(0); + + QLabel *title = new QLabel(tr("Download Failed")); + title->setStyleSheet("font-size: 90px; font-weight: 500;"); + main_layout->addWidget(title, 0, Qt::AlignTop | Qt::AlignLeft); + + main_layout->addSpacing(67); + + QLabel *body = new QLabel(tr("Ensure the entered URL is valid, and the device’s internet connection is good.")); + body->setWordWrap(true); + body->setAlignment(Qt::AlignTop | Qt::AlignLeft); + body->setStyleSheet("font-size: 80px; font-weight: 300; margin-right: 100px;"); + main_layout->addWidget(body); + + main_layout->addStretch(); + + // reboot + start over buttons + QHBoxLayout *blayout = new QHBoxLayout(); + blayout->setSpacing(50); + main_layout->addLayout(blayout, 0); + + QPushButton *reboot = new QPushButton(tr("Reboot device")); + reboot->setObjectName("navBtn"); + blayout->addWidget(reboot); + QObject::connect(reboot, &QPushButton::clicked, this, [=]() { + Hardware::reboot(); + }); + + QPushButton *restart = new QPushButton(tr("Start over")); + restart->setObjectName("navBtn"); + restart->setProperty("primary", true); + blayout->addWidget(restart); + QObject::connect(restart, &QPushButton::clicked, this, [=]() { + setCurrentIndex(2); + }); + + widget->setStyleSheet(R"( + QLabel { + margin-left: 117; + } + )"); + return widget; +} + +void Setup::prevPage() { + setCurrentIndex(currentIndex() - 1); +} + +void Setup::nextPage() { + setCurrentIndex(currentIndex() + 1); +} + +Setup::Setup(QWidget *parent) : QStackedWidget(parent) { + std::stringstream buffer; + buffer << std::ifstream("/sys/class/hwmon/hwmon1/in1_input").rdbuf(); + float voltage = (float)std::atoi(buffer.str().c_str()) / 1000.; + if (voltage < 7) { + addWidget(low_voltage()); + } + + addWidget(getting_started()); + addWidget(network_setup()); + + downloading_widget = downloading(); + addWidget(downloading_widget); + + failed_widget = download_failed(); + addWidget(failed_widget); + + QObject::connect(this, &Setup::finished, [=](bool success) { + // hide setup on success + qDebug() << "finished" << success; + if (success) { + QTimer::singleShot(3000, this, &QWidget::hide); + } else { + setCurrentWidget(failed_widget); + } + }); + + // TODO: revisit pressed bg color + setStyleSheet(R"( + * { + color: white; + font-family: Inter; + } + Setup { + background-color: black; + } + QPushButton#navBtn { + height: 160; + font-size: 55px; + font-weight: 400; + border-radius: 10px; + background-color: #333333; + } + QPushButton#navBtn:disabled, QPushButton[primary='true']:disabled { + color: #808080; + background-color: #333333; + } + QPushButton#navBtn:pressed { + background-color: #444444; + } + QPushButton[primary='true'], #navBtn[primary='true'] { + background-color: #465BEA; + } + QPushButton[primary='true']:pressed, #navBtn:pressed[primary='true'] { + background-color: #3049F4; + } + )"); +} + +int main(int argc, char *argv[]) { + QApplication a(argc, argv); + Setup setup; + setMainWindow(&setup); + return a.exec(); +} diff --git a/selfdrive/ui/qt/setup/setup.h b/selfdrive/ui/qt/setup/setup.h new file mode 100644 index 000000000..f990b5a6c --- /dev/null +++ b/selfdrive/ui/qt/setup/setup.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include + +class Setup : public QStackedWidget { + Q_OBJECT + +public: + explicit Setup(QWidget *parent = 0); + +private: + QWidget *low_voltage(); + QWidget *getting_started(); + QWidget *network_setup(); + QWidget *downloading(); + QWidget *download_failed(); + + QWidget *failed_widget; + QWidget *downloading_widget; + +signals: + void finished(bool success); + +public slots: + void nextPage(); + void prevPage(); + void download(QString url); +}; diff --git a/selfdrive/ui/qt/setup/updater.cc b/selfdrive/ui/qt/setup/updater.cc new file mode 100644 index 000000000..fd7148c53 --- /dev/null +++ b/selfdrive/ui/qt/setup/updater.cc @@ -0,0 +1,176 @@ +#include +#include +#include + +#include "system/hardware/hw.h" +#include "selfdrive/ui/qt/util.h" +#include "selfdrive/ui/qt/qt_window.h" +#include "selfdrive/ui/qt/setup/updater.h" +#include "selfdrive/ui/qt/offroad/networking.h" + +Updater::Updater(const QString &updater_path, const QString &manifest_path, QWidget *parent) + : updater(updater_path), manifest(manifest_path), QStackedWidget(parent) { + + assert(updater.size()); + assert(manifest.size()); + + // initial prompt screen + prompt = new QWidget; + { + QVBoxLayout *layout = new QVBoxLayout(prompt); + layout->setContentsMargins(100, 250, 100, 100); + + QLabel *title = new QLabel(tr("Update Required")); + title->setStyleSheet("font-size: 80px; font-weight: bold;"); + layout->addWidget(title); + + layout->addSpacing(75); + + QLabel *desc = new QLabel(tr("An operating system update is required. Connect your device to Wi-Fi for the fastest update experience. The download size is approximately 1GB.")); + desc->setWordWrap(true); + desc->setStyleSheet("font-size: 65px;"); + layout->addWidget(desc); + + layout->addStretch(); + + QHBoxLayout *hlayout = new QHBoxLayout; + hlayout->setSpacing(30); + layout->addLayout(hlayout); + + QPushButton *connect = new QPushButton(tr("Connect to Wi-Fi")); + connect->setObjectName("navBtn"); + QObject::connect(connect, &QPushButton::clicked, [=]() { + setCurrentWidget(wifi); + }); + hlayout->addWidget(connect); + + QPushButton *install = new QPushButton(tr("Install")); + install->setObjectName("navBtn"); + install->setStyleSheet("background-color: #465BEA;"); + QObject::connect(install, &QPushButton::clicked, this, &Updater::installUpdate); + hlayout->addWidget(install); + } + + // wifi connection screen + wifi = new QWidget; + { + QVBoxLayout *layout = new QVBoxLayout(wifi); + layout->setContentsMargins(100, 100, 100, 100); + + Networking *networking = new Networking(this, false); + networking->setStyleSheet("Networking { background-color: #292929; border-radius: 13px; }"); + layout->addWidget(networking, 1); + + QPushButton *back = new QPushButton(tr("Back")); + back->setObjectName("navBtn"); + back->setStyleSheet("padding-left: 60px; padding-right: 60px;"); + QObject::connect(back, &QPushButton::clicked, [=]() { + setCurrentWidget(prompt); + }); + layout->addWidget(back, 0, Qt::AlignLeft); + } + + // progress screen + progress = new QWidget; + { + QVBoxLayout *layout = new QVBoxLayout(progress); + layout->setContentsMargins(150, 330, 150, 150); + layout->setSpacing(0); + + text = new QLabel(tr("Loading...")); + text->setStyleSheet("font-size: 90px; font-weight: 600;"); + layout->addWidget(text, 0, Qt::AlignTop); + + layout->addSpacing(100); + + bar = new QProgressBar(); + bar->setRange(0, 100); + bar->setTextVisible(false); + bar->setFixedHeight(72); + layout->addWidget(bar, 0, Qt::AlignTop); + + layout->addStretch(); + + reboot = new QPushButton(tr("Reboot")); + reboot->setObjectName("navBtn"); + reboot->setStyleSheet("padding-left: 60px; padding-right: 60px;"); + QObject::connect(reboot, &QPushButton::clicked, [=]() { + Hardware::reboot(); + }); + layout->addWidget(reboot, 0, Qt::AlignLeft); + reboot->hide(); + + layout->addStretch(); + } + + addWidget(prompt); + addWidget(wifi); + addWidget(progress); + + setStyleSheet(R"( + * { + color: white; + outline: none; + font-family: Inter; + } + Updater { + color: white; + background-color: black; + } + QPushButton#navBtn { + height: 160; + font-size: 55px; + font-weight: 400; + border-radius: 10px; + background-color: #333333; + } + QProgressBar { + border: none; + background-color: #292929; + } + QProgressBar::chunk { + background-color: #364DEF; + } + )"); +} + +void Updater::installUpdate() { + setCurrentWidget(progress); + QObject::connect(&proc, &QProcess::readyReadStandardOutput, this, &Updater::readProgress); + QObject::connect(&proc, QOverload::of(&QProcess::finished), this, &Updater::updateFinished); + proc.setProcessChannelMode(QProcess::ForwardedErrorChannel); + proc.start(updater, {"--swap", manifest}); +} + +void Updater::readProgress() { + auto lines = QString(proc.readAllStandardOutput()); + for (const QString &line : lines.trimmed().split("\n")) { + auto parts = line.split(":"); + if (parts.size() == 2) { + text->setText(parts[0]); + bar->setValue((int)parts[1].toDouble()); + } else { + qDebug() << line; + } + } + update(); +} + +void Updater::updateFinished(int exitCode, QProcess::ExitStatus exitStatus) { + qDebug() << "finished with " << exitCode; + if (exitCode == 0) { + Hardware::reboot(); + } else { + text->setText(tr("Update failed")); + reboot->show(); + } +} + +int main(int argc, char *argv[]) { + initApp(argc, argv); + QApplication a(argc, argv); + Updater updater(argv[1], argv[2]); + setMainWindow(&updater); + a.installEventFilter(&updater); + return a.exec(); +} diff --git a/selfdrive/ui/qt/setup/updater.h b/selfdrive/ui/qt/setup/updater.h new file mode 100644 index 000000000..ce46c0aab --- /dev/null +++ b/selfdrive/ui/qt/setup/updater.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +class Updater : public QStackedWidget { + Q_OBJECT + +public: + explicit Updater(const QString &updater_path, const QString &manifest_path, QWidget *parent = 0); + +private slots: + void installUpdate(); + void readProgress(); + void updateFinished(int exitCode, QProcess::ExitStatus exitStatus); + +private: + QProcess proc; + QString updater, manifest; + + QLabel *text; + QProgressBar *bar; + QPushButton *reboot; + QWidget *prompt, *wifi, *progress; +}; diff --git a/selfdrive/ui/qt/util.cc b/selfdrive/ui/qt/util.cc index 59903e337..4f5231064 100644 --- a/selfdrive/ui/qt/util.cc +++ b/selfdrive/ui/qt/util.cc @@ -2,11 +2,14 @@ #include #include +#include #include #include #include #include #include +#include +#include #include "common/params.h" #include "common/swaglog.h" @@ -218,3 +221,37 @@ QColor interpColor(float xv, std::vector xp, std::vector fp) { ); } } + +static QHash load_bootstrap_icons() { + QHash icons; + + QFile f(":/bootstrap-icons.svg"); + if (f.open(QIODevice::ReadOnly | QIODevice::Text)) { + QDomDocument xml; + xml.setContent(&f); + QDomNode n = xml.documentElement().firstChild(); + while (!n.isNull()) { + QDomElement e = n.toElement(); + if (!e.isNull() && e.hasAttribute("id")) { + QString svg_str; + QTextStream stream(&svg_str); + n.save(stream, 0); + svg_str.replace("", ""); + icons[e.attribute("id")] = svg_str.toUtf8(); + } + n = n.nextSibling(); + } + } + return icons; +} + +QPixmap bootstrapPixmap(const QString &id) { + static QHash icons = load_bootstrap_icons(); + + QPixmap pixmap; + if (auto it = icons.find(id); it != icons.end()) { + pixmap.loadFromData(it.value(), "svg"); + } + return pixmap; +} diff --git a/selfdrive/ui/qt/util.h b/selfdrive/ui/qt/util.h index 61a27a866..3188f3f9b 100644 --- a/selfdrive/ui/qt/util.h +++ b/selfdrive/ui/qt/util.h @@ -22,6 +22,7 @@ void swagLogMessageHandler(QtMsgType type, const QMessageLogContext &context, co void initApp(int argc, char *argv[]); QWidget* topWidget (QWidget* widget); QPixmap loadPixmap(const QString &fileName, const QSize &size = {}, Qt::AspectRatioMode aspectRatioMode = Qt::KeepAspectRatio); +QPixmap bootstrapPixmap(const QString &id); QRect getTextRect(QPainter &p, int flags, const QString &text); void drawRoundedRect(QPainter &painter, const QRectF &rect, qreal xRadiusTop, qreal yRadiusTop, qreal xRadiusBottom, qreal yRadiusBottom); diff --git a/selfdrive/ui/qt/widgets/cameraview.cc b/selfdrive/ui/qt/widgets/cameraview.cc index 347cdb1dc..8c7a7072e 100644 --- a/selfdrive/ui/qt/widgets/cameraview.cc +++ b/selfdrive/ui/qt/widgets/cameraview.cc @@ -358,8 +358,8 @@ void CameraWidget::vipcThread() { while (!QThread::currentThread()->isInterruptionRequested()) { if (!vipc_client || cur_stream != requested_stream_type) { clearFrames(); - qDebug() << "connecting to stream " << requested_stream_type << ", was connected to " << cur_stream; - cur_stream = requested_stream_type;; + qDebug().nospace() << "connecting to stream " << requested_stream_type << ", was connected to " << cur_stream; + cur_stream = requested_stream_type; vipc_client.reset(new VisionIpcClient(stream_name, cur_stream, false)); } active_stream_type = cur_stream; @@ -382,6 +382,10 @@ void CameraWidget::vipcThread() { } } emit vipcThreadFrameReceived(); + } else { + if (!isVisible()) { + vipc_client->connected = false; + } } } diff --git a/selfdrive/ui/qt/widgets/cameraview.h b/selfdrive/ui/qt/widgets/cameraview.h index 7cc3847f9..f8b97be03 100644 --- a/selfdrive/ui/qt/widgets/cameraview.h +++ b/selfdrive/ui/qt/widgets/cameraview.h @@ -35,6 +35,7 @@ public: void setFrameId(int frame_id) { draw_frame_id = frame_id; } void setStreamType(VisionStreamType type) { requested_stream_type = type; } VisionStreamType getStreamType() { return active_stream_type; } + void stopVipcThread(); signals: void clicked(); @@ -51,7 +52,6 @@ protected: void updateCalibration(const mat3 &calib); void vipcThread(); void clearFrames(); - void stopVipcThread(); bool zoomed_view; GLuint frame_vao, frame_vbo, frame_ibo; diff --git a/selfdrive/ui/qt/widgets/input.cc b/selfdrive/ui/qt/widgets/input.cc index 75453e1b9..2026a7047 100644 --- a/selfdrive/ui/qt/widgets/input.cc +++ b/selfdrive/ui/qt/widgets/input.cc @@ -296,12 +296,13 @@ MultiOptionDialog::MultiOptionDialog(const QString &prompt_text, const QStringLi group->addButton(selectionLabel); listLayout->addWidget(selectionLabel); } + // add stretch to keep buttons spaced correctly + listLayout->addStretch(1); ScrollView *scroll_view = new ScrollView(listWidget, this); scroll_view->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); main_layout->addWidget(scroll_view); - main_layout->addStretch(1); main_layout->addSpacing(35); // cancel + confirm buttons diff --git a/selfdrive/ui/soundd/sound.cc b/selfdrive/ui/soundd/sound.cc index 6830450d8..49c28373c 100644 --- a/selfdrive/ui/soundd/sound.cc +++ b/selfdrive/ui/soundd/sound.cc @@ -12,7 +12,7 @@ // TODO: detect when we can't play sounds // TODO: detect when we can't display the UI -Sound::Sound(QObject *parent) : sm({"carState", "controlsState", "deviceState"}) { +Sound::Sound(QObject *parent) : sm({"controlsState", "deviceState", "microphone"}) { qInfo() << "default audio device: " << QAudioDeviceInfo::defaultOutputDevice().deviceName(); for (auto &[alert, fn, loops] : sound_list) { @@ -20,7 +20,6 @@ Sound::Sound(QObject *parent) : sm({"carState", "controlsState", "deviceState"}) QObject::connect(s, &QSoundEffect::statusChanged, [=]() { assert(s->status() != QSoundEffect::Error); }); - s->setVolume(Hardware::MIN_VOLUME); s->setSource(QUrl::fromLocalFile("../../assets/sounds/" + fn)); sounds[alert] = {s, loops}; } @@ -47,14 +46,11 @@ void Sound::update() { return; } - // scale volume with speed - if (sm.updated("carState")) { - float volume = util::map_val(sm["carState"].getCarState().getVEgo(), 11.f, 20.f, 0.f, 1.0f); + // scale volume using ambient noise level + if (sm.updated("microphone")) { + float volume = util::map_val(sm["microphone"].getMicrophone().getFilteredSoundPressureWeightedDb(), 30.f, 60.f, 0.f, 1.f); volume = QAudio::convertVolume(volume, QAudio::LogarithmicVolumeScale, QAudio::LinearVolumeScale); - volume = util::map_val(volume, 0.f, 1.f, Hardware::MIN_VOLUME, Hardware::MAX_VOLUME); - for (auto &[s, loops] : sounds) { - s->setVolume(std::round(100 * volume) / 100); - } + Hardware::set_volume(volume); } setAlert(Alert::get(sm, started_frame)); diff --git a/selfdrive/ui/translations/languages.json b/selfdrive/ui/translations/languages.json index 072d320c1..86d3e62d8 100644 --- a/selfdrive/ui/translations/languages.json +++ b/selfdrive/ui/translations/languages.json @@ -1,5 +1,6 @@ { "English": "main_en", + "Deutsch": "main_de", "Português": "main_pt-BR", "中文(繁體)": "main_zh-CHT", "中文(简体)": "main_zh-CHS", diff --git a/selfdrive/ui/translations/main_de.ts b/selfdrive/ui/translations/main_de.ts new file mode 100644 index 000000000..b40812af7 --- /dev/null +++ b/selfdrive/ui/translations/main_de.ts @@ -0,0 +1,1097 @@ + + + + + AbstractAlert + + Close + Schließen + + + Snooze Update + Update pausieren + + + Reboot and Update + Aktualisieren und neu starten + + + + AdvancedNetworking + + Back + Zurück + + + Enable Tethering + Tethering aktivieren + + + Tethering Password + Tethering Passwort + + + EDIT + ÄNDERN + + + Enter new tethering password + Neues tethering Passwort eingeben + + + IP Address + IP Adresse + + + Enable Roaming + Roaming aktivieren + + + APN Setting + APN Einstellungen + + + Enter APN + APN eingeben + + + leave blank for automatic configuration + für automatische Konfiguration leer lassen + + + Cellular Metered + Getaktete Verbindung + + + Prevent large data uploads when on a metered connection + Hochladen großer Dateien über getaktete Verbindungen unterbinden + + + + AnnotatedCameraWidget + + km/h + km/h + + + mph + mph + + + MAX + MAX + + + SPEED + Geschwindigkeit + + + LIMIT + LIMIT + + + + ConfirmationDialog + + Ok + Ok + + + Cancel + Abbrechen + + + + DeclinePage + + You must accept the Terms and Conditions in order to use openpilot. + Du musst die Nutzungsbedingungen akzeptieren, um Openpilot zu benutzen. + + + Back + Zurück + + + Decline, uninstall %1 + Ablehnen, deinstallieren %1 + + + + DevicePanel + + Dongle ID + Dongle ID + + + N/A + Nicht verfügbar + + + Serial + Seriennummer + + + Driver Camera + Fahrerkamera + + + PREVIEW + VORSCHAU + + + Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off) + Vorschau der auf den Fahrer gerichteten Kamera, um sicherzustellen, dass die Fahrerüberwachung eine gute Sicht hat. (Fahrzeug muss aus sein) + + + Reset Calibration + Neu kalibrieren + + + RESET + RESET + + + Are you sure you want to reset calibration? + Bist du sicher, dass du die Kalibrierung zurücksetzen möchtest? + + + Review Training Guide + Trainingsanleitung wiederholen + + + REVIEW + TRAINING + + + Review the rules, features, and limitations of openpilot + Wiederhole die Regeln, Fähigkeiten und Limitierungen von Openpilot + + + Are you sure you want to review the training guide? + Bist du sicher, dass du die Trainingsanleitung wiederholen möchtest? + + + Regulatory + Rechtliche Hinweise + + + VIEW + ANSEHEN + + + Change Language + Sprache ändern + + + CHANGE + ÄNDERN + + + Select a language + Sprache wählen + + + Reboot + Neustart + + + Power Off + Ausschalten + + + openpilot requires the device to be mounted within 4° left or right and within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required. + Damit Openpilot funktioniert, darf die Installationsposition nicht mehr als 4° nach rechts/links, 5° nach oben und 8° nach unten abweichen. Openpilot kalibriert sich durchgehend, ein Zurücksetzen ist selten notwendig. + + + Your device is pointed %1° %2 and %3° %4. + Deine Geräteausrichtung ist %1° %2 und %3° %4. + + + down + unten + + + up + oben + + + left + links + + + right + rechts + + + Are you sure you want to reboot? + Bist du sicher, dass du das Gerät neu starten möchtest? + + + Disengage to Reboot + Für Neustart deaktivieren + + + Are you sure you want to power off? + Bist du sicher, dass du das Gerät ausschalten möchtest? + + + Disengage to Power Off + Zum Ausschalten deaktivieren + + + Reset + Zurücksetzen + + + Review + Überprüfen + + + + DriveStats + + Drives + Fahrten + + + Hours + Stunden + + + ALL TIME + Gesamtzeit + + + PAST WEEK + Letzte Woche + + + KM + KM + + + Miles + Meilen + + + + DriverViewScene + + camera starting + Kamera startet + + + + ExperimentalModeButton + + EXPERIMENTAL MODE ON + EXPERIMENTELLER MODUS AN + + + CHILL MODE ON + ENTSPANNTER MODUS AN + + + + InputDialog + + Cancel + Abbrechen + + + Need at least %n character(s)! + + Mindestens %n Buchstabe benötigt! + Mindestens %n Buchstaben benötigt! + + + + + Installer + + Installing... + Installiere... + + + + MapETA + + eta + Ankunft + + + min + min + + + hr + std + + + km + km + + + mi + mi + + + + MapInstructions + + km + km + + + m + m + + + mi + mi + + + ft + fuß + + + + MapPanel + + Current Destination + Aktuelles Ziel + + + CLEAR + LÖSCHEN + + + Recent Destinations + Letzte Ziele + + + Try the Navigation Beta + Beta Navigation ausprobieren + + + Get turn-by-turn directions displayed and more with a comma +prime subscription. Sign up now: https://connect.comma.ai + Erhalte echtzeit Wegführung und mehr mit dem comma prime +Abonnement. Melde dich jetzt an: https://connect.comma.ai + + + No home +location set + Keine Heimadresse gesetzt + + + No work +location set + Keine Arbeitsadresse gesetzt + + + no recent destinations + Keine kürzlich gewählten Ziele + + + + MapWindow + + Map Loading + Karte wird geladen + + + Waiting for GPS + Warten auf GPS + + + + MultiOptionDialog + + Select + Auswählen + + + Cancel + Abbrechen + + + + Networking + + Advanced + Erweitert + + + Enter password + Passwort eingeben + + + for "%1" + für "%1" + + + Wrong password + Falsches Passwort + + + + OffroadHome + + UPDATE + Aktualisieren + + + ALERTS + HINWEISE + + + ALERT + HINWEIS + + + + PairingPopup + + Pair your device to your comma account + Verbinde dein Gerät mit deinem comma Konto + + + Go to https://connect.comma.ai on your phone + Gehe zu https://connect.comma.ai auf deinem Handy + + + Click "add new device" and scan the QR code on the right + Klicke auf "neues Gerät hinzufügen" und scanne den QR code rechts + + + Bookmark connect.comma.ai to your home screen to use it like an app + Füge connect.comma.ai als Lesezeichen auf deinem Homescreen hinzu um es wie eine App zu verwenden + + + + ParamControl + + Cancel + Abbrechen + + + Enable + Aktivieren + + + + PrimeAdWidget + + Upgrade Now + Jetzt abonieren + + + Become a comma prime member at connect.comma.ai + Werde Comma Prime Mitglied auf connect.comma.ai + + + PRIME FEATURES: + PRIME FUNKTIONEN: + + + Remote access + Fernzugriff + + + 1 year of storage + 1 Jahr Speicherplatz + + + Developer perks + Entwickler Vorteile + + + + PrimeUserWidget + + ✓ SUBSCRIBED + ✓ ABBONIERT + + + comma prime + comma prime + + + CONNECT.COMMA.AI + CONNECT.COMMA.AI + + + COMMA POINTS + COMMA PUNKTE + + + + QObject + + Reboot + Neustart + + + Exit + Verlassen + + + dashcam + dashcam + + + openpilot + openpilot + + + %n minute(s) ago + + vor %n Minute + vor %n Minuten + + + + %n hour(s) ago + + vor %n Stunde + vor %n Stunden + + + + %n day(s) ago + + vor %n Tag + vor %n Tagen + + + + + Reset + + Reset failed. Reboot to try again. + Zurücksetzen fehlgeschlagen. Starte das Gerät neu und versuche es wieder. + + + Are you sure you want to reset your device? + Bist du sicher, dass du das Gerät auf Werkseinstellungen zurücksetzen möchtest? + + + Resetting device... + Gerät wird zurückgesetzt... + + + System Reset + System auf Werkseinstellungen zurücksetzen + + + System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. + Zurücksetzen auf Werkseinstellungen wurde ausgewählt. Drücke Annehmen, um alle Inhalte und Einstellungen zu löschen. Drücke Abbrechen, um mit dem Starten des Gerätes fortzufahren. + + + Cancel + Abbrechen + + + Reboot + Neustart + + + Confirm + Bestätigen + + + Unable to mount data partition. Press confirm to reset your device. + Datenpartition kann nicht geöffnet werden. Drücke Annehmen, um dein Gerät auf Werkseinstellungen zurückzusetzen. + + + + SettingsWindow + + × + x + + + Device + Gerät + + + Network + Netzwerk + + + Toggles + Schalter + + + Software + Software + + + Navigation + Navigation + + + + Setup + + WARNING: Low Voltage + Warnung: Batteriespannung niedrig + + + Power your device in a car with a harness or proceed at your own risk. + Versorge dein Gerät über einen Kabelbaum im Auto mit Strom, oder fahre auf eigene Gefahr fort. + + + Power off + Ausschalten + + + Continue + Fortsetzen + + + Getting Started + Loslegen + + + Before we get on the road, let’s finish installation and cover some details. + Bevor wir uns auf die Straße begeben, lass uns die Installation fertigstellen und einige Details prüfen. + + + Connect to Wi-Fi + Mit WLAN verbinden + + + Back + Zurück + + + Continue without Wi-Fi + Ohne WLAN fortsetzen + + + Waiting for internet + Auf Internet warten + + + Enter URL + URL eingeben + + + for Custom Software + für spezifische Software + + + Downloading... + Herunterladen... + + + Download Failed + Herunterladen fehlgeschlagen + + + Ensure the entered URL is valid, and the device’s internet connection is good. + Stelle sicher, dass die eingegebene URL korrekt ist und dein Gerät eine stabile Internetverbindung hat. + + + Reboot device + Gerät neustarten + + + Start over + Von neuem beginnen + + + + SetupWidget + + Finish Setup + Einrichtung beenden + + + Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. + Koppele dein Gerät mit Comma Connect (connect.comma.ai) und sichere dir dein Comma Prime Angebot. + + + Pair device + Gerät koppeln + + + + Sidebar + + CONNECT + This is a brand/service name for comma connect, don't translate + CONNECT + + + OFFLINE + OFFLINE + + + ONLINE + ONLINE + + + ERROR + FEHLER + + + TEMP + TEMP + + + HIGH + HOCH + + + GOOD + GUT + + + OK + OK + + + VEHICLE + FAHRZEUG + + + NO + KEIN + + + PANDA + PANDA + + + GPS + GPS + + + SEARCH + SUCHEN + + + -- + -- + + + Wi-Fi + WLAN + + + ETH + LAN + + + 2G + 2G + + + 3G + 3G + + + LTE + LTE + + + 5G + 5G + + + + SoftwarePanel + + UNINSTALL + Too long for UI + DEINSTALL + + + Uninstall %1 + Deinstalliere %1 + + + Are you sure you want to uninstall? + Bist du sicher, dass du Openpilot entfernen möchtest? + + + CHECK + ÜBERPRÜFEN + + + Updates are only downloaded while the car is off. + Updates werden nur heruntergeladen, wenn das Auto aus ist. + + + Current Version + Aktuelle Version + + + Download + Download + + + Install Update + Update installieren + + + INSTALL + INSTALLIEREN + + + Target Branch + Ziel Branch + + + SELECT + AUSWÄHLEN + + + Select a branch + Wähle einen Branch + + + Uninstall + Deinstallieren + + + + SshControl + + SSH Keys + SSH Schlüssel + + + Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username other than your own. A comma employee will NEVER ask you to add their GitHub username. + Warnung: Dies ermöglicht SSH zugriff für alle öffentlichen Schlüssel in deinen Github Einstellungen. Gib niemals einen anderen Benutzernamen, als deinen Eigenen an. Comma Angestellte fragen dich niemals danach ihren Github Benutzernamen hinzuzufügen. + + + ADD + HINZUFÜGEN + + + Enter your GitHub username + Gib deinen GitHub Benutzernamen ein + + + LOADING + LADEN + + + REMOVE + LÖSCHEN + + + Username '%1' has no keys on GitHub + Benutzername '%1' hat keine Schlüssel auf GitHub + + + Request timed out + Zeitüberschreitung der Anforderung + + + Username '%1' doesn't exist on GitHub + Benutzername '%1' existiert nicht auf GitHub + + + + SshToggle + + Enable SSH + SSH aktivieren + + + + TermsPage + + Terms & Conditions + Benutzungsbedingungen + + + Decline + Ablehnen + + + Scroll to accept + Scrolle herunter um zu akzeptieren + + + Agree + Zustimmen + + + + TogglesPanel + + Enable openpilot + Openpilot aktivieren + + + Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + Benutze das Openpilot System als adaptiven Tempomaten und Spurhalteassistenten. Deine Aufmerksamkeit ist jederzeit erforderlich, um diese Funktion zu nutzen. Diese Einstellung wird übernommen, wenn das Auto aus ist. + + + Enable Lane Departure Warnings + Spurverlassenswarnungen aktivieren + + + Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h). + Erhalte Warnungen, zurück in die Spur zu lenken, wenn dein Auto über eine erkannte Fahrstreifenmarkierung ohne aktivierten Blinker mit mehr als 50 km/h fährt. + + + Use Metric System + Benutze das metrische System + + + Display speed in km/h instead of mph. + Zeige die Geschwindigkeit in km/h anstatt von mph. + + + Record and Upload Driver Camera + Fahrerkamera aufnehmen und hochladen + + + Upload data from the driver facing camera and help improve the driver monitoring algorithm. + Lade Daten der Fahreraufmerksamkeitsüberwachungskamera hoch, um die Fahreraufmerksamkeitsüberwachungsalgorithmen zu verbessern. + + + When enabled, pressing the accelerator pedal will disengage openpilot. + Wenn aktiviert, deaktiviert sich Openpilot sobald das Gaspedal betätigt wird. + + + Use 24h format instead of am/pm + Benutze das 24Stunden Format anstatt am/pm + + + Show Map on Left Side of UI + Too long for UI + Zeige die Karte auf der linken Seite + + + Show map on left side when in split screen view. + Zeige die Karte auf der linken Seite der Benutzeroberfläche bei geteilten Bildschirm. + + + Show ETA in 24h Format + Too long for UI + Zeige die Ankunftszeit im 24 Stunden Format + + + Experimental Mode + Experimenteller Modus + + + Experimental openpilot Longitudinal Control + Experimenteller Openpilot Tempomat + + + WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). + WARNUNG: Der Openpilot Tempomat ist für dieses Auto experimentell und deaktiviert den Notbremsassistenten. + + + Disengage on Accelerator Pedal + Bei Gasbetätigung ausschalten + + + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. + Bei diesem auto wird standardmäßig der im Auto eingebaute adaptive Tempomat anstelle des Openpilot Tempomats benutzt. Aktiviere diesen Schalter, um zum Openpilot Tempomaten zu wechseln. Es ist empfohlen den Experimentellen Modus bei Nutzung des Openpilot Tempomats zu aktivieren. + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + Openpilot fährt standardmäßig im <b>entspannten Modus</b>. Der Experimentelle Modus aktiviert<b>Alpha-level Funktionen</b>, die noch nicht für den entspannten Modus bereit sind. Die experimentellen Funktionen sind die Folgenden: + + + 🌮 End-to-End Longitudinal Control 🌮 + 🌮 Ende-zu-Ende Tempomat 🌮 + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + Lass das Fahrmodell Gas und Bremse kontrollieren. Openpilot wird so fahren, wie es dies von einem Menschen erwarten würde; inklusive des Anhaltens für Ampeln und Stoppschildern. Da das Fahrmodell entscheidet wie schnell es fährt stellt die gesetzte Geschwindigkeit lediglich das obere Limit dar. Dies ist ein Alpha-level Funktion. Fehler sind zu erwarten. + + + New Driving Visualization + Neue Fahrvisualisierung + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + Die Fahrvisualisierung wechselt bei niedrigen Geschwindigkeiten zur Straßengewandten Weitwinkelkamera, um manche Kurven besser zu zeigen. Außerdem wird das Experimenteller Modus logo oben rechts angezeigt. + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + Der experimentelle Modus ist momentan für dieses Auto nicht verfügbar da es den eingebauten adaptiven Tempomaten des Autos benutzt. + + + openpilot longitudinal control may come in a future update. + + + + An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + + + + Enable experimental longitudinal control to allow Experimental mode. + Aktiviere den experimentellen Openpilot Tempomaten für experimentelle Funktionen. + + + + Updater + + Update Required + Aktualisierung notwendig + + + An operating system update is required. Connect your device to Wi-Fi for the fastest update experience. The download size is approximately 1GB. + Eine Aktualisierung des Betriebssystems ist notwendig. Verbinde dein Gerät mit WLAN für ein schnelleres Update. Die Download Größe ist ungefähr 1GB. + + + Connect to Wi-Fi + Mit WLAN verbinden + + + Install + Installieren + + + Back + Zurück + + + Loading... + Laden... + + + Reboot + Neustart + + + Update failed + Aktualisierung fehlgeschlagen + + + + WifiUI + + Scanning for networks... + Suche nach Netzwerken... + + + CONNECTING... + VERBINDEN... + + + FORGET + VERGESSEN + + + Forget Wi-Fi Network "%1"? + WLAN Netzwerk "%1" vergessen? + + + Forget + Vergessen + + + diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index 963eeadd8..ed08a52df 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -285,11 +285,11 @@ ExperimentalModeButton EXPERIMENTAL MODE ON - + 実験モード CHILL MODE ON - + チルモード @@ -311,18 +311,6 @@ Installing... インストールしています... - - Receiving objects: - オブジェクトをダウンロードしています: - - - Resolving deltas: - デルタを解決しています: - - - Updating files: - ファイルを更新しています: - MapETA @@ -684,18 +672,6 @@ location set Waiting for internet インターネット接続を待機中 - - Choose Software to Install - インストールするソフトウェアを選択してください - - - Dashcam - ドライブレコーダー - - - Custom Software - カスタムソフトウェア - Enter URL URL を入力 @@ -1015,19 +991,43 @@ location set On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. + openpilotはこの車の場合、車に内蔵されているACCを標準で利用します。この機能を有効にすることで実験段階のopenpilotによるアクセル制御を利用できます。実験モードと合わせて利用することをお勧めします。 + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + openpilotは標準ではゆっくりとくつろげる運転を提供します。この実験モードを有効にすると、以下のくつろげる段階ではない開発中の機能を利用する事ができます。 + + + 🌮 End-to-End Longitudinal Control 🌮 + 🌮 エンドツーエンドアクセル制御 🌮 + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + openpilotにアクセルとブレーキを任せます。openpilotは赤信号や一時停止サインでの停止を含み、人間と同じように考えて運転を行います。openpilotが運転速度を決定するため、あなたが設定する速度は上限速度になります。この機能は実験段階のため、openpilotの運転ミスに常に備えて注意してください。 + + + New Driving Visualization + 新しい運転画面 + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + 新しい運転画面では、低速時に広角カメラの映像を表示することで、曲がる際の道路の視覚を向上します。実験段階を表すマークが右上に表示されます。 + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + この車のACCがアクセル制御を行うため実験モードを利用することができません。 + + + openpilot longitudinal control may come in a future update. - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. <br> <h4>New Driving Visualization</h4> The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. - - - - Enable experimental longitudinal control to allow experimental mode. - + Enable experimental longitudinal control to allow Experimental mode. + 実験段階のopenpilotによるアクセル制御を有効にしてください。 diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index 9defc2c36..7b742a504 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -285,11 +285,11 @@ ExperimentalModeButton EXPERIMENTAL MODE ON - + 실험적 모드 사용 CHILL MODE ON - + 안정적 모드 사용 @@ -311,18 +311,6 @@ Installing... 설치중... - - Receiving objects: - 수신중: - - - Resolving deltas: - 델타병합: - - - Updating files: - 파일갱신: - MapETA @@ -501,7 +489,7 @@ location set Become a comma prime member at connect.comma.ai - connect.comma.ai에서 comma prime에 가입합니다 + connect.comma.ai 접속 comma prime 가입 PRIME FEATURES: @@ -684,18 +672,6 @@ location set Waiting for internet 네트워크 접속을 기다립니다 - - Choose Software to Install - 설치할 소프트웨어를 선택하세요 - - - Dashcam - Dashcam - - - Custom Software - Custom Software - Enter URL URL 입력 @@ -875,7 +851,7 @@ location set Uninstall - 삭제 + 제거 @@ -1015,19 +991,43 @@ location set On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. - + 이 차량은 openpilot 롱컨트롤 대신 차량의 내장 ACC로 기본 설정됩니다. openpilot 롱컨트롤을 사용하려면 이 옵션을 활성화하세요. 실험적 openpilot 롱컨트롤을 사용하는 경우 실험적 모드를 활성화 하세요. - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. <br> <h4>New Driving Visualization</h4> The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. - + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + openpilot은 기본적으로 <b>안정적 모드</b>로 주행합니다. 실험적 모드는 안정적 모드에 준비되지 않은 <b>알파 수준 기능</b>을 활성화 합니다. 실험적 모드의 특징은 아래에 나열되어 있습니다 - Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. - + 🌮 End-to-End Longitudinal Control 🌮 + 🌮 E2E 롱컨트롤 🌮 - Enable experimental longitudinal control to allow experimental mode. - + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + 주행모델이 가속과 감속을 제어하도록 합니다. openpilot은 신호등과 정지표지판을 보고 멈추는 것을 포함하여 운전자가 생각하는것처럼 주행합니다. 주행 모델이 주행할 속도를 결정하므로 설정된 속도는 상한선으로만 작용합니다. 이것은 알파 기능이므로 사용에 주의해야 합니다. + + + New Driving Visualization + 새로운 주행 시각화 + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + 주행 시각화는 저속에서 도로를 향하는 광각 카메라로 전환되어 일부 회전을 더 잘 보여줍니다. 실험적 모드의 로고도 우측상단에 표시됩니다. + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + 차량에 장착된 ACC가 롱컨트롤에 사용되기 때문에 현재 이 차량은 실험적 모드를 사용할 수 없습니다. + + + openpilot longitudinal control may come in a future update. + 오픈파일럿 롱컨트롤은 향후 업데이트에서 제공될 수 있습니다. + + + An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + 오픈파일럿 롱컨트롤의 실험 버전은 실험적 모드와 함께 릴리즈 되지 않은 브랜치에서 테스트할 수 있습니다. + + + Enable experimental longitudinal control to allow Experimental mode. + 실험적 롱컨트롤을 사용하려면 실험적 모드를 활성화 하세요. diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index a1c966da4..a25c51de3 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -132,19 +132,19 @@ Driver Camera - Câmera voltada para o Motorista + Câmera do Motorista PREVIEW - PREVISUAL + VER Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off) - Pré-visualizar a câmera voltada para o motorista para garantir que monitor tem uma boa visibilidade (veículo precisa estar desligado) + Pré-visualizar a câmera voltada para o motorista para garantir que o monitoramento do sistema tenha uma boa visibilidade (veículo precisa estar desligado) Reset Calibration - Resetar Calibragem + Reinicializar Calibragem RESET @@ -200,7 +200,7 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required. - o openpilot requer que o dispositivo seja montado dentro de 4° esquerda ou direita e dentro de 5° para cima ou 8° para baixo. o openpilot está continuamente calibrando, resetar raramente é necessário. + O openpilot requer que o dispositivo seja montado dentro de 4° esquerda ou direita e dentro de 5° para cima ou 8° para baixo. O openpilot está continuamente calibrando, resetar raramente é necessário. Your device is pointed %1° %2 and %3° %4. @@ -285,11 +285,11 @@ ExperimentalModeButton EXPERIMENTAL MODE ON - + MODO EXPERIMENTAL ATIVADO CHILL MODE ON - + MODO CHILL ATIVADO @@ -312,18 +312,6 @@ Installing... Instalando... - - Receiving objects: - Recebendo objetos: - - - Resolving deltas: - Resolvendo deltas: - - - Updating files: - Atualizando arquivos: - MapETA @@ -388,8 +376,8 @@ Get turn-by-turn directions displayed and more with a comma prime subscription. Sign up now: https://connect.comma.ai - Obtenha instruções passo a passo exibidas e muito mais com -uma assinatura prime Inscreva-se agora: https://connect.comma.ai + Obtenha instruções passo a passo exibidas e muito mais com +uma assinatura prime. Inscreva-se agora: https://connect.comma.ai No home @@ -502,7 +490,7 @@ trabalho definido Become a comma prime member at connect.comma.ai - Torne-se um membro comma prime em connect.comma.ai + Seja um membro comma prime em connect.comma.ai PRIME FEATURES: @@ -514,11 +502,11 @@ trabalho definido 1 year of storage - 1 ano de armazenamento + 1 ano na nuvem Developer perks - Benefícios para desenvolvedor + Benefícios para devs @@ -688,18 +676,6 @@ trabalho definido Waiting for internet Esperando pela internet - - Choose Software to Install - Escolher Software para Instalar - - - Dashcam - Dashcam - - - Custom Software - Sofware Customizado - Enter URL Preencher URL @@ -835,7 +811,7 @@ trabalho definido Current Version - Versao Atual + Versão Atual Download @@ -863,11 +839,11 @@ trabalho definido UNINSTALL - DESINSTAL + REMOVER Uninstall %1 - Desintalar o %1 + Desinstalar o %1 Are you sure you want to uninstall? @@ -987,7 +963,7 @@ trabalho definido Disengage on Accelerator Pedal - Desacionar Com Pedal Do Acelerador + Desacionar com Pedal do Acelerador When enabled, pressing the accelerator pedal will disengage openpilot. @@ -995,7 +971,7 @@ trabalho definido Show ETA in 24h Format - Mostrar ETA em formato 24h + Mostrar ETA em Formato 24h Use 24h format instead of am/pm @@ -1019,19 +995,43 @@ trabalho definido On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. - + Neste carro o penpilot por padrão utiliza o ACC nativo do veículo ao invés de controlar longitudinalmente. Ative isto para mudar para o controle longitudinal do openpilot. Ativar o Modo Experimental é recomendado quando em uso do controle longitudinal experimental do openpilot. - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. <br> <h4>New Driving Visualization</h4> The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. - + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + openpilot por padrão funciona em <b>modo chill</b>. modo Experimental ativa <b>recursos de nível-alfa</b> que não estão prontos para o modo chill. Recursos experimentais estão listados abaixo: - Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. - + 🌮 End-to-End Longitudinal Control 🌮 + 🌮 Controle Longitudinal de Ponta a Ponta 🌮 - Enable experimental longitudinal control to allow experimental mode. - + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + Deixe o modelo de IA controlar o acelerador e os freios. O openpilot irá dirigir como pensa que um humano faria, incluindo parar em sinais vermelhos e sinais de parada. Uma vez que o modelo de condução decide a velocidade a conduzir, a velocidade definida apenas funcionará como um limite superior. Este é um recurso de qualidade alfa; erros devem ser esperados. + + + New Driving Visualization + Nova Visualização de Condução + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + A visualização da direção fará a transição para a câmera grande angular voltada para a estrada em baixas velocidades para mostrar melhor algumas curvas. O logotipo do modo Experimental também será exibido no canto superior direito. + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + O modo Experimental está atualmente indisponível para este carro já que o ACC original do carro é usado para controle longitudinal. + + + openpilot longitudinal control may come in a future update. + O controle longitudinal openpilot pode vir em uma atualização futura. + + + An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + Uma versão experimental do controle longitudinal openpilot pode ser testada, juntamente com o modo Experimental, em branches de desenvolvimento. + + + Enable experimental longitudinal control to allow Experimental mode. + Ative o controle longitudinal experimental para permitir o modo Experimental. diff --git a/selfdrive/ui/translations/main_th.ts b/selfdrive/ui/translations/main_th.ts index 0de0ba5f9..ce394ecb9 100644 --- a/selfdrive/ui/translations/main_th.ts +++ b/selfdrive/ui/translations/main_th.ts @@ -238,6 +238,14 @@ Disengage to Power Off ยกเลิกระบบช่วยขับเพื่อปิดเครื่อง + + Reset + รีเซ็ต + + + Review + ทบทวน + DriveStats @@ -273,6 +281,17 @@ กำลังเปิดกล้อง + + ExperimentalModeButton + + EXPERIMENTAL MODE ON + คุณกำลังใช้โหมดทดลอง + + + CHILL MODE ON + คุณกำลังใช้โหมดชิล + + InputDialog @@ -463,6 +482,17 @@ location set จดจำ connect.comma.ai โดยการเพิ่มไปยังหน้าจอโฮม เพื่อใช้งานเหมือนเป็นแอปพลิเคชัน + + ParamControl + + Enable + เปิดใช้งาน + + + Cancel + ยกเลิก + + PrimeAdWidget @@ -585,13 +615,6 @@ location set ไม่สามารถเมานต์พาร์ติชั่นข้อมูล กดยืนยันเพื่อรีเซ็ตอุปกรณ์ของคุณ - - RichTextDialog - - Ok - ตกลง - - SettingsWindow @@ -850,6 +873,10 @@ location set Select a branch เลือก Branch + + Uninstall + ถอนการติดตั้ง + SshControl @@ -974,29 +1001,57 @@ location set Show map on left side when in split screen view. แสดงแผนที่ด้านซ้ายของหน้าจอเมื่ออยู่ในโหมดแบ่งหน้าจอ - - 🌮 End-to-end longitudinal (extremely alpha) 🌮 - 🌮 ควบคุมเร่ง/เบรคแบบ End-to-end (อยู่ขั้นพัฒนา) 🌮 - Experimental openpilot Longitudinal Control ทดลองใช้ระบบควบคุมการเร่ง/เบรคโดย openpilot - <b>WARNING: openpilot longitudinal control is experimental for this car and will disable AEB.</b> - <b>คำเตือน: การควบคุมการเร่ง/เบรคโดย openpilot สำหรับรถคันนี้ยังอยู่ในขั้นทดลอง และระบบเบรคฉุกเฉินอัตโนมัติ (AEB) จะถูกปิด</b> + Experimental Mode + โหมดทดลอง - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would. Super experimental. - ให้ openpilot ควบคุมการเร่ง/เบรคแบบ end-to-end โดย openpilot จะขับอย่างที่มนุษย์คิด ระบบยังอยู่ในขั้นทดลอง + WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). + คำเตือน: การควบคุมการเร่ง/เบรคโดย openpilot สำหรับรถคันนี้ยังอยู่ในขั้นพัฒนา และระบบเบรคฉุกเฉินอัตโนมัติ (AEB) จะถูกปิด - openpilot longitudinal control is not currently available for this car. - ขณะนี้ยังไม่มีระบบควบคุมการเร่ง/เบรคโดย openpilot สำหรับรถคันนี้ + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. + โดยปกติสำหรับรถคันนี้ openpilot จะควบคุมการเร่ง/เบรคด้วยระบบ ACC จากโรงงาน แทนการควยคุมโดย openpilot เปิดสวิตซ์นี้เพื่อให้ openpilot ควบคุมการเร่ง/เบรค แนะนำให้เปิดโหมดทดลองเมื่อต้องการให้ openpilot ควบคุมการเร่ง/เบรค ซึ่งอยู่ในขั้นพัฒนา - Enable experimental longitudinal control to enable this. - เปิดใช้งานระบบควบคุมการเร่ง/เบรคขั้นทดลอง เพื่อเปิดใช้งานสิ่งนี้ + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + โดยปกติ openpilot จะขับใน<b>โหมดชิล</b> เปิดโหมดทดลองเพื่อใช้<b>ความสามารถในขั้นพัฒนา</b> ซึ่งยังไม่พร้อมสำหรับโหมดชิล ความสามารถในขั้นพัฒนามีดังนี้: + + + 🌮 End-to-End Longitudinal Control 🌮 + 🌮 ควบคุมเร่ง/เบรคแบบ End-to-End 🌮 + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + ให้ openpilot ควบคุมการเร่ง/เบรค โดย openpilot จะขับอย่างที่มนุษย์คิด รวมถึงการหยุดที่ไฟแดง และป้ายหยุดรถ เนื่องจาก openpilot จะกำหนดความเร็วในการขับด้วยตัวเอง การตั้งความเร็วจะเป็นเพียงการกำหนดความเร็วสูงสูดเท่านั้น ความสามารถนี้ยังอยู่ในขั้นพัฒนา อาจเกิดข้อผิดพลาดขึ้นได้ + + + New Driving Visualization + การแสดงภาพการขับขี่แบบใหม่ + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + การแสดงภาพการขับขี่จะเปลี่ยนไปใช้กล้องมุมกว้างที่หันหน้าไปทางถนนเมื่ออยู่ในความเร็วต่ำ เพื่อแสดงภาพการเลี้ยวที่ดีขึ้น โลโก้โหมดการทดลองจะแสดงที่มุมบนขวาด้วย + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + ขณะนี้โหมดทดลองไม่สามารถใช้งานได้ในรถคันนี้ เนื่องจากเปิดใช้ระบบควบคุมการเร่ง/เบรคของรถที่ติดตั้งจากโรงงานอยู่ + + + openpilot longitudinal control may come in a future update. + ระบบควบคุมการเร่ง/เบรคโดย openpilot อาจมาในการอัปเดตในอนาคต + + + An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + เวอร์ชันทดลองของระบบควบคุมการเร่ง/เบรคโดย openpilot สามารถทดสอบได้พร้อมกับโหมดการทดลอง บน branch ที่กำลังพัฒนา + + + Enable experimental longitudinal control to allow Experimental mode. + เปิดระบบควบคุมการเร่ง/เบรคขั้นพัฒนาโดย openpilot เพื่อเปิดใช้งานโหมดทดลอง @@ -1052,5 +1107,9 @@ location set Forget Wi-Fi Network "%1"? เลิกใช้เครือข่าย Wi-Fi "%1"? + + Forget + เลิกใช้ + diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index e77b3ef63..68c1bb766 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -285,11 +285,11 @@ ExperimentalModeButton EXPERIMENTAL MODE ON - + 试验模式运行 CHILL MODE ON - + 轻松模式运行 @@ -311,18 +311,6 @@ Installing... 正在安装…… - - Receiving objects: - 正在接收: - - - Resolving deltas: - 正在处理: - - - Updating files: - 正在更新文件: - MapETA @@ -682,18 +670,6 @@ location set Waiting for internet 等待网络连接 - - Choose Software to Install - 选择要安装的软件 - - - Dashcam - Dashcam(行车记录仪) - - - Custom Software - 自定义软件 - Enter URL 输入网址 @@ -1013,19 +989,43 @@ location set On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. + 针对此车辆,openpilot默认使用车辆自带的ACC,而非openpilot的纵向控制。启用此选项将切换到openpilot纵向控制。当使用试验性的openpilot纵向控制时,建议同时启用试验模式。 + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + openpilot 默认 <b>轻松模式</b>驾驶车辆。试验模式启用一些轻松模式之外的 <b>试验性功能</b>。试验性功能包括: + + + 🌮 End-to-End Longitudinal Control 🌮 + 🌮 端到端(End-to-End) 纵向控制 🌮 + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + 允许驾驶模型控制加速和制动,openpilot将模仿人类驾驶车辆,包括在红灯和停车让行标识前停车。鉴于驾驶模型确定行驶车速,所设定的车速仅作为上限。此功能尚处于早期测试状态,有可能会出现操作错误。 + + + New Driving Visualization + 新驾驶视角 + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + 当低速行驶时,驾驶视角将切换到前向广角摄像头,便于更完整地显示转向路径。右上角将显示试验模式图标。 + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + 由于此车辆使用自带的ACC纵向控制,当前无法使用试验模式。 + + + openpilot longitudinal control may come in a future update. - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. <br> <h4>New Driving Visualization</h4> The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. - - - - Enable experimental longitudinal control to allow experimental mode. - + Enable experimental longitudinal control to allow Experimental mode. + 启用试验性的纵向控制,以便允许使用试验模式。 diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index f667fdc97..71315e118 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -113,7 +113,7 @@ Decline, uninstall %1 - 拒絕並卸載 %1 + 拒絕並解除安裝 %1 @@ -132,7 +132,7 @@ Driver Camera - 駕駛員攝像頭 + 駕駛員監控鏡頭 PREVIEW @@ -285,11 +285,11 @@ ExperimentalModeButton EXPERIMENTAL MODE ON - + 實驗模式 ON CHILL MODE ON - + 輕鬆模式 ON @@ -311,18 +311,6 @@ Installing... 安裝中… - - Receiving objects: - 接收對象: - - - Resolving deltas: - 分析差異: - - - Updating files: - 更新檔案: - MapETA @@ -684,18 +672,6 @@ location set Waiting for internet 連接至網路中 - - Choose Software to Install - 選擇要安裝的軟體 - - - Dashcam - 行車記錄器 - - - Custom Software - 定制的軟體 - Enter URL 輸入網址 @@ -859,15 +835,15 @@ location set UNINSTALL - 卸載 + 解除安裝 Uninstall %1 - 卸載 %1 + 解除安裝 %1 Are you sure you want to uninstall? - 您確定您要卸載嗎? + 您確定您要解除安裝嗎? CHECK @@ -875,7 +851,7 @@ location set Uninstall - 卸載 + 解除安裝 @@ -1015,19 +991,43 @@ location set On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. + 在本車輛中,openpilot預設將使用原車內建的ACC系統,而非openpilot縱向控制。開啟此開關來啟用openpilot縱向控制,使用此選項時建議一併啟用實驗模式。 + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + openpilot 預設以 <b>輕鬆模式</b> 駕駛。 實驗模式啟用了尚未準備好進入輕鬆模式的 <b>alpha 級功能</b>。實驗功能如下: + + + 🌮 End-to-End Longitudinal Control 🌮 + 🌮端到端縱向控制🌮 + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + 讓駕駛模型來控制油門及煞車。openpilot將會模擬人類的駕駛行為,包含在看見紅燈及停止標示時停車。由於車速將由駕駛模型決定,因此您設定的時速將成為速度上限。本功能仍在早期實驗階段,請預期模型有犯錯的可能性。 + + + New Driving Visualization + 新的駕駛視覺介面 + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + 低速行駛時,將會切換成路側廣角鏡頭,以完整顯示轉彎路徑,右上角將出現實驗模式圖案。 + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + 因車輛使用內建ACC系統,無法在本車輛上啟動實驗模式。 + + + openpilot longitudinal control may come in a future update. - openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: <br> <h4>🌮 End-to-End Longitudinal Control 🌮</h4> Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. <br> <h4>New Driving Visualization</h4> The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. - Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. - - - - Enable experimental longitudinal control to allow experimental mode. - + Enable experimental longitudinal control to allow Experimental mode. + 啟用實驗性縱向控制以使用實驗模式。 diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 2d4533afe..6c850b8ca 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -55,9 +55,8 @@ void update_leads(UIState *s, const cereal::RadarState::Reader &radar_state, con } void update_line_data(const UIState *s, const cereal::ModelDataV2::XYZTData::Reader &line, - float y_off, float z_off, QPolygonF *pvd, int max_idx, bool allow_invert=true) { + float y_off, float z_off, QPolygonF *pvd, int max_idx, bool allow_invert=true) { const auto line_x = line.getX(), line_y = line.getY(), line_z = line.getZ(); - QPolygonF left_points, right_points; left_points.reserve(max_idx + 1); right_points.reserve(max_idx + 1); @@ -113,6 +112,39 @@ void update_model(UIState *s, const cereal::ModelDataV2::Reader &model) { update_line_data(s, model_position, 0.9, 1.22, &scene.track_vertices, max_idx, false); } +void update_dmonitoring(UIState *s, const cereal::DriverStateV2::Reader &driverstate, float dm_fade_state, bool is_rhd) { + UIScene &scene = s->scene; + const auto driver_orient = is_rhd ? driverstate.getRightDriverData().getFaceOrientation() : driverstate.getLeftDriverData().getFaceOrientation(); + for (int i = 0; i < std::size(scene.driver_pose_vals); i++) { + float v_this = (i == 0 ? (driver_orient[i] < 0 ? 0.7 : 0.9) : 0.4) * driver_orient[i]; + scene.driver_pose_diff[i] = fabs(scene.driver_pose_vals[i] - v_this); + scene.driver_pose_vals[i] = 0.8 * v_this + (1 - 0.8) * scene.driver_pose_vals[i]; + scene.driver_pose_sins[i] = sinf(scene.driver_pose_vals[i]*(1.0-dm_fade_state)); + scene.driver_pose_coss[i] = cosf(scene.driver_pose_vals[i]*(1.0-dm_fade_state)); + } + + const mat3 r_xyz = (mat3){{ + scene.driver_pose_coss[1]*scene.driver_pose_coss[2], + scene.driver_pose_coss[1]*scene.driver_pose_sins[2], + -scene.driver_pose_sins[1], + + -scene.driver_pose_sins[0]*scene.driver_pose_sins[1]*scene.driver_pose_coss[2] - scene.driver_pose_coss[0]*scene.driver_pose_sins[2], + -scene.driver_pose_sins[0]*scene.driver_pose_sins[1]*scene.driver_pose_sins[2] + scene.driver_pose_coss[0]*scene.driver_pose_coss[2], + -scene.driver_pose_sins[0]*scene.driver_pose_coss[1], + + scene.driver_pose_coss[0]*scene.driver_pose_sins[1]*scene.driver_pose_coss[2] - scene.driver_pose_sins[0]*scene.driver_pose_sins[2], + scene.driver_pose_coss[0]*scene.driver_pose_sins[1]*scene.driver_pose_sins[2] + scene.driver_pose_sins[0]*scene.driver_pose_coss[2], + scene.driver_pose_coss[0]*scene.driver_pose_coss[1], + }}; + + // transform vertices + for (int kpi = 0; kpi < std::size(default_face_kpts_3d); kpi++) { + vec3 kpt_this = default_face_kpts_3d[kpi]; + kpt_this = matvecmul3(r_xyz, kpt_this); + scene.face_kpts_draw[kpi] = (vec3){{(float)kpt_this.v[0], (float)kpt_this.v[1], (float)(kpt_this.v[2] * (1.0-dm_fade_state) + 8 * dm_fade_state)}}; + } +} + static void update_sockets(UIState *s) { s->sm->update(0); } @@ -164,7 +196,7 @@ static void update_state(UIState *s) { scene.longitudinal_control = sm["carParams"].getCarParams().getOpenpilotLongitudinalControl(); } if (sm.updated("wideRoadCameraState")) { - float scale = (sm["wideRoadCameraState"].getWideRoadCameraState().getSensor() == cereal::FrameData::ImageSensor::AR0321) ? 6.0f : 1.0f; + float scale = (sm["wideRoadCameraState"].getWideRoadCameraState().getSensor() == cereal::FrameData::ImageSensor::AR0231) ? 6.0f : 1.0f; scene.light_sensor = std::max(100.0f - scale * sm["wideRoadCameraState"].getWideRoadCameraState().getExposureValPercent(), 0.0f); } scene.started = sm["deviceState"].getDeviceState().getStarted() && scene.ignition; @@ -214,7 +246,7 @@ void UIState::updateStatus() { UIState::UIState(QObject *parent) : QObject(parent) { sm = std::make_unique>({ "modelV2", "controlsState", "liveCalibration", "radarState", "deviceState", "roadCameraState", - "pandaStates", "carParams", "driverMonitoringState", "carState", "liveLocationKalman", + "pandaStates", "carParams", "driverMonitoringState", "carState", "liveLocationKalman", "driverStateV2", "wideRoadCameraState", "managerState", "navInstruction", "navRoute", "gnssMeasurements", }); diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index d6f5c3e2e..e3eb97a76 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -20,11 +20,21 @@ const int bdr_s = 30; const int header_h = 420; const int footer_h = 280; -const int UI_FREQ = 20; // Hz +const int UI_FREQ = 20; // Hz typedef cereal::CarControl::HUDControl::AudibleAlert AudibleAlert; const mat3 DEFAULT_CALIBRATION = {{ 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0 }}; +const vec3 default_face_kpts_3d[] = { + {-5.98, -51.20, 8.00}, {-17.64, -49.14, 8.00}, {-23.81, -46.40, 8.00}, {-29.98, -40.91, 8.00}, {-32.04, -37.49, 8.00}, + {-34.10, -32.00, 8.00}, {-36.16, -21.03, 8.00}, {-36.16, 6.40, 8.00}, {-35.47, 10.51, 8.00}, {-32.73, 19.43, 8.00}, + {-29.30, 26.29, 8.00}, {-24.50, 33.83, 8.00}, {-19.01, 41.37, 8.00}, {-14.21, 46.17, 8.00}, {-12.16, 47.54, 8.00}, + {-4.61, 49.60, 8.00}, {4.99, 49.60, 8.00}, {12.53, 47.54, 8.00}, {14.59, 46.17, 8.00}, {19.39, 41.37, 8.00}, + {24.87, 33.83, 8.00}, {29.67, 26.29, 8.00}, {33.10, 19.43, 8.00}, {35.84, 10.51, 8.00}, {36.53, 6.40, 8.00}, + {36.53, -21.03, 8.00}, {34.47, -32.00, 8.00}, {32.42, -37.49, 8.00}, {30.36, -40.91, 8.00}, {24.19, -46.40, 8.00}, + {18.02, -49.14, 8.00}, {6.36, -51.20, 8.00}, {-5.98, -51.20, 8.00}, +}; + struct Alert { QString text1; QString text2; @@ -78,7 +88,7 @@ typedef enum UIStatus { } UIStatus; const QColor bg_colors [] = { - [STATUS_DISENGAGED] = QColor(0x17, 0x33, 0x49, 0xc8), + [STATUS_DISENGAGED] = QColor(0x17, 0x33, 0x49, 0xc8), [STATUS_OVERRIDE] = QColor(0x91, 0x9b, 0x95, 0xf1), [STATUS_ENGAGED] = QColor(0x17, 0x86, 0x44, 0xf1), [STATUS_WARNING] = QColor(0xDA, 0x6F, 0x25, 0xf1), @@ -103,6 +113,13 @@ typedef struct UIScene { // lead QPointF lead_vertices[2]; + // DMoji state + float driver_pose_vals[3]; + float driver_pose_diff[3]; + float driver_pose_sins[3]; + float driver_pose_coss[3]; + vec3 face_kpts_draw[std::size(default_face_kpts_3d)]; + float light_sensor; bool started, ignition, is_metric, map_on_left, longitudinal_control; uint64_t started_frame; @@ -152,7 +169,6 @@ private: UIState *uiState(); // device management class - class Device : public QObject { Q_OBJECT @@ -184,6 +200,7 @@ public slots: void ui_update_params(UIState *s); int get_path_length_idx(const cereal::ModelDataV2::XYZTData::Reader &line, const float path_height); void update_model(UIState *s, const cereal::ModelDataV2::Reader &model); +void update_dmonitoring(UIState *s, const cereal::DriverStateV2::Reader &driverstate, float dm_fade_state, bool is_rhd); void update_leads(UIState *s, const cereal::RadarState::Reader &radar_state, const cereal::ModelDataV2::XYZTData::Reader &line); void update_line_data(const UIState *s, const cereal::ModelDataV2::XYZTData::Reader &line, float y_off, float z_off, QPolygonF *pvd, int max_idx, bool allow_invert); diff --git a/selfdrive/updated.py b/selfdrive/updated.py index 9da2a05a1..2cb7d1c13 100755 --- a/selfdrive/updated.py +++ b/selfdrive/updated.py @@ -423,6 +423,9 @@ def main() -> None: wait_helper = WaitTimeHelper() wait_helper.only_check_for_update = True + # invalidate old finalized update + set_consistent_flag(False) + # Run the update loop while True: wait_helper.ready_event.clear() diff --git a/system/camerad/SConscript b/system/camerad/SConscript index ddc763b53..3ecc3f6d7 100644 --- a/system/camerad/SConscript +++ b/system/camerad/SConscript @@ -2,17 +2,13 @@ Import('env', 'arch', 'cereal', 'messaging', 'common', 'gpucommon', 'visionipc') libs = ['m', 'pthread', common, 'jpeg', 'OpenCL', 'yuv', cereal, messaging, 'zmq', 'capnp', 'kj', visionipc, gpucommon, 'atomic'] -cenv = env.Clone() -cenv['CPPPATH'].append('include/') - -camera_obj = cenv.Object(['cameras/camera_qcom2.cc', 'cameras/camera_common.cc', 'cameras/camera_util.cc']) -cenv.Program('camerad', [ +camera_obj = env.Object(['cameras/camera_qcom2.cc', 'cameras/camera_common.cc', 'cameras/camera_util.cc']) +env.Program('camerad', [ 'main.cc', camera_obj, ], LIBS=libs) if GetOption("test") and arch == "x86_64": - cenv.Program('test/ae_gray_test', [ - 'test/ae_gray_test.cc', - camera_obj, - ], LIBS=libs) + env.Program('test/ae_gray_test', + ['test/ae_gray_test.cc', camera_obj], + LIBS=libs) diff --git a/system/camerad/cameras/camera_common.cc b/system/camerad/cameras/camera_common.cc index 30e2810ec..7ee373805 100644 --- a/system/camerad/cameras/camera_common.cc +++ b/system/camerad/cameras/camera_common.cc @@ -167,7 +167,7 @@ void fill_frame_data(cereal::FrameData::Builder &framed, const FrameMetadata &fr framed.setExposureValPercent(perc); if (c->camera_id == CAMERA_ID_AR0231) { - framed.setSensor(cereal::FrameData::ImageSensor::AR0321); + framed.setSensor(cereal::FrameData::ImageSensor::AR0231); } else if (c->camera_id == CAMERA_ID_OX03C10) { framed.setSensor(cereal::FrameData::ImageSensor::OX03C10); } diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index 2ee06e372..92b3bde41 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -60,15 +60,15 @@ CameraInfo cameras_supported[CAMERA_ID_MAX] = { const float DC_GAIN_AR0231 = 2.5; const float DC_GAIN_OX03C10 = 7.32; -const float DC_GAIN_ON_GREY_AR0231= 0.2; +const float DC_GAIN_ON_GREY_AR0231 = 0.2; const float DC_GAIN_OFF_GREY_AR0231 = 0.3; -const float DC_GAIN_ON_GREY_OX03C10= 0.25; -const float DC_GAIN_OFF_GREY_OX03C10 = 0.35; +const float DC_GAIN_ON_GREY_OX03C10 = 0.9; +const float DC_GAIN_OFF_GREY_OX03C10 = 1.0; const int DC_GAIN_MIN_WEIGHT_AR0231 = 0; const int DC_GAIN_MAX_WEIGHT_AR0231 = 1; -const int DC_GAIN_MIN_WEIGHT_OX03C10 = 16; -const int DC_GAIN_MAX_WEIGHT_OX03C10 = 32; +const int DC_GAIN_MIN_WEIGHT_OX03C10 = 1; // always on is fine +const int DC_GAIN_MAX_WEIGHT_OX03C10 = 1; const float TARGET_GREY_FACTOR_AR0231 = 1.0; const float TARGET_GREY_FACTOR_OX03C10 = 0.02; @@ -96,10 +96,16 @@ const uint32_t ox03c10_analog_gains_reg[] = { const int ANALOG_GAIN_MIN_IDX_AR0231 = 0x1; // 0.25x const int ANALOG_GAIN_REC_IDX_AR0231 = 0x6; // 0.8x const int ANALOG_GAIN_MAX_IDX_AR0231 = 0xD; // 4.0x +const int ANALOG_GAIN_COST_DELTA_AR0231 = 0; +const float ANALOG_GAIN_COST_LOW_AR0231 = 0.1; +const float ANALOG_GAIN_COST_HIGH_AR0231 = 5.0; const int ANALOG_GAIN_MIN_IDX_OX03C10 = 0x0; const int ANALOG_GAIN_REC_IDX_OX03C10 = 0x11; // 2.5x const int ANALOG_GAIN_MAX_IDX_OX03C10 = 0x36; +const int ANALOG_GAIN_COST_DELTA_OX03C10 = -1; +const float ANALOG_GAIN_COST_LOW_OX03C10 = 0.4; +const float ANALOG_GAIN_COST_HIGH_OX03C10 = 6.4; const int EXPOSURE_TIME_MIN_AR0231 = 2; // with HDR, fastest ss const int EXPOSURE_TIME_MAX_AR0231 = 0x0855; // with HDR, slowest ss, 40ms @@ -532,6 +538,9 @@ void CameraState::camera_set_parameters() { analog_gain_min_idx = ANALOG_GAIN_MIN_IDX_AR0231; analog_gain_rec_idx = ANALOG_GAIN_REC_IDX_AR0231; analog_gain_max_idx = ANALOG_GAIN_MAX_IDX_AR0231; + analog_gain_cost_delta = ANALOG_GAIN_COST_DELTA_AR0231; + analog_gain_cost_low = ANALOG_GAIN_COST_LOW_AR0231; + analog_gain_cost_high = ANALOG_GAIN_COST_HIGH_AR0231; for (int i=0; i<=analog_gain_max_idx; i++) { sensor_analog_gains[i] = sensor_analog_gains_AR0231[i]; } @@ -548,6 +557,9 @@ void CameraState::camera_set_parameters() { analog_gain_min_idx = ANALOG_GAIN_MIN_IDX_OX03C10; analog_gain_rec_idx = ANALOG_GAIN_REC_IDX_OX03C10; analog_gain_max_idx = ANALOG_GAIN_MAX_IDX_OX03C10; + analog_gain_cost_delta = ANALOG_GAIN_COST_DELTA_OX03C10; + analog_gain_cost_low = ANALOG_GAIN_COST_LOW_OX03C10; + analog_gain_cost_high = ANALOG_GAIN_COST_HIGH_OX03C10; for (int i=0; i<=analog_gain_max_idx; i++) { sensor_analog_gains[i] = sensor_analog_gains_OX03C10[i]; } @@ -1029,6 +1041,30 @@ void CameraState::handle_camera_event(void *evdat) { } } +void CameraState::update_exposure_score(float desired_ev, int exp_t, int exp_g_idx, float exp_gain) { + float score = 1e6; + if (camera_id == CAMERA_ID_AR0231) { + // Cost of ev diff + score = std::abs(desired_ev - (exp_t * exp_gain)) * 10; + // Cost of absolute gain + float m = exp_g_idx > analog_gain_rec_idx ? analog_gain_cost_high : analog_gain_cost_low; + score += std::abs(exp_g_idx - (int)analog_gain_rec_idx) * m; + // Cost of changing gain + score += std::abs(exp_g_idx - gain_idx) * (score + 1.0) / 10.0; + } else if (camera_id == CAMERA_ID_OX03C10) { + score = std::abs(desired_ev - (exp_t * exp_gain)); + float m = exp_g_idx > analog_gain_rec_idx ? analog_gain_cost_high : analog_gain_cost_low; + score += std::abs(exp_g_idx - (int)analog_gain_rec_idx) * m; + score += ((1 - analog_gain_cost_delta) + analog_gain_cost_delta * (exp_g_idx - analog_gain_min_idx) / (analog_gain_max_idx - analog_gain_min_idx)) * std::abs(exp_g_idx - gain_idx) * 5.0; + } + + if (score < best_ev_score) { + new_exp_t = exp_t; + new_exp_g = exp_g_idx; + best_ev_score = score; + } +} + void CameraState::set_camera_exposure(float grey_frac) { if (!enabled) return; const float dt = 0.05; @@ -1054,9 +1090,9 @@ void CameraState::set_camera_exposure(float grey_frac) { float k = (1.0 - k_ev) / 3.0; desired_ev = (k * cur_ev[0]) + (k * cur_ev[1]) + (k * cur_ev[2]) + (k_ev * desired_ev); - float best_ev_score = 1e6; - int new_g = 0; - int new_t = 0; + best_ev_score = 1e6; + new_exp_g = 0; + new_exp_t = 0; // Hysteresis around high conversion gain // We usually want this on since it results in lower noise, but turn off in very bright day scenes @@ -1083,8 +1119,8 @@ void CameraState::set_camera_exposure(float grey_frac) { gain_idx = std::stoi(gain_bytes); exposure_time = std::stoi(time_bytes); - new_g = gain_idx; - new_t = exposure_time; + new_exp_g = gain_idx; + new_exp_t = exposure_time; enable_dc_gain = false; } else { // Simple brute force optimizer to choose sensor parameters @@ -1100,23 +1136,7 @@ void CameraState::set_camera_exposure(float grey_frac) { continue; } - // Compute error to desired ev - float score = std::abs(desired_ev - (t * gain)) * 10; - - // Going below recommended gain needs lower penalty to not overexpose - float m = g > analog_gain_rec_idx ? 5.0 : 0.1; - score += std::abs(g - (int)analog_gain_rec_idx) * m; - - // LOGE("cam: %d - gain: %d, t: %d (%.2f), score %.2f, score + gain %.2f, %.3f, %.3f", camera_num, g, t, desired_ev / gain, score, score + std::abs(g - gain_idx) * (score + 1.0) / 10.0, desired_ev, min_ev); - - // Small penalty on changing gain - score += std::abs(g - gain_idx) * (score + 1.0) / 10.0; - - if (score < best_ev_score) { - new_t = t; - new_g = g; - best_ev_score = score; - } + update_exposure_score(desired_ev, t, g, gain); } } @@ -1125,9 +1145,9 @@ void CameraState::set_camera_exposure(float grey_frac) { measured_grey_fraction = grey_frac; target_grey_fraction = target_grey; - analog_gain_frac = sensor_analog_gains[new_g]; - gain_idx = new_g; - exposure_time = new_t; + analog_gain_frac = sensor_analog_gains[new_exp_g]; + gain_idx = new_exp_g; + exposure_time = new_exp_t; dc_gain_enabled = enable_dc_gain; float gain = analog_gain_frac * (1 + dc_gain_weight * (dc_gain_factor-1) / dc_gain_max_weight); @@ -1144,7 +1164,7 @@ void CameraState::set_camera_exposure(float grey_frac) { // LOGE("ae - camera %d, cur_t %.5f, sof %.5f, dt %.5f", camera_num, 1e-9 * nanos_since_boot(), 1e-9 * buf.cur_frame_data.timestamp_sof, 1e-9 * (nanos_since_boot() - buf.cur_frame_data.timestamp_sof)); if (camera_id == CAMERA_ID_AR0231) { - uint16_t analog_gain_reg = 0xFF00 | (new_g << 4) | new_g; + uint16_t analog_gain_reg = 0xFF00 | (new_exp_g << 4) | new_exp_g; struct i2c_random_wr_payload exp_reg_array[] = { {0x3366, analog_gain_reg}, {0x3362, (uint16_t)(dc_gain_enabled ? 0x1 : 0x0)}, @@ -1152,15 +1172,16 @@ void CameraState::set_camera_exposure(float grey_frac) { }; sensors_i2c(exp_reg_array, sizeof(exp_reg_array)/sizeof(struct i2c_random_wr_payload), CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG, true); } else if (camera_id == CAMERA_ID_OX03C10) { - // t_HCG + t_LCG + t_VS on LPD, t_SPD on SPD - uint32_t hcg_time = std::max((dc_gain_weight * exposure_time / dc_gain_max_weight), 0); - uint32_t lcg_time = std::max(((dc_gain_max_weight - dc_gain_weight) * exposure_time / dc_gain_max_weight), 0); - // uint32_t spd_time = std::max(hcg_time / 16, (uint32_t)exposure_time_min); - uint32_t vs_time = std::min(std::max((uint32_t)exposure_time / 128, VS_TIME_MIN_OX03C10), VS_TIME_MAX_OX03C10); - uint32_t spd_time = vs_time; + // t_HCG&t_LCG + t_VS on LPD, t_SPD on SPD + uint32_t hcg_time = exposure_time; + uint32_t lcg_time = hcg_time; + uint32_t spd_time = exposure_time_max + VS_TIME_MAX_OX03C10; + uint32_t vs_time = std::min(std::max((uint32_t)exposure_time / 40, VS_TIME_MIN_OX03C10), VS_TIME_MAX_OX03C10); - uint32_t real_gain = ox03c10_analog_gains_reg[new_g]; + uint32_t real_gain = ox03c10_analog_gains_reg[new_exp_g]; uint32_t min_gain = ox03c10_analog_gains_reg[0]; + uint32_t spd_gain = 0xF00; + struct i2c_random_wr_payload exp_reg_array[] = { {0x3501, hcg_time>>8}, {0x3502, hcg_time&0xFF}, {0x3581, lcg_time>>8}, {0x3582, lcg_time&0xFF}, @@ -1169,7 +1190,7 @@ void CameraState::set_camera_exposure(float grey_frac) { {0x3508, real_gain>>8}, {0x3509, real_gain&0xFF}, {0x3588, min_gain>>8}, {0x3589, min_gain&0xFF}, - {0x3548, min_gain>>8}, {0x3549, min_gain&0xFF}, + {0x3548, spd_gain>>8}, {0x3549, spd_gain&0xFF}, {0x35c8, min_gain>>8}, {0x35c9, min_gain&0xFF}, }; sensors_i2c(exp_reg_array, sizeof(exp_reg_array)/sizeof(struct i2c_random_wr_payload), CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG, false); diff --git a/system/camerad/cameras/camera_qcom2.h b/system/camerad/cameras/camera_qcom2.h index 1e25e605c..9e0109ab2 100644 --- a/system/camerad/cameras/camera_qcom2.h +++ b/system/camerad/cameras/camera_qcom2.h @@ -41,9 +41,15 @@ public: int analog_gain_min_idx; int analog_gain_max_idx; int analog_gain_rec_idx; + int analog_gain_cost_delta; + float analog_gain_cost_low; + float analog_gain_cost_high; float cur_ev[3]; float min_ev, max_ev; + float best_ev_score; + int new_exp_g; + int new_exp_t; float measured_grey_fraction; float target_grey_fraction; @@ -55,6 +61,7 @@ public: int camera_num; void handle_camera_event(void *evdat); + void update_exposure_score(float desired_ev, int exp_t, int exp_g_idx, float exp_gain); void set_camera_exposure(float grey_frac); void sensors_start(); diff --git a/system/camerad/cameras/sensor2_i2c.h b/system/camerad/cameras/sensor2_i2c.h index ab51059d9..83fcb8f7a 100644 --- a/system/camerad/cameras/sensor2_i2c.h +++ b/system/camerad/cameras/sensor2_i2c.h @@ -126,7 +126,7 @@ struct i2c_random_wr_payload init_array_ox03c10[] = { {0x3219, 0x08}, {0x3506, 0x20}, {0x3507, 0x00}, // hcg fine exposure - {0x350a, 0x04}, {0x350b, 0x00}, {0x350c, 0x00}, // hcg digital gain + {0x350a, 0x01}, {0x350b, 0x00}, {0x350c, 0x00}, // hcg digital gain {0x3586, 0x40}, {0x3587, 0x00}, // lcg fine exposure {0x358a, 0x01}, {0x358b, 0x00}, {0x358c, 0x00}, // lcg digital gain @@ -711,11 +711,11 @@ struct i2c_random_wr_payload init_array_ox03c10[] = { {0x4221, 0x03}, // this is changed from 1 -> 3 // DCG exposure coarse - {0x3501, 0x01}, {0x3502, 0xc8}, + // {0x3501, 0x01}, {0x3502, 0xc8}, // SPD exposure coarse - {0x3541, 0x01}, {0x3542, 0xc8}, + // {0x3541, 0x01}, {0x3542, 0xc8}, // VS exposure coarse - {0x35c1, 0x00}, {0x35c2, 0x01}, + // {0x35c1, 0x00}, {0x35c2, 0x01}, // crc reference {0x420e, 0x66}, {0x420f, 0x5d}, {0x4210, 0xa8}, {0x4211, 0x55}, diff --git a/system/hardware/base.h b/system/hardware/base.h index b70948d48..6cfc1d874 100644 --- a/system/hardware/base.h +++ b/system/hardware/base.h @@ -16,10 +16,13 @@ public: static int get_voltage() { return 0; }; static int get_current() { return 0; }; + static std::string get_serial() { return "cccccc"; } + static void reboot() {} static void poweroff() {} static void set_brightness(int percent) {} static void set_display_power(bool on) {} + static void set_volume(float volume) {} static bool get_ssh_enabled() { return false; } static void set_ssh_enabled(bool enabled) {} diff --git a/system/hardware/hw.h b/system/hardware/hw.h index f50e94abe..5599e7918 100644 --- a/system/hardware/hw.h +++ b/system/hardware/hw.h @@ -7,15 +7,7 @@ #include "system/hardware/tici/hardware.h" #define Hardware HardwareTici #else -class HardwarePC : public HardwareNone { -public: - static std::string get_os_version() { return "openpilot for PC"; } - static std::string get_name() { return "pc"; }; - static cereal::InitData::DeviceType get_device_type() { return cereal::InitData::DeviceType::PC; }; - static bool PC() { return true; } - static bool TICI() { return util::getenv("TICI", 0) == 1; } - static bool AGNOS() { return util::getenv("TICI", 0) == 1; } -}; +#include "system/hardware/pc/hardware.h" #define Hardware HardwarePC #endif diff --git a/system/hardware/pc/hardware.h b/system/hardware/pc/hardware.h new file mode 100644 index 000000000..529b4bfe9 --- /dev/null +++ b/system/hardware/pc/hardware.h @@ -0,0 +1,21 @@ +#pragma once + +#include "system/hardware/base.h" + +class HardwarePC : public HardwareNone { +public: + static std::string get_os_version() { return "openpilot for PC"; } + static std::string get_name() { return "pc"; }; + static cereal::InitData::DeviceType get_device_type() { return cereal::InitData::DeviceType::PC; }; + static bool PC() { return true; } + static bool TICI() { return util::getenv("TICI", 0) == 1; } + static bool AGNOS() { return util::getenv("TICI", 0) == 1; } + + static void set_volume(float volume) { + volume = util::map_val(volume, 0.f, 1.f, MIN_VOLUME, MAX_VOLUME); + + char volume_str[6]; + snprintf(volume_str, sizeof(volume_str), "%.3f", volume); + std::system(("pactl set-sink-volume @DEFAULT_SINK@ " + std::string(volume_str)).c_str()); + } +}; diff --git a/system/hardware/tici/amplifier.py b/system/hardware/tici/amplifier.py old mode 100755 new mode 100644 index a8b279863..8233834d1 --- a/system/hardware/tici/amplifier.py +++ b/system/hardware/tici/amplifier.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python from smbus2 import SMBus from collections import namedtuple @@ -63,11 +62,43 @@ BASE_CONFIG = [ AmpConfig("Zero-crossing detection disabled", 0b0, 0x49, 5, 0b00100000), ] -BASE_CONFIG += configs_from_eq_params(0x84, EQParams(0x274F, 0xC0FF, 0x3BF9, 0x0B3C, 0x1656)) -BASE_CONFIG += configs_from_eq_params(0x8E, EQParams(0x1009, 0xC6BF, 0x2952, 0x1C97, 0x30DF)) -BASE_CONFIG += configs_from_eq_params(0x98, EQParams(0x0F75, 0xCBE5, 0x0ED2, 0x2528, 0x3E42)) -BASE_CONFIG += configs_from_eq_params(0xA2, EQParams(0x091F, 0x3D4C, 0xCE11, 0x1266, 0x2807)) -BASE_CONFIG += configs_from_eq_params(0xAC, EQParams(0x0A9E, 0x3F20, 0xE573, 0x0A8B, 0x3A3B)) +CONFIGS = { + "tici": [ + *configs_from_eq_params(0x84, EQParams(0x274F, 0xC0FF, 0x3BF9, 0x0B3C, 0x1656)), + *configs_from_eq_params(0x8E, EQParams(0x1009, 0xC6BF, 0x2952, 0x1C97, 0x30DF)), + *configs_from_eq_params(0x98, EQParams(0x0F75, 0xCBE5, 0x0ED2, 0x2528, 0x3E42)), + *configs_from_eq_params(0xA2, EQParams(0x091F, 0x3D4C, 0xCE11, 0x1266, 0x2807)), + *configs_from_eq_params(0xAC, EQParams(0x0A9E, 0x3F20, 0xE573, 0x0A8B, 0x3A3B)), + ], + "tizi": [ + AmpConfig("Left speaker output from left DAC", 0b1, 0x2B, 0, 0b11111111), + AmpConfig("Left Speaker Mixer Gain", 0b00, 0x2D, 0, 0b00000011), + AmpConfig("Left speaker output volume", 0x1F, 0x3D, 0, 0b00011111), + AmpConfig("Right speaker output volume", 0x1F, 0x3E, 0, 0b00011111), + AmpConfig("DAI1 attenuation (DV1)", 0x4, 0x2F, 0, 0b00001111), + AmpConfig("DAI2 attenuation (DV2)", 0x4, 0x31, 0, 0b00001111), + AmpConfig("DAI2: DC blocking", 0b0, 0x20, 0, 0b00000001), + AmpConfig("ALC enable", 0b0, 0x43, 7, 0b10000000), + AmpConfig("DAI2 EQ attenuation", 0x2, 0x32, 0, 0b00001111), + AmpConfig("Excursion limiter upper corner freq", 0b001, 0x41, 4, 0b01110000), + AmpConfig("Excursion limiter threshold", 0b100, 0x42, 0, 0b00001111), + AmpConfig("Distortion limit (THDCLP)", 0x0, 0x46, 4, 0b11110000), + AmpConfig("Distortion limiter release time constant", 0b1, 0x46, 0, 0b00000001), + AmpConfig("Left DAC input mixer: DAI1 left", 0b0, 0x22, 7, 0b10000000), + AmpConfig("Left DAC input mixer: DAI1 right", 0b0, 0x22, 6, 0b01000000), + AmpConfig("Left DAC input mixer: DAI2 left", 0b1, 0x22, 5, 0b00100000), + AmpConfig("Left DAC input mixer: DAI2 right", 0b0, 0x22, 4, 0b00010000), + AmpConfig("Right DAC input mixer: DAI2 left", 0b0, 0x22, 1, 0b00000010), + AmpConfig("Right DAC input mixer: DAI2 right", 0b1, 0x22, 0, 0b00000001), + AmpConfig("Volume adjustment smoothing disabled", 0b1, 0x49, 6, 0b01000000), + + *configs_from_eq_params(0x84, EQParams(0x3084, 0xC023, 0x3D60, 0x042B, 0x1222)), + *configs_from_eq_params(0x8E, EQParams(0x2FB2, 0xC05C, 0x3BD3, 0x06C5, 0x16BB)), + *configs_from_eq_params(0x98, EQParams(0x21F5, 0xDF73, 0x2DFE, 0x371A, 0x2C80)), + *configs_from_eq_params(0xA2, EQParams(0x2A5A, 0x0AD0, 0x14FA, 0x3F14, 0x3C76)), + *configs_from_eq_params(0xAC, EQParams(0x1577, 0x3FAE, 0xEE60, 0x0664, 0x3D86)), + ], +} class Amplifier: AMP_I2C_BUS = 0 @@ -91,14 +122,13 @@ class Amplifier: def set_global_shutdown(self, amp_disabled): self.set_config(AmpConfig("Global shutdown", 0b0 if amp_disabled else 0b1, 0x51, 7, 0b10000000)) - def initialize_configuration(self): + def initialize_configuration(self, model): self.set_global_shutdown(amp_disabled=True) for config in BASE_CONFIG: self.set_config(config) + for config in CONFIGS[model]: + self.set_config(config) + self.set_global_shutdown(amp_disabled=False) - - -if __name__ == "__main__": - Amplifier(debug=True).initialize_configuration() diff --git a/system/hardware/tici/hardware.h b/system/hardware/tici/hardware.h index dcccb9f3d..5f6fb2dc5 100644 --- a/system/hardware/tici/hardware.h +++ b/system/hardware/tici/hardware.h @@ -10,7 +10,7 @@ class HardwareTici : public HardwareNone { public: static constexpr float MAX_VOLUME = 0.9; - static constexpr float MIN_VOLUME = 0.2; + static constexpr float MIN_VOLUME = 0.1; static bool TICI() { return true; } static bool AGNOS() { return true; } static std::string get_os_version() { @@ -21,13 +21,32 @@ public: static int get_voltage() { return std::atoi(util::read_file("/sys/class/hwmon/hwmon1/in1_input").c_str()); }; static int get_current() { return std::atoi(util::read_file("/sys/class/hwmon/hwmon1/curr1_input").c_str()); }; + static std::string get_serial() { + static std::string serial(""); + if (serial.empty()) { + std::ifstream stream("/proc/cmdline"); + std::string cmdline; + std::getline(stream, cmdline); + + auto start = cmdline.find("serialno="); + if (start == std::string::npos) { + serial = "cccccc"; + } else { + auto end = cmdline.find(" ", start + 9); + serial = cmdline.substr(start + 9, end - start - 9); + } + } + return serial; + } static void reboot() { std::system("sudo reboot"); }; static void poweroff() { std::system("sudo poweroff"); }; static void set_brightness(int percent) { + std::string max = util::read_file("/sys/class/backlight/panel0-backlight/max_brightness"); + std::ofstream brightness_control("/sys/class/backlight/panel0-backlight/brightness"); if (brightness_control.is_open()) { - brightness_control << (percent * (int)(1023/100.)) << "\n"; + brightness_control << (int)(percent * (std::stof(max)/100.)) << "\n"; brightness_control.close(); } }; @@ -38,6 +57,13 @@ public: bl_power_control.close(); } }; + static void set_volume(float volume) { + volume = util::map_val(volume, 0.f, 1.f, MIN_VOLUME, MAX_VOLUME); + + char volume_str[6]; + snprintf(volume_str, sizeof(volume_str), "%.3f", volume); + std::system(("pactl set-sink-volume @DEFAULT_SINK@ " + std::string(volume_str)).c_str()); + } static bool get_ssh_enabled() { return Params().getBool("SshEnabled"); }; static void set_ssh_enabled(bool enabled) { Params().putBool("SshEnabled", enabled); }; diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index e2fd20c1b..9c1cc930c 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -85,6 +85,16 @@ class Tici(HardwareBase): def amplifier(self): return Amplifier() + @cached_property + def model(self): + with open("/sys/firmware/devicetree/base/model") as f: + model = f.read().strip('\x00') + model = model.split('comma ')[-1] + # TODO: remove this with AGNOS 7+ + if model.startswith('Qualcomm'): + model = 'tici' + return model + def get_os_version(self): with open("/VERSION") as f: return f.read().strip() @@ -385,15 +395,22 @@ class Tici(HardwareBase): def set_screen_brightness(self, percentage): try: + with open("/sys/class/backlight/panel0-backlight/max_brightness") as f: + max_brightness = float(f.read().strip()) + + val = int(percentage * (max_brightness / 100.)) with open("/sys/class/backlight/panel0-backlight/brightness", "w") as f: - f.write(str(int(percentage * 10.23))) + f.write(str(val)) except Exception: pass def get_screen_brightness(self): try: + with open("/sys/class/backlight/panel0-backlight/max_brightness") as f: + max_brightness = float(f.read().strip()) + with open("/sys/class/backlight/panel0-backlight/brightness") as f: - return int(float(f.read()) / 10.23) + return int(float(f.read()) / (max_brightness / 100.)) except Exception: return 0 @@ -401,7 +418,7 @@ class Tici(HardwareBase): # amplifier, 100mW at idle self.amplifier.set_global_shutdown(amp_disabled=powersave_enabled) if not powersave_enabled: - self.amplifier.initialize_configuration() + self.amplifier.initialize_configuration(self.model) # *** CPU config *** @@ -416,6 +433,7 @@ class Tici(HardwareBase): # *** IRQ config *** affine_irq(5, 565) # kgsl-3d0 + affine_irq(4, 126) # SPI goes on boardd core affine_irq(4, 740) # xhci-hcd:usb1 goes on the boardd core affine_irq(4, 1069) # xhci-hcd:usb3 goes on the boardd core for irq in range(237, 246): @@ -429,7 +447,7 @@ class Tici(HardwareBase): return 0 def initialize_hardware(self): - self.amplifier.initialize_configuration() + self.amplifier.initialize_configuration(self.model) # Allow thermald to write engagement status to kmsg os.system("sudo chmod a+w /dev/kmsg") diff --git a/system/micd.py b/system/micd.py new file mode 100755 index 000000000..97ba0c262 --- /dev/null +++ b/system/micd.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +import numpy as np + +from cereal import messaging +from common.filter_simple import FirstOrderFilter +from common.realtime import Ratekeeper +from system.swaglog import cloudlog + +RATE = 10 +FFT_SAMPLES = 4096 +REFERENCE_SPL = 2e-5 # newtons/m^2 +SAMPLE_RATE = 44100 +FILTER_DT = 1. / (SAMPLE_RATE / FFT_SAMPLES) + + +def calculate_spl(measurements): + # https://www.engineeringtoolbox.com/sound-pressure-d_711.html + sound_pressure = np.sqrt(np.mean(measurements ** 2)) # RMS of amplitudes + if sound_pressure > 0: + sound_pressure_level = 20 * np.log10(sound_pressure / REFERENCE_SPL) # dB + else: + sound_pressure_level = 0 + return sound_pressure, sound_pressure_level + + +def apply_a_weighting(measurements: np.ndarray) -> np.ndarray: + # Generate a Hanning window of the same length as the audio measurements + measurements_windowed = measurements * np.hanning(len(measurements)) + + # Calculate the frequency axis for the signal + freqs = np.fft.fftfreq(measurements_windowed.size, d=1 / SAMPLE_RATE) + + # Calculate the A-weighting filter + # https://en.wikipedia.org/wiki/A-weighting + A = 12194 ** 2 * freqs ** 4 / ((freqs ** 2 + 20.6 ** 2) * (freqs ** 2 + 12194 ** 2) * np.sqrt((freqs ** 2 + 107.7 ** 2) * (freqs ** 2 + 737.9 ** 2))) + A /= np.max(A) # Normalize the filter + + # Apply the A-weighting filter to the signal + return np.abs(np.fft.ifft(np.fft.fft(measurements_windowed) * A)) + + +class Mic: + def __init__(self, pm): + self.pm = pm + self.rk = Ratekeeper(RATE) + + self.measurements = np.empty(0) + + self.sound_pressure = 0 + self.sound_pressure_weighted = 0 + self.sound_pressure_level_weighted = 0 + + self.spl_filter_weighted = FirstOrderFilter(0, 2.5, FILTER_DT, initialized=False) + + def update(self): + msg = messaging.new_message('microphone') + msg.microphone.soundPressure = float(self.sound_pressure) + msg.microphone.soundPressureWeighted = float(self.sound_pressure_weighted) + + msg.microphone.soundPressureWeightedDb = float(self.sound_pressure_level_weighted) + msg.microphone.filteredSoundPressureWeightedDb = float(self.spl_filter_weighted.x) + + self.pm.send('microphone', msg) + self.rk.keep_time() + + def callback(self, indata, frames, time, status): + """ + Using amplitude measurements, calculate an uncalibrated sound pressure and sound pressure level. + Then apply A-weighting to the raw amplitudes and run the same calculations again. + + Logged A-weighted equivalents are rough approximations of the human-perceived loudness. + """ + + self.measurements = np.concatenate((self.measurements, indata[:, 0])) + + while self.measurements.size >= FFT_SAMPLES: + measurements = self.measurements[:FFT_SAMPLES] + + self.sound_pressure, _ = calculate_spl(measurements) + measurements_weighted = apply_a_weighting(measurements) + self.sound_pressure_weighted, self.sound_pressure_level_weighted = calculate_spl(measurements_weighted) + self.spl_filter_weighted.update(self.sound_pressure_level_weighted) + + self.measurements = self.measurements[FFT_SAMPLES:] + + def micd_thread(self): + # sounddevice must be imported after forking processes + import sounddevice as sd # pylint: disable=import-outside-toplevel + + with sd.InputStream(channels=1, samplerate=SAMPLE_RATE, callback=self.callback) as stream: + cloudlog.info(f"micd stream started: {stream.samplerate=} {stream.channels=} {stream.dtype=} {stream.device=}") + while True: + self.update() + + +def main(pm=None): + if pm is None: + pm = messaging.PubMaster(['microphone']) + + mic = Mic(pm) + mic.micd_thread() + + +if __name__ == "__main__": + main() diff --git a/system/version.py b/system/version.py index 603153155..55f80afa3 100644 --- a/system/version.py +++ b/system/version.py @@ -7,7 +7,7 @@ from functools import lru_cache from common.basedir import BASEDIR from system.swaglog import cloudlog -RELEASE_BRANCHES = ['release3-staging', 'dashcam3-staging', 'release3', 'dashcam3'] +RELEASE_BRANCHES = ['release3-staging', 'dashcam3-staging', 'release3', 'dashcam3', 'nightly'] TESTED_BRANCHES = RELEASE_BRANCHES + ['devel', 'devel-staging'] training_version: bytes = b"0.2.0" diff --git a/pyextra/.gitignore b/third_party/.gitignore similarity index 100% rename from pyextra/.gitignore rename to third_party/.gitignore diff --git a/third_party/SConscript b/third_party/SConscript index e5bbfaa07..e8d1789ee 100644 --- a/third_party/SConscript +++ b/third_party/SConscript @@ -4,3 +4,5 @@ env.Library('json11', ['json11/json11.cpp'], CCFLAGS=env['CCFLAGS'] + ['-Wno-unq env.Append(CPPPATH=[Dir('json11')]) env.Library('kaitai', ['kaitai/kaitaistream.cpp'], CPPDEFINES=['KS_STR_ENCODING_NONE']) + +SConscript(['cluster/SConscript']) diff --git a/pyextra/acados_template/__init__.py b/third_party/acados/acados_template/__init__.py similarity index 100% rename from pyextra/acados_template/__init__.py rename to third_party/acados/acados_template/__init__.py diff --git a/pyextra/acados_template/acados_layout.json b/third_party/acados/acados_template/acados_layout.json similarity index 100% rename from pyextra/acados_template/acados_layout.json rename to third_party/acados/acados_template/acados_layout.json diff --git a/pyextra/acados_template/acados_model.py b/third_party/acados/acados_template/acados_model.py similarity index 100% rename from pyextra/acados_template/acados_model.py rename to third_party/acados/acados_template/acados_model.py diff --git a/pyextra/acados_template/acados_ocp.py b/third_party/acados/acados_template/acados_ocp.py similarity index 100% rename from pyextra/acados_template/acados_ocp.py rename to third_party/acados/acados_template/acados_ocp.py diff --git a/pyextra/acados_template/acados_ocp_solver.py b/third_party/acados/acados_template/acados_ocp_solver.py similarity index 100% rename from pyextra/acados_template/acados_ocp_solver.py rename to third_party/acados/acados_template/acados_ocp_solver.py diff --git a/pyextra/acados_template/acados_ocp_solver_pyx.pyx b/third_party/acados/acados_template/acados_ocp_solver_pyx.pyx similarity index 100% rename from pyextra/acados_template/acados_ocp_solver_pyx.pyx rename to third_party/acados/acados_template/acados_ocp_solver_pyx.pyx diff --git a/pyextra/acados_template/acados_sim.py b/third_party/acados/acados_template/acados_sim.py similarity index 100% rename from pyextra/acados_template/acados_sim.py rename to third_party/acados/acados_template/acados_sim.py diff --git a/pyextra/acados_template/acados_sim_layout.json b/third_party/acados/acados_template/acados_sim_layout.json similarity index 100% rename from pyextra/acados_template/acados_sim_layout.json rename to third_party/acados/acados_template/acados_sim_layout.json diff --git a/pyextra/acados_template/acados_sim_solver.py b/third_party/acados/acados_template/acados_sim_solver.py similarity index 100% rename from pyextra/acados_template/acados_sim_solver.py rename to third_party/acados/acados_template/acados_sim_solver.py diff --git a/pyextra/acados_template/acados_solver_common.pxd b/third_party/acados/acados_template/acados_solver_common.pxd similarity index 100% rename from pyextra/acados_template/acados_solver_common.pxd rename to third_party/acados/acados_template/acados_solver_common.pxd diff --git a/pyextra/acados_template/builders.py b/third_party/acados/acados_template/builders.py similarity index 100% rename from pyextra/acados_template/builders.py rename to third_party/acados/acados_template/builders.py diff --git a/pyextra/acados_template/c_templates_tera/CMakeLists.in.txt b/third_party/acados/acados_template/c_templates_tera/CMakeLists.in.txt similarity index 100% rename from pyextra/acados_template/c_templates_tera/CMakeLists.in.txt rename to third_party/acados/acados_template/c_templates_tera/CMakeLists.in.txt diff --git a/pyextra/acados_template/c_templates_tera/CPPLINT.cfg b/third_party/acados/acados_template/c_templates_tera/CPPLINT.cfg similarity index 100% rename from pyextra/acados_template/c_templates_tera/CPPLINT.cfg rename to third_party/acados/acados_template/c_templates_tera/CPPLINT.cfg diff --git a/pyextra/acados_template/c_templates_tera/Makefile.in b/third_party/acados/acados_template/c_templates_tera/Makefile.in similarity index 100% rename from pyextra/acados_template/c_templates_tera/Makefile.in rename to third_party/acados/acados_template/c_templates_tera/Makefile.in diff --git a/pyextra/acados_template/c_templates_tera/acados_mex_create.in.c b/third_party/acados/acados_template/c_templates_tera/acados_mex_create.in.c similarity index 100% rename from pyextra/acados_template/c_templates_tera/acados_mex_create.in.c rename to third_party/acados/acados_template/c_templates_tera/acados_mex_create.in.c diff --git a/pyextra/acados_template/c_templates_tera/acados_mex_free.in.c b/third_party/acados/acados_template/c_templates_tera/acados_mex_free.in.c similarity index 100% rename from pyextra/acados_template/c_templates_tera/acados_mex_free.in.c rename to third_party/acados/acados_template/c_templates_tera/acados_mex_free.in.c diff --git a/pyextra/acados_template/c_templates_tera/acados_mex_set.in.c b/third_party/acados/acados_template/c_templates_tera/acados_mex_set.in.c similarity index 100% rename from pyextra/acados_template/c_templates_tera/acados_mex_set.in.c rename to third_party/acados/acados_template/c_templates_tera/acados_mex_set.in.c diff --git a/pyextra/acados_template/c_templates_tera/acados_mex_solve.in.c b/third_party/acados/acados_template/c_templates_tera/acados_mex_solve.in.c similarity index 100% rename from pyextra/acados_template/c_templates_tera/acados_mex_solve.in.c rename to third_party/acados/acados_template/c_templates_tera/acados_mex_solve.in.c diff --git a/pyextra/acados_template/c_templates_tera/acados_sim_solver.in.c b/third_party/acados/acados_template/c_templates_tera/acados_sim_solver.in.c similarity index 100% rename from pyextra/acados_template/c_templates_tera/acados_sim_solver.in.c rename to third_party/acados/acados_template/c_templates_tera/acados_sim_solver.in.c diff --git a/pyextra/acados_template/c_templates_tera/acados_sim_solver.in.h b/third_party/acados/acados_template/c_templates_tera/acados_sim_solver.in.h similarity index 100% rename from pyextra/acados_template/c_templates_tera/acados_sim_solver.in.h rename to third_party/acados/acados_template/c_templates_tera/acados_sim_solver.in.h diff --git a/pyextra/acados_template/c_templates_tera/acados_sim_solver_sfun.in.c b/third_party/acados/acados_template/c_templates_tera/acados_sim_solver_sfun.in.c similarity index 100% rename from pyextra/acados_template/c_templates_tera/acados_sim_solver_sfun.in.c rename to third_party/acados/acados_template/c_templates_tera/acados_sim_solver_sfun.in.c diff --git a/pyextra/acados_template/c_templates_tera/acados_solver.in.c b/third_party/acados/acados_template/c_templates_tera/acados_solver.in.c similarity index 100% rename from pyextra/acados_template/c_templates_tera/acados_solver.in.c rename to third_party/acados/acados_template/c_templates_tera/acados_solver.in.c diff --git a/pyextra/acados_template/c_templates_tera/acados_solver.in.h b/third_party/acados/acados_template/c_templates_tera/acados_solver.in.h similarity index 100% rename from pyextra/acados_template/c_templates_tera/acados_solver.in.h rename to third_party/acados/acados_template/c_templates_tera/acados_solver.in.h diff --git a/pyextra/acados_template/c_templates_tera/acados_solver.in.pxd b/third_party/acados/acados_template/c_templates_tera/acados_solver.in.pxd similarity index 100% rename from pyextra/acados_template/c_templates_tera/acados_solver.in.pxd rename to third_party/acados/acados_template/c_templates_tera/acados_solver.in.pxd diff --git a/pyextra/acados_template/c_templates_tera/acados_solver_sfun.in.c b/third_party/acados/acados_template/c_templates_tera/acados_solver_sfun.in.c similarity index 100% rename from pyextra/acados_template/c_templates_tera/acados_solver_sfun.in.c rename to third_party/acados/acados_template/c_templates_tera/acados_solver_sfun.in.c diff --git a/pyextra/acados_template/c_templates_tera/cost_y_0_fun.in.h b/third_party/acados/acados_template/c_templates_tera/cost_y_0_fun.in.h similarity index 100% rename from pyextra/acados_template/c_templates_tera/cost_y_0_fun.in.h rename to third_party/acados/acados_template/c_templates_tera/cost_y_0_fun.in.h diff --git a/pyextra/acados_template/c_templates_tera/cost_y_e_fun.in.h b/third_party/acados/acados_template/c_templates_tera/cost_y_e_fun.in.h similarity index 100% rename from pyextra/acados_template/c_templates_tera/cost_y_e_fun.in.h rename to third_party/acados/acados_template/c_templates_tera/cost_y_e_fun.in.h diff --git a/pyextra/acados_template/c_templates_tera/cost_y_fun.in.h b/third_party/acados/acados_template/c_templates_tera/cost_y_fun.in.h similarity index 100% rename from pyextra/acados_template/c_templates_tera/cost_y_fun.in.h rename to third_party/acados/acados_template/c_templates_tera/cost_y_fun.in.h diff --git a/pyextra/acados_template/c_templates_tera/external_cost.in.h b/third_party/acados/acados_template/c_templates_tera/external_cost.in.h similarity index 100% rename from pyextra/acados_template/c_templates_tera/external_cost.in.h rename to third_party/acados/acados_template/c_templates_tera/external_cost.in.h diff --git a/pyextra/acados_template/c_templates_tera/external_cost_0.in.h b/third_party/acados/acados_template/c_templates_tera/external_cost_0.in.h similarity index 100% rename from pyextra/acados_template/c_templates_tera/external_cost_0.in.h rename to third_party/acados/acados_template/c_templates_tera/external_cost_0.in.h diff --git a/pyextra/acados_template/c_templates_tera/external_cost_e.in.h b/third_party/acados/acados_template/c_templates_tera/external_cost_e.in.h similarity index 100% rename from pyextra/acados_template/c_templates_tera/external_cost_e.in.h rename to third_party/acados/acados_template/c_templates_tera/external_cost_e.in.h diff --git a/pyextra/acados_template/c_templates_tera/h_constraint.in.h b/third_party/acados/acados_template/c_templates_tera/h_constraint.in.h similarity index 100% rename from pyextra/acados_template/c_templates_tera/h_constraint.in.h rename to third_party/acados/acados_template/c_templates_tera/h_constraint.in.h diff --git a/pyextra/acados_template/c_templates_tera/h_e_constraint.in.h b/third_party/acados/acados_template/c_templates_tera/h_e_constraint.in.h similarity index 100% rename from pyextra/acados_template/c_templates_tera/h_e_constraint.in.h rename to third_party/acados/acados_template/c_templates_tera/h_e_constraint.in.h diff --git a/pyextra/acados_template/c_templates_tera/main.in.c b/third_party/acados/acados_template/c_templates_tera/main.in.c similarity index 100% rename from pyextra/acados_template/c_templates_tera/main.in.c rename to third_party/acados/acados_template/c_templates_tera/main.in.c diff --git a/pyextra/acados_template/c_templates_tera/main_mex.in.c b/third_party/acados/acados_template/c_templates_tera/main_mex.in.c similarity index 100% rename from pyextra/acados_template/c_templates_tera/main_mex.in.c rename to third_party/acados/acados_template/c_templates_tera/main_mex.in.c diff --git a/pyextra/acados_template/c_templates_tera/main_sim.in.c b/third_party/acados/acados_template/c_templates_tera/main_sim.in.c similarity index 100% rename from pyextra/acados_template/c_templates_tera/main_sim.in.c rename to third_party/acados/acados_template/c_templates_tera/main_sim.in.c diff --git a/pyextra/acados_template/c_templates_tera/make_main_mex.in.m b/third_party/acados/acados_template/c_templates_tera/make_main_mex.in.m similarity index 100% rename from pyextra/acados_template/c_templates_tera/make_main_mex.in.m rename to third_party/acados/acados_template/c_templates_tera/make_main_mex.in.m diff --git a/pyextra/acados_template/c_templates_tera/make_mex.in.m b/third_party/acados/acados_template/c_templates_tera/make_mex.in.m similarity index 100% rename from pyextra/acados_template/c_templates_tera/make_mex.in.m rename to third_party/acados/acados_template/c_templates_tera/make_mex.in.m diff --git a/pyextra/acados_template/c_templates_tera/make_sfun.in.m b/third_party/acados/acados_template/c_templates_tera/make_sfun.in.m similarity index 100% rename from pyextra/acados_template/c_templates_tera/make_sfun.in.m rename to third_party/acados/acados_template/c_templates_tera/make_sfun.in.m diff --git a/pyextra/acados_template/c_templates_tera/make_sfun_sim.in.m b/third_party/acados/acados_template/c_templates_tera/make_sfun_sim.in.m similarity index 100% rename from pyextra/acados_template/c_templates_tera/make_sfun_sim.in.m rename to third_party/acados/acados_template/c_templates_tera/make_sfun_sim.in.m diff --git a/pyextra/acados_template/c_templates_tera/mex_solver.in.m b/third_party/acados/acados_template/c_templates_tera/mex_solver.in.m similarity index 100% rename from pyextra/acados_template/c_templates_tera/mex_solver.in.m rename to third_party/acados/acados_template/c_templates_tera/mex_solver.in.m diff --git a/pyextra/acados_template/c_templates_tera/model.in.h b/third_party/acados/acados_template/c_templates_tera/model.in.h similarity index 100% rename from pyextra/acados_template/c_templates_tera/model.in.h rename to third_party/acados/acados_template/c_templates_tera/model.in.h diff --git a/pyextra/acados_template/c_templates_tera/phi_constraint.in.h b/third_party/acados/acados_template/c_templates_tera/phi_constraint.in.h similarity index 100% rename from pyextra/acados_template/c_templates_tera/phi_constraint.in.h rename to third_party/acados/acados_template/c_templates_tera/phi_constraint.in.h diff --git a/pyextra/acados_template/c_templates_tera/phi_e_constraint.in.h b/third_party/acados/acados_template/c_templates_tera/phi_e_constraint.in.h similarity index 100% rename from pyextra/acados_template/c_templates_tera/phi_e_constraint.in.h rename to third_party/acados/acados_template/c_templates_tera/phi_e_constraint.in.h diff --git a/pyextra/acados_template/generate_c_code_constraint.py b/third_party/acados/acados_template/generate_c_code_constraint.py similarity index 100% rename from pyextra/acados_template/generate_c_code_constraint.py rename to third_party/acados/acados_template/generate_c_code_constraint.py diff --git a/pyextra/acados_template/generate_c_code_discrete_dynamics.py b/third_party/acados/acados_template/generate_c_code_discrete_dynamics.py similarity index 100% rename from pyextra/acados_template/generate_c_code_discrete_dynamics.py rename to third_party/acados/acados_template/generate_c_code_discrete_dynamics.py diff --git a/pyextra/acados_template/generate_c_code_explicit_ode.py b/third_party/acados/acados_template/generate_c_code_explicit_ode.py similarity index 100% rename from pyextra/acados_template/generate_c_code_explicit_ode.py rename to third_party/acados/acados_template/generate_c_code_explicit_ode.py diff --git a/pyextra/acados_template/generate_c_code_external_cost.py b/third_party/acados/acados_template/generate_c_code_external_cost.py similarity index 100% rename from pyextra/acados_template/generate_c_code_external_cost.py rename to third_party/acados/acados_template/generate_c_code_external_cost.py diff --git a/pyextra/acados_template/generate_c_code_gnsf.py b/third_party/acados/acados_template/generate_c_code_gnsf.py similarity index 100% rename from pyextra/acados_template/generate_c_code_gnsf.py rename to third_party/acados/acados_template/generate_c_code_gnsf.py diff --git a/pyextra/acados_template/generate_c_code_implicit_ode.py b/third_party/acados/acados_template/generate_c_code_implicit_ode.py similarity index 100% rename from pyextra/acados_template/generate_c_code_implicit_ode.py rename to third_party/acados/acados_template/generate_c_code_implicit_ode.py diff --git a/pyextra/acados_template/generate_c_code_nls_cost.py b/third_party/acados/acados_template/generate_c_code_nls_cost.py similarity index 100% rename from pyextra/acados_template/generate_c_code_nls_cost.py rename to third_party/acados/acados_template/generate_c_code_nls_cost.py diff --git a/pyextra/acados_template/simulink_default_opts.json b/third_party/acados/acados_template/simulink_default_opts.json similarity index 100% rename from pyextra/acados_template/simulink_default_opts.json rename to third_party/acados/acados_template/simulink_default_opts.json diff --git a/pyextra/acados_template/utils.py b/third_party/acados/acados_template/utils.py similarity index 100% rename from pyextra/acados_template/utils.py rename to third_party/acados/acados_template/utils.py diff --git a/third_party/bootstrap/bootstrap-icons.svg b/third_party/bootstrap/bootstrap-icons.svg new file mode 100644 index 000000000..61f2720db --- /dev/null +++ b/third_party/bootstrap/bootstrap-icons.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/third_party/bootstrap/pull.sh b/third_party/bootstrap/pull.sh new file mode 100755 index 000000000..0b03b4db9 --- /dev/null +++ b/third_party/bootstrap/pull.sh @@ -0,0 +1,14 @@ +#!/bin/bash +set -e + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +cd $DIR + +if [ ! -d icons/ ]; then + git clone https://github.com/twbs/icons/ +fi + +cd icons +git fetch --all +git checkout d5aa187483a1b0b186f87adcfa8576350d970d98 +cp bootstrap-icons.svg ../ diff --git a/selfdrive/controls/lib/cluster/LICENSE b/third_party/cluster/LICENSE similarity index 100% rename from selfdrive/controls/lib/cluster/LICENSE rename to third_party/cluster/LICENSE diff --git a/selfdrive/controls/lib/cluster/README b/third_party/cluster/README similarity index 100% rename from selfdrive/controls/lib/cluster/README rename to third_party/cluster/README diff --git a/selfdrive/controls/lib/cluster/SConscript b/third_party/cluster/SConscript similarity index 100% rename from selfdrive/controls/lib/cluster/SConscript rename to third_party/cluster/SConscript diff --git a/selfdrive/controls/lib/cluster/__init__.py b/third_party/cluster/__init__.py similarity index 100% rename from selfdrive/controls/lib/cluster/__init__.py rename to third_party/cluster/__init__.py diff --git a/selfdrive/controls/lib/cluster/fastcluster.cpp b/third_party/cluster/fastcluster.cpp similarity index 100% rename from selfdrive/controls/lib/cluster/fastcluster.cpp rename to third_party/cluster/fastcluster.cpp diff --git a/selfdrive/controls/lib/cluster/fastcluster.h b/third_party/cluster/fastcluster.h similarity index 100% rename from selfdrive/controls/lib/cluster/fastcluster.h rename to third_party/cluster/fastcluster.h diff --git a/selfdrive/controls/lib/cluster/fastcluster_R_dm.cpp b/third_party/cluster/fastcluster_R_dm.cpp similarity index 100% rename from selfdrive/controls/lib/cluster/fastcluster_R_dm.cpp rename to third_party/cluster/fastcluster_R_dm.cpp diff --git a/selfdrive/controls/lib/cluster/fastcluster_dm.cpp b/third_party/cluster/fastcluster_dm.cpp similarity index 100% rename from selfdrive/controls/lib/cluster/fastcluster_dm.cpp rename to third_party/cluster/fastcluster_dm.cpp diff --git a/selfdrive/controls/lib/cluster/fastcluster_py.py b/third_party/cluster/fastcluster_py.py similarity index 100% rename from selfdrive/controls/lib/cluster/fastcluster_py.py rename to third_party/cluster/fastcluster_py.py diff --git a/selfdrive/controls/lib/cluster/test.cpp b/third_party/cluster/test.cpp similarity index 100% rename from selfdrive/controls/lib/cluster/test.cpp rename to third_party/cluster/test.cpp diff --git a/system/camerad/include/media/cam_cpas.h b/third_party/linux/include/media/cam_cpas.h similarity index 100% rename from system/camerad/include/media/cam_cpas.h rename to third_party/linux/include/media/cam_cpas.h diff --git a/system/camerad/include/media/cam_defs.h b/third_party/linux/include/media/cam_defs.h similarity index 100% rename from system/camerad/include/media/cam_defs.h rename to third_party/linux/include/media/cam_defs.h diff --git a/system/camerad/include/media/cam_fd.h b/third_party/linux/include/media/cam_fd.h similarity index 100% rename from system/camerad/include/media/cam_fd.h rename to third_party/linux/include/media/cam_fd.h diff --git a/system/camerad/include/media/cam_icp.h b/third_party/linux/include/media/cam_icp.h similarity index 100% rename from system/camerad/include/media/cam_icp.h rename to third_party/linux/include/media/cam_icp.h diff --git a/system/camerad/include/media/cam_isp.h b/third_party/linux/include/media/cam_isp.h similarity index 100% rename from system/camerad/include/media/cam_isp.h rename to third_party/linux/include/media/cam_isp.h diff --git a/system/camerad/include/media/cam_isp_ife.h b/third_party/linux/include/media/cam_isp_ife.h similarity index 100% rename from system/camerad/include/media/cam_isp_ife.h rename to third_party/linux/include/media/cam_isp_ife.h diff --git a/system/camerad/include/media/cam_isp_vfe.h b/third_party/linux/include/media/cam_isp_vfe.h similarity index 100% rename from system/camerad/include/media/cam_isp_vfe.h rename to third_party/linux/include/media/cam_isp_vfe.h diff --git a/system/camerad/include/media/cam_jpeg.h b/third_party/linux/include/media/cam_jpeg.h similarity index 100% rename from system/camerad/include/media/cam_jpeg.h rename to third_party/linux/include/media/cam_jpeg.h diff --git a/system/camerad/include/media/cam_lrme.h b/third_party/linux/include/media/cam_lrme.h similarity index 100% rename from system/camerad/include/media/cam_lrme.h rename to third_party/linux/include/media/cam_lrme.h diff --git a/system/camerad/include/media/cam_req_mgr.h b/third_party/linux/include/media/cam_req_mgr.h similarity index 100% rename from system/camerad/include/media/cam_req_mgr.h rename to third_party/linux/include/media/cam_req_mgr.h diff --git a/system/camerad/include/media/cam_sensor.h b/third_party/linux/include/media/cam_sensor.h similarity index 100% rename from system/camerad/include/media/cam_sensor.h rename to third_party/linux/include/media/cam_sensor.h diff --git a/system/camerad/include/media/cam_sensor_cmn_header.h b/third_party/linux/include/media/cam_sensor_cmn_header.h similarity index 100% rename from system/camerad/include/media/cam_sensor_cmn_header.h rename to third_party/linux/include/media/cam_sensor_cmn_header.h diff --git a/system/camerad/include/media/cam_sync.h b/third_party/linux/include/media/cam_sync.h similarity index 100% rename from system/camerad/include/media/cam_sync.h rename to third_party/linux/include/media/cam_sync.h diff --git a/system/camerad/include/msm_cam_sensor.h b/third_party/linux/include/msm_cam_sensor.h similarity index 100% rename from system/camerad/include/msm_cam_sensor.h rename to third_party/linux/include/msm_cam_sensor.h diff --git a/system/camerad/include/msm_camsensor_sdk.h b/third_party/linux/include/msm_camsensor_sdk.h similarity index 100% rename from system/camerad/include/msm_camsensor_sdk.h rename to third_party/linux/include/msm_camsensor_sdk.h diff --git a/selfdrive/modeld/thneed/include/msm_kgsl.h b/third_party/linux/include/msm_kgsl.h similarity index 100% rename from selfdrive/modeld/thneed/include/msm_kgsl.h rename to third_party/linux/include/msm_kgsl.h diff --git a/system/camerad/include/msmb_camera.h b/third_party/linux/include/msmb_camera.h similarity index 100% rename from system/camerad/include/msmb_camera.h rename to third_party/linux/include/msmb_camera.h diff --git a/system/camerad/include/msmb_isp.h b/third_party/linux/include/msmb_isp.h similarity index 100% rename from system/camerad/include/msmb_isp.h rename to third_party/linux/include/msmb_isp.h diff --git a/system/camerad/include/msmb_ispif.h b/third_party/linux/include/msmb_ispif.h similarity index 100% rename from system/camerad/include/msmb_ispif.h rename to third_party/linux/include/msmb_ispif.h diff --git a/tinygrad_repo/accel/opencl/ops_opencl.py b/tinygrad_repo/accel/opencl/ops_opencl.py index 7c684ca21..f39434ea0 100644 --- a/tinygrad_repo/accel/opencl/ops_opencl.py +++ b/tinygrad_repo/accel/opencl/ops_opencl.py @@ -3,9 +3,9 @@ from __future__ import annotations import os from tinygrad.llops.ops_gpu import GPUBuffer, CL, CLProgram, CLBuffer -from tinygrad.ops import ProcessingOps, ReduceOps, UnaryOps, BinaryOps -from tinygrad.helpers import prod, ConvArgs -from typing import List, Tuple, Optional, Dict, Set +from tinygrad.ops import ProcessingOps, ReduceOps, UnaryOps, BinaryOps, MovementOps, get_buffers, get_lazyops, get_lazyop_info, LazyOp, Op +from tinygrad.helpers import prod, ConvArgs, dedup +from typing import List, Tuple, Optional, Dict, Set, Union import numpy as np import pyopencl as cl @@ -137,8 +137,9 @@ class OpenCLBuffer(GPUBuffer): UnaryOps.LOG: "native_log(A)" if NATIVE_EXPLOG else "log(A)", UnaryOps.RECIPROCAL: "native_recip(A)" if NATIVE_EXPLOG else "((float)1.0/A)", BinaryOps.ADD: "(A+B)", BinaryOps.SUB: "(A-B)", BinaryOps.MUL: "(A*B)", BinaryOps.DIV: "(A/B)", BinaryOps.POW: "pow(A,B)", BinaryOps.CMPEQ: "(A==B)", - ReduceOps.SUM: "(acc + A)", ReduceOps.MAX: "max(A, acc)" + ReduceOps.SUM: "(acc + A)", ReduceOps.MAX: "max(A, acc)", MovementOps.RESHAPE: "(A)" } + start_for_op = {ReduceOps.SUM: "0.0", ReduceOps.MAX: "-INFINITY"} def __init__(self, shape, hostbuf:Optional[OpenCLBuffer]=None, backing:Optional[np.ndarray]=None): self._image = hostbuf._image if hostbuf is not None else None self.copied_backing = False @@ -211,7 +212,7 @@ class OpenCLBuffer(GPUBuffer): SUPPORTS_PADDING = True def processing_op(x, op:ProcessingOps, w:GPUBuffer, C:ConvArgs): assert op == ProcessingOps.CONV, f"{op} isn't supported" - return type(x)(C.out_shape)._processing_op([("input", x.contiguous_op()), ("weight", w.contiguous_op())], "acc", C) + return type(x)(C.out_shape)._processing_op([("input", x.contiguous()), ("weight", w.contiguous())], "acc", C) def contiguous_view_constant_fold(x, name:str, reduce:Optional[int]=None) -> Tuple[str, Optional[str], str]: # this will only be for convs, for reduce we have to fall back to cl @@ -228,13 +229,104 @@ class OpenCLBuffer(GPUBuffer): float4 dat = read_imagef(x, smp, l_smp); return valid ? (idx4 == 0 ? dat.x : (idx4 == 1 ? dat.y : (idx4 == 2 ? dat.z : dat.w))) : 0.0; }}""", f"read_only image2d_t {name}_g", f"get_{name}(smp, {name}_g, gid);" - #ewtypes.append(f"read_only image2d_t {name}_g") - return super().contiguous_view_constant_fold(name, reduce) + else: + idx_getter = f"int valid = 1; {'long' if prod(x.shape) >= 2**31 else 'int'} idx = gid; {'idx *= '+str(reduce)+'; idx += subidx;' if reduce is not None else ''} {x.st.expr().replace('//', '/')};" + constant = x._backing[0] if x._base_shape == (1,) and x._backing is not None else None + args = (["__global const float *x"] if constant is None else []) + ["int gid"] + (["int subidx"] if reduce is not None else []) + return f"inline float get_{name}({','.join(args)}) {{ {idx_getter} return valid ? {constant if constant is not None else 'x[idx]'} : 0.0;}}", \ + f"__global const float *{name}_g" if constant is None else None, \ + f"get_{name}({name+'_g, ' if constant is None else ''}gid{', subidx' if reduce is not None else ''});" - def _processing_op(ret, bufs: List[Tuple[str, OpenCLBuffer]]=[], code:str="acc", C=None, op=ReduceOps.SUM, reduce_shape=None, earlybufs:Set[str]=set(), earlycode:str="acc"): + @classmethod + def exec_ast(cls, ast:LazyOp): + # copied from llvm + bufs = dedup(get_buffers(ast)) + reduceops = dedup([x for x in get_lazyops(ast) if isinstance(x.op, ReduceOps) or isinstance(x.op, ProcessingOps)]) + assert len(reduceops) <= 1, f"max one reduce op in an ast, {reduceops}" + earlybufs = dedup(get_buffers(reduceops[0])) if len(reduceops) > 0 else [] + reduce_shape = (earlybufs[0].shape, reduceops[0].arg) if len(reduceops) > 0 and isinstance(reduceops[0].op, ReduceOps) else None + info = get_lazyop_info(ast) + ret = cls(info.shape) + + buf_names : Dict[GPUBuffer, str] = {x:f"arg_{i}" for i,x in enumerate(bufs)} + + # special names for input and weight + if len(reduceops) > 0 and isinstance(reduceops[0].op, ProcessingOps): + buf_names[reduceops[0].src[0]] = "input" + buf_names[reduceops[0].src[1]] = "weight" + + def _ast(x: Union[GPUBuffer, LazyOp], buf_names: Dict[GPUBuffer, str], code_for_op: Dict[Op, str], allow_reduce=False) -> str: + if isinstance(x, GPUBuffer): + return buf_names[x] + if not allow_reduce and type(x.op) in [ProcessingOps, ReduceOps]: + return "acc" + srcs_code = [_ast(src, buf_names, code_for_op) for src in x.src] + code = code_for_op[x.op] + if len(srcs_code) >= 1: + code = code.replace("A", srcs_code[0]) + if len(srcs_code) >= 2: + code = code.replace("B", srcs_code[1]) + return code + + earlycode = _ast(reduceops[0], buf_names, cls.code_for_op, allow_reduce=True) if len(reduceops) > 0 and isinstance(reduceops[0].op, ReduceOps) else "acc" + code = _ast(ast, buf_names, cls.code_for_op) + + C = reduceops[0].arg if len(reduceops) > 0 and isinstance(reduceops[0].op, ProcessingOps) else None + reduce_op = reduceops[0].op if len(reduceops) > 0 and isinstance(reduceops[0].op, ReduceOps) else ReduceOps.SUM + return ret._processing_op([(buf_names[x], x) for x in bufs], code, C, reduce_op, reduce_shape, set(buf_names[x] for x in earlybufs), earlycode, info.flops) + + def _simple_processing_op(ret, bufs: List[Tuple[str, GPUBuffer]]=[], code:str="acc", C:Optional[ConvArgs]=None, op=ReduceOps.SUM, reduce_shape=None, earlybufs:Set[str]=set(), earlycode:str="acc", op_estimate=0) -> GPUBuffer: + assert C is None, f"conv isn't handled by GPU anymore {C}" + + # get the input/output shape and the reduce amount + reduce_shape = (bufs[0][1].shape, ret.shape) if reduce_shape is None else reduce_shape + red = prod([s for s,n in zip(*reduce_shape) if n == 1]) + assert red < 2**31, f"reduce must be under 2**31, {red} isn't" + + # if it's a partial reduce, assert last non reduced axis is before the first reduced axis + if red > 1 and prod(ret.shape) != 1: + assert max([i for i,(s,n) in enumerate(zip(*reduce_shape)) if s == n and n != 1]) < min([i for i,(s,n) in enumerate(zip(*reduce_shape)) if s != 1 and n == 1]) + + kernel_name = "reduce" if red > 1 else "elementwise" + early_views = {name:buf.contiguous_view_constant_fold(name, red) for name, buf in bufs if name in earlybufs} + late_views = {name:buf.contiguous_view_constant_fold(name) for name, buf in bufs if name not in earlybufs} + views = {**early_views, **late_views} + + buf_types : List[str] = [views[name][1] for name, _ in bufs if views[name][1] is not None] # type: ignore + buf_cl = [buf.cl if 'image2d_t' not in views[name][1] else buf.image for name, buf in bufs if views[name][1] is not None] # type: ignore + + # use local memory if it's a multistage reduce + inter_red = 256 if (prod(ret.shape) < 8192 and red >= 256) else 1 + if inter_red > 1: + buf_cl.append(cl.LocalMemory(inter_red*4)) + + reduce_loop = f"int mid = get_global_id(1); for (int subidx = {red//inter_red + 1} * mid; subidx < min({red}, {red//inter_red + 1} * (mid+1)); subidx++)" if inter_red > 1 else f"for (int subidx = 0; subidx < {red}; subidx++)" + conv_prg = CLProgram(kernel_name, f"""{chr(10).join([x[0] for x in views.values()])} + __kernel void {kernel_name}({','.join(["__global float* restrict output"] + buf_types + (["__local float *temp"] if inter_red > 1 else []))}) {{ + const sampler_t smp = CLK_NORMALIZED_COORDS_FALSE | CLK_ADDRESS_CLAMP | CLK_FILTER_NEAREST; + float acc = {GPUBuffer.start_for_op[op]}; + int gid = get_global_id(0); + {reduce_loop} {{ +{chr(10).join([f' float {name} = ' + early_views[name][2] for name in early_views])} + acc = {earlycode}; + }}"""+(f""" + temp[mid] = acc; barrier(CLK_LOCAL_MEM_FENCE); + if (mid == 0) {{ acc = {GPUBuffer.start_for_op[op]}; + for (int rdx = 0; rdx < {inter_red}; rdx++) {{ + acc = {GPUBuffer.code_for_op[op].replace('A', 'temp[rdx]')}; + }}""" if inter_red != 1 else "{")+f""" +{chr(10).join([f' float {name} = ' + late_views[name][2] for name in late_views])} + output[gid] = {code}; + }} + }}""", op_estimate=op_estimate) + + conv_prg([prod(ret.shape), inter_red, 1], [1, inter_red, 1] if inter_red > 1 else None, ret.cl, *buf_cl) + return ret + + def _processing_op(ret, bufs: List[Tuple[str, OpenCLBuffer]]=[], code:str="acc", C=None, op=ReduceOps.SUM, reduce_shape=None, earlybufs:Set[str]=set(), earlycode:str="acc", op_estimate=0): if C is None or earlycode != "acc": # TODO: handle an opencl conv without the conv part - return super()._processing_op(bufs, code, C, op, reduce_shape, earlybufs, earlycode) + return ret._simple_processing_op(bufs, code, C, op, reduce_shape, earlybufs, earlycode, op_estimate) assert earlycode == "acc" x = [x for x in bufs if x[0] == "input"][0][1] @@ -261,7 +353,7 @@ class OpenCLBuffer(GPUBuffer): # fix widths replacements["get_image_width(output)"] = f"({ret._image.shape[0]})" - x, w = x.contiguous_op(), w.contiguous_op() + x, w = x.contiguous(), w.contiguous() options = [] if C.bs > 1: options.append("-DBATCH") @@ -288,7 +380,8 @@ class OpenCLBuffer(GPUBuffer): #print(conv_src) conv_prg = CLProgram("matmul", conv_src, options=tuple(options), - argdtypes=tuple([None, None, None, None] + [np.int16]*len(conv_args) + [None]*len(ewbufs)) + argdtypes=tuple([None, None, None, None] + [np.int16]*len(conv_args) + [None]*len(ewbufs)), + op_estimate=op_estimate ) global_work_size = [4, 16, C.cout//4] @@ -328,7 +421,8 @@ class OpenCLBuffer(GPUBuffer): #print(conv_src) conv_prg = CLProgram("image_conv", conv_src, options=tuple(options), - argdtypes=tuple([None, None, None] + [np.int16]*len(conv_args) + [None]*len(ewbufs)) + argdtypes=tuple([None, None, None] + [np.int16]*len(conv_args) + [None]*len(ewbufs)), + op_estimate=op_estimate ) global_work_size = [C.cout//4, (C.ox+NUM_OUTPUTS-1)//NUM_OUTPUTS, C.bs*C.oy] conv_prg(global_work_size, None, ret.image, x.image, w.image, *conv_args, *[buf.image if 'image2d_t' in typ else buf.cl for typ, (_, buf) in zip(ewtypes, ewbufs)]) diff --git a/tinygrad_repo/accel/opencl/preprocessing.py b/tinygrad_repo/accel/opencl/preprocessing.py index 120b2ab0c..12df69cba 100644 --- a/tinygrad_repo/accel/opencl/preprocessing.py +++ b/tinygrad_repo/accel/opencl/preprocessing.py @@ -3,7 +3,7 @@ from tinygrad.ops import MovementOps, ProcessingOps # input format is N, H x W, C//4 x 4 # dweight format is oc//4 x ch, cw x 4(oc) # weight format is oc//4 x ch, ic//4, cw, 4(oc) x 4(ic) -def preprocessing_op(x,w,C): +def preprocessing_op(x,w,C,make_image=True): w = w.movement_op(MovementOps.RESHAPE, (C.groups, C.rcout, C.cin, C.H, C.W)) #print(x.shape, w.shape) @@ -58,8 +58,8 @@ def preprocessing_op(x,w,C): #w = contiguous(ctx, w, w.shapetracker) if not w.shapetracker.contiguous else w # contiguous before image, always - x = x.contiguous_op() - w = w.contiguous_op() + x = x.contiguous() + w = w.contiguous() # early realize on the weights bw = w @@ -67,7 +67,9 @@ def preprocessing_op(x,w,C): bw = bw.op.src[0] if bw.realized: # weights are static - w.realize().image + wr = w.realize() #.image + if make_image: + wr.image return x,w,C def postprocessing_op(ret, C, C_initial): diff --git a/tinygrad_repo/extra/onnx.py b/tinygrad_repo/extra/onnx.py index b5d28a9ed..a2eb403a0 100644 --- a/tinygrad_repo/extra/onnx.py +++ b/tinygrad_repo/extra/onnx.py @@ -1,11 +1,11 @@ import os import numpy as np -import functools -from onnx.mapping import TENSOR_TYPE_TO_NP_TYPE from tinygrad.tensor import Tensor from tinygrad.helpers import prod -from tinygrad.nn import batch_normalize from tinygrad.ops import DEBUG +from onnx.mapping import TENSOR_TYPE_TO_NP_TYPE + +ONNXLIMIT = int(os.getenv("ONNXLIMIT", "-1")) def get_run_onnx(onnx_model): def shape_to_tuple(s): return tuple(x.dim_value for x in s.dim) @@ -60,6 +60,7 @@ def get_run_onnx(onnx_model): def run_onnx(inputs={}, debug=False): input_tensors = {} intermediate_tensors = {} + output_tensor_names = [x.name for x in onnx_model.graph.output] # get inputs for inp in onnx_model.graph.input: @@ -114,7 +115,7 @@ def get_run_onnx(onnx_model): ret = ret.reshape([s for i,s in enumerate(shape) if i != axis]) if len(indices) == 1 else ret # squeeze if needed elif n.op_type == "BatchNormalization": invstd = inp[4].add(opt.get('epsilon', 1e-5))**-0.5 - ret = batch_normalize(inp[0], inp[1], inp[2], inp[3], invstd) + ret = inp[0].batchnorm(inp[1], inp[2], inp[3], invstd) elif n.op_type == "Gemm": ret = inp[0].linear(inp[1].transpose() if opt.get('transB', 0) == 1 else inp[1], inp[2]) elif n.op_type == "Conv": x,w,b = inp if len(inp) == 3 else (inp[0], inp[1], None) @@ -173,6 +174,9 @@ def get_run_onnx(onnx_model): if debug: print(ret.shape) intermediate_tensors[n.output[0]] = ret #print(ret.numpy().mean()) + if num == ONNXLIMIT: + output_tensor_names = n.output + break - return {outp.name:intermediate_tensors[outp.name] for outp in onnx_model.graph.output} + return {outp:intermediate_tensors[outp] for outp in output_tensor_names} return run_onnx diff --git a/tinygrad_repo/extra/thneed.py b/tinygrad_repo/extra/thneed.py index 58d4124ae..d398eafe4 100644 --- a/tinygrad_repo/extra/thneed.py +++ b/tinygrad_repo/extra/thneed.py @@ -19,6 +19,7 @@ class Thneed: self.gobj = 0 # build graph + # NOTE: if CLCACHE=1, this is wrong! nodes = defaultdict(lambda: {'in_edges': [], 'out_edges': []}) for _, args in self.cl_cache: # output is always the first parameter @@ -35,9 +36,15 @@ class Thneed: if len(nodes[n]['out_edges']) == 0: self.outputs.append(n) - for n in self.inputs.values(): - assert n in self.buffers_to_save, f"{n} was not an input" - self.buffers_to_save.remove(n) + fake_inputs = [] + for k,n in self.inputs.items(): + if n in self.buffers_to_save: + self.buffers_to_save.remove(n) + else: + print(f"WARNING: {k} was not a used input, removing it") + fake_inputs.append(k) + for k in fake_inputs: + del self.inputs[k] def load(self, input_fn): float32 = not FLOAT16 @@ -266,49 +273,67 @@ class Thneed: events.append(prg.clprg(CL().cl_queue, *args)) mt = time.monotonic() CL().cl_queue.finish() - et = time.monotonic() - print(f"submit in {(mt-st)*1000.0:.2f} ms, total runtime is {(et-st)*1000.0:.2f} ms") + et = time.monotonic() - st + print(f"submit in {(mt-st)*1000.0:.2f} ms, total runtime is {et*1000.0:.2f} ms") - if DEBUGCL: + if DEBUGCL >= 1: + scale_total_runtime = sum([(e.profile.end - e.profile.start) for e in events])/1e9 + # TODO: Mac OS has a scaling issue, this hack fixes it + scale = 1 if (et/scale_total_runtime) < 10 else (et/scale_total_runtime) total_runtime = 0 for i, ((prg, args), e) in enumerate(zip(self.cl_cache, events)): - runtime = (e.profile.end - e.profile.start) - print(f"{i:3d} time {total_runtime/1e6:5.2f} ms running {prg.name:20s} with {str(args[0]):15s} {str(args[1]):15s} count {len(args)-2:2d} runtime {runtime/1e3:7.2f} us {prg.options}") + runtime = (e.profile.end - e.profile.start)*scale + print(f"{i:3d} time {total_runtime/1e6:5.2f} ms running {prg.name:20s} with {str(args[0]):15s} {str(args[1]):15s} count {len(args)-2:2d} runtime {runtime/1e3:7.2f} us {(prg.op_estimate)/runtime:9.2f} GFLOPS {prg.options} -> {args[2].shape if hasattr(args[2], 'shape') else args[2].size}") + if (DEBUGCL >= 2 and int(os.getenv("PRINT_KERNEL", "-1")) == i) or DEBUGCL >= 3: + print(prg.prg) total_runtime += runtime - print(f"total runtime: {total_runtime/1e6:.2f} ms") + print(f"total runtime: {total_runtime/1e6:.2f} ms wall time: {et*1000.0:.2f} ms") + return et def optimize_local_workgroup(self): MAX_WORKGROUP = CL.cl_ctx.devices[0].max_work_group_size local_cl_cache = [] for prg, args in self.cl_cache: + potential_locals = [tuple(args[1])] if args[1] is not None else [] + runtimes = [] args = list(args) + + if args[1] is None and len(args[0]) == 1: + for l1 in [args[0][0], 1, 4, 16, MAX_WORKGROUP//4, MAX_WORKGROUP]: + potential_locals.append((l1,)) + if args[1] is None and len(args[0]) == 2: - args[1] = [min(MAX_WORKGROUP, args[0][0]), 1] - try: - e = prg.clprg(CL().cl_queue, *args) - except cl.LogicError: - # INVALID_WORK_GROUP_SIZE - args[1] = None + for l2 in [1, 4, 16, MAX_WORKGROUP//4, MAX_WORKGROUP]: + potential_locals.append((min(MAX_WORKGROUP, args[0][0]), l2)) if args[1] is None and len(args[0]) == 3: - runtimes = [] for l2 in [16,args[0][1],MAX_WORKGROUP]: for l3 in [4,16,args[0][2],MAX_WORKGROUP]: for l1 in [max(1, MAX_WORKGROUP//(l2*l3)), args[0][0], 4, 16, MAX_WORKGROUP]: if l1 > args[0][0] or l2 > args[0][1] or l3 > args[0][2]: continue - local_args = (l1, l2, l3) - if prod(local_args) > MAX_WORKGROUP: continue - args[1] = local_args - try: - e = prg.clprg(CL().cl_queue, *args) - except (cl.LogicError, cl.RuntimeError): - # INVALID_WORK_GROUP_SIZE - continue - CL().cl_queue.finish() - runtime = e.profile.end - e.profile.start - #print(runtime, args[0], args[1]) - runtimes.append((runtime, local_args)) - #print(sorted(runtimes)[0:5]) + potential_locals.append((l1, l2, l3)) + + # TODO: why does this introduce a bug? + #if args[1] is not None and len(args[0]) == 3: + # for l2 in [args[1][1], args[0][1]]: # might be required, whole thing is always safe + # for l3 in [4,16,args[0][2],MAX_WORKGROUP,args[1][2]]: + # potential_locals.append((args[1][0], l2, l3)) + + for local_args in potential_locals: + if prod(local_args) > MAX_WORKGROUP: continue + args[1] = local_args + # 3 runs just in case + for i in range(3): + try: + e = prg.clprg(CL().cl_queue, *args) + except (cl.LogicError, cl.RuntimeError): + # INVALID_WORK_GROUP_SIZE + continue + CL().cl_queue.finish() + runtime = e.profile.end - e.profile.start + #print(runtime, args[0], args[1]) + runtimes.append((runtime, local_args)) + if len(runtimes) > 0: args[1] = sorted(runtimes)[0][1] else: diff --git a/tinygrad_repo/openpilot/compile.py b/tinygrad_repo/openpilot/compile.py index b66c551fa..065b1c550 100644 --- a/tinygrad_repo/openpilot/compile.py +++ b/tinygrad_repo/openpilot/compile.py @@ -1,30 +1,25 @@ #!/usr/bin/env python3 -from ast import Assert -import pathlib, sys +import os, time, io, pathlib, sys sys.path.insert(0, str(pathlib.Path(__file__).parent.parent)) -from collections import defaultdict -import pyopencl as cl -import os -import time -import io - -os.environ['OPT'] = '99' +if os.getenv("OPT", None) is None: + os.environ['OPT'] = '99' if os.getenv("GPU", None) is None: os.environ['OPENCL'] = '1' +ALLOWED_KERNEL_COUNT = int(os.getenv("ALLOWED_KERNEL_COUNT", 0)) DEBUGCL = int(os.getenv("DEBUGCL", 0)) import onnx import numpy as np -import tinygrad.ops as ops +import tinygrad.graph as graph +from tinygrad.ops import GlobalCounters -from tinygrad.llops.ops_gpu import CL, CLProgram, CLBuffer +from tinygrad.llops.ops_gpu import CL from extra.utils import fetch from extra.onnx import get_run_onnx from tinygrad.tensor import Tensor -from tinygrad.helpers import prod OPENPILOT_MODEL = "https://github.com/commaai/openpilot/raw/6c5693e965b9c63f8678f52b9e9b5abe35f23feb/selfdrive/modeld/models/supercombo.onnx" @@ -62,8 +57,8 @@ def get_random_input_tensors(input_shapes): def compile(dat, output_fn): Tensor.no_grad = True - using_graph = ops.GRAPH - ops.GRAPH = False + using_graph = graph.GRAPH + graph.GRAPH = False onnx_model = onnx.load(io.BytesIO(dat)) run_onnx = get_run_onnx(onnx_model) @@ -77,7 +72,7 @@ def compile(dat, output_fn): # initial run(s) to load weights for _ in range(2): st = time.monotonic() - tinygrad_out = run_onnx(inputs)['outputs'] + tinygrad_out = next(iter(run_onnx(inputs).values())) mt = time.monotonic() tinygrad_out.realize() mt2 = time.monotonic() @@ -94,26 +89,31 @@ def compile(dat, output_fn): # real run inputs, np_inputs = get_random_input_tensors(input_shapes) print("***** REAL RUN *****") - tinygrad_out = run_onnx(inputs)['outputs'] + tinygrad_out = next(iter(run_onnx(inputs).values())) # note, since CL.CACHE is enabled, it doesn't actually run the kernels + start_ops = GlobalCounters.global_ops CL.CACHE = [] - if using_graph: ops.GRAPH = True + if using_graph: graph.GRAPH = True CL.kernel_count = -1 tinygrad_out.realize() - ops.GRAPH = False + graph.GRAPH = False print("kernel count:", len(CL.CACHE)) + assert len(CL.CACHE) <= ALLOWED_KERNEL_COUNT or ALLOWED_KERNEL_COUNT == 0, "too many kernels!" + used_ops = GlobalCounters.global_ops - start_ops from extra.thneed import Thneed t = Thneed(CL.CACHE, {k:inputs[k].lazydata.realized.cl for k in inputs.keys()}) CL.CACHE = None - t.optimize_local_workgroup() + if int(os.getenv("OPTWG", "0")): + t.optimize_local_workgroup() # save thneed (before run) t.save(output_fn) print(f"buffers to save: {len(t.buffers_to_save)}, outputs: {t.outputs}") - t.run() + runtime = t.run() + print(f"network using {used_ops/1e9:.2f} GOPS with runtime {runtime*1e3:.2f} ms that's {used_ops/runtime*1e-9:.2f} GFLOPS") # confirm thneed found the right output thneed_out = np.empty((t.outputs[0].size//4,), dtype=np.float32).reshape(tinygrad_out.shape) diff --git a/tinygrad_repo/tinygrad/__init__.py b/tinygrad_repo/tinygrad/__init__.py new file mode 100644 index 000000000..e49d8ccd5 --- /dev/null +++ b/tinygrad_repo/tinygrad/__init__.py @@ -0,0 +1 @@ +from tinygrad import tensor, nn # noqa: F401 diff --git a/tinygrad_repo/tinygrad/ast.py b/tinygrad_repo/tinygrad/ast.py new file mode 100644 index 000000000..1927ad4d9 --- /dev/null +++ b/tinygrad_repo/tinygrad/ast.py @@ -0,0 +1,164 @@ +from enum import Enum +import itertools +from typing import List, Tuple +from tinygrad.helpers import prod, dedup, all_same +from tinygrad.ops import LazyOp, MovementOps, get_lazyop_info, get_buffers, ReduceOps, get_lazyops +from tinygrad.shape import ShapeTracker + +def get_first_reduce(shapes): + for i in range(len(shapes[0])): + if not all_same([x[i] for x in shapes]): + return i + return len(shapes[0]) # off the end + +Types = Enum("Types", ["FLOAT", "FLOAT4"]) +class Token: + def __init__(self, tok:str, typ:Types, ptr:bool=False): + assert isinstance(tok, str) + self.tok, self.typ, self.ptr = tok, typ, ptr + self.axis : List[Tuple[int, int]] = [] + def array(self, length, stride): self.axis.append((length, stride)) + def size(self): return prod(x[0] for x in self.axis) + def offsets(self): return [sum(t) for t in itertools.product(*[[y*x[1] for y in range(x[0])] for x in self.axis[::-1]])] if len(self.axis) else [0] + def decltype(self): return ('float' if self.typ == Types.FLOAT else 'float4') + ('*' if self.ptr else '') + def __repr__(self): return f"<{self.typ}{'*' if self.ptr else ''} {self.tok}{f'[{self.axis}]' if len(self.axis) else ''}>" + +# ast kernel can contain one ReduceOp with arbitrary Binary/Unary ops +class ASTKernel: + def __init__(self, ast:LazyOp): + # key for lookup in cache (can change, str might not be right) + self.input_ast = ast + self.key = str(ast) + + # if the AST ends with a RESHAPE, we remove it and create the buffer accordingly + if ast.op == MovementOps.RESHAPE: + output_shape = ast.arg + ast = ast.src[0] + else: + output_shape = None + + self.info = get_lazyop_info(ast) + self.bufs = dedup(get_buffers(ast)) + reduceops = [x for x in get_lazyops(ast) if x.op in ReduceOps] + assert len(dedup(reduceops)) <= 1, "max one reduce op in an ast" + self.reduceop = reduceops[0] if reduceops else None + self.earlybufs = dedup(get_buffers(self.reduceop)) if self.reduceop else [] + self.ast = ast + + # create the buffer we are returning (as the same type as the input buffers) and add it as the first buffer + self.ret = type(self.bufs[0])(output_shape if output_shape else self.info.shape) + if hasattr(self.ret, "cl"): self.ret.cl # does the allocation of unbacked buffer, pylint: disable=W0104 + self.bufs = [type(self.ret)(self.info.shape, hostbuf=self.ret)] + self.bufs + self.buftokens = [Token(f"data{i}", Types.FLOAT, ptr=True) for i in range(len(self.bufs))] + + # check valid AST kernel + assert all_same([x.shape for x in self.earlybufs]), "all earlybufs must have the same shape" + assert all_same([x.shape for x in self.bufs if x not in self.earlybufs]), "all latebufs must have the same shape" + assert all_same([len(x.shape) for x in self.bufs]), "all bufs must have the same shape size" + + def print(self): + buf_count = -1 + op_count = -1 + cache = {} + def print_ast(x, name=None): + nonlocal buf_count, op_count + if x not in cache: + if not isinstance(x, LazyOp): + if name is None: + buf_count += 1 + name = f"buf{buf_count}" + print(f"buf{buf_count} = {x}") + cache[x] = name + else: + srcs = [print_ast(y) for y in x.src] + if name is None: + op_count += 1 + name = f"op{op_count}" + print(f"{name} = LazyOp({str(x.op)}, ({','.join(srcs)},), {x.arg})") + cache[x] = name + return cache[x] + print_ast(self.input_ast, "ast") + + + def process(self): + # get shape, strides, and offset + # if it's a multiview buffer we take the final view + self.shapes = [x.shape for x in self.bufs] + self.strides = [x.st.views[-1].strides for x in self.bufs] + self.offsets = [x.st.views[-1].offset for x in self.bufs] # include the offsets (as is) + self.simplify_ones() + self.simplify_merge_adjacent() + + def simplify_ones(self): + # remove places where the shape is all ones + # TODO: this should be factored in to multi shape stride + all_ones = [all(s[i]==1 for s in self.shapes) for i in range(len(self.shapes[0]))] + # keep at least 1 one + if all(all_ones): + all_ones[-1] = False + self.shapes = [[s[i] for i in range(len(s)) if not all_ones[i]] for s in self.shapes] + self.strides = [[s[i] for i in range(len(s)) if not all_ones[i]] for s in self.strides] + # find first mismatch, don't reduce this + self.first_reduce = get_first_reduce(self.shapes) + + def simplify_merge_adjacent(self): + shapes, strides = self.shapes, self.strides + + # merge dimensions if we can, multi get_shape_strides + # TODO: does this always preserve the reduce dimension, NO + # TODO: move this into shapetracker, with tests! + rets = [[(shapes[j][0], strides[j][0])] for j in range(len(shapes))] + for i in range(1, len(shapes[0])): + can_merge = [] + for j in range(len(shapes)): + # TODO: added the always mergability of 1s, is this right? if so, add to shapetracker in the 1 case + can_merge.append((strides[j][i] != 0 and rets[j][-1][1] == shapes[j][i]*strides[j][i]) or (strides[j][i] == 0 and rets[j][-1][1] == 0)) + # more can merge than this + can_merge = all(can_merge) and i != self.first_reduce + for j in range(len(shapes)): + if can_merge: + rets[j][-1] = (rets[j][-1][0] * shapes[j][i], strides[j][i]) + else: + rets[j].append((shapes[j][i], strides[j][i])) + self.shapes, self.strides = [[y[0] for y in x] for x in rets], [[y[1] for y in x] for x in rets] + self.first_reduce = get_first_reduce(self.shapes) + + @property + def shape_len(self): return len(self.shapes[0]) + + # this should be aware of the three parts to the shape + # * the input/output dimensions + # * the reduce dimensions + # * the size outputted by each kernel + def reshape_and_permute(self, new_shape_fxn, axis): + new_shapes, new_strides = [], [] + for shape, stride in zip(self.shapes, self.strides): + st = ShapeTracker(tuple(shape)) + st.strided(*zip(shape, stride)) + # TODO: handle reduced shape here + if new_shape_fxn is not None: st.reshape(*new_shape_fxn(shape)) + if axis is not None: st.permute(*axis) + assert len(st.views) == 1 + new_shapes.append(st.shape) + new_strides.append(st.strides) + self.shapes, self.strides = new_shapes, new_strides + + # drops the final dimension + def upcast(self): + upcasted = [x[-1] for x in self.shapes if x[-1] != 1] + assert len(upcasted) >= 1 and all_same(upcasted), f"can't upcast mismatch {upcasted}" + for i in range(len(self.bufs)): + if self.shapes[i][-1] == upcasted[0]: + # multiview shapetrackers can slice through a float4, so don't allow them + can_merge = (not self.bufs[i].st.needs_valid() and len(self.bufs[i].st.views) == 1) or "Image" in str(type(self.bufs[i]._buf)) # TODO: terrible hack + if self.shapes[i][-1] == 4 and self.buftokens[i].typ == Types.FLOAT and self.strides[i][-1] == 1 and can_merge: + # this is an upcast to FLOAT4 + self.buftokens[i].typ = Types.FLOAT4 + assert all(x%upcasted[0] == 0 for x in self.strides[i][0:-1]) + assert self.offsets[i]%upcasted[0] == 0 + else: + self.buftokens[i].array(upcasted[0], self.strides[i][-1]) + + # remove the last dimension + self.shapes = [x[:-1] for x in self.shapes] + self.strides = [x[:-1] for x in self.strides] diff --git a/tinygrad_repo/tinygrad/graph.py b/tinygrad_repo/tinygrad/graph.py new file mode 100644 index 000000000..07762e874 --- /dev/null +++ b/tinygrad_repo/tinygrad/graph.py @@ -0,0 +1,69 @@ +import os +import atexit +import itertools +from collections import defaultdict +from typing import Dict, List +from tinygrad.ops import DeviceBuffer, DEBUG, UnaryOps, BinaryOps, ReduceOps, MovementOps, ProcessingOps, LoadOps, Op, OpType + +GRAPH = int(os.getenv("GRAPH", "0")) + +# **** debugging and graphing **** + +cnts : Dict[OpType, int] = defaultdict(int) +if GRAPH: + import networkx as nx # type: ignore + G = nx.DiGraph() + def save_graph_exit(): + for k,v in cnts.items(): + print(k, v) + if int(os.getenv("PRUNEGRAPH", "0")): + dead_nodes = [] + for n in G.nodes: + # prune movementops and loadops + if 'fillcolor' in G.nodes[n] and G.nodes[n]['fillcolor'] in ["#80ff8080", "#80ff80", "#FFFF8080", "#FFFF80"]: + for (x,_),(_,y) in itertools.product(G.in_edges(n), G.out_edges(n)): + G.add_edge(x, y) + dead_nodes.append(n) + for n in dead_nodes: + G.remove_node(n) + print("saving", G) + nx.drawing.nx_pydot.write_dot(G, '/tmp/net.dot') + # -Gnslimit=100 can make it finish, but you won't like results + os.system('dot -Tsvg /tmp/net.dot -o /tmp/net.svg') + atexit.register(save_graph_exit) + +global_num_max = 0 +def log_op(optype : OpType, op : List[Op], ret : DeviceBuffer, inp : List[DeviceBuffer]): + cnts[optype] += 1 + if DEBUG >= 3: + print(f"{op} : {', '.join([str(x.shape) for x in inp])} -> {ret.shape}") + if GRAPH: + def nm(x): + global global_num_max + if not hasattr(x, 'global_num'): + setattr(x, 'global_num', global_num_max) + global_num_max += 1 + return f"<<< {x.global_num} >>>" + + top_colors = {LoadOps: '#FFFF80', UnaryOps: "#c0c0c0", ReduceOps: "#8080ff", BinaryOps: "#c0c0c0", MovementOps: "#80ff80", ProcessingOps: "#ff8080"} + dashed = (optype == LoadOps and hasattr(ret, "_backing")) or (hasattr(ret, "st") and not ret.st.contiguous) # type: ignore + + for x in inp: + if len(op) <= 2: + sop = '.'.join([str(y).split(".")[1] for y in op][::-1]) + elif len(op) <= 4: + sop = '.'.join([str(y).split(".")[1][0:2] for y in op][::-1]) + else: + sop = str(len(op)) + G.add_edge(nm(x), nm(ret), label=sop) + if 'label' not in G.nodes[nm(x)]: + G.nodes[nm(x)]['label'] = str(x.shape) + if nm(ret) not in G.nodes: + G.add_node(nm(ret)) + + if optype == ReduceOps: + G.nodes[nm(ret)]['label'] = str(set(x.shape for x in inp))+"\n"+str(ret.shape) + else: + G.nodes[nm(ret)]['label'] = str(ret.shape) + G.nodes[nm(ret)]['fillcolor'] = (top_colors[optype] + ('80' if dashed else '')) if optype in top_colors else "#ffffff" + G.nodes[nm(ret)]['style'] = 'filled, dashed' if dashed else 'filled' diff --git a/tinygrad_repo/tinygrad/helpers.py b/tinygrad_repo/tinygrad/helpers.py index d9a55049a..889004cba 100644 --- a/tinygrad_repo/tinygrad/helpers.py +++ b/tinygrad_repo/tinygrad/helpers.py @@ -1,10 +1,19 @@ from collections import namedtuple import os, math +def dedup(x): return list(dict.fromkeys(x)) # retains list order def prod(x): return math.prod(x) def argfix(*x): return tuple() if len(x) == 0 else tuple(x[0]) if isinstance(x[0], tuple) or isinstance(x[0], list) else tuple(x) def argsort(x): return sorted(range(len(x)), key=x.__getitem__) # https://stackoverflow.com/questions/3382352/equivalent-of-numpy-argsort-in-basic-python +def all_same(items): return all(x == items[0] for x in items) if len(items) > 0 else True +def colored(st, color): return f"\u001b[{30+['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'].index(color)}m{st}\u001b[0m" # replace the termcolor library with one line +def partition(lst, fxn): return [x for x in lst if fxn(x)], [x for x in lst if not fxn(x)] +def modn(x, a): return -((-x)%a) if x < 0 else x%a + def reduce_shape(shape, axis): return tuple(1 if i in axis else shape[i] for i in range(len(shape))) +def shape_to_axis(old_shape, new_shape): + assert len(old_shape) == len(new_shape), "reduce shapes must have same dimensions" + return tuple([i for i,(a,b) in enumerate(zip(old_shape, new_shape)) if a != b]) ConvArgs = namedtuple('ConvArgs', ['H', 'W', 'groups', 'rcout', 'cin', 'oy', 'ox', 'iy', 'ix', 'sy', 'sx', 'bs', 'cout', 'py', 'py_', 'px', 'px_', 'dy', 'dx', 'out_shape']) def get_conv_args(x_shape, w_shape, stride=1, groups=1, padding=0, dilation=1, out_shape=None): diff --git a/tinygrad_repo/tinygrad/lazy.py b/tinygrad_repo/tinygrad/lazy.py new file mode 100644 index 000000000..9ace5422e --- /dev/null +++ b/tinygrad_repo/tinygrad/lazy.py @@ -0,0 +1,330 @@ +from __future__ import annotations +from typing import Optional, Tuple, Union, List, Dict +from copy import copy +import os, sys, weakref +from tinygrad.helpers import ConvArgs, get_available_llops, prod +from tinygrad.shape import ShapeTracker +from tinygrad.ops import DeviceBuffer, UnaryOps, BinaryOps, ReduceOps, MovementOps, ProcessingOps, LoadOps, OpType, LazyOp, get_buffers, get_lazyops, DEBUG +from tinygrad.graph import log_op + +# lazy can recurse a lot +sys.setrecursionlimit(10000) + +OPT = int(os.getenv("OPT", "2")) +NOCONV = int(os.getenv("NOCONV", "0")) +IMAGE = int(os.getenv("IMAGE", "0")) + +# TODO: movement ops that only change shape are really nops. treat them as such +REMOVE_MOVEMENT_NOPS, MERGE_UNARY_OPS, MERGE_ELEMENTWISE_INTO_REDUCE, SHUFFLE_MOVEMENT_OPS = OPT>=1, OPT>=1, OPT>=1, OPT>=1 +MERGE_ELEMENTWISE_OPS, MERGE_ONE_REDUCE_INTO_ELEMENTWISE = OPT>=2, OPT>=2 +SHUFFLE_PAD_OPS = OPT>=3 # NOTE: 0/0 is NaN if you pad, so this can change the output + +# **** enumerate supported devices **** + +class Device: + _buffers, DEFAULT = get_available_llops() + for name in _buffers.keys(): + vars()[name] = name + +# **** realize helpers **** +def realize_buffers(real_srcs, x:LazyOp) -> LazyOp: + if x in real_srcs: + return realize_buffers(real_srcs, real_srcs[x]) if isinstance(real_srcs[x], LazyOp) else real_srcs[x] + return LazyOp(x.op, tuple(realize_buffers(real_srcs, y) for y in x.src), x.arg) + +# **** realize functions **** +# TODO: make all _realize functions return an AST, perhaps unrealized + +def _realize_loadops(self:LazyBuffer) -> Tuple[DeviceBuffer, List[DeviceBuffer], Optional[OpType]]: + if self.op.op == LoadOps.FROMCPU: + return Device._buffers[self.device].fromCPU(self.op.arg), [], LoadOps + elif self.op.op == LoadOps.CONTIGUOUS: + real_src = self.op.src[0].realize(self.device) + ret = real_src.contiguous() + return ret, [real_src], LoadOps if id(ret) != id(real_src) else None + else: + raise NotImplementedError(f"unknown LoadOp {self.op.op}") + +def _realize_movementops(self:LazyBuffer) -> Tuple[DeviceBuffer, List[DeviceBuffer], OpType]: + src = self.op.src[0] + + # fuse RESHAPE and ReduceOps + if src.realized is None and src.optype == ReduceOps and self.op.op == MovementOps.RESHAPE and len(src.children) <= 1: + return _realize_reduceops_w_shape(src, output_shape = self.op.arg) + + real_src = src.realize(self.device) + return real_src.movement_op(self.op.op, self.op.arg), [real_src], MovementOps + +def _realize_processingops(self:LazyBuffer) -> Tuple[DeviceBuffer, List[DeviceBuffer], OpType]: + real_src_x, real_src_w = [x.realize(self.device) for x in self.op.src] + return real_src_x.processing_op(self.op.op, real_src_w, self.op.arg), [real_src_x, real_src_w], ProcessingOps + +# this supports late merging an upstream Elementwise op +def _realize_reduceops_w_shape(self:LazyBuffer, output_shape=None) -> Tuple[DeviceBuffer, List[DeviceBuffer], OpType]: + # TODO: this can also corealize a binary op after the reduce, not just before + src = self.op.src[0] + if MERGE_ELEMENTWISE_INTO_REDUCE and src.realized is None and src.optype == BinaryOps and len(src.children) <= 1: + # this is the new version, deprecate _processing_op + real_srcs : Dict[LazyBuffer, DeviceBuffer] = {x:x.realize(self.device) for x in get_buffers(src.op)} + ast = LazyOp(self.op.op, (realize_buffers(real_srcs, src.op),), self.op.arg) + else: + real_src = src.realize(self.device) + real_srcs = {src:real_src} + ast = LazyOp(self.op.op, (real_src,), self.op.arg) + if output_shape is not None: ast = LazyOp(MovementOps.RESHAPE, (ast, ), output_shape) + return self.dbuffer.exec_ast(ast), list(real_srcs.values()), ReduceOps +def _realize_reduceops(self:LazyBuffer) -> Tuple[DeviceBuffer, List[DeviceBuffer], OpType]: return _realize_reduceops_w_shape(self) + +# this supports late merging an upstream Reduce op and even an Elementwise op above that +def _realize_binaryops(self:LazyBuffer) -> Tuple[DeviceBuffer, List[DeviceBuffer], OpType]: + real_srcs : Dict[LazyBuffer, Union[None, LazyOp, DeviceBuffer]] = {x:None for x in get_buffers(self.op)} + op_type : OpType = BinaryOps + if DEBUG >= 3: + for k,x in zip(real_srcs.keys(), map(get_movementroot_contiguous, real_srcs.keys())): + if x.optype in [ProcessingOps,ReduceOps] and x.realized is None: + print("\nHIT", k,x) + for tk in k.children: print("k", tk) + for tx in x.children: print("x", tx) + # NOTE: contiguous does not always mean the same size with SHRINK. this is still mergeable but requires more thought how + psrcs : List[Tuple[LazyBuffer, LazyBuffer]] = [(k,x) for k,x in zip(real_srcs.keys(), map(get_movementroot_contiguous, real_srcs.keys())) if x.optype in [ProcessingOps,ReduceOps] and x.realized is None and prod(k.shape) == prod(x.shape) and len(x.children) <= 1 and len(k.children) <= 1] + intermediate_shape = self.shape + if len(psrcs) == 1 and MERGE_ONE_REDUCE_INTO_ELEMENTWISE and (self.device != "OPENCL" or self.shape[-1] == 4): + if psrcs[0][1].optype == ProcessingOps: + real_srcs[psrcs[0][0]] = psrcs[0][1].op + for x in psrcs[0][1].op.src: + real_srcs[x] = x.realize(self.device) + op_type = ProcessingOps + elif psrcs[0][1].optype == ReduceOps: + src = psrcs[0][1].op.src[0] + if MERGE_ELEMENTWISE_INTO_REDUCE and src.realized is None and src.optype == BinaryOps and len(src.children) <= 1: + src = src.op + real_srcs[psrcs[0][0]] = LazyOp(psrcs[0][1].op.op, (src,), psrcs[0][1].op.arg) + for x in get_buffers(real_srcs[psrcs[0][0]]): # type: ignore + # these are the early buffers + real_srcs[x] = x.realize(self.device) + op_type = ReduceOps + # if the ReduceOp is followed by a reshape, we push this reshape before all the ElementwiseOp inputs + if psrcs[0][0].shape != psrcs[0][1].shape: + intermediate_shape = psrcs[0][1].shape + assert psrcs[0][0].shape == self.shape, f"shape mismatch {psrcs[0][0].shape} != {self.shape}" + + # reshape all the late ops into the output shape + # NOTE: these RESHAPEs will return self if they don't change the shape + for x in real_srcs.keys(): + if real_srcs[x] is None: + real_srcs[x] = x.movement_op(MovementOps.RESHAPE, intermediate_shape).realize(self.device) + ast = LazyOp(MovementOps.RESHAPE, (realize_buffers(real_srcs, self.op), ), self.shape) + ret = self.dbuffer.exec_ast(ast) + return ret, [x for x in real_srcs.values() if not isinstance(x, LazyOp) and x is not None], op_type + +_realize = {LoadOps:_realize_loadops, ReduceOps:_realize_reduceops, MovementOps:_realize_movementops, BinaryOps:_realize_binaryops, ProcessingOps:_realize_processingops} + +# **** lazy operations **** + +def get_weakop(op:LazyOp) -> LazyOp: return LazyOp(op.op, tuple(get_weakop(x) if isinstance(x, LazyOp) else weakref.ref(x) for x in op.src), op.arg) +def get_movementroot(root:LazyBuffer) -> LazyBuffer: return get_movementroot(root.op.src[0]) if root.realized is None and (root.optype == MovementOps or (root.op.op == LoadOps.CONTIGUOUS and root.op.src[0].st.contiguous)) else root +def get_movementroot_contiguous(x:LazyBuffer) -> LazyBuffer: return get_movementroot(x) if x.optype == MovementOps and x.st.contiguous else x + +LAZY = int(os.getenv("LAZY", "1")) + +class LazyBuffer: + lazycache : weakref.WeakValueDictionary[LazyOp, LazyBuffer] = weakref.WeakValueDictionary() + def __new__(cls, device, shape, optype, op): + # fromcpu aren't cached + if optype == LoadOps and op.op == LoadOps.FROMCPU: + return super().__new__(cls) + wop = (device, optype, get_weakop(op)) # NOTE: shape should be deterministic. annoying to cache with the ShapeTracker + # NOTE: we need "ret" to prevent the new buffer from being immediately deleted + if wop not in LazyBuffer.lazycache: + LazyBuffer.lazycache[wop] = ret = super().__new__(cls) # noqa: F841, pylint: disable=W0612 + return LazyBuffer.lazycache[wop] + + def __init__(self, device, shape:Union[ShapeTracker, Tuple[int, ...]], optype:OpType, op:LazyOp): + if hasattr(self, 'device'): + return # cache hit, we return and don't reinit + self.st = shape if isinstance(shape, ShapeTracker) else ShapeTracker(tuple(shape)) + self.shape, self.optype, self.op = self.st.shape, optype, op + self.realized : Optional[DeviceBuffer] = None + self.device, self.dbuffer = device, Device._buffers[device] + self.children : weakref.WeakSet[LazyBuffer] = weakref.WeakSet() + # NOTE: op should be read only after construction of LazyBuffer + for x in get_buffers(op): + x.children.add(self) + if not LAZY: + self.realize() + + def __repr__(self): return f"" + + # this produces a device buffer + def realize(self:LazyBuffer, required_device=None) -> DeviceBuffer: + if required_device is not None: + assert required_device == self.device + if self.realized is None: + # we haven't realized the Buffer yet + self.realized, real_srcs, real_type = _realize[self.optype](self) + # in lazy mode, we don't log until we realize + if real_type is not None: + log_op(real_type, [x.op for x in get_lazyops(self.op)], self.realized, real_srcs) + # no need to keep the op after realization + del self.op + + assert self.realized.shape == self.shape + assert isinstance(self.realized, Device._buffers[self.device]) + return self.realized + + @staticmethod + def fromCPU(x, device): return LazyBuffer(device, x.shape, LoadOps, LazyOp(LoadOps.FROMCPU, tuple(), x.copy())) + def toCPU(self): return self.realize().toCPU() + + def unary_op(self:LazyBuffer, op:UnaryOps) -> LazyBuffer: return elementwise_op(op, self) + def binary_op(self:LazyBuffer, op:BinaryOps, y:LazyBuffer) -> LazyBuffer: return elementwise_op(op, self, y) + def contiguous(self:LazyBuffer) -> LazyBuffer: return LazyBuffer(self.device, self.shape, LoadOps, LazyOp(LoadOps.CONTIGUOUS, (self,))) + + def reduce_op(self:LazyBuffer, op:ReduceOps, new_shape:Tuple[int, ...]) -> LazyBuffer: + if self.shape == tuple(new_shape): + return self + reduce = list(enumerate(zip(self.shape, new_shape))) + # move the reduce axes to the end + x = self.movement_op(MovementOps.PERMUTE, [i for i,(s,n) in reduce if s == n] + [i for i,(s,n) in reduce if s != n]) + new_tmp_shape = tuple([n for _,(s,n) in reduce if s == n] + [n for _,(s,n) in reduce if s != n]) + # NOTE: this reshape can only move around 1s + return LazyBuffer(x.device, new_tmp_shape, ReduceOps, LazyOp(op, (x,), new_tmp_shape)).movement_op(MovementOps.RESHAPE, new_shape) + + # syntactic sugar around PAD and SHRINK + # TODO: turn RESHAPE into EXPAND and CONTRACT (current EXPAND should be REPEAT) + def slice(self:LazyBuffer, arg): + padding = [(max(0, -p[0]), max(0, p[1]-self.shape[i])) for i,p in enumerate(arg)] + return self.movement_op(MovementOps.PAD, padding).movement_op(MovementOps.SHRINK, tuple((p[0] + padding[i][0], p[1] + padding[i][0]) for i,p in enumerate(arg))) + + def movement_op(self:LazyBuffer, op:MovementOps, arg) -> LazyBuffer: + # TODO: look into why that copy is needed + arg = tuple(copy(arg)) + local_st = ShapeTracker(self.shape).movement_op(op, arg) + + # instant nops + if local_st.contiguous and self.shape == local_st.shape and op != MovementOps.STRIDED: + return self + + # two ops in a row is one op. merge them if unresolved + if self.realized is None and self.op.op == op: + if op in [MovementOps.RESHAPE, MovementOps.EXPAND, MovementOps.SHRINK]: + return self.op.src[0].movement_op(op, arg) + if op == MovementOps.PERMUTE: + return self.op.src[0].movement_op(op, tuple(self.op.arg[i] for i in arg)) + if op == MovementOps.PAD: + return self.op.src[0].movement_op(op, tuple((b1+b2, e1+e2) for (b1,e1),(b2,e2) in zip(self.op.arg, arg))) + # TODO: MovementOps.FLIP / MovementOps.STRIDED? + + # some permutes are actually just reshapes + if op == MovementOps.PERMUTE and local_st.contiguous: + return self.movement_op(MovementOps.RESHAPE, tuple(self.shape[i] for i in arg)) + + # some strideds are actually just reshapes + # NOTE: due to how strided works, we have to check the parent to be contiguous also + if op == MovementOps.STRIDED and local_st.contiguous and self.st.contiguous: + return self.movement_op(MovementOps.RESHAPE, tuple(i for i,_ in arg)) + + # if this MovementOp is being applied to a BinaryOp, apply the MovementOp to all the BinaryOp inputs instead + if SHUFFLE_MOVEMENT_OPS and self.optype == BinaryOps and self.realized is None and len(self.children) == 0 and (SHUFFLE_PAD_OPS or op != MovementOps.PAD) and op not in [MovementOps.EXPAND, MovementOps.STRIDED]: + def replace_with_movement_op(y:Union[LazyOp, LazyBuffer]) -> LazyBuffer: + if isinstance(y, LazyBuffer): + return y.movement_op(op, arg) + assert y.op in BinaryOps or y.op in UnaryOps + return elementwise_op(y.op, *[replace_with_movement_op(z) for z in y.src]) # type: ignore + return replace_with_movement_op(self.op) + + # create the buffer + ret = LazyBuffer(self.device, ShapeTracker(self.st).movement_op(op, arg), MovementOps, LazyOp(op, (self,), arg)) + + # if the ShapeTracker becomes contiguous, replace the whole thing with a reshape (or nothing if shapes match) + # NOTE: if ret is in the cache, it can already be realized + if REMOVE_MOVEMENT_NOPS and ret.realized is None and self.realized is None and ret.st.contiguous: + # MovementOps aren't stacked any more, they each have one parent, find the root + root = get_movementroot(self) + if root.st.contiguous and root != self and prod(ret.st.shape) == prod(root.shape): + return root.movement_op(MovementOps.RESHAPE, ret.st.shape) if ret.st.shape != root.shape else root + + return ret + + def processing_op(self:LazyBuffer, op:ProcessingOps, w:LazyBuffer, C:ConvArgs) -> LazyBuffer: + x = self + + if IMAGE >= 1: + from accel.opencl.preprocessing import preprocessing_op, postprocessing_op # type: ignore + Cold = C + x,w,C = preprocessing_op(x, w, Cold, False) + + # set up the conv + # (C.bs*C.iy, C.ix*C.groups*C.cin//4, 4) + x = x.movement_op(MovementOps.RESHAPE, (C.bs, C.iy, C.ix, C.groups, C.cin)) + # padding (implicit is fine in image) + x = x.slice(((0, x.shape[0]), (-C.py, x.shape[1]+C.py_), (-C.px, x.shape[2]+C.px_), (0, x.shape[3]), (0, x.shape[4]))) + + x = x.movement_op(MovementOps.STRIDED, ( + (C.bs, x.shape[1]*x.shape[2]*C.groups*C.cin), + (C.oy, C.sy*x.shape[2]*C.groups*C.cin), (C.ox, C.sx*C.groups*C.cin), + (C.groups, C.cin), (1, 1), (1, 1), + (C.H, C.dy*x.shape[2]*C.groups*C.cin), (C.W, C.dx*C.groups*C.cin), (C.cin//4 if C.cin >= 4 else 1, 4), (4 if C.cin >= 4 else 1, 1) + )) + x = x.movement_op(MovementOps.EXPAND, (C.bs, C.oy, C.ox, C.groups, C.rcout//4 if C.rcout >= 4 else 1, 4 if C.rcout >= 4 else 1, C.H, C.W, C.cin//4 if C.cin >= 4 else 1, 4 if C.cin >= 4 else 1)) + x = x.movement_op(MovementOps.RESHAPE, (C.bs, C.oy, C.ox, C.cout//4, 4, C.H, C.W, C.cin//4 if C.cin >= 4 else 1, 4 if C.cin >= 4 else 1)) + + # set up the weights + if C.cin == 1: + # depthwise + w = w.movement_op(MovementOps.RESHAPE, (C.cout//4, C.H, C.W, 4)) + w = w.movement_op(MovementOps.PERMUTE, (0,3,1,2)) + w = w.movement_op(MovementOps.RESHAPE, (1, 1, 1, C.cout//4, 4, C.H, C.W, 1, 1)) \ + .movement_op(MovementOps.EXPAND, (C.bs, C.oy, C.ox, C.cout//4, 4, C.H, C.W, 1, 1)) + else: + w = w.movement_op(MovementOps.RESHAPE, (C.cout//4, C.H, C.cin//4, C.W, 4, 4)) + w = w.movement_op(MovementOps.PERMUTE, (0,4,1,3,2,5)) + w = w.movement_op(MovementOps.RESHAPE, (1, 1, 1, C.cout//4, 4, C.H, C.W, C.cin//4, 4)) \ + .movement_op(MovementOps.EXPAND, (C.bs, C.oy, C.ox, C.cout//4, 4, C.H, C.W, C.cin//4, 4)) + + # now do the conv in this space + ret = x.binary_op(BinaryOps.MUL, w).reduce_op(ReduceOps.SUM, (C.bs, C.oy, C.ox, C.cout//4, 4, 1, 1, 1, 1)) + ret = ret.movement_op(MovementOps.RESHAPE, (C.bs*C.oy, C.ox*C.cout//4, 4)).contiguous() #True) + return postprocessing_op(ret, C, Cold) + + # TODO: fixup C? + if NOCONV or not getattr(x.dbuffer, "SUPPORTS_PADDING", False): + x = x.slice(((0, x.shape[0]), (0, x.shape[1]), (-C.py, x.shape[2]+C.py_), (-C.px, x.shape[3]+C.px_))) + + if NOCONV or not getattr(x.dbuffer, "processing_op", False): + # universal conv, just mul and reduce + # TODO: is there any way to replace strided with other movement ops? answer: not really + if C.sy == 1 and C.sx == 1 and C.H == 1 and C.W == 1 and False: + # TODO: this doesn't belong here, ShapeTracker or lazy should be able to infer this from STRIDED + # TODO: this is disabled. it breaks fusion of ops without pushing PERMUTES. this is also a depthwise conv + x = x.movement_op(MovementOps.RESHAPE, (C.bs, C.groups, C.cin, C.oy, C.ox, 1, C.H, C.W)) + x = x.movement_op(MovementOps.PERMUTE, (0,1,5,3,4,2,6,7)) + else: + x = x.movement_op(MovementOps.STRIDED, ( + (C.bs, C.groups*C.cin*x.shape[2]*x.shape[3]), (C.groups, C.cin*x.shape[2]*x.shape[3]), + (1, 1), (C.oy, C.sy*x.shape[3]), (C.ox, C.sx), + (C.cin, x.shape[2]*x.shape[3]), (C.H, C.dy*x.shape[3]), (C.W, C.dx))) + #if C.H <= 3 and C.W <= 3: # max 9x the RAM overhead, this is im2col + # x = x.contiguous() + x = x.movement_op(MovementOps.EXPAND, (C.bs, C.groups, C.rcout, C.oy, C.ox, C.cin, C.H, C.W)) + w = w.movement_op(MovementOps.RESHAPE, (1, C.groups, C.rcout, 1, 1, C.cin, C.H, C.W)) \ + .movement_op(MovementOps.EXPAND, (C.bs, C.groups, C.rcout, C.oy, C.ox, C.cin, C.H, C.W)) + return x.binary_op(BinaryOps.MUL, w).reduce_op(ReduceOps.SUM, (C.bs, C.groups, C.rcout, C.oy, C.ox, 1, 1, 1)) \ + .movement_op(MovementOps.RESHAPE, (C.bs, C.cout, C.oy, C.ox)) + elif x.device == "OPENCL": + # TODO: these can be properties on the device buffer + from accel.opencl.preprocessing import preprocessing_op, postprocessing_op # type: ignore + x,w,Cn = preprocessing_op(x, w, C) + ret = LazyBuffer(x.device, Cn.out_shape, ProcessingOps, LazyOp(op, (x, w), Cn)) + return postprocessing_op(ret, Cn, C) + else: + return LazyBuffer(x.device, C.out_shape, ProcessingOps, LazyOp(op, (x, w), C)) + +def elementwise_op(op:Union[UnaryOps, BinaryOps], *srcs:LazyBuffer) -> LazyBuffer: + out_device, out_shape = srcs[0].device, srcs[0].shape + + if MERGE_ELEMENTWISE_OPS or (MERGE_UNARY_OPS and len(set(srcs)) == 1): + # remove the buffers from any (childless) BinaryOps that feed into this + srcs = tuple(x.op if x.optype == BinaryOps and len(x.children) == 0 and x.realized is None else x for x in srcs) # type: ignore + + return LazyBuffer(out_device, out_shape, BinaryOps, LazyOp(op, srcs)) diff --git a/tinygrad_repo/tinygrad/llops/ops_gpu.py b/tinygrad_repo/tinygrad/llops/ops_gpu.py index 4d9f7cb47..bb75ad16b 100644 --- a/tinygrad_repo/tinygrad/llops/ops_gpu.py +++ b/tinygrad_repo/tinygrad/llops/ops_gpu.py @@ -4,11 +4,21 @@ import numpy as np import pyopencl as cl # type: ignore from collections import defaultdict from typing import List, Tuple, Optional, Dict, Union, Set -from tinygrad.helpers import prod, ConvArgs -from tinygrad.ops import DEBUG, UnaryOps, BinaryOps, ReduceOps, MovementOps -from tinygrad.shapetracker import ShapeTracker +from tinygrad.helpers import prod +from tinygrad.ops import DEBUG, UnaryOps, BinaryOps, ReduceOps, MovementOps, LazyOp, Op, ExplicitExecAST, GlobalCounters +from tinygrad.ast import ASTKernel, Token, Types +from tinygrad.lazy import IMAGE +from tinygrad.shape import ShapeTracker, View, ZeroView +from tinygrad.shape.symbolic import Variable, ModNode + +VALIDHACKS = int(os.getenv("VALIDHACKS", "0")) # TODO: remove the need for this +NATIVE_EXPLOG = int(os.getenv("NATIVE_EXPLOG", "0")) # this is needed as a switch for the tests to pass CLCACHE = int(os.getenv("CLCACHE", "1")) +FLOAT16 = int(os.getenv("FLOAT16", "0")) +PRINT_AST = int(os.getenv("PRINT_AST", "0")) +TEST_AST = int(os.getenv("TEST_AST", "0")) + class CLBuffer: def __init__(self, size): if len(CL.BUFFER_CACHE[size]) > 0: @@ -24,155 +34,399 @@ class CLBuffer: else: CL.mem_used -= self.cl.size +class CLImage: + fmt = cl.ImageFormat(cl.channel_order.RGBA, cl.channel_type.HALF_FLOAT if FLOAT16 else cl.channel_type.FLOAT) + + def __init__(self, shape): + self.cl = cl.Image(CL().cl_ctx, cl.mem_flags.READ_WRITE, CLImage.fmt, shape=(shape[1], shape[0])) + CL.mem_used += self.cl.row_pitch * self.cl.height + + def __del__(self): + CL.mem_used -= self.cl.row_pitch * self.cl.height + class CL: CACHE, kernel_count, mem_used, time_sum, ops_sum = None, -1, 0, 0.0, 0.0 BUFFER_CACHE : Dict[int, List[cl.Buffer]] = defaultdict(list) cl_ctx : Optional[cl.Context] = None cl_queue : Optional[cl.CommandQueue] = None def __init__(self): - if CL.cl_queue is not None: # already initted - return + if CL.cl_queue is not None: return # already initted devices = sum([x.get_devices(device_type=cl.device_type.GPU) for x in cl.get_platforms()], []) if len(devices) == 0: # settle for CPU devices = sum([x.get_devices(device_type=cl.device_type.CPU) for x in cl.get_platforms()], []) CL.cl_ctx = cl.Context(devices=[devices[int(os.getenv("CL_DEVICE", "0"))]]) - if len(devices) > 1 or DEBUG >= 1: - print(f"using {CL.cl_ctx.devices}") + if len(devices) > 1 or DEBUG >= 1: print(f"using {CL.cl_ctx.devices}") CL.cl_queue = cl.CommandQueue(self.cl_ctx, properties=cl.command_queue_properties.PROFILING_ENABLE) # this is an in-order command queue @staticmethod def enqueue_copy(a, b, is_blocking=False): - if CL.CACHE is not None: - assert False, "can't copy while caching" - if DEBUG >= 1: - print(f"**CL** copy in {b.shape}" if isinstance(b, np.ndarray) else f"**CL** copy OUT {a.shape}") + if CL.CACHE is not None: assert False, f"can't copy {a} -> {b} while caching" + if DEBUG >= 1: print(f"**CL** copy in {b.shape}" if isinstance(b, np.ndarray) else f"**CL** copy OUT {a.shape}") cl.enqueue_copy(CL().cl_queue, a, b, is_blocking=is_blocking) @functools.lru_cache(maxsize=None) class CLProgram: - kernel_cnt = 0 - def __init__(self, name:str, prg:str, options:Tuple[str, ...]=tuple(), argdtypes=None, rename=True, binary=False): - self.name, self.prg, self.options, self.argdtypes = f"{name}_{CLProgram.kernel_cnt}" if rename else name, prg.replace(f"{name}(", f"{name}_{CLProgram.kernel_cnt}(") if rename else prg, options, argdtypes + kernel_cnt : Dict[str, int] = defaultdict(int) + def __init__(self, name:str, prg:str, options:Tuple[str, ...]=tuple(), argdtypes=None, rename=True, binary=False, op_estimate=0): + self.name = f"{name}{('_N'+str(CLProgram.kernel_cnt[name])) if CLProgram.kernel_cnt[name] else ''}" if rename else name + self.prg, self.options, self.argdtypes, self.op_estimate = prg.replace(f"{name}(", f"{self.name}(") if rename else prg, options, argdtypes, op_estimate self.clprogram = cl.Program(CL().cl_ctx, CL().cl_ctx.devices, [self.prg]) if binary else cl.Program(CL().cl_ctx, self.prg) # type: ignore - self.clprg = self.clprogram.build(options=list(self.options)).__getattr__(self.name) + try: + self.clprg = self.clprogram.build(options=list(self.options)).__getattr__(self.name) + except cl.RuntimeError as e: + print("FAILED TO BUILD", self.prg) + raise e if self.argdtypes is not None: self.clprg.set_scalar_arg_dtypes(self.argdtypes) - CLProgram.kernel_cnt += 1 - def __call__(self, *args, op_estimate=0): + CLProgram.kernel_cnt[name] += 1 + def __call__(self, *args): CL.kernel_count += 1 - if CL.CACHE is not None: - CL.CACHE.append((self, args)) - else: - e = self.clprg(CL().cl_queue, *args) - if DEBUG >= 4: - print(self.prg) - if DEBUG >= 2: - CL.cl_queue.finish() + if CL.CACHE is not None: CL.CACHE.append((self, args)) + else: e = self.clprg(CL().cl_queue, *args) + if DEBUG >= 4: print(self.prg) + if DEBUG >= 2: CL.cl_queue.finish() if DEBUG >= 1: CL.time_sum += 0 if DEBUG <= 1 or CL.CACHE is not None else (e.profile.end - e.profile.start) - CL.ops_sum += op_estimate - print(f"**CL** {CL.kernel_count:6d} {self.name:20s} args {len(args[2:]):5d} kernels {str(args[0]):18s} {str(args[1]):12s} OPs {op_estimate/1e6:7.1f}M/{CL.ops_sum/1e9:7.2f}G mem {CL.mem_used/1e9:5.2f} GB " + - ("" if DEBUG <= 1 or CL.CACHE is not None else f"tm {(e.profile.end - e.profile.start)/1e3:9.2f}us/{CL.time_sum/1e6:9.2f}ms ({op_estimate/(e.profile.end - e.profile.start):8.2f} GFLOPS)")) + CL.ops_sum += self.op_estimate + print(f"**CL** {CL.kernel_count:6d} {self.name:28s} args {len(args[2:]):5d} kernels {str(args[0]):18s} {str(args[1]):12s} OPs {self.op_estimate/1e6:7.1f}M/{CL.ops_sum/1e9:7.2f}G mem {CL.mem_used/1e9:5.2f} GB " + + (str() if DEBUG <= 1 or CL.CACHE is not None else f"tm {(e.profile.end - e.profile.start)/1e3:9.2f}us/{CL.time_sum/1e6:9.2f}ms ({self.op_estimate/(e.profile.end - e.profile.start):8.2f} GFLOPS)")) + GlobalCounters.global_ops += self.op_estimate + GlobalCounters.global_mem += sum([x.size//4 for x in args[2:] if isinstance(x, cl.Buffer)]) # **** end CL wrappers **** -class GPUBuffer: - code_for_op = { - UnaryOps.NOOP: "(A)", UnaryOps.NEG: "(-(A))", UnaryOps.RELU: "max(A, (float)0.)", - UnaryOps.EXP: "exp(A)", UnaryOps.LOG: "log(A)", UnaryOps.SIGN: "sign(A)", UnaryOps.RECIPROCAL: "((float)1.0/A)", +def group_float4(x): + assert all(y.typ == Types.FLOAT for y in x) and len(x)%4 == 0 + return [Token(f"(float4)({','.join([x[i+j].tok for j in range(4)])})", Types.FLOAT4) for i in range(0, len(x), 4)] +def split_float4(x): + assert all(y.typ == Types.FLOAT4 for y in x) + return sum([[Token(acc.tok+f".s{s}", Types.FLOAT) for s in range(4)] for acc in x], []) + +class CLASTKernel(ASTKernel): + code_for_op : Dict[Op, str] = { + UnaryOps.NOOP: "(A)", UnaryOps.NEG: "(-(A))", UnaryOps.RELU: "max(A, (float)0.)", UnaryOps.SIGN: "sign(A)", + UnaryOps.EXP: "native_exp(A)" if NATIVE_EXPLOG else "exp(A)", + UnaryOps.LOG: "native_log(A)" if NATIVE_EXPLOG else "log(A)", + UnaryOps.RECIPROCAL: "native_recip(A)" if NATIVE_EXPLOG else "((float)1.0/A)", BinaryOps.ADD: "(A+B)", BinaryOps.SUB: "(A-B)", BinaryOps.MUL: "(A*B)", BinaryOps.DIV: "(A/B)", BinaryOps.POW: "pow(A,B)", BinaryOps.CMPEQ: "(A==B)", - ReduceOps.SUM: "(acc + A)", ReduceOps.MAX: "max(A, acc)" + ReduceOps.SUM: "A+=B", ReduceOps.MAX: "A=max(A,B)" } start_for_op = {ReduceOps.SUM: "0.0", ReduceOps.MAX: "-INFINITY"} - def __init__(self, shape:Union[ShapeTracker, Tuple[int, ...]], hostbuf:Optional[GPUBuffer]=None, backing:Optional[np.ndarray]=None): - self.st = shape if isinstance(shape, ShapeTracker) else ShapeTracker(tuple(shape)) - self.shape = self.st.shape + # TODO: move to shapetracker + def compute_buf_index_symbolic(self, st, buf_index, offset=0): + view = View(self.shapes[buf_index], self.strides[buf_index], self.offsets[buf_index] + offset) + idx = view.expr_idxs([f"idx{i}" for i in range(self.shape_len)]) + valid = Variable.num(1) + for v in st.views[0:-1][::-1]: + if isinstance(v, ZeroView): valid = v.expr_node(valid, idx) + else: idx = v.expr_node(idx) + return idx, valid + + def store(self, buf_index, value:List[Token]): + if len(value) == self.buftokens[buf_index].size()*4: value = group_float4(value) + if len(value)*4 == self.buftokens[buf_index].size(): value = split_float4(value) + assert len(value) == self.buftokens[buf_index].size(), f"size mismatch {len(value)} != {self.buftokens[buf_index].size()}" + for v, o in zip(value, self.buftokens[buf_index].offsets()): + idxy, valid = self.compute_buf_index_symbolic(self.bufs[buf_index].st, buf_index, o) + assert str(valid) == "1" + if isinstance(self.bufs[buf_index]._buf, CLImage): + assert self.buftokens[buf_index].typ == Types.FLOAT4, "image must be FLOAT4" + idx = (idxy//4)%self.bufs[buf_index]._base_shape[1] + idy = (idxy//(4*self.bufs[buf_index]._base_shape[1]))%self.bufs[buf_index]._base_shape[0] + self.kernel.append(f"write_imagef(data{buf_index}, (int2)({idx.cl}, {idy.cl}), {v.tok}); /* {self.bufs[buf_index]._base_shape} */\n") + else: + assert self.buftokens[buf_index].typ == v.typ, f"buf must be {v.typ}" + self.kernel.append(f"data{buf_index}[{(idxy//(4 if v.typ == Types.FLOAT4 else 1)).cl}] = {v.tok};\n") + + def load(self, buf_index:int) -> List[Token]: + tokens = [] + + # constant folding + if self.bufs[buf_index]._base_shape == (1,) and self.bufs[buf_index]._backing is not None: + assert self.buftokens[buf_index].typ == Types.FLOAT + self.bufs_to_delete.add(buf_index) + const = Token(f"({self.bufs[buf_index]._backing[0]}f)", self.buftokens[buf_index].typ) + if self.bufs[buf_index].st.needs_valid(): + for o in self.buftokens[buf_index].offsets(): + _, valid = self.compute_buf_index_symbolic(self.bufs[buf_index].st, buf_index, o) + tokens.append(Token(f"({valid.cl} ? {const.tok} : 0.0f)", const.typ) if str(valid) != "1" else const) + return tokens + else: + return [const]*self.buftokens[buf_index].size() + + # not constant folded + for o in self.buftokens[buf_index].offsets(): + if (buf_index, o) not in self.loaded_keys: + idxy, valid = self.compute_buf_index_symbolic(self.bufs[buf_index].st, buf_index, o) + if isinstance(self.bufs[buf_index]._buf, CLImage): + assert self.buftokens[buf_index].typ == Types.FLOAT4, f"image must be FLOAT4 {self.buftokens[buf_index]} {self.bufs[buf_index].st}" + idx = (idxy//4)%self.bufs[buf_index]._base_shape[1] + idy = (idxy//(4*self.bufs[buf_index]._base_shape[1]))%self.bufs[buf_index]._base_shape[0] + + if VALIDHACKS: + if isinstance(idx, ModNode) and idx.max < idx.b*2: idx = idx.a + if isinstance(idy, ModNode) and idy.max < idy.b*2: idy = idy.a + valid = None + + ldrt = f"read_imagef({self.buftokens[buf_index].tok}, smp, (int2)({idx.cl}, {idy.cl})) /* {self.bufs[buf_index]._base_shape} */" + ldr = Token(f"({valid.cl} ? \\ \n {ldrt} : (float4)(0.0, 0.0, 0.0, 0.0))" if str(valid) != "1" and valid is not None else ldrt, Types.FLOAT4) + else: + ldr = Token(f"{self.buftokens[buf_index].tok}[{(idxy//(4 if self.buftokens[buf_index].typ == Types.FLOAT4 else 1)).cl}]", self.buftokens[buf_index].typ) + ldr = Token(f"({valid.cl} ? {ldr.tok} : 0.0f)", ldr.typ) if str(valid) != "1" else ldr + self.kernel.append(f"{ldr.decltype()} val{buf_index}_{o} = {ldr.tok};\n") + self.loaded_keys[(buf_index,o)] = Token(f"val{buf_index}_{o}", ldr.typ) + tokens.append(self.loaded_keys[(buf_index,o)]) + return tokens + + def ast_parse(self, x:Union[GPUBuffer, LazyOp], acc:List[Token], do_reduce=False) -> List[Token]: + if not isinstance(x, LazyOp): return self.load(self.bufs.index(x)) + if isinstance(x.op, ReduceOps) and not do_reduce: return acc + values = ([acc] if isinstance(x.op, ReduceOps) else []) + [self.ast_parse(v, acc, do_reduce) for v in x.src] + code = CLASTKernel.code_for_op[x.op] # TODO: replace this with a function + if len(values) == 2: + # TODO: sometimes this is split, sometimes it's multiply + if isinstance(x.op, ReduceOps) and values[0][0].typ == Types.FLOAT4 and len(values[0])*4 == len(values[1]): values[0] = split_float4(values[0]) + if values[0][0].typ != values[1][0].typ: + if isinstance(x.op, ReduceOps): + if x.op == ReduceOps.SUM: self.prekernel.add("float clreduce(float4 x) { return x.x + x.y + x.z + x.w; }\n") + elif x.op == ReduceOps.MAX: self.prekernel.add("float clreduce(float4 x) { return max(max(x.x, x.y), max(x.z, x.w)); }\n") + values[1] = [Token(f"clreduce({x.tok})", Types.FLOAT) for x in values[1]] + elif values[0][0].typ == Types.FLOAT: values[0] = group_float4(values[0]) + elif values[1][0].typ == Types.FLOAT: values[1] = group_float4(values[1]) + assert len(values[0]) == len(values[1]), f"values mismatch {values}" + return [Token(code.replace("A", a.tok).replace("B", b.tok), a.typ) for a,b in zip(values[0], values[1])] + else: + return [Token(code.replace("A", a.tok), a.typ) for a in values[0]] + + def codegen(self): + # TODO: fetch from quick cache before processing + self.process() + if DEBUG >= 3: + print("old:", self.shapes) + print("old:", self.strides) + + self.prekernel = set() + + # if there's images in the earlybufs, we have to make an axis the 4 loading one + # shove the axis to the end and remove + if any(isinstance(buf._buf, CLImage) for buf in self.earlybufs): + eb_valids = [True] * len(self.shapes[0]) + for i in range(len(self.bufs)): + if isinstance(self.bufs[i]._buf, CLImage) and self.bufs[i] in self.earlybufs: + valids = [self.shapes[i][j]%4 == 0 and self.strides[i][j] == 1 for j in range(len(self.shapes[i]))] + eb_valids = [x and y for x,y in zip(eb_valids, valids)] + assert any(eb_valids), f"invalid op with images {eb_valids}" + eb_valid = eb_valids.index(True) + if DEBUG >= 3: print(f"early merging axis {eb_valid} from {eb_valids}") + + # no change, we added a dimension + self.reshape_and_permute( + lambda x: list(x[0:eb_valid]) + ([x[eb_valid]//4, 4] if x[eb_valid] > 1 else [1,1]) + list(x[eb_valid+1:]), + [i for i in range(self.shape_len+1) if i != eb_valid+1] + [eb_valid+1]) + + # drop the last dimension + self.upcast() + + # simplify (sets first_reduce) + self.simplify_ones() + + # are we grouping? + self.group_for_reduce = [] + if self.buftokens[0].typ != Types.FLOAT4 and self.first_reduce <= 2 and self.first_reduce + 1 <= self.shape_len and prod(self.shapes[0][:self.first_reduce]) <= 2048: + for sz in ([256, 16] if prod(self.shapes[0][:self.first_reduce]) <= 32 else [16]): + if all([x[self.first_reduce] % sz == 0 or x[self.first_reduce] == 1 for x in self.shapes]): + self.group_for_reduce.append(sz) + break + + # if there's images in the latebufs, we have to make an axis the 4 storing one. this affects the kernel shape + self.upcast_in_mid_reduce = False + if any(isinstance(buf._buf, CLImage) for buf in self.bufs if buf not in self.earlybufs) and self.buftokens[0].typ != Types.FLOAT4: + lb_valids = [True] * len(self.shapes[0]) + for i in range(len(self.bufs)): + valids = [self.shapes[i][j]%4 == 0 and (self.strides[i][j] == 1 or not isinstance(self.bufs[i]._buf, CLImage) or self.bufs[i] in self.earlybufs) for j in range(len(self.shapes[i]))] + lb_valids = [x and y for x,y in zip(lb_valids, valids)] + assert any(lb_valids), f"invalid op with images {lb_valids}" + lb_valid = lb_valids.index(True) + assert lb_valid < self.first_reduce, f"can't be in the reduce {lb_valid}" + if DEBUG >= 3: print(f"late merging axis {lb_valid} from {lb_valids}") + + # no change, we added a dimension + self.reshape_and_permute( + lambda x: list(x[0:lb_valid]) + [x[lb_valid]//4, 4] + list(x[lb_valid+1:]), + [i for i in range(self.shape_len+1) if i != lb_valid+1] + [lb_valid+1]) + + if self.group_for_reduce and self.first_reduce <= 2: + self.upcast_in_mid_reduce = True + self.group_for_reduce.append(4) + else: + # drop the last dimension + self.upcast() + + # simplify (sets first_reduce) + self.simplify_ones() + + # split to 4 float4s + if self.buftokens[0].typ == Types.FLOAT4 and any(isinstance(buf._buf, CLImage) for buf in self.earlybufs) and prod(self.shapes[0][:self.first_reduce]) >= 2048 and not self.group_for_reduce: + xb_choices = [] + for i in range(self.first_reduce): + if all(x[i]%4 == 0 for x in self.shapes): + xb_choices.append((sum(x[i]>0 for x in self.strides), sum(x[i] for x in self.strides), i)) + + if len(xb_choices): + xb_choice = sorted(xb_choices)[0][2] + if DEBUG >= 3: print(f"float4 merging axis {xb_choice} : {xb_choices}") + + # this leaves the last axis in place + self.reshape_and_permute( + lambda x: list(x[0:xb_choice]) + [x[xb_choice]//4, 4] + list(x[xb_choice+1:]), + [i for i in range(self.shape_len+1) if i != xb_choice+1] + [xb_choice+1]) + + # drop the last dimension + self.upcast() + + # re-simplify + self.simplify_ones() + + # use more opencl indexing + if self.first_reduce == 2 and isinstance(self.bufs[0]._buf, CLImage): + base_shape = self.bufs[0]._base_shape + if all([(base_shape[0]*base_shape[1])%x[0] == 0 and x[0]//base_shape[0] != 0 for x in self.shapes]): + if DEBUG >= 3: print("split opencl", base_shape, self.shapes[0]) + self.reshape_and_permute(lambda x: [base_shape[0], x[0]//base_shape[0]]+list(x[1:]), None) + self.simplify_ones() + + # group for reduce + self.output_shape = self.shapes[0][:self.first_reduce] + if len(self.group_for_reduce): + # with permute for memory coalesing + if len(self.group_for_reduce) == 2: + permute_axis = list(range(0, self.first_reduce)) + [self.first_reduce+1, self.shape_len, self.first_reduce] + list(range(self.first_reduce+2, self.shape_len)) + else: + permute_axis = list(range(0, self.first_reduce)) + [self.first_reduce+1, self.first_reduce] + list(range(self.first_reduce+2, self.shape_len+1)) + self.reshape_and_permute(lambda x: list(x[0:self.first_reduce]) + [max(1, x[self.first_reduce]//self.group_for_reduce[0]), min(x[self.first_reduce], self.group_for_reduce[0])] + list(x[self.first_reduce+1:]), permute_axis) + + self.first_reduce += len(self.group_for_reduce) + self.output_shape += self.group_for_reduce + + if DEBUG >= 3: + print(f"first_reduce: {self.first_reduce} shape_len: {self.shape_len}") + print("output shape", self.output_shape) + for i in range(len(self.bufs)): + print(self.buftokens[i], self.bufs[i] in self.earlybufs, self.shapes[i], self.strides[i]) + + self.bufs_to_delete : Set[int] = set() + self.loaded_keys : Dict[Tuple[int,int], Token] = {} + + self.kernel : List[str] = ["const sampler_t smp = CLK_NORMALIZED_COORDS_FALSE | CLK_ADDRESS_CLAMP | CLK_FILTER_NEAREST;\n"] + self.kernel += [f"int idx{i} = get_global_id({min(3, len(self.output_shape))-1-i}); /* {self.output_shape[i]} */\n" for i in range(min(3, len(self.output_shape)))] + if len(self.output_shape) > 3: + # compact all the dimensions into the final one + for i in range(len(self.output_shape)-1, 2, -1): + self.kernel += [f"int idx{i} = idx2 % {self.output_shape[i]};", f"idx2 = idx2 / {self.output_shape[i]};\n"] + self.output_shape = list(self.output_shape[0:2]) + [prod(self.output_shape[2:])] + + # early ast + accumulators : List[Token] = [Token("acc%d" % i, self.buftokens[0].typ) for i in range(self.buftokens[0].size())] + if self.reduceop: + full_shape = [x for x in self.shapes if x != self.shapes[0]] + full_shape = self.shapes[0] if len(full_shape) == 0 else full_shape[0] + + self.kernel += [f"{accumulator.decltype()} {accumulator.tok} = {CLASTKernel.start_for_op[self.reduceop.op]};\n" for accumulator in accumulators] + self.kernel += [f"for (int idx{i} = 0; idx{i} < {full_shape[i]}; idx{i}++) {{\n" for i in range(self.first_reduce, self.shape_len)] + self.kernel += [f"{x.tok};\n" for x in self.ast_parse(self.reduceop, accumulators, do_reduce=True)] + ["}\n"] * (self.shape_len - self.first_reduce) + + # middle + if self.group_for_reduce: + self.kernel.append(f"__local {accumulators[0].decltype()} temp[{prod(self.group_for_reduce)}]; // second stage\n") + + if self.upcast_in_mid_reduce: + # it should be the last dimension + self.kernel.append(f"int mid_idx = idx{self.first_reduce-2}*{self.group_for_reduce[1]} + idx{self.first_reduce-1}; temp[mid_idx] = {accumulators[0].tok}; barrier(CLK_LOCAL_MEM_FENCE);\n") + self.reshape_and_permute(None, [i for i in range(self.shape_len) if i != self.first_reduce-1] + [self.first_reduce-1]) + self.upcast() + else: + self.kernel.append(f"int mid_idx = idx{self.first_reduce-1}; temp[mid_idx] = {accumulators[0].tok}; barrier(CLK_LOCAL_MEM_FENCE);\n") + + self.kernel.append("if (mid_idx == 0) {\n") + accumulators = [Token("output", self.buftokens[0].typ)] + self.kernel.append(f"{accumulators[0].decltype()} {accumulators[0].tok} = 0.0;\n") + if self.upcast_in_mid_reduce: + self.kernel.append(f"for (int mid = 0; mid < {prod(self.group_for_reduce)//4}; mid++) {{ {CLASTKernel.code_for_op[self.reduceop.op].replace('A', accumulators[0].tok).replace('B', 'vload4(0, &temp[mid*4])')}; }}\n") + else: + self.kernel.append(f"for (int mid = 0; mid < {prod(self.group_for_reduce)}; mid++) {{ {CLASTKernel.code_for_op[self.reduceop.op].replace('A', accumulators[0].tok).replace('B', 'temp[mid]')}; }}\n") + + # late ast + self.store(0, self.ast_parse(self.ast, accumulators)) + if self.group_for_reduce: self.kernel.append("}") + self.kernel.append("}") + + # kernel function definition + function_name = ("re_S" if self.reduceop else "ew_S") + '_'.join([str(x) for x in self.bufs[0].shape if x != 1]) + buftypes = [f"{'read_only' if i > 0 else 'write_only'} image2d_t" if isinstance(x._buf, CLImage) else ("__global "+self.buftokens[i].decltype()) for i,x in enumerate(self.bufs)] + self.kernel = list(self.prekernel) + [f"__kernel void {function_name}(",] + \ + [', '.join([f'{t} data{i}' for i,t in enumerate(buftypes) if i not in self.bufs_to_delete])] + \ + [") {\n"] + self.kernel + + # compile kernel + self.fxn = CLProgram(function_name, ' '.join(self.kernel), op_estimate=self.info.flops) + + if DEBUG >= 3 and len(self.bufs_to_delete): print(f"deleting buffers {self.bufs_to_delete}") + def runner(*bufs): + clbufs = [x.cl for i,x in enumerate(bufs) if i not in self.bufs_to_delete] + return self.fxn(self.output_shape[::-1] if len(self.output_shape) > 0 else [1], (self.group_for_reduce[::-1] + [1]*(len(self.output_shape)-len(self.group_for_reduce))) if self.group_for_reduce else None, *clbufs) + return runner + + def print(self): + super().print() + for i in range(len(self.bufs)): + print(self.buftokens[i], self.bufs[i] in self.earlybufs, self.shapes[i], self.strides[i]) + print(self.fxn.prg) + +class GPUBuffer(ExplicitExecAST): + def __init__(self, shape:Union[ShapeTracker, Tuple[int, ...]], hostbuf:Optional[GPUBuffer]=None, backing:Optional[np.ndarray]=None, force_create=False): + super().__init__(shape, hostbuf) self._buf : Optional[CLBuffer] = hostbuf._buf if hostbuf is not None else None self._base_shape : Tuple[int, ...] = hostbuf._base_shape if hostbuf is not None else self.shape self._backing : Optional[np.ndarray] = hostbuf._backing if hostbuf is not None else backing # early copy in for large buffers - if self._backing is not None and self._backing.shape != (1,): + if (self._backing is not None and self._backing.shape != (1,)) or force_create: self.cl @property def cl(self): if self._buf is None: - self._buf = CLBuffer(4*prod(self._base_shape)) + possible_split_shape = [x for x in self._base_shape if x != 1] + # TODO: this is broken, and a hack. I suspect the issue is unaligned float4 accesses, would be caught by the Image valid thing if it worked. + if IMAGE >= 3 and len(possible_split_shape) == 1 and possible_split_shape[0] % 4 == 0 and self._backing is None and possible_split_shape[0] != 6140: + self._base_shape = (1, possible_split_shape[0]//4, 4) + self._buf = CLImage(self._base_shape) if (len(self._base_shape) == 3 and self._base_shape[2] == 4 and IMAGE >= 2) else CLBuffer(4*prod(self._base_shape)) if self._backing is not None: - CL.enqueue_copy(self._buf.cl, self._backing, is_blocking=False) + CL().enqueue_copy(self._buf.cl, self._backing, is_blocking=False) self._backing = None return self._buf.cl - def __repr__(self): return f"" + def __repr__(self): return f"GPUBuffer(shape={self.st}, hostbuf=GPUBuffer(shape={self._base_shape}" + (f", backing=np.array({self._backing}, dtype=np.float32)))" if self._backing else ", force_create=True))") @staticmethod def fromCPU(x): return GPUBuffer(x.shape, backing=x.view(np.ndarray).astype(np.float32).ravel()) def toCPU(self): data = np.empty(self.shape, dtype=np.float32) - CL.enqueue_copy(data, self.contiguous_op().cl, is_blocking=True) + cl_buf = self.contiguous() + cl_buf = cl_buf if isinstance(cl_buf._buf, CLBuffer) else self.movement_op(MovementOps.RESHAPE, list(self.shape)+[1]).unary_op(UnaryOps.NOOP) + CL().enqueue_copy(data, cl_buf.cl, is_blocking=True) return data - def contiguous_view_constant_fold(x, name:str, reduce:Optional[int]=None) -> Tuple[str, Optional[str], str]: - idx_getter = f"int valid = 1; {'long' if prod(x.shape) >= 2**31 else 'int'} idx = gid; {'idx *= '+str(reduce)+'; idx += subidx;' if reduce is not None else ''} {x.st.expr().replace('//', '/')};" - constant = x._backing[0] if x._base_shape == (1,) and x._backing is not None else None - args = (["__global const float *x"] if constant is None else []) + ["int gid"] + (["int subidx"] if reduce is not None else []) - return f"inline float get_{name}({','.join(args)}) {{ {idx_getter} return valid ? {constant if constant is not None else 'x[idx]'} : 0.0;}}", \ - f"__global const float *{name}_g" if constant is None else None, \ - f"get_{name}({name+'_g, ' if constant is None else ''}gid{', subidx' if reduce is not None else ''});" - - def unary_op(x, op:UnaryOps): return type(x)(x.shape)._processing_op([("A", x)], GPUBuffer.code_for_op[op]) - def binary_op(x, op:BinaryOps, y:GPUBuffer): return type(x)(x.shape)._processing_op([("A", x), ("B", y)], GPUBuffer.code_for_op[op]) - def contiguous_op(x): return x if x.st.contiguous else x.unary_op(UnaryOps.NOOP) - def movement_op(x, op:MovementOps, arg) -> GPUBuffer: return type(x)(ShapeTracker(x.st).movement_op(op, arg), x) - def reduce_op(x, op:ReduceOps, new_shape:Tuple[int, ...]): return type(x)(new_shape)._processing_op([("A", x)], code="acc", earlycode=GPUBuffer.code_for_op[op], earlybufs=set("A"), op=op) - - def _processing_op(ret, bufs: List[Tuple[str, GPUBuffer]]=[], code:str="acc", C:Optional[ConvArgs]=None, op=ReduceOps.SUM, reduce_shape=None, earlybufs:Set[str]=set(), earlycode:str="acc") -> GPUBuffer: - assert C is None - - # get the input/output shape and the reduce amount - reduce_shape = (bufs[0][1].shape, ret.shape) if reduce_shape is None else reduce_shape - red = prod([s for s,n in zip(*reduce_shape) if n == 1]) - assert red < 2**31, f"reduce must be under 2**31, {red} isn't" - - # if it's a partial reduce, assert last non reduced axis is before the first reduced axis - if red > 1 and prod(ret.shape) != 1: - assert max([i for i,(s,n) in enumerate(zip(*reduce_shape)) if s == n and n != 1]) < min([i for i,(s,n) in enumerate(zip(*reduce_shape)) if s != 1 and n == 1]) - - kernel_name = "reduce" if red > 1 else "elementwise" - early_views = {name:buf.contiguous_view_constant_fold(name, red) for name, buf in bufs if name in earlybufs} - late_views = {name:buf.contiguous_view_constant_fold(name) for name, buf in bufs if name not in earlybufs} - views = {**early_views, **late_views} - - buf_types : List[str] = [views[name][1] for name, _ in bufs if views[name][1] is not None] # type: ignore - buf_cl = [buf.cl if 'image2d_t' not in views[name][1] else buf.image for name, buf in bufs if views[name][1] is not None] # type: ignore - - # use local memory if it's a multistage reduce - inter_red = 256 if (prod(ret.shape) < 8192 and red >= 256) else 1 - if inter_red > 1: - buf_cl.append(cl.LocalMemory(inter_red*4)) - - reduce_loop = f"int mid = get_global_id(1); for (int subidx = {red//inter_red + 1} * mid; subidx < min({red}, {red//inter_red + 1} * (mid+1)); subidx++)" if inter_red > 1 else f"for (int subidx = 0; subidx < {red}; subidx++)" - conv_prg = CLProgram(kernel_name, f"""{chr(10).join([x[0] for x in views.values()])} - __kernel void {kernel_name}({','.join(["__global float* restrict output"] + buf_types + (["__local float *temp"] if inter_red > 1 else []))}) {{ - const sampler_t smp = CLK_NORMALIZED_COORDS_FALSE | CLK_ADDRESS_CLAMP | CLK_FILTER_NEAREST; - float acc = {GPUBuffer.start_for_op[op]}; - int gid = get_global_id(0); - {reduce_loop} {{ -{chr(10).join([f' float {name} = ' + early_views[name][2] for name in early_views])} - acc = {earlycode}; - }}"""+(f""" - temp[mid] = acc; barrier(CLK_LOCAL_MEM_FENCE); - if (mid == 0) {{ acc = {GPUBuffer.start_for_op[op]}; - for (int rdx = 0; rdx < {inter_red}; rdx++) {{ - acc = {GPUBuffer.code_for_op[op].replace('A', 'temp[rdx]')}; - }}""" if inter_red != 1 else "{")+f""" -{chr(10).join([f' float {name} = ' + late_views[name][2] for name in late_views])} - output[gid] = {code}; - }} - }}""") - - conv_prg([prod(ret.shape), inter_red, 1], [1, inter_red, 1] if inter_red > 1 else None, ret.cl, *buf_cl, op_estimate=prod(reduce_shape[0])*len(earlybufs) + prod(reduce_shape[1])*len(bufs)) - return ret + @classmethod + def exec_ast(cls, ast:LazyOp): + k = CLASTKernel(ast) + k.codegen()(*k.bufs) + if PRINT_AST: + print(k.fxn.name) + k.print() + if TEST_AST: + from test.lib_test_ast import test_ast # type: ignore + test_ast(k) + return k.ret diff --git a/tinygrad_repo/tinygrad/mlops.py b/tinygrad_repo/tinygrad/mlops.py index a0007ad07..7b72123e7 100644 --- a/tinygrad_repo/tinygrad/mlops.py +++ b/tinygrad_repo/tinygrad/mlops.py @@ -2,15 +2,20 @@ from tinygrad.helpers import prod, argsort, reduce_shape, get_conv_args from tinygrad.ops import UnaryOps, BinaryOps, ReduceOps, MovementOps, ProcessingOps from tinygrad.tensor import Function +class Contiguous(Function): + def forward(self, x): return x.contiguous() + def backward(self, grad_output): return grad_output + # ************* unary ops ************* class ReLU(Function): def forward(self, x): - self.save_for_backward(x) - return x.unary_op(UnaryOps.RELU) + ret = x.unary_op(UnaryOps.RELU) + self.save_for_backward(ret) + return ret def backward(self, grad_output): - return self.saved_tensors[0].unary_op(UnaryOps.SIGN).unary_op(UnaryOps.RELU).binary_op(BinaryOps.MUL, grad_output) + return self.saved_tensors[0].unary_op(UnaryOps.SIGN).binary_op(BinaryOps.MUL, grad_output) class Log(Function): def forward(self, x): @@ -23,7 +28,7 @@ class Log(Function): class Exp(Function): def forward(self, x): ret = x.unary_op(UnaryOps.EXP) - self.save_for_backward(ret) # we save the output here, not the input + self.save_for_backward(ret) return ret def backward(self, grad_output): @@ -63,8 +68,7 @@ class Max(Function): max_is_1s = x.binary_op(BinaryOps.CMPEQ, ret.movement_op(MovementOps.EXPAND, x.shape)) # sum of locations, averaged - div = max_is_1s.reduce_op(ReduceOps.SUM, grad_output.shape) - div = div.movement_op(MovementOps.EXPAND, x.shape) + div = max_is_1s.reduce_op(ReduceOps.SUM, grad_output.shape).movement_op(MovementOps.EXPAND, x.shape) max_is_amount = max_is_1s.binary_op(BinaryOps.DIV, div) grad_output_expanded = grad_output.movement_op(MovementOps.EXPAND, x.shape) diff --git a/tinygrad_repo/tinygrad/nn/__init__.py b/tinygrad_repo/tinygrad/nn/__init__.py index 9563234e6..989634905 100644 --- a/tinygrad_repo/tinygrad/nn/__init__.py +++ b/tinygrad_repo/tinygrad/nn/__init__.py @@ -1,9 +1,5 @@ from tinygrad.tensor import Tensor -def batch_normalize(x, weight, bias, mean, invstd): - x = (x - mean.reshape(shape=[1, -1, 1, 1])) * weight.reshape(shape=[1, -1, 1, 1]) - return x.mul(invstd.reshape(shape=[1, -1, 1, 1])) + bias.reshape(shape=[1, -1, 1, 1]) - class BatchNorm2D: def __init__(self, sz, eps=1e-5, affine=True, track_running_stats=True, momentum=0.1): assert affine, "BatchNorm2D is only supported with affine" @@ -34,11 +30,11 @@ class BatchNorm2D: else: batch_mean, batch_var = self.running_mean, self.running_var # NOTE: this can be precomputed for static inference. if you manually update running_var, you have to reset this - if getattr(self, "batch_invstd", None) is None: + if not hasattr(self, "batch_invstd") or not self.batch_invstd: self.batch_invstd = batch_var.add(self.eps)**-0.5 batch_invstd = self.batch_invstd - return batch_normalize(x, self.weight, self.bias, batch_mean, batch_invstd) + return x.batchnorm(self.weight, self.bias, batch_mean, batch_invstd) # TODO: is this good weight init? class Conv2d: diff --git a/tinygrad_repo/tinygrad/ops.py b/tinygrad_repo/tinygrad/ops.py index fc0e914e0..e523fd558 100644 --- a/tinygrad_repo/tinygrad/ops.py +++ b/tinygrad_repo/tinygrad/ops.py @@ -1,13 +1,11 @@ -from __future__ import annotations +import os from enum import Enum -from typing import Optional, Tuple, NamedTuple, Union, Any, List, Dict, Type -from copy import copy -import os, sys, functools, itertools, operator, weakref -from tinygrad.helpers import ConvArgs, get_available_llops, prod -from tinygrad.shapetracker import ShapeTracker +from typing import Union, Type, NamedTuple, Tuple, Any, List +import functools, operator +from tinygrad.helpers import prod +from tinygrad.shape import ShapeTracker -# lazy can recurse a lot -sys.setrecursionlimit(10000) +DEBUG = int(os.getenv("DEBUG", "0")) # these are the llops your accelerator must implement, along with toCpu UnaryOps = Enum("UnaryOps", ["NOOP", "NEG", "RELU", "EXP", "LOG", "SIGN", "RECIPROCAL"]) @@ -20,361 +18,74 @@ LoadOps = Enum("LoadOps", ["FROMCPU", "CONTIGUOUS"]) Op = Union[UnaryOps, BinaryOps, ReduceOps, MovementOps, ProcessingOps, LoadOps] OpType = Union[Type[UnaryOps], Type[BinaryOps], Type[ReduceOps], Type[MovementOps], Type[ProcessingOps], Type[LoadOps]] -DEBUG = int(os.getenv("DEBUG", "0")) -GRAPH = int(os.getenv("GRAPH", "0")) -OPT = int(os.getenv("OPT", "1")) -NOCONV = int(os.getenv("NOCONV", "0")) - -# TODO: movement ops that only change shape are really nops. treat them as such -REMOVE_MOVEMENT_NOPS, MERGE_UNARY_OPS, MERGE_ELEMENTWISE_INTO_REDUCE, SHUFFLE_MOVEMENT_OPS = OPT>=1, OPT>=1, OPT>=1, OPT>=1 -MERGE_ELEMENTWISE_OPS, MERGE_ONE_REDUCE_INTO_ELEMENTWISE = OPT>=2, OPT>=2 -SHUFFLE_PAD_OPS = OPT>=3 # NOTE: 0/0 is NaN if you pad, so this can change the output - -# **** enumerate supported devices **** - -class Device: - _buffers, DEFAULT = get_available_llops() - for name in _buffers.keys(): - vars()[name] = name - -# TODO: get device buffer types -DeviceBuffer = Any - -# **** debugging and graphing **** - -import atexit -from collections import defaultdict -cnts : Dict[OpType, int] = defaultdict(int) -if GRAPH: - import networkx as nx # type: ignore - G = nx.DiGraph() - def save_graph_exit(): - for k,v in cnts.items(): - print(k, v) - if int(os.getenv("PRUNEGRAPH", "0")): - dead_nodes = [] - for n in G.nodes: - # prune movementops and loadops - if 'fillcolor' in G.nodes[n] and G.nodes[n]['fillcolor'] in ["#80ff8080", "#80ff80", "#FFFF8080", "#FFFF80"]: - for (x,_),(_,y) in itertools.product(G.in_edges(n), G.out_edges(n)): - G.add_edge(x, y) - dead_nodes.append(n) - for n in dead_nodes: - G.remove_node(n) - print("saving", G) - nx.drawing.nx_pydot.write_dot(G, '/tmp/net.dot') - # -Gnslimit=100 can make it finish, but you won't like results - os.system('dot -Tsvg /tmp/net.dot -o /tmp/net.svg') - atexit.register(save_graph_exit) - -global_num_max = 0 -def log_op(optype : OpType, op : List[Op], ret : DeviceBuffer, inp : List[DeviceBuffer]): - cnts[optype] += 1 - if DEBUG >= 3: - print(f"{op} : {', '.join([str(x.shape) for x in inp])} -> {ret.shape}") - if GRAPH: - def nm(x): - global global_num_max - if getattr(x, 'global_num', None) is None: - setattr(x, 'global_num', global_num_max) - global_num_max += 1 - return f"<<< {x.global_num} >>>" - - top_colors = {LoadOps: '#FFFF80', UnaryOps: "#c0c0c0", ReduceOps: "#8080ff", BinaryOps: "#c0c0c0", MovementOps: "#80ff80", ProcessingOps: "#ff8080"} - dashed = (optype == LoadOps and getattr(ret, "_backing", None) is not None) or (getattr(ret, "st", None) is not None and not ret.st.contiguous) - - for x in inp: - if len(op) <= 2: - sop = '.'.join([str(y).split(".")[1] for y in op][::-1]) - elif len(op) <= 4: - sop = '.'.join([str(y).split(".")[1][0:2] for y in op][::-1]) - else: - sop = str(len(op)) - G.add_edge(nm(x), nm(ret), label=sop) - if 'label' not in G.nodes[nm(x)]: - G.nodes[nm(x)]['label'] = str(x.shape) - if nm(ret) not in G.nodes: - G.add_node(nm(ret)) - - if optype == ReduceOps: - G.nodes[nm(ret)]['label'] = str(set(x.shape for x in inp))+"\n"+str(ret.shape) - else: - G.nodes[nm(ret)]['label'] = str(ret.shape) - G.nodes[nm(ret)]['fillcolor'] = (top_colors[optype] + ('80' if dashed else '')) if optype in top_colors else "#ffffff" - G.nodes[nm(ret)]['style'] = 'filled, dashed' if dashed else 'filled' - - -# **** realize helpers **** - -def _ast(x: Union[LazyBuffer, LazyOp], buf_names: Dict[LazyBuffer, str], code_for_op: Dict[Op, str]) -> str: - if isinstance(x, LazyBuffer): - return buf_names[x] - srcs_code = [_ast(src, buf_names, code_for_op) for src in x.src] - code = code_for_op[x.op] - if len(srcs_code) >= 1: - code = code.replace("A", srcs_code[0]) - if len(srcs_code) >= 2: - code = code.replace("B", srcs_code[1]) - return code - -# **** realize functions **** - -def _realize_loadops(self:LazyBuffer) -> Tuple[DeviceBuffer, List[DeviceBuffer], Optional[OpType]]: - if self.op.op == LoadOps.FROMCPU: - return Device._buffers[self.device].fromCPU(self.op.arg), [], LoadOps - elif self.op.op == LoadOps.CONTIGUOUS: - real_src = self.op.src[0].realize(self.device) - ret = real_src.contiguous_op() - return ret, [real_src], LoadOps if ret != real_src else None - else: - raise NotImplementedError(f"unknown LoadOp {self.op.op}") - -def _realize_movementops(self:LazyBuffer) -> Tuple[DeviceBuffer, List[DeviceBuffer], OpType]: - real_src = self.op.src[0].realize(self.device) - return real_src.movement_op(self.op.op, self.op.arg), [real_src], MovementOps - -# TODO: unify _realize_reduceops, _realize_processingops, and _realize_binaryops -def _realize_reduceops(self:LazyBuffer) -> Tuple[DeviceBuffer, List[DeviceBuffer], OpType]: - # TODO: this can also corealize a binary op after the reduce, not just before - src = self.op.src[0] - if MERGE_ELEMENTWISE_INTO_REDUCE and getattr(self.dbuffer, "start_for_op", None) and src.realized is None and src.optype == BinaryOps and len(src.children) <= 1: - # TODO: this code is (somewhat) repeated in _realize_binaryops - real_srcs : Dict[LazyBuffer, DeviceBuffer] = {x:x.realize(self.device) for x in get_lazybuffers(src.op)} - buf_names : Dict[LazyBuffer, str] = {x:f"arg_{i}" for i,x in enumerate(real_srcs.keys())} - - return self.dbuffer(self.shape)._processing_op([(buf_names[lb], db) for lb,db in real_srcs.items()], - earlycode=_ast(LazyOp(self.op.op, (src.op,), self.op.arg), buf_names, self.dbuffer.code_for_op), earlybufs=buf_names.values(), op=self.op.op), \ - list(real_srcs.values()), ReduceOps - else: - real_src = src.realize(self.device) - return real_src.reduce_op(self.op.op, self.op.arg), [real_src], ReduceOps - -def _realize_processingops(self:LazyBuffer) -> Tuple[DeviceBuffer, List[DeviceBuffer], OpType]: - real_src_x, real_src_w = [x.realize(self.device) for x in self.op.src] - return real_src_x.processing_op(self.op.op, real_src_w, self.op.arg), [real_src_x, real_src_w], ProcessingOps - -def _realize_binaryops(self:LazyBuffer) -> Tuple[DeviceBuffer, List[DeviceBuffer], OpType]: - real_srcs : Dict[LazyBuffer, DeviceBuffer] = {x:None for x in get_lazybuffers(self.op)} - if getattr(self.dbuffer, "_processing_op", None) is not None: - buf_names : Dict[LazyBuffer, str] = {x:f"arg_{i}" for i,x in enumerate(real_srcs.keys())} - reduce_shape = (list(real_srcs.keys())[0].shape, list(real_srcs.keys())[0].shape) - earlycode = "acc" - conv_args : Optional[ConvArgs] = None - - # if there's *one* processing or reduce op in here, we can corealize it. we can corealize binary op siblings as well - # NOTE: if it references the same conv multiple times, they should already be merged by the dictionary - #for k,x in zip(real_srcs.keys(), map(get_movementroot_contiguous, real_srcs.keys())): - # print(k,x, len(x.children), [x for x in x.children]) - psrcs : List[Tuple[LazyBuffer, LazyBuffer]] = [(k,x) for k,x in zip(real_srcs.keys(), map(get_movementroot_contiguous, real_srcs.keys())) if x.optype in [ProcessingOps,ReduceOps] and x.realized is None and len(x.children) <= 1 and len(k.children) <= 1] - if len(psrcs) == 1 and MERGE_ONE_REDUCE_INTO_ELEMENTWISE and (self.device != "OPENCL" or self.shape[-1] == 4): - if psrcs[0][1].optype == ProcessingOps: - # TODO: do something similar to what i did with reduceop to use the ast engine? - # it's hard because conv also has convargs - conv_args = psrcs[0][1].op.arg - real_srcs[psrcs[0][1].op.src[0]], real_srcs[psrcs[0][1].op.src[1]] = None, None - buf_names[psrcs[0][1].op.src[0]], buf_names[psrcs[0][1].op.src[1]] = "input", "weight" # NOTE: these will not be in the ast - elif psrcs[0][1].optype == ReduceOps: - src = psrcs[0][1].op.src[0] - reduce_shape = (src.shape, psrcs[0][1].shape) - - if MERGE_ELEMENTWISE_INTO_REDUCE and getattr(self.dbuffer, "start_for_op", None) and src.realized is None and src.optype == BinaryOps and len(src.children) <= 1: - src = src.op - for i,x in enumerate(get_lazybuffers(src) if isinstance(src, LazyOp) else [src]): - real_srcs[x] = None - buf_names[x] = f"earlyarg_{i}" - earlycode = _ast(LazyOp(psrcs[0][1].op.op, (src,), psrcs[0][1].op.arg), buf_names, self.dbuffer.code_for_op) - - del real_srcs[psrcs[0][0]] - buf_names[psrcs[0][0]] = "acc" - - for x in real_srcs.keys(): - real_srcs[x] = x.realize(self.device) - # fast path, no middle buffers - return self.dbuffer(self.shape)._processing_op([(buf_names[lb], db) for lb,db in real_srcs.items()], - _ast(self.op, buf_names, self.dbuffer.code_for_op), earlycode=earlycode, earlybufs=set(x for x in buf_names.values() if x.startswith("earlyarg_")), - C=conv_args, reduce_shape=reduce_shape), \ - list(real_srcs.values()), ProcessingOps if conv_args is not None else (ReduceOps if reduce_shape[0] != reduce_shape[1] else BinaryOps) - else: - for x in real_srcs.keys(): - real_srcs[x] = x.realize(self.device) - # slow path, creates middle buffers - def ast_eval(x: Union[LazyBuffer, LazyOp]) -> DeviceBuffer: - if isinstance(x, LazyBuffer): - return real_srcs[x] - if x.op in UnaryOps: - return ast_eval(x.src[0]).unary_op(x.op) - if x.op in BinaryOps: - return ast_eval(x.src[0]).binary_op(x.op, ast_eval(x.src[1])) - return ast_eval(self.op), list(real_srcs.values()), BinaryOps - -_realize = {LoadOps:_realize_loadops, ReduceOps:_realize_reduceops, MovementOps:_realize_movementops, BinaryOps:_realize_binaryops, ProcessingOps:_realize_processingops} - -# **** lazy operations **** - class LazyOp(NamedTuple): op: Op - src: Tuple[Union[LazyOp, LazyBuffer], ...] # type: ignore + # Any == Union[LazyOp, LazyBuffer, DeviceBuffer] + src: Tuple[Any, ...] # type: ignore arg: Any = None # TODO: add dest to support multiple outputs -def get_lazybuffers(op:LazyOp) -> List[LazyBuffer]: return functools.reduce(operator.add, [get_lazybuffers(x) if isinstance(x, LazyOp) else [x] for x in op.src], []) +# Any == Union[LazyBuffer, DeviceBuffer] +def get_buffers(op:LazyOp) -> List[Any]: return functools.reduce(operator.add, [get_buffers(x) if isinstance(x, LazyOp) else [x] for x in op.src], []) def get_lazyops(op:LazyOp) -> List[LazyOp]: return functools.reduce(operator.add, [get_lazyops(x) for x in op.src if isinstance(x, LazyOp)], [op]) -def get_weakop(op:LazyOp) -> LazyOp: return LazyOp(op.op, tuple(get_weakop(x) if isinstance(x, LazyOp) else weakref.ref(x) for x in op.src), op.arg) -def get_movementroot(root:LazyBuffer) -> LazyBuffer: return get_movementroot(root.op.src[0]) if root.optype == MovementOps and root.realized is None else root -def get_movementroot_contiguous(x:LazyBuffer) -> LazyBuffer: return get_movementroot(x) if x.optype == MovementOps and x.st.contiguous else x -LAZY = int(os.getenv("LAZY", "1")) - -class LazyBuffer: - lazycache : weakref.WeakValueDictionary[LazyOp, LazyBuffer] = weakref.WeakValueDictionary() - def __new__(cls, device, shape, optype, op): - # loadops aren't cached - if optype == LoadOps: - return super().__new__(cls) - wop = (device, optype, get_weakop(op)) # NOTE: shape should be deterministic. annoying to cache with the ShapeTracker - # NOTE: we need "ret" to prevent the new buffer from being immediately deleted - if wop not in LazyBuffer.lazycache: - LazyBuffer.lazycache[wop] = ret = super().__new__(cls) # noqa: F841, pylint: disable=W0612 - return LazyBuffer.lazycache[wop] - - def __init__(self, device, shape:Union[ShapeTracker, Tuple[int, ...]], optype:OpType, op:LazyOp): - if getattr(self, 'device', None) is not None: - return # cache hit, we return and don't reinit - self.st = shape if isinstance(shape, ShapeTracker) else ShapeTracker(tuple(shape)) - self.shape, self.optype, self.op = self.st.shape, optype, op - self.realized : Optional[DeviceBuffer] = None - self.device, self.dbuffer = device, Device._buffers[device] - self.children : weakref.WeakSet[LazyBuffer] = weakref.WeakSet() - # NOTE: op should be read only after construction of LazyBuffer - for x in get_lazybuffers(op): - x.children.add(self) - if not LAZY: - self.realize() - - def __repr__(self): return f"" - - # this produces a device buffer - def realize(self:LazyBuffer, required_device=None) -> DeviceBuffer: - if required_device is not None: - assert required_device == self.device - if self.realized is None: - # we haven't realized the Buffer yet - self.realized, real_srcs, real_type = _realize[self.optype](self) - # in lazy mode, we don't log until we realize - if real_type is not None: - log_op(real_type, [x.op for x in get_lazyops(self.op)], self.realized, real_srcs) - # no need to keep the op after realization - del self.op - - assert self.realized.shape == self.shape - assert isinstance(self.realized, Device._buffers[self.device]) - return self.realized - - @staticmethod - def fromCPU(x, device): return LazyBuffer(device, x.shape, LoadOps, LazyOp(LoadOps.FROMCPU, tuple(), x.copy())) - def toCPU(self): return self.realize().toCPU() - - def unary_op(self:LazyBuffer, op:UnaryOps) -> LazyBuffer: return elementwise_op(op, self) - def binary_op(self:LazyBuffer, op:BinaryOps, y:LazyBuffer) -> LazyBuffer: return elementwise_op(op, self, y) - def contiguous_op(self:LazyBuffer) -> LazyBuffer: return LazyBuffer(self.device, self.shape, LoadOps, LazyOp(LoadOps.CONTIGUOUS, (self,))) - - def reduce_op(self:LazyBuffer, op:ReduceOps, new_shape:Tuple[int, ...]) -> LazyBuffer: - if self.shape == tuple(new_shape): - return self - reduce = list(enumerate(zip(self.shape, new_shape))) - # move the reduce axes to the end - x = self.movement_op(MovementOps.PERMUTE, [i for i,(s,n) in reduce if s == n] + [i for i,(s,n) in reduce if s != n]) - new_tmp_shape = tuple([n for _,(s,n) in reduce if s == n] + [n for _,(s,n) in reduce if s != n]) - # NOTE: this reshape can only move around 1s - return LazyBuffer(x.device, new_tmp_shape, ReduceOps, LazyOp(op, (x,), new_tmp_shape)).movement_op(MovementOps.RESHAPE, new_shape) - - # syntactic sugar around PAD and SHRINK - # TODO: turn RESHAPE into EXPAND and CONTRACT (current EXPAND should be REPEAT) - def slice(self:LazyBuffer, arg): - padding = [(max(0, -p[0]), max(0, p[1]-self.shape[i])) for i,p in enumerate(arg)] - return self.movement_op(MovementOps.PAD, padding).movement_op(MovementOps.SHRINK, tuple((p[0] + padding[i][0], p[1] + padding[i][0]) for i,p in enumerate(arg))) - - def movement_op(self:LazyBuffer, op:MovementOps, arg) -> LazyBuffer: - # TODO: look into why that copy is needed - arg = tuple(copy(arg)) - - # instant nops - if op in [MovementOps.RESHAPE, MovementOps.EXPAND] and arg == self.shape: - return self - if op == MovementOps.PERMUTE and arg == tuple(range(len(self.shape))): - return self - if op == MovementOps.SHRINK and arg == tuple((0,i) for i in self.shape): - return self - if op == MovementOps.PAD and arg == tuple((0,0) for _ in self.shape): - return self - if op == MovementOps.FLIP and all(s == 1 or i not in arg for i,s in enumerate(self.shape)): - return self - - # two ops in a row is one op - if op in [MovementOps.RESHAPE, MovementOps.EXPAND, MovementOps.SHRINK] and self.realized is None and self.op.op == op: - return self.op.src[0].movement_op(op, arg) - if op == MovementOps.PERMUTE and self.realized is None and self.op.op == op: - return self.op.src[0].movement_op(op, tuple(self.op.arg[i] for i in arg)) - if op == MovementOps.PAD and self.realized is None and self.op.op == op: - return self.op.src[0].movement_op(op, tuple((b1+b2, e1+e2) for (b1,e1),(b2,e2) in zip(self.op.arg, arg))) - - # some permutes are actually just reshapes - if op == MovementOps.PERMUTE and ShapeTracker(self.shape).movement_op(op, arg).contiguous: - return self.movement_op(MovementOps.RESHAPE, tuple(self.shape[i] for i in arg)) - - if SHUFFLE_MOVEMENT_OPS and self.optype == BinaryOps and self.realized is None and len(self.children) == 0 and (SHUFFLE_PAD_OPS or op != MovementOps.PAD) and op not in [MovementOps.EXPAND, MovementOps.STRIDED]: - # if this MovementOp is being applied to a BinaryOp, apply the MovementOp to all the BinaryOp inputs instead - def replace_with_movement_op(y:Union[LazyOp, LazyBuffer]) -> LazyBuffer: - if isinstance(y, LazyBuffer): - return y.movement_op(op, arg) - assert y.op in BinaryOps or y.op in UnaryOps - return elementwise_op(y.op, *[replace_with_movement_op(z) for z in y.src]) # type: ignore - return replace_with_movement_op(self.op) - - # create the buffer - ret = LazyBuffer(self.device, ShapeTracker(self.st).movement_op(op, arg), MovementOps, LazyOp(op, (self,), arg)) - - # NOTE: if ret is in the cache, it can already be realized - if REMOVE_MOVEMENT_NOPS and ret.realized is None and self.realized is None and ret.st.contiguous: - # MovementOps aren't stacked any more, they each have one parent, find the root - root = get_movementroot(self) - if root.st.contiguous and root != self and prod(ret.st.shape) == prod(root.shape): - return root.movement_op(MovementOps.RESHAPE, ret.st.shape) if ret.st.shape != root.shape else root +# a placeholder class to extend by the exec classes +class DeviceBuffer: + shape: Any # should be Tuple[int, ...] but ndarray and torch.tensor have incompatible types +# extend this if you don't have an exec_ast function +# used in CPUBuffer and TorchBuffer +class GenericExecAST(DeviceBuffer): + @classmethod + def exec_ast(cls, ast:LazyOp, preprocess=lambda x: x): + srcs = [cls.exec_ast(x, preprocess) if isinstance(x, LazyOp) else preprocess(x) for x in ast.src] + if ast.op in UnaryOps: + ret = srcs[0].unary_op(ast.op) + elif ast.op in BinaryOps: + assert srcs[0].shape == srcs[1].shape, f"BinaryOps shape mismatch {srcs[0].shape} != {srcs[1].shape}" + ret = srcs[0].binary_op(ast.op, srcs[1]) + elif ast.op in ReduceOps: + assert all(r == n or n == 1 for r,n in zip(srcs[0].shape, ast.arg)), f"ReduceOps can't reduce {srcs[0].shape} -> {ast.arg}" + ret = srcs[0].reduce_op(ast.op, ast.arg) + elif ast.op in MovementOps: + ret = srcs[0].movement_op(ast.op, ast.arg) + elif ast.op in ProcessingOps: + ret = srcs[0].processing_op(ast.op, srcs[1], ast.arg) + else: + raise Exception("unknown op") return ret - def processing_op(self:LazyBuffer, op:ProcessingOps, w:LazyBuffer, C:ConvArgs) -> LazyBuffer: - x = self - # TODO: fixup C? - if NOCONV or not getattr(x.dbuffer, "SUPPORTS_PADDING", False): - x = x.slice(((0, x.shape[0]), (0, x.shape[1]), (-C.py, x.shape[2]+C.py_), (-C.px, x.shape[3]+C.px_))) +class GlobalCounters: + global_ops, global_mem = 0, 0 - if NOCONV or not getattr(x.dbuffer, "processing_op", False): - # universal conv, just mul and reduce - # TODO: is there any way to replace strided with other movement ops? - x = x.movement_op(MovementOps.STRIDED, ( - (C.bs, C.groups*C.cin*x.shape[2]*x.shape[3]), (C.groups, C.cin*x.shape[2]*x.shape[3]), - (C.rcout, 0), (C.oy, C.sy*x.shape[3]), (C.ox, C.sx), - (C.cin, x.shape[2]*x.shape[3]), (C.H, C.dy*x.shape[3]), (C.W, C.dx))) - w = w.movement_op(MovementOps.RESHAPE, (1, C.groups, C.rcout, 1, 1, C.cin, C.H, C.W)) \ - .movement_op(MovementOps.EXPAND, (C.bs, C.groups, C.rcout, C.oy, C.ox, C.cin, C.H, C.W)) - #print(x.st.views, w.st.views) - return x.binary_op(BinaryOps.MUL, w).reduce_op(ReduceOps.SUM, (C.bs, C.groups, C.rcout, C.oy, C.ox, 1, 1, 1)) \ - .movement_op(MovementOps.RESHAPE, (C.bs, C.cout, C.oy, C.ox)) - elif x.device == "OPENCL": - # TODO: these can be properties on the device buffer - from accel.opencl.preprocessing import preprocessing_op, postprocessing_op # type: ignore - x,w,Cn = preprocessing_op(x, w, C) - ret = LazyBuffer(x.device, Cn.out_shape, ProcessingOps, LazyOp(op, (x, w), Cn)) - return postprocessing_op(ret, Cn, C) - else: - return LazyBuffer(x.device, C.out_shape, ProcessingOps, LazyOp(op, (x, w), C)) +class GenericShape(GenericExecAST): + def __init__(self, shape, flops=0): self.shape, self.flops = shape, flops + def unary_op(self, op:UnaryOps): return GenericShape(self.shape, self.flops + prod(self.shape)) + def binary_op(self, op:BinaryOps, y): return GenericShape(self.shape, self.flops + y.flops + prod(self.shape)) + def reduce_op(self, op:ReduceOps, new_shape:Tuple[int, ...]): return GenericShape(new_shape, self.flops + prod(self.shape)) + def movement_op(self, op:MovementOps, arg): return GenericShape(ShapeTracker(self.shape).movement_op(op, arg).shape, self.flops) + # https://docs.nvidia.com/deeplearning/performance/dl-performance-convolutional/index.html + def processing_op(self, op:ProcessingOps, w, C): return GenericShape(C.out_shape, 2 * (C.bs * C.cout * C.oy * C.ox) * (C.cin * C.H * C.W)) +def get_lazyop_info(ast:LazyOp): return GenericShape.exec_ast(ast, lambda x: GenericShape(x.shape)) -def elementwise_op(op:Union[UnaryOps, BinaryOps], *srcs:LazyBuffer) -> LazyBuffer: - out_device, out_shape = srcs[0].device, srcs[0].shape +# assumes you are using ShapeTracker +# used in GPUBuffer, OpenCLBuffer, and LLVMBuffer +class ExplicitExecAST(DeviceBuffer): + def __init__(self, shape:Union[ShapeTracker, Tuple[int, ...]], hostbuf=None): + self.st = shape if isinstance(shape, ShapeTracker) else ShapeTracker(tuple(shape)) + self.shape = self.st.shape - if MERGE_ELEMENTWISE_OPS or (MERGE_UNARY_OPS and len(set(srcs)) == 1): - # remove the buffers from any (childless) BinaryOps that feed into this - srcs = tuple(x.op if x.optype == BinaryOps and len(x.children) == 0 and x.realized is None else x for x in srcs) # type: ignore + @classmethod + def exec_ast(cls, ast:LazyOp): raise NotImplementedError("must be implemented") - return LazyBuffer(out_device, out_shape, BinaryOps, LazyOp(op, srcs)) + # universal + def unary_op(self, op:UnaryOps): return type(self)(self.shape).exec_ast(LazyOp(op=op, src=(self,))) + def binary_op(self, op:BinaryOps, y): return type(self)(self.shape).exec_ast(LazyOp(op=op, src=(self, y))) + def reduce_op(self, op:ReduceOps, new_shape:Tuple[int, ...]): return type(self)(new_shape).exec_ast(LazyOp(op=op, src=(self,), arg=new_shape)) + + # universal for shape tracked + def movement_op(self, op:MovementOps, arg): return type(self)(ShapeTracker(self.st).movement_op(op, arg), self) + + # TODO: creating a new object is making a copy, breaking the thneed compiler + def contiguous(self): return self if self.st.contiguous else self.unary_op(UnaryOps.NOOP) + #def contiguous(self): return type(self)(self.shape, hostbuf=self) if self.st.contiguous else self.unary_op(UnaryOps.NOOP) \ No newline at end of file diff --git a/tinygrad_repo/tinygrad/shapetracker.py b/tinygrad_repo/tinygrad/shape/__init__.py similarity index 54% rename from tinygrad_repo/tinygrad/shapetracker.py rename to tinygrad_repo/tinygrad/shape/__init__.py index 18dc4576b..a2da527be 100644 --- a/tinygrad_repo/tinygrad/shapetracker.py +++ b/tinygrad_repo/tinygrad/shape/__init__.py @@ -1,19 +1,20 @@ # ShapeTracker allows movement operations to a buffer that don't require a copy to be made. from __future__ import annotations +import os import functools -from typing import Tuple, Union, List +from typing import Tuple, Union, List, Optional from tinygrad.helpers import prod +from tinygrad.shape.symbolic import Variable -def divmodidx(acc, d, mod=True): - lr = f"(idx//{acc})" if acc != 1 else "idx" - return f"({lr}%{d})" if mod else lr # don't mod the top shape dimension +# TODO: fix DEBUG import +DEBUG = int(os.getenv("DEBUG", "0")) @functools.lru_cache(maxsize=None) def to_shape_strides(shape:Tuple[int, ...], strides:Tuple[int, ...]) -> List[Tuple[int, int]]: assert len(shape) == len(strides) - ret = [(shape[0], strides[0])] + ret = [(shape[0], strides[0])] if len(shape) > 0 else [] for i in range(1, len(shape)): - if (strides[i] != 0 and ret[-1][1] == shape[i]*strides[i]) or (strides[i] == 0 and ret[-1][1] == 0): + if (strides[i] != 0 and ret[-1][1] == shape[i]*strides[i]) or ret[-1][0] == 1 or (strides[i] == 0 and ret[-1][1] == 0): ret[-1] = (ret[-1][0] * shape[i], strides[i]) else: ret.append((shape[i], strides[i])) @@ -24,34 +25,49 @@ class View: self.shape, self.strides, self.offset = tuple(shape), tuple(strides), offset self.shape_strides = to_shape_strides(self.shape, self.strides) - def __repr__(self): return f"View<{self.shape}, {self.strides}, {self.offset}>" + def __repr__(self): return f"View({self.shape}, {self.strides}, {self.offset})" @functools.cached_property def contiguous(self): return self.offset == 0 and all(s1 == s2 or s == 1 for s,s1,s2 in zip(self.shape, self.strides, strides_for_shape(self.shape))) + def expr_node(self, idx): + ret = [Variable.num(self.offset)] + acc = 1 + for d,s in self.shape_strides[::-1]: + if d != 1 and s != 0: + ret.append(((idx//acc)%d)*s) + acc *= d + return Variable.sum(ret) + @functools.cached_property def expr(self): - ret = [f"{self.offset}"] if self.offset != 0 else [] - acc = 1 - for i,(d,s) in enumerate(self.shape_strides[::-1]): - if d != 1 and s != 0: - lr = divmodidx(acc, d, i != len(self.shape_strides)-1 and d != prod(self.shape)) - lr = f"({lr}*{s})" if s != 1 else lr - ret.append(lr) - acc *= d - return 'idx=' + ('+'.join(ret) if len(ret) > 0 else "0") + return 'idx=' + str(self.expr_node(Variable('idx', 0, prod([x[0] for x in self.shape_strides])-1))) + + # generate an expression if you have a variable or expression for each index + def expr_idxs(self, idxs): + return Variable.sum([Variable.num(self.offset)] + [Variable(idxs[i], 0, sh-1)*st for i,(sh,st) in enumerate(zip(self.shape, self.strides)) if sh != 1 and st != 0]) class ZeroView: def __init__(self, old_shape, arg): - self.shape = [] - expr, acc = ['valid'], 1 - for s,(x,y) in list(zip(old_shape, arg))[::-1]: + self.old_shape, self.arg, self.shape = old_shape, arg, [] + + def expr_node(self, valid, idx): + expr, acc = [valid] if valid is not None else [], 1 + for s,(x,y) in list(zip(self.old_shape, self.arg))[::-1]: self.shape = [y-x] + self.shape - base = divmodidx(acc, self.shape[0], len(self.shape) != len(old_shape)) + f"+{x}" - expr += ([f"(({base}) >= 0)"] if x < 0 else []) + ([f"(({base}) < {s})"] if y > s else []) + base = idx//acc + base = (base % self.shape[0]) + x + expr += ([base >= 0] if x < 0 else []) + ([base < s] if y > s else []) acc *= self.shape[0] - self.expr = 'valid=' + ' && '.join(expr) + return Variable.ands(expr) + + @functools.cached_property + def expr(self): + max_idx = prod([y-x for x,y in self.arg]) + return 'valid=' + str(self.expr_node(Variable('valid', 0, 1), Variable('idx', 0, max_idx-1))) + + def __repr__(self): return f"ZeroView({self.old_shape}, {self.arg})" ViewTypes = Union[View, ZeroView] @@ -68,9 +84,9 @@ def view_from_shape(shape:Tuple[int, ...]) -> View: return View(tuple(shape), strides_for_shape(shape)) class ShapeTracker: - def __init__(self, shape:Union[ShapeTracker, Tuple[int, ...]]): - self.views : List[ViewTypes] = shape.views[:] if isinstance(shape, ShapeTracker) else [view_from_shape(shape)] - def __repr__(self): return f"{'Complex' if len(self.views) > 1 else ''}ShapeTracker<{self.shape}, {self.views}>" + def __init__(self, shape:Union[ShapeTracker, Tuple[int, ...]], views:Optional[List[ViewTypes]]=None): + self.views : List[ViewTypes] = views if views is not None else (shape.views[:] if isinstance(shape, ShapeTracker) else [view_from_shape(shape)]) + def __repr__(self): return f"ShapeTracker(shape={self.shape}, views={self.views})" @property def contiguous(self): return len(self.views) == 1 and self.views[-1].contiguous @@ -84,23 +100,36 @@ class ShapeTracker: @property def offset(self): return self.views[-1].offset - def expr(self): return ';'.join([v.expr for v in self.views[::-1] if v.expr != 'idx=idx' and v.expr != 'valid=valid']) - def movement_op(self, op, arg): - getattr(self, str(op).split(".")[1].lower())(*arg) - return self + def expr_node(self): + idx = Variable('idx', 0, prod(self.shape)-1) + valid = None #Variable.num(1) + for v in self.views[::-1]: + if isinstance(v, ZeroView): valid = v.expr_node(valid, idx) + else: idx = v.expr_node(idx) + return idx, valid + + def expr(self): + idx, valid = self.expr_node() + if valid is not None and str(valid) != "valid": return f"valid={valid};idx={idx}" + else: return f"idx={idx}" + + #def expr(self): return ';'.join([v.expr for v in self.views[::-1] if v.expr != 'idx=idx' and v.expr != 'valid=valid']) + def movement_op(self, op, arg): return getattr(self, str(op).split(".")[1].lower())(*arg) def needs_valid(self): return any(isinstance(v, ZeroView) for v in self.views) # TODO: do we really need this for conv? # if we replace, confirm the ops taken fold into one view def strided(self, *arg): view = View([x[0] for x in arg], [x[1] for x in arg]) - if self.contiguous: + # TODO: this does not always require a new view if non contiguous + if self.views[-1].contiguous: self.views[-1] = view else: self.views.append(view) + return self def reshape(self, *new_shape): - assert all(isinstance(x, int) for x in new_shape) + assert all(isinstance(x, int) and x != 0 for x in new_shape), f"shape must be ints and can't contain 0 {new_shape}" assert prod(self.shape) == prod(new_shape), f"can't reshape {self.shape} -> {new_shape}" # check if this is adding or removing 1s (only) @@ -108,22 +137,50 @@ class ShapeTracker: old_strides = [y for x,y in zip(self.shape, self.strides) if x != 1] new_strides = [0 if x == 1 else old_strides.pop(0) for x in new_shape] self.views[-1] = View(new_shape, new_strides, self.offset) - return + return self + + # check if the new dimensions factorize from the old ones + # NOTE: if you don't make a copy here, the list is popped in the lrucache + min_shape_strides = to_shape_strides(self.shape, self.strides)[:] + curr_dim, curr_stride = min_shape_strides.pop(0) + new_strides = [] + for s in new_shape: + if curr_dim%s == 0: + curr_dim //= s + new_strides.append(curr_stride * curr_dim) + if curr_dim == 1: + if len(min_shape_strides) == 0: + # there might still be 1s in the shape + while len(new_strides) != len(new_shape): + assert new_shape[len(new_strides)] == 1 + new_strides.append(1) + break + curr_dim, curr_stride = min_shape_strides.pop(0) + else: + break # didn't factorize + + if len(new_shape) == len(new_strides): + self.views[-1] = View(new_shape, new_strides, self.offset) + return self view = View(new_shape, strides_for_shape(new_shape)) if self.contiguous: self.views[-1] = view # NOTE: if it's contiguous it can't have an offset else: + if DEBUG >= 2: + print(f"WARNING: reshape from {self.shape} w strides {self.strides} -> {new_shape} is creating another view") self.views.append(view) + return self def permute(self, *axis): - assert all(isinstance(x, int) and x >= 0 and x < len(self.shape) for x in axis) + assert all(isinstance(x, int) and x >= 0 and x < len(self.shape) for x in axis), f"invalid permute {axis} for {self.shape}" assert len(set(axis)) == len(axis) and len(axis) == len(self.shape), f"can't permute {self.shape} with {axis}" self.views[-1] = View([self.shape[a] for a in axis], [self.strides[a] for a in axis], self.offset) + return self # TODO: this is a special case of slice with strides, remove it # though it's nice that it can't change size - def flip(self, *axis): self.stride(*[-1 if i in axis else 1 for i in range(len((self.shape)))]) + def flip(self, *axis): return self.stride(*[-1 if i in axis else 1 for i in range(len((self.shape)))]) # *** under this line are not invertible *** @@ -141,12 +198,14 @@ class ShapeTracker: if zeroview.expr != "valid=valid": # if we add a ZeroView, we add another (stock) view also for modding self.views += [zeroview, View(self.shape, strides_for_shape(self.shape))] + return self def expand(self, *new_shape): assert all(isinstance(x, int) for x in new_shape) assert all(x == y or x == 1 for x,y in zip(self.shape, new_shape)), f"can't expand {self.shape} into {new_shape}" strides = [s if x == y else 0 for s,(x,y) in zip(self.strides, zip(self.shape, new_shape))] self.views[-1] = View(new_shape, strides, self.offset) + return self # TODO: combine with slice? this doesn't require a ZeroView, though slice shouldn't always either def stride(self, *mul): @@ -155,4 +214,4 @@ class ShapeTracker: new_shape = [(s+(abs(m)-1))//abs(m) for s,m in zip(self.shape, mul)] offset = sum([(s-1)*z for s,z,m in zip(self.shape, self.strides, mul) if m < 0]) self.views[-1] = View(new_shape, strides, self.offset + offset) - + return self diff --git a/tinygrad_repo/tinygrad/shape/symbolic.py b/tinygrad_repo/tinygrad/shape/symbolic.py new file mode 100644 index 000000000..7f583c397 --- /dev/null +++ b/tinygrad_repo/tinygrad/shape/symbolic.py @@ -0,0 +1,150 @@ +from __future__ import annotations +import math +from typing import List +from tinygrad.helpers import partition, modn, all_same + +class Node: + b, min, max = 0, -math.inf, math.inf # make mypy happy + def __str__(self): + if self.min == self.max: return str(self.min) # this is universal + return self.expr + @property + def cl(self): + return str(self).replace('//', '/') + def __add__(self, b:int): return Variable.sum([self, Variable.num(b)]) + def __mul__(self, b:int): + if b == 0: return NumNode(0) + elif b == 1: return self + return MulNode(self, b) + def __floordiv__(self, b:int): + assert b != 0 + if b == 1: return self + if isinstance(self, MulNode) and self.b%b == 0: return self.a*(self.b//b) + if isinstance(self, MulNode) and b%self.b == 0: return self.a//(b//self.b) + if isinstance(self, SumNode): + factors, tmp_nofactor = partition(self.nodes, lambda x: (isinstance(x, MulNode) or isinstance(x, NumNode)) and x.b%b == 0) + nofactor = [] + # ugh, i doubt this is universally right + for x in tmp_nofactor: + if isinstance(x, NumNode): + if modn(x.b, b) != x.b: + factors.append(Variable.num(x.b - modn(x.b, b))) # python does floor division + nofactor.append(Variable.num(modn(x.b, b))) + else: + nofactor.append(x) + gcd = [math.gcd(x.b, b) if isinstance(x, MulNode) or isinstance(x, NumNode) else None for x in nofactor] + if len(factors) > 0: + # these don't have to be the same, just having a common factor + if len(gcd) > 0 and all_same(gcd) and gcd[0] is not None and gcd[0] > 1: + nofactor_term = Variable.sum([(x.a * (x.b//gcd[0])) if isinstance(x, MulNode) else Variable.num(x.b//gcd[0]) for x in nofactor])//(b//gcd[0]) + else: + nofactor_term = Variable.sum(nofactor)//b + return Variable.sum([(x.a * (x.b//b)) if isinstance(x, MulNode) else Variable.num(x.b//b) for x in factors] + [nofactor_term]) + return DivNode(self, b) + def __mod__(self, b:int): + if b == 1: return NumNode(0) + if isinstance(self, SumNode): + a = Variable.sum([(x if not isinstance(x, NumNode) else Variable.num(modn(x.b, b))) for x in self.nodes if not (isinstance(x, MulNode) or isinstance(x, NumNode)) or (x.b%b != 0)]) + else: + a = self + if a.min >= 0 and a.max < b: return a + return ModNode(a, b) + def __ge__(self, b:int): + if self.max < b: return Variable.num(0) + if self.min >= b: return Variable.num(1) + return GeNode(self, b) + def __lt__(self, b:int): + if self.max < b: return Variable.num(1) + if self.min >= b: return Variable.num(0) + return LtNode(self, b) + +# constructors +class Variable(Node): + def __init__(self, expr:str, nmin:int, nmax:int): + self.expr, self.min, self.max = expr, nmin, nmax + + @staticmethod + def num(num:int) -> Node: + return NumNode(num) + + @staticmethod + def sum(nodes:List[Node]) -> Node: + if any([isinstance(x, SumNode) for x in nodes]): + nodes, sum_nodes = partition(nodes, lambda x: not isinstance(x, SumNode)) + for x in sum_nodes: nodes += x.nodes + return Variable.sum(nodes) + nodes = [x for x in nodes if x.min != 0 or x.max != 0] + if len(nodes) == 0: return NumNode(0) + elif len(nodes) == 1: return nodes[0] + return SumNode(nodes) + + @staticmethod + def ands(nodes:List[Node]) -> Node: + if any((x.min == 0 and x.max == 0) for x in nodes): return NumNode(0) + nodes = [x for x in nodes if x.min != x.max] + if len(nodes) == 0: return NumNode(1) + elif len(nodes) == 1: return nodes[0] + return AndNode(nodes) + +class NumNode(Node): + def __init__(self, num:int): + self.b, self.min, self.max = num, num, num + +class MulNode(Node): + def __init__(self, a:Node, b:int): + self.a, self.b = a, b + self.min, self.max = a.min*b, a.max*b + @property + def expr(self): + return f"({self.a}*{self.b})" + +class DivNode(Node): + def __init__(self, a:Node, b:int): + self.a, self.b = a, b + self.min, self.max = int(a.min/b), int(a.max/b) + @property + def expr(self): + return f"({self.a}//{self.b})" + +class ModNode(Node): + def __init__(self, a:Node, b:int): + self.a, self.b = a, b + self.min, self.max = min(a.min, 0), max(a.max, b-1) + @property + def expr(self): + assert self.a != self + return f"({self.a}%{self.b})" + +class GeNode(Node): + def __init__(self, a:Node, b:int): + self.a, self.b = a, b + self.min, self.max = 0, 1 + @property + def expr(self): + return f"({self.a}>={self.b})" + +class LtNode(Node): + def __init__(self, a:Node, b:int): + self.a, self.b = a, b + self.min, self.max = 0, 1 + @property + def expr(self): + return f"({self.a}<{self.b})" + +# reduce nodes + +class SumNode(Node): + def __init__(self, nodes:List[Node]): + self.nodes = nodes + self.min, self.max = sum([x.min for x in nodes]), sum([x.max for x in nodes]) + @property + def expr(self): + return f"({'+'.join([str(x) for x in self.nodes])})" + +class AndNode(Node): + def __init__(self, nodes:List[Node]): + self.nodes = nodes + self.min, self.max = min([x.min for x in nodes]), max([x.max for x in nodes]) + @property + def expr(self): + return f"({'&&'.join([str(x) for x in self.nodes])})" diff --git a/tinygrad_repo/tinygrad/tensor.py b/tinygrad_repo/tinygrad/tensor.py index 447de56d0..35d2222e7 100644 --- a/tinygrad_repo/tinygrad/tensor.py +++ b/tinygrad_repo/tinygrad/tensor.py @@ -4,9 +4,7 @@ import inspect, functools, importlib, itertools import numpy as np from tinygrad.helpers import prod, argfix from typing import List, Tuple, Callable, Optional -from tinygrad.ops import Device - -from tinygrad.ops import LazyBuffer +from tinygrad.lazy import Device, LazyBuffer # **** start with two base classes, Tensor and Function **** @@ -21,8 +19,7 @@ class Tensor: data = data.realize().toCPU() if isinstance(data, np.ndarray): - if data.shape == tuple(): - data = data.reshape((1,)) + data = data if data.shape else data.reshape((1,)) self.lazydata = LazyBuffer.fromCPU(data.astype(np.float32), device) elif isinstance(data, LazyBuffer): self.lazydata = data @@ -89,6 +86,9 @@ class Tensor: # TODO: remove use of numpy here + @classmethod + def zeros_like(cls, tensor, **kwargs): return cls.zeros(*tensor.shape, **kwargs) + @classmethod def zeros(cls, *shape, **kwargs): return cls(np.zeros(shape, dtype=np.float32), **kwargs) @@ -303,7 +303,7 @@ class Tensor: # TODO: fix the kwargs problem, then remove these (or not, since they now fix tuples) def reshape(self, shape, *args): return self._reshape(shape=argfix(shape, *args)) - def expand(self, shape, *args): return self._expand(shape=argfix(shape, *args)) + def expand(self, shape, *args): return self._expand(shape=tuple(x if x != -1 else s for s,x in zip(self.shape, argfix(shape, *args)))) def permute(self, order, *args): return self._permute(order=argfix(order, *args)) def linear(self, weight:Tensor, bias:Optional[Tensor]=None): @@ -316,6 +316,10 @@ class Tensor: y = (self - self.mean(axis=axis, keepdim=True)) return y.div((y*y).mean(axis=axis, keepdim=True).add(eps).sqrt()) + def batchnorm(self, weight:Tensor, bias:Tensor, mean:Tensor, invstd:Tensor): + x = (self - mean.reshape(shape=[1, -1, 1, 1])) * weight.reshape(shape=[1, -1, 1, 1]) + return x.mul(invstd.reshape(shape=[1, -1, 1, 1])) + bias.reshape(shape=[1, -1, 1, 1]) + # An instantiation of the Function is the Context class Function: def __init__(self, device:str, *tensors:Tensor): @@ -345,7 +349,7 @@ for device in [device for device in Device.__dict__.keys() if device[0] != "_"]: # register all the mlops "math" operations def register(name:str, fxn:Function): - setattr(Tensor, "_"+name if (getattr(Tensor, name, None) is not None) else name, functools.partialmethod(fxn.apply)) + setattr(Tensor, "_"+name if hasattr(Tensor, name) else name, functools.partialmethod(fxn.apply)) for name, cls in inspect.getmembers(importlib.import_module('tinygrad.mlops'), inspect.isclass): if name[0] != "_" and name != "Function" and not name.endswith("Ops"): register(name.lower(), cls) diff --git a/tools/replay/camera.cc b/tools/replay/camera.cc index 72b385dca..66898c924 100644 --- a/tools/replay/camera.cc +++ b/tools/replay/camera.cc @@ -56,7 +56,7 @@ void CameraServer::cameraThread(Camera &cam) { .timestamp_eof = eidx.getTimestampEof(), }; yuv->set_frame_id(eidx.getFrameId()); - vipc_server_->send(yuv, &extra, false); + vipc_server_->send(yuv, &extra); } else { rError("camera[%d] failed to get frame: %lu", cam.type, eidx.getSegmentId()); } diff --git a/tools/replay/consoleui.cc b/tools/replay/consoleui.cc index 5f165ac31..5ad702590 100644 --- a/tools/replay/consoleui.cc +++ b/tools/replay/consoleui.cc @@ -50,14 +50,6 @@ void add_str(WINDOW *w, const char *str, Color color = Color::Default, bool bold if (color != Color::Default) wattroff(w, COLOR_PAIR(color)); } -std::string format_seconds(int s) { - int total_minutes = s / 60; - int seconds = s % 60; - int hours = total_minutes / 60; - int minutes = total_minutes % 60; - return util::string_format("%02d:%02d:%02d", hours, minutes, seconds); -} - } // namespace ConsoleUI::ConsoleUI(Replay *replay, QObject *parent) : replay(replay), sm({"carState", "liveParameters"}), QObject(parent) { @@ -156,13 +148,13 @@ void ConsoleUI::timerEvent(QTimerEvent *ev) { } void ConsoleUI::updateStatus() { - auto write_item = [this](int y, int x, const char *key, const std::string &value, const char *unit, + auto write_item = [this](int y, int x, const char *key, const std::string &value, const std::string &unit, bool bold = false, Color color = Color::BrightWhite) { auto win = w[Win::CarState]; wmove(win, y, x); add_str(win, key); add_str(win, value.c_str(), color, bold); - add_str(win, unit); + add_str(win, unit.c_str()); }; static const std::pair status_text[] = { {"loading...", Color::Red}, @@ -177,9 +169,8 @@ void ConsoleUI::updateStatus() { } auto [status_str, status_color] = status_text[status]; write_item(0, 0, "STATUS: ", status_str, " ", false, status_color); - std::string suffix = util::string_format(" / %s [%d/%d] ", format_seconds(replay->totalSeconds()).c_str(), - replay->currentSeconds() / 60, replay->route()->segments().size()); - write_item(0, 25, "TIME: ", format_seconds(replay->currentSeconds()), suffix.c_str(), true); + std::string current_segment = " - " + std::to_string((int)(replay->currentSeconds() / 60)); + write_item(0, 25, "TIME: ", replay->currentDateTime().toString("ddd MMMM dd hh:mm:ss").toStdString(), current_segment, true); auto p = sm["liveParameters"].getLiveParameters(); write_item(1, 0, "STIFFNESS: ", util::string_format("%.2f %%", p.getStiffnessFactor() * 100), " "); diff --git a/tools/replay/main.cc b/tools/replay/main.cc index 40dace0c9..6b624aa1f 100644 --- a/tools/replay/main.cc +++ b/tools/replay/main.cc @@ -1,6 +1,7 @@ #include #include +#include "common/prefix.h" #include "tools/replay/consoleui.h" #include "tools/replay/replay.h" @@ -23,9 +24,11 @@ int main(int argc, char *argv[]) { parser.addPositionalArgument("route", "the drive to replay. find your drives at connect.comma.ai"); parser.addOption({{"a", "allow"}, "whitelist of services to send", "allow"}); parser.addOption({{"b", "block"}, "blacklist of services to send", "block"}); + parser.addOption({{"c", "cache"}, "cache segments in memory. default is 5", "n"}); parser.addOption({{"s", "start"}, "start from ", "seconds"}); parser.addOption({"demo", "use a demo route instead of providing your own"}); parser.addOption({"data_dir", "local directory with routes", "data_dir"}); + parser.addOption({"prefix", "set OPENPILOT_PREFIX", "prefix"}); for (auto &[name, _, desc] : flags) { parser.addOption({name, desc}); } @@ -46,7 +49,17 @@ int main(int argc, char *argv[]) { replay_flags |= flag; } } + + std::unique_ptr op_prefix; + auto prefix = parser.value("prefix"); + if (!prefix.isEmpty()) { + op_prefix.reset(new OpenpilotPrefix(prefix.toStdString())); + } + Replay *replay = new Replay(route, allow, block, nullptr, replay_flags, parser.value("data_dir"), &app); + if (!parser.value("c").isEmpty()) { + replay->setSegmentCacheLimit(parser.value("c").toInt()); + } if (!replay->load()) { return 0; } diff --git a/tools/replay/replay.cc b/tools/replay/replay.cc index 1337a4ef2..178b116a8 100644 --- a/tools/replay/replay.cc +++ b/tools/replay/replay.cc @@ -25,6 +25,13 @@ Replay::Replay(QString route, QStringList allow, QStringList block, SubMaster *s s.push_back(it.name); } } + + if (!allow_list.empty()) { + // the following events are needed for replay to work properly. + allow_list.insert(cereal::Event::Which::INIT_DATA); + allow_list.insert(cereal::Event::Which::CAR_PARAMS); + } + qDebug() << "services " << s; qDebug() << "loading route " << route; @@ -51,9 +58,9 @@ void Replay::stop() { stream_thread_->wait(); stream_thread_ = nullptr; } - segments_.clear(); camera_server_.reset(nullptr); timeline_future.waitForFinished(); + segments_.clear(); rInfo("shutdown: done"); } @@ -107,6 +114,7 @@ void Replay::seekTo(double seconds, bool relative) { rInfo("seeking to %d s, segment %d", (int)seconds, seg); current_segment_ = seg; cur_mono_time_ = route_start_ts_ + seconds * 1e9; + emit seekedTo(seconds); return isSegmentMerged(seg); }); queueSegment(); @@ -123,9 +131,9 @@ void Replay::buildTimeline() { uint64_t alert_begin = 0; TimelineType alert_type = TimelineType::None; - for (int i = 0; i < segments_.size() && !exit_; ++i) { + for (auto it = segments_.cbegin(); it != segments_.cend() && !exit_; ++it) { LogReader log; - if (!log.load(route_->at(i).qlog.toStdString(), &exit_, + if (!log.load(route_->at(it->first).qlog.toStdString(), &exit_, {cereal::Event::Which::CONTROLS_STATE, cereal::Event::Which::USER_FLAG}, !hasFlag(REPLAY_FLAG_NO_FILE_CACHE), 0, 3)) continue; @@ -209,11 +217,17 @@ void Replay::segmentLoadFinished(bool success) { void Replay::queueSegment() { if (segments_.empty()) return; - SegmentMap::iterator cur, end; - cur = end = segments_.lower_bound(std::min(current_segment_.load(), segments_.rbegin()->first)); - for (int i = 0; end != segments_.end() && i <= segment_cache_limit + FORWARD_FETCH_SEGS; ++i) { + SegmentMap::iterator begin, cur; + begin = cur = segments_.lower_bound(std::min(current_segment_.load(), segments_.rbegin()->first)); + int distance = std::max(std::ceil(segment_cache_limit / 2.0) - 1, segment_cache_limit - std::distance(cur, segments_.end())); + for (int i = 0; begin != segments_.begin() && i < distance; ++i) { + --begin; + } + auto end = begin; + for (int i = 0; end != segments_.end() && i < segment_cache_limit; ++i) { ++end; } + // load one segment at a time for (auto it = cur; it != end; ++it) { auto &[n, seg] = *it; @@ -227,12 +241,6 @@ void Replay::queueSegment() { } } - const auto &cur_segment = cur->second; - // merge the previous adjacent segment if it's loaded - auto begin = segments_.find(cur_segment->seg_num - 1); - if (begin == segments_.end() || !(begin->second && begin->second->isLoaded())) { - begin = cur; - } mergeSegments(begin, end); // free segments out of current semgnt window. @@ -240,6 +248,7 @@ void Replay::queueSegment() { std::for_each(end, segments_.end(), [](auto &e) { e.second.reset(nullptr); }); // start stream thread + const auto &cur_segment = cur->second; if (stream_thread_ == nullptr && cur_segment->isLoaded()) { startStream(cur_segment.get()); emit streamStarted(); @@ -247,12 +256,13 @@ void Replay::queueSegment() { } void Replay::mergeSegments(const SegmentMap::iterator &begin, const SegmentMap::iterator &end) { - // merge 3 segments in sequence. std::vector segments_need_merge; size_t new_events_size = 0; - for (auto it = begin; it != end && it->second && it->second->isLoaded() && segments_need_merge.size() < segment_cache_limit; ++it) { - segments_need_merge.push_back(it->first); - new_events_size += it->second->log->events.size(); + for (auto it = begin; it != end; ++it) { + if (it->second && it->second->isLoaded()) { + segments_need_merge.push_back(it->first); + new_events_size += it->second->log->events.size(); + } } if (segments_need_merge != segments_merged_) { @@ -266,8 +276,12 @@ void Replay::mergeSegments(const SegmentMap::iterator &begin, const SegmentMap:: new_events_->reserve(new_events_size); for (int n : segments_need_merge) { const auto &e = segments_[n]->log->events; - auto middle = new_events_->insert(new_events_->end(), e.begin(), e.end()); - std::inplace_merge(new_events_->begin(), middle, new_events_->end(), Event::lessThan()); + if (e.size() > 0) { + auto insert_from = e.begin(); + if (new_events_->size() > 0 && (*insert_from)->which == cereal::Event::Which::INIT_DATA) ++insert_from; + auto middle = new_events_->insert(new_events_->end(), insert_from, e.end()); + std::inplace_merge(new_events_->begin(), middle, new_events_->end(), Event::lessThan()); + } } updateEvents([&]() { @@ -358,6 +372,7 @@ void Replay::publishFrame(const Event *e) { void Replay::stream() { cereal::Event::Which cur_which = cereal::Event::Which::INIT_DATA; + double prev_replay_speed = 1.0; std::unique_lock lk(stream_lock_); while (true) { @@ -397,10 +412,11 @@ void Replay::stream() { long rtime = nanos_since_boot() - loop_start_ts; long behind_ns = etime - rtime; // if behind_ns is greater than 1 second, it means that an invalid segemnt is skipped by seeking/replaying - if (behind_ns >= 1 * 1e9) { - // reset start times + if (behind_ns >= 1 * 1e9 || speed_ != prev_replay_speed) { + // reset event start times evt_start_ts = cur_mono_time_; loop_start_ts = nanos_since_boot(); + prev_replay_speed = speed_; } else if (behind_ns > 0 && !hasFlag(REPLAY_FLAG_FULL_SPEED)) { precise_nano_sleep(behind_ns); } diff --git a/tools/replay/replay.h b/tools/replay/replay.h index 88c285125..6788a97d0 100644 --- a/tools/replay/replay.h +++ b/tools/replay/replay.h @@ -10,7 +10,7 @@ const QString DEMO_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36"; // one segment uses about 100M of memory -constexpr int FORWARD_FETCH_SEGS = 3; +constexpr int MIN_SEGMENTS_CACHE = 5; enum REPLAY_FLAGS { REPLAY_FLAG_NONE = 0x0000, @@ -58,12 +58,13 @@ public: event_filter = filter; } inline int segmentCacheLimit() const { return segment_cache_limit; } - inline void setSegmentCacheLimit(int n) { segment_cache_limit = std::max(3, n); } + inline void setSegmentCacheLimit(int n) { segment_cache_limit = std::max(MIN_SEGMENTS_CACHE, n); } inline bool hasFlag(REPLAY_FLAGS flag) const { return flags_ & flag; } inline void addFlag(REPLAY_FLAGS flag) { flags_ |= flag; } inline void removeFlag(REPLAY_FLAGS flag) { flags_ &= ~flag; } inline const Route* route() const { return route_.get(); } inline double currentSeconds() const { return double(cur_mono_time_ - route_start_ts_) / 1e9; } + inline QDateTime currentDateTime() const { return route_->datetime().addSecs(currentSeconds()); } inline uint64_t routeStartTime() const { return route_start_ts_; } inline int toSeconds(uint64_t mono_time) const { return (mono_time - route_start_ts_) / 1e9; } inline int totalSeconds() const { return segments_.size() * 60; } @@ -79,6 +80,7 @@ public: signals: void streamStarted(); void segmentsMerged(); + void seekedTo(double sec); protected slots: void segmentLoadFinished(bool success); @@ -133,5 +135,5 @@ protected: float speed_ = 1.0; replayEventFilter event_filter = nullptr; void *filter_opaque = nullptr; - int segment_cache_limit = 3; + int segment_cache_limit = MIN_SEGMENTS_CACHE; }; diff --git a/tools/replay/route.cc b/tools/replay/route.cc index f0d6ec5a1..619aeb3f5 100644 --- a/tools/replay/route.cc +++ b/tools/replay/route.cc @@ -19,18 +19,19 @@ Route::Route(const QString &route, const QString &data_dir) : data_dir_(data_dir } RouteIdentifier Route::parseRoute(const QString &str) { - QRegExp rx(R"(^([a-z0-9]{16})([|_/])(\d{4}-\d{2}-\d{2}--\d{2}-\d{2}-\d{2})(?:(--|/)(\d*))?$)"); + QRegExp rx(R"(^(?:([a-z0-9]{16})([|_/]))?(\d{4}-\d{2}-\d{2}--\d{2}-\d{2}-\d{2})(?:(--|/)(\d*))?$)"); if (rx.indexIn(str) == -1) return {}; const QStringList list = rx.capturedTexts(); - return {list[1], list[3], list[5].toInt(), list[1] + "|" + list[3]}; + return {.dongle_id = list[1], .timestamp = list[3], .segment_id = list[5].toInt(), .str = list[1] + "|" + list[3]}; } bool Route::load() { - if (route_.str.isEmpty()) { + if (route_.str.isEmpty() || (data_dir_.isEmpty() && route_.dongle_id.isEmpty())) { rInfo("invalid route format"); return false; } + date_time_ = QDateTime::fromString(route_.timestamp, "yyyy-MM-dd--HH-mm-ss"); return data_dir_.isEmpty() ? loadFromServer() : loadFromLocal(); } diff --git a/tools/replay/route.h b/tools/replay/route.h index 6b78ebad8..86adf6a14 100644 --- a/tools/replay/route.h +++ b/tools/replay/route.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include "tools/replay/framereader.h" @@ -27,6 +28,7 @@ public: Route(const QString &route, const QString &data_dir = {}); bool load(); inline const QString &name() const { return route_.str; } + inline const QDateTime datetime() const { return date_time_; } inline const QString &dir() const { return data_dir_; } inline const RouteIdentifier &identifier() const { return route_; } inline const std::map &segments() const { return segments_; } @@ -41,6 +43,7 @@ protected: RouteIdentifier route_ = {}; QString data_dir_; std::map segments_; + QDateTime date_time_; }; class Segment : public QObject {