commit
70ddf6fd84
123 changed files with 2974 additions and 425 deletions
@ -1,9 +1,9 @@ |
||||
# Updating your panda |
||||
|
||||
Panda should update automatically via the [Chffr](http://chffr.comma.ai/) app ([apple](https://itunes.apple.com/us/app/chffr-dash-cam-that-remembers/id1146683979) and [android](https://play.google.com/store/apps/details?id=ai.comma.chffr)) |
||||
Panda should update automatically via the [openpilot](http://openpilot.comma.ai/). |
||||
|
||||
If it doesn't however, you can use the following commands on linux or Mac OSX |
||||
`sudo pip install --upgrade pandacan` |
||||
` PYTHONPATH="" sudo python -c "import panda; panda.flash_release()"` |
||||
|
||||
(You'll need to have `pip` and `sudo` installed.) |
||||
On Linux or Mac OSX, you can manually update it using: |
||||
``` |
||||
sudo pip install --upgrade pandacan` |
||||
PYTHONPATH="" sudo python -c "import panda; panda.flash_release()"` |
||||
``` |
||||
|
@ -1 +1 @@ |
||||
v1.5.3 |
||||
v1.5.9 |
@ -1 +1 @@ |
||||
from .python import Panda, PandaWifiStreaming, PandaDFU, ESPROM, CesantaFlasher, flash_release, BASEDIR, ensure_st_up_to_date, build_st, PandaSerial |
||||
from .python import Panda, PandaWifiStreaming, PandaDFU, ESPROM, CesantaFlasher, flash_release, BASEDIR, ensure_st_up_to_date, build_st, PandaSerial # noqa: F401 |
||||
|
@ -0,0 +1,247 @@ |
||||
// ///////////// //
|
||||
// Uno + Harness //
|
||||
// ///////////// //
|
||||
|
||||
void uno_enable_can_transciever(uint8_t transciever, bool enabled) { |
||||
switch (transciever){ |
||||
case 1U: |
||||
set_gpio_output(GPIOC, 1, !enabled); |
||||
break; |
||||
case 2U: |
||||
set_gpio_output(GPIOC, 13, !enabled); |
||||
break; |
||||
case 3U: |
||||
set_gpio_output(GPIOA, 0, !enabled); |
||||
break; |
||||
case 4U: |
||||
set_gpio_output(GPIOB, 10, !enabled); |
||||
break; |
||||
default: |
||||
puts("Invalid CAN transciever ("); puth(transciever); puts("): enabling failed\n"); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
void uno_enable_can_transcievers(bool enabled) { |
||||
for(uint8_t i=1U; i<=4U; i++){ |
||||
uno_enable_can_transciever(i, enabled); |
||||
} |
||||
} |
||||
|
||||
void uno_set_led(uint8_t color, bool enabled) { |
||||
switch (color){ |
||||
case LED_RED: |
||||
set_gpio_output(GPIOC, 9, !enabled); |
||||
break; |
||||
case LED_GREEN: |
||||
set_gpio_output(GPIOC, 7, !enabled); |
||||
break; |
||||
case LED_BLUE: |
||||
set_gpio_output(GPIOC, 6, !enabled); |
||||
break; |
||||
default: |
||||
break; |
||||
} |
||||
} |
||||
|
||||
void uno_set_gps_load_switch(bool enabled) { |
||||
set_gpio_output(GPIOC, 12, enabled); |
||||
} |
||||
|
||||
void uno_set_usb_power_mode(uint8_t mode) { |
||||
UNUSED(mode); |
||||
puts("Setting USB mode makes no sense on UNO\n"); |
||||
} |
||||
|
||||
void uno_set_esp_gps_mode(uint8_t mode) { |
||||
switch (mode) { |
||||
case ESP_GPS_DISABLED: |
||||
// GPS OFF
|
||||
set_gpio_output(GPIOB, 1, 0); |
||||
set_gpio_output(GPIOC, 5, 0); |
||||
uno_set_gps_load_switch(false); |
||||
break; |
||||
case ESP_GPS_ENABLED: |
||||
// GPS ON
|
||||
set_gpio_output(GPIOB, 1, 1); |
||||
set_gpio_output(GPIOC, 5, 1); |
||||
uno_set_gps_load_switch(true); |
||||
break; |
||||
case ESP_GPS_BOOTMODE: |
||||
set_gpio_output(GPIOB, 1, 1); |
||||
set_gpio_output(GPIOC, 5, 0); |
||||
uno_set_gps_load_switch(true); |
||||
break; |
||||
default: |
||||
puts("Invalid ESP/GPS mode\n"); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
void uno_set_can_mode(uint8_t mode){ |
||||
switch (mode) { |
||||
case CAN_MODE_NORMAL: |
||||
case CAN_MODE_OBD_CAN2: |
||||
if ((bool)(mode == CAN_MODE_NORMAL) != (bool)(car_harness_status == HARNESS_STATUS_NORMAL)) { |
||||
// B12,B13: disable OBD mode
|
||||
set_gpio_mode(GPIOB, 12, MODE_INPUT); |
||||
set_gpio_mode(GPIOB, 13, MODE_INPUT); |
||||
|
||||
// B5,B6: normal CAN2 mode
|
||||
set_gpio_alternate(GPIOB, 5, GPIO_AF9_CAN2); |
||||
set_gpio_alternate(GPIOB, 6, GPIO_AF9_CAN2); |
||||
} else { |
||||
// B5,B6: disable normal CAN2 mode
|
||||
set_gpio_mode(GPIOB, 5, MODE_INPUT); |
||||
set_gpio_mode(GPIOB, 6, MODE_INPUT); |
||||
|
||||
// B12,B13: OBD mode
|
||||
set_gpio_alternate(GPIOB, 12, GPIO_AF9_CAN2); |
||||
set_gpio_alternate(GPIOB, 13, GPIO_AF9_CAN2); |
||||
} |
||||
break; |
||||
default: |
||||
puts("Tried to set unsupported CAN mode: "); puth(mode); puts("\n"); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
void uno_set_bootkick(bool enabled){ |
||||
set_gpio_output(GPIOB, 14, !enabled); |
||||
} |
||||
|
||||
void uno_usb_power_mode_tick(uint64_t tcnt){ |
||||
if(tcnt == 3U){ |
||||
uno_set_bootkick(false); |
||||
} |
||||
} |
||||
|
||||
bool uno_check_ignition(void){ |
||||
// ignition is checked through harness
|
||||
return harness_check_ignition(); |
||||
} |
||||
|
||||
void uno_set_usb_switch(bool phone){ |
||||
set_gpio_output(GPIOB, 3, phone); |
||||
} |
||||
|
||||
void uno_set_ir_power(uint8_t percentage){ |
||||
pwm_set(TIM4, 2, percentage); |
||||
} |
||||
|
||||
void uno_set_fan_power(uint8_t percentage){ |
||||
// Enable fan power only if percentage is non-zero.
|
||||
set_gpio_output(GPIOA, 1, (percentage != 0U)); |
||||
fan_set_power(percentage); |
||||
} |
||||
|
||||
uint32_t uno_read_current(void){ |
||||
// No current sense on Uno
|
||||
return 0U; |
||||
} |
||||
|
||||
void uno_init(void) { |
||||
common_init_gpio(); |
||||
|
||||
// A8,A15: normal CAN3 mode
|
||||
set_gpio_alternate(GPIOA, 8, GPIO_AF11_CAN3); |
||||
set_gpio_alternate(GPIOA, 15, GPIO_AF11_CAN3); |
||||
|
||||
// C0: OBD_SBU1 (orientation detection)
|
||||
// C3: OBD_SBU2 (orientation detection)
|
||||
set_gpio_mode(GPIOC, 0, MODE_ANALOG); |
||||
set_gpio_mode(GPIOC, 3, MODE_ANALOG); |
||||
|
||||
// C10: OBD_SBU1_RELAY (harness relay driving output)
|
||||
// C11: OBD_SBU2_RELAY (harness relay driving output)
|
||||
set_gpio_mode(GPIOC, 10, MODE_OUTPUT); |
||||
set_gpio_mode(GPIOC, 11, MODE_OUTPUT); |
||||
set_gpio_output_type(GPIOC, 10, OUTPUT_TYPE_OPEN_DRAIN); |
||||
set_gpio_output_type(GPIOC, 11, OUTPUT_TYPE_OPEN_DRAIN); |
||||
set_gpio_output(GPIOC, 10, 1); |
||||
set_gpio_output(GPIOC, 11, 1); |
||||
|
||||
// C8: FAN PWM aka TIM3_CH3
|
||||
set_gpio_alternate(GPIOC, 8, GPIO_AF2_TIM3); |
||||
|
||||
// Turn on GPS load switch.
|
||||
uno_set_gps_load_switch(true); |
||||
|
||||
// Turn on phone regulator
|
||||
set_gpio_output(GPIOB, 4, 1); |
||||
|
||||
// Initialize IR PWM and set to 0%
|
||||
set_gpio_alternate(GPIOB, 7, GPIO_AF2_TIM4); |
||||
pwm_init(TIM4, 2); |
||||
uno_set_ir_power(0U); |
||||
|
||||
// Initialize fan and set to 0%
|
||||
fan_init(); |
||||
uno_set_fan_power(0U); |
||||
|
||||
// Initialize harness
|
||||
harness_init(); |
||||
|
||||
// Initialize RTC
|
||||
rtc_init(); |
||||
|
||||
// Enable CAN transcievers
|
||||
uno_enable_can_transcievers(true); |
||||
|
||||
// Disable LEDs
|
||||
uno_set_led(LED_RED, false); |
||||
uno_set_led(LED_GREEN, false); |
||||
uno_set_led(LED_BLUE, false); |
||||
|
||||
// Set normal CAN mode
|
||||
uno_set_can_mode(CAN_MODE_NORMAL); |
||||
|
||||
// flip CAN0 and CAN2 if we are flipped
|
||||
if (car_harness_status == HARNESS_STATUS_NORMAL) { |
||||
can_flip_buses(0, 2); |
||||
} |
||||
|
||||
// init multiplexer
|
||||
can_set_obd(car_harness_status, false); |
||||
|
||||
// Switch to phone usb mode if harness connection is powered by less than 7V
|
||||
if(adc_get_voltage() < 7000U){ |
||||
uno_set_usb_switch(true); |
||||
} else { |
||||
uno_set_usb_switch(false); |
||||
} |
||||
|
||||
// Bootkick phone
|
||||
uno_set_bootkick(true); |
||||
} |
||||
|
||||
const harness_configuration uno_harness_config = { |
||||
.has_harness = true, |
||||
.GPIO_SBU1 = GPIOC, |
||||
.GPIO_SBU2 = GPIOC, |
||||
.GPIO_relay_normal = GPIOC, |
||||
.GPIO_relay_flipped = GPIOC, |
||||
.pin_SBU1 = 0, |
||||
.pin_SBU2 = 3, |
||||
.pin_relay_normal = 10, |
||||
.pin_relay_flipped = 11, |
||||
.adc_channel_SBU1 = 10, |
||||
.adc_channel_SBU2 = 13 |
||||
}; |
||||
|
||||
const board board_uno = { |
||||
.board_type = "Uno", |
||||
.harness_config = &uno_harness_config, |
||||
.init = uno_init, |
||||
.enable_can_transciever = uno_enable_can_transciever, |
||||
.enable_can_transcievers = uno_enable_can_transcievers, |
||||
.set_led = uno_set_led, |
||||
.set_usb_power_mode = uno_set_usb_power_mode, |
||||
.set_esp_gps_mode = uno_set_esp_gps_mode, |
||||
.set_can_mode = uno_set_can_mode, |
||||
.usb_power_mode_tick = uno_usb_power_mode_tick, |
||||
.check_ignition = uno_check_ignition, |
||||
.read_current = uno_read_current, |
||||
.set_fan_power = uno_set_fan_power, |
||||
.set_ir_power = uno_set_ir_power |
||||
}; |
@ -0,0 +1,36 @@ |
||||
void fan_init(void){ |
||||
// Init PWM speed control
|
||||
pwm_init(TIM3, 3); |
||||
|
||||
// Init TACH interrupt
|
||||
SYSCFG->EXTICR[0] = SYSCFG_EXTICR1_EXTI2_PD; |
||||
EXTI->IMR |= (1U << 2); |
||||
EXTI->RTSR |= (1U << 2); |
||||
EXTI->FTSR |= (1U << 2); |
||||
NVIC_EnableIRQ(EXTI2_IRQn); |
||||
} |
||||
|
||||
void fan_set_power(uint8_t percentage){ |
||||
pwm_set(TIM3, 3, percentage); |
||||
} |
||||
|
||||
uint16_t fan_tach_counter = 0U; |
||||
uint16_t fan_rpm = 0U; |
||||
|
||||
// Can be way more acurate than this, but this is probably good enough for our purposes.
|
||||
|
||||
// Call this every second
|
||||
void fan_tick(void){ |
||||
// 4 interrupts per rotation
|
||||
fan_rpm = fan_tach_counter * 15U; |
||||
fan_tach_counter = 0U; |
||||
} |
||||
|
||||
// TACH interrupt handler
|
||||
void EXTI2_IRQHandler(void) { |
||||
volatile unsigned int pr = EXTI->PR & (1U << 2); |
||||
if ((pr & (1U << 2)) != 0U) { |
||||
fan_tach_counter++; |
||||
} |
||||
EXTI->PR = (1U << 2); |
||||
} |
@ -0,0 +1,55 @@ |
||||
#define PWM_COUNTER_OVERFLOW 2000U // To get ~50kHz
|
||||
|
||||
void pwm_init(TIM_TypeDef *TIM, uint8_t channel){ |
||||
// Enable timer and auto-reload
|
||||
TIM->CR1 = TIM_CR1_CEN | TIM_CR1_ARPE; |
||||
|
||||
// Set channel as PWM mode 1 and enable output
|
||||
switch(channel){ |
||||
case 1U: |
||||
TIM->CCMR1 |= (TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1PE); |
||||
TIM->CCER |= TIM_CCER_CC1E; |
||||
break; |
||||
case 2U: |
||||
TIM->CCMR1 |= (TIM_CCMR1_OC2M_2 | TIM_CCMR1_OC2M_1 | TIM_CCMR1_OC2PE); |
||||
TIM->CCER |= TIM_CCER_CC2E; |
||||
break; |
||||
case 3U: |
||||
TIM->CCMR2 |= (TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC3PE); |
||||
TIM->CCER |= TIM_CCER_CC3E; |
||||
break; |
||||
case 4U: |
||||
TIM->CCMR2 |= (TIM_CCMR2_OC4M_2 | TIM_CCMR2_OC4M_1 | TIM_CCMR2_OC4PE); |
||||
TIM->CCER |= TIM_CCER_CC4E; |
||||
break; |
||||
default: |
||||
break; |
||||
} |
||||
|
||||
// Set max counter value
|
||||
TIM->ARR = PWM_COUNTER_OVERFLOW; |
||||
|
||||
// Update registers and clear counter
|
||||
TIM->EGR |= TIM_EGR_UG; |
||||
} |
||||
|
||||
// TODO: Implement for 32-bit timers
|
||||
void pwm_set(TIM_TypeDef *TIM, uint8_t channel, uint8_t percentage){ |
||||
uint16_t comp_value = (((uint16_t) percentage * PWM_COUNTER_OVERFLOW) / 100U); |
||||
switch(channel){ |
||||
case 1U: |
||||
TIM->CCR1 = comp_value; |
||||
break; |
||||
case 2U: |
||||
TIM->CCR2 = comp_value; |
||||
break; |
||||
case 3U: |
||||
TIM->CCR3 = comp_value; |
||||
break; |
||||
case 4U: |
||||
TIM->CCR4 = comp_value; |
||||
break; |
||||
default: |
||||
break; |
||||
} |
||||
} |
@ -0,0 +1,108 @@ |
||||
#define RCC_BDCR_OPTIONS (RCC_BDCR_RTCEN | RCC_BDCR_RTCSEL_0 | RCC_BDCR_LSEON) |
||||
#define RCC_BDCR_MASK (RCC_BDCR_RTCEN | RCC_BDCR_RTCSEL | RCC_BDCR_LSEMOD | RCC_BDCR_LSEBYP | RCC_BDCR_LSEON) |
||||
|
||||
#define YEAR_OFFSET 2000U |
||||
|
||||
typedef struct __attribute__((packed)) timestamp_t { |
||||
uint16_t year; |
||||
uint8_t month; |
||||
uint8_t day; |
||||
uint8_t weekday; |
||||
uint8_t hour; |
||||
uint8_t minute; |
||||
uint8_t second; |
||||
} timestamp_t; |
||||
|
||||
uint8_t to_bcd(uint16_t value){ |
||||
return (((value / 10U) & 0x0FU) << 4U) | ((value % 10U) & 0x0FU); |
||||
} |
||||
|
||||
uint16_t from_bcd(uint8_t value){ |
||||
return (((value & 0xF0U) >> 4U) * 10U) + (value & 0x0FU); |
||||
} |
||||
|
||||
void rtc_init(void){ |
||||
if(board_has_rtc()){ |
||||
// Initialize RTC module and clock if not done already.
|
||||
if((RCC->BDCR & RCC_BDCR_MASK) != RCC_BDCR_OPTIONS){ |
||||
puts("Initializing RTC\n"); |
||||
// Reset backup domain
|
||||
RCC->BDCR |= RCC_BDCR_BDRST; |
||||
|
||||
// Disable write protection
|
||||
PWR->CR |= PWR_CR_DBP; |
||||
|
||||
// Clear backup domain reset
|
||||
RCC->BDCR &= ~(RCC_BDCR_BDRST); |
||||
|
||||
// Set RTC options
|
||||
RCC->BDCR = RCC_BDCR_OPTIONS | (RCC->BDCR & (~RCC_BDCR_MASK)); |
||||
|
||||
// Enable write protection
|
||||
PWR->CR &= ~(PWR_CR_DBP); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void rtc_set_time(timestamp_t time){ |
||||
if(board_has_rtc()){ |
||||
puts("Setting RTC time\n"); |
||||
|
||||
// Disable write protection
|
||||
PWR->CR |= PWR_CR_DBP; |
||||
RTC->WPR = 0xCA; |
||||
RTC->WPR = 0x53; |
||||
|
||||
// Enable initialization mode
|
||||
RTC->ISR |= RTC_ISR_INIT; |
||||
while((RTC->ISR & RTC_ISR_INITF) == 0){} |
||||
|
||||
// Set time
|
||||
RTC->TR = (to_bcd(time.hour) << RTC_TR_HU_Pos) | (to_bcd(time.minute) << RTC_TR_MNU_Pos) | (to_bcd(time.second) << RTC_TR_SU_Pos); |
||||
RTC->DR = (to_bcd(time.year - YEAR_OFFSET) << RTC_DR_YU_Pos) | (time.weekday << RTC_DR_WDU_Pos) | (to_bcd(time.month) << RTC_DR_MU_Pos) | (to_bcd(time.day) << RTC_DR_DU_Pos); |
||||
|
||||
// Set options
|
||||
RTC->CR = 0U; |
||||
|
||||
// Disable initalization mode
|
||||
RTC->ISR &= ~(RTC_ISR_INIT); |
||||
|
||||
// Wait for synchronization
|
||||
while((RTC->ISR & RTC_ISR_RSF) == 0){} |
||||
|
||||
// Re-enable write protection
|
||||
RTC->WPR = 0x00; |
||||
PWR->CR &= ~(PWR_CR_DBP); |
||||
} |
||||
} |
||||
|
||||
timestamp_t rtc_get_time(void){ |
||||
timestamp_t result; |
||||
// Init with zero values in case there is no RTC running
|
||||
result.year = 0U; |
||||
result.month = 0U; |
||||
result.day = 0U; |
||||
result.weekday = 0U; |
||||
result.hour = 0U; |
||||
result.minute = 0U; |
||||
result.second = 0U; |
||||
|
||||
if(board_has_rtc()){ |
||||
// Wait until the register sync flag is set
|
||||
while((RTC->ISR & RTC_ISR_RSF) == 0){} |
||||
|
||||
// Read time and date registers. Since our HSE > 7*LSE, this should be fine.
|
||||
uint32_t time = RTC->TR; |
||||
uint32_t date = RTC->DR; |
||||
|
||||
// Parse values
|
||||
result.year = from_bcd((date & (RTC_DR_YT | RTC_DR_YU)) >> RTC_DR_YU_Pos) + YEAR_OFFSET; |
||||
result.month = from_bcd((date & (RTC_DR_MT | RTC_DR_MU)) >> RTC_DR_MU_Pos); |
||||
result.day = from_bcd((date & (RTC_DR_DT | RTC_DR_DU)) >> RTC_DR_DU_Pos); |
||||
result.weekday = ((date & RTC_DR_WDU) >> RTC_DR_WDU_Pos); |
||||
result.hour = from_bcd((time & (RTC_TR_HT | RTC_TR_HU)) >> RTC_TR_HU_Pos); |
||||
result.minute = from_bcd((time & (RTC_TR_MNT | RTC_TR_MNU)) >> RTC_TR_MNU_Pos); |
||||
result.second = from_bcd((time & (RTC_TR_ST | RTC_TR_SU)) >> RTC_TR_SU_Pos); |
||||
} |
||||
return result; |
||||
} |
@ -1,3 +1,3 @@ |
||||
#!/bin/bash |
||||
sudo apt-get install gcc-arm-none-eabi python-pip |
||||
sudo pip2 install libusb1 pycrypto requests |
||||
sudo pip install libusb1 pycrypto requests |
||||
|
@ -0,0 +1,3 @@ |
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:3eeeb716701baca7c9aec136ada74ac29fa5731d188c1728f66b13e54a69ab64 |
||||
size 9482 |
@ -1,3 +1,3 @@ |
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6c7e75d09887cb3035f18bc6f4fdbd7392a50379cd556315deb3bcb762d99078 |
||||
size 40062 |
||||
oid sha256:9bf43ac370e82ed7c9cc9a9fb5b624276fa03d15f96103550c7ab3b043f048f2 |
||||
size 62628 |
||||
|
@ -0,0 +1,3 @@ |
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:d649eaa2fb1c421add02950ab9c111ccabb8be27197f3d37e03f03357e817ff8 |
||||
size 1677 |
@ -1,3 +1,3 @@ |
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:03a80f17ff18c5d0e3b234009f98698cd402b8ea972df687a6cfafd8b8a78654 |
||||
size 102146 |
||||
oid sha256:01793bb15ec31729c8d1d4422a2e86ce5e0728b6a8834b94f08ea46c4c43b3b5 |
||||
size 109421 |
||||
|
@ -1,3 +1,3 @@ |
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:83c5d601dd5640a863fe4dc43cd228b46239bbf9f07b51102bf0c1c8f235cdfa |
||||
size 112561 |
||||
oid sha256:dd13b2caea9d4490a5d49cdb86fbfa3e0d7b01343dc0ad8560c10a924626320e |
||||
size 120868 |
||||
|
@ -1,3 +0,0 @@ |
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:1fdd226d82bde2a90231ba47a59377158b8907e4cada2a6215f32dcf13ab35cd |
||||
size 3533 |
@ -1,3 +0,0 @@ |
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c9b8e36d05097da5d493f197aa7f81ce517f4532536925b568ccbd6e2c01eb8a |
||||
size 3549 |
@ -1,3 +0,0 @@ |
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:5d338d36730ccbd138126078e17f69c308a015f0a88d85bd639af072e527d53d |
||||
size 3566 |
@ -0,0 +1,3 @@ |
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:16660b4b9af5c288949272e55dfc463fd704acd7bbbe34f0fa6acbdea2d41c22 |
||||
size 11691 |
@ -0,0 +1,167 @@ |
||||
const int VOLKSWAGEN_MAX_STEER = 250; // 2.5 Nm (EPS side max of 3.0Nm with fault if violated)
|
||||
const int VOLKSWAGEN_MAX_RT_DELTA = 75; // 4 max rate up * 50Hz send rate * 250000 RT interval / 1000000 = 50 ; 50 * 1.5 for safety pad = 75
|
||||
const uint32_t VOLKSWAGEN_RT_INTERVAL = 250000; // 250ms between real time checks
|
||||
const int VOLKSWAGEN_MAX_RATE_UP = 4; // 2.0 Nm/s available rate of change from the steering rack (EPS side delta-limit of 5.0 Nm/s)
|
||||
const int VOLKSWAGEN_MAX_RATE_DOWN = 10; // 5.0 Nm/s available rate of change from the steering rack (EPS side delta-limit of 5.0 Nm/s)
|
||||
const int VOLKSWAGEN_DRIVER_TORQUE_ALLOWANCE = 80; |
||||
const int VOLKSWAGEN_DRIVER_TORQUE_FACTOR = 3; |
||||
|
||||
struct sample_t volkswagen_torque_driver; // last few driver torques measured
|
||||
int volkswagen_rt_torque_last = 0; |
||||
int volkswagen_desired_torque_last = 0; |
||||
uint32_t volkswagen_ts_last = 0; |
||||
int volkswagen_gas_prev = 0; |
||||
|
||||
// Safety-relevant CAN messages for the Volkswagen MQB platform.
|
||||
#define MSG_EPS_01 0x09F |
||||
#define MSG_MOTOR_20 0x121 |
||||
#define MSG_ACC_06 0x122 |
||||
#define MSG_HCA_01 0x126 |
||||
#define MSG_GRA_ACC_01 0x12B |
||||
#define MSG_LDW_02 0x397 |
||||
#define MSG_KLEMMEN_STATUS_01 0x3C0 |
||||
|
||||
static void volkswagen_init(int16_t param) { |
||||
UNUSED(param); // May use param in the future to indicate MQB vs PQ35/PQ46/NMS vs MLB, or wiring configuration.
|
||||
controls_allowed = 0; |
||||
} |
||||
|
||||
static void volkswagen_rx_hook(CAN_FIFOMailBox_TypeDef *to_push) { |
||||
int bus = GET_BUS(to_push); |
||||
int addr = GET_ADDR(to_push); |
||||
|
||||
// Update driver input torque samples from EPS_01.Driver_Strain for absolute torque, and EPS_01.Driver_Strain_VZ
|
||||
// for the direction.
|
||||
if ((bus == 0) && (addr == MSG_EPS_01)) { |
||||
int torque_driver_new = GET_BYTE(to_push, 5) | ((GET_BYTE(to_push, 6) & 0x1F) << 8); |
||||
int sign = (GET_BYTE(to_push, 6) & 0x80) >> 7; |
||||
if (sign == 1) { |
||||
torque_driver_new *= -1; |
||||
} |
||||
|
||||
update_sample(&volkswagen_torque_driver, torque_driver_new); |
||||
} |
||||
|
||||
// Monitor ACC_06.ACC_Status_ACC for stock ACC status. Because the current MQB port is lateral-only, OP's control
|
||||
// allowed state is directly driven by stock ACC engagement. Permit the ACC message to come from either bus, in
|
||||
// order to accommodate future camera-side integrations if needed.
|
||||
if (addr == MSG_ACC_06) { |
||||
int acc_status = (GET_BYTE(to_push, 7) & 0x70) >> 4; |
||||
controls_allowed = ((acc_status == 3) || (acc_status == 4) || (acc_status == 5)) ? 1 : 0; |
||||
} |
||||
|
||||
// exit controls on rising edge of gas press. Bits [12-20)
|
||||
if (addr == MSG_MOTOR_20) { |
||||
int gas = (GET_BYTES_04(to_push) >> 12) & 0xFF; |
||||
if ((gas > 0) && (volkswagen_gas_prev == 0) && long_controls_allowed) { |
||||
controls_allowed = 0; |
||||
} |
||||
volkswagen_gas_prev = gas; |
||||
} |
||||
} |
||||
|
||||
static int volkswagen_tx_hook(CAN_FIFOMailBox_TypeDef *to_send) { |
||||
int addr = GET_ADDR(to_send); |
||||
int bus = GET_BUS(to_send); |
||||
int tx = 1; |
||||
|
||||
// Safety check for HCA_01 Heading Control Assist torque.
|
||||
if (addr == MSG_HCA_01) { |
||||
bool violation = false; |
||||
|
||||
int desired_torque = GET_BYTE(to_send, 2) | ((GET_BYTE(to_send, 3) & 0x3F) << 8); |
||||
int sign = (GET_BYTE(to_send, 3) & 0x80) >> 7; |
||||
if (sign == 1) { |
||||
desired_torque *= -1; |
||||
} |
||||
|
||||
uint32_t ts = TIM2->CNT; |
||||
|
||||
if (controls_allowed) { |
||||
|
||||
// *** global torque limit check ***
|
||||
violation |= max_limit_check(desired_torque, VOLKSWAGEN_MAX_STEER, -VOLKSWAGEN_MAX_STEER); |
||||
|
||||
// *** torque rate limit check ***
|
||||
violation |= driver_limit_check(desired_torque, volkswagen_desired_torque_last, &volkswagen_torque_driver, |
||||
VOLKSWAGEN_MAX_STEER, VOLKSWAGEN_MAX_RATE_UP, VOLKSWAGEN_MAX_RATE_DOWN, |
||||
VOLKSWAGEN_DRIVER_TORQUE_ALLOWANCE, VOLKSWAGEN_DRIVER_TORQUE_FACTOR); |
||||
volkswagen_desired_torque_last = desired_torque; |
||||
|
||||
// *** torque real time rate limit check ***
|
||||
violation |= rt_rate_limit_check(desired_torque, volkswagen_rt_torque_last, VOLKSWAGEN_MAX_RT_DELTA); |
||||
|
||||
// every RT_INTERVAL set the new limits
|
||||
uint32_t ts_elapsed = get_ts_elapsed(ts, volkswagen_ts_last); |
||||
if (ts_elapsed > VOLKSWAGEN_RT_INTERVAL) { |
||||
volkswagen_rt_torque_last = desired_torque; |
||||
volkswagen_ts_last = ts; |
||||
} |
||||
} |
||||
|
||||
// no torque if controls is not allowed
|
||||
if (!controls_allowed && (desired_torque != 0)) { |
||||
violation = true; |
||||
} |
||||
|
||||
// reset to 0 if either controls is not allowed or there's a violation
|
||||
if (violation || !controls_allowed) { |
||||
volkswagen_desired_torque_last = 0; |
||||
volkswagen_rt_torque_last = 0; |
||||
volkswagen_ts_last = ts; |
||||
} |
||||
|
||||
if (violation) { |
||||
tx = 0; |
||||
} |
||||
} |
||||
|
||||
// FORCE CANCEL: ensuring that only the cancel button press is sent when controls are off.
|
||||
// This avoids unintended engagements while still allowing resume spam
|
||||
if ((bus == 2) && (addr == MSG_GRA_ACC_01) && !controls_allowed) { |
||||
// disallow resume and set: bits 16 and 19
|
||||
if ((GET_BYTE(to_send, 2) & 0x9) != 0) { |
||||
tx = 0; |
||||
} |
||||
} |
||||
|
||||
// 1 allows the message through
|
||||
return tx; |
||||
} |
||||
|
||||
static int volkswagen_fwd_hook(int bus_num, CAN_FIFOMailBox_TypeDef *to_fwd) { |
||||
int addr = GET_ADDR(to_fwd); |
||||
int bus_fwd = -1; |
||||
|
||||
// NOTE: Will need refactoring for other bus layouts, such as no-forwarding at camera or J533 running-gear CAN
|
||||
|
||||
switch (bus_num) { |
||||
case 0: |
||||
// Forward all traffic from J533 gateway to Extended CAN devices.
|
||||
bus_fwd = 2; |
||||
break; |
||||
case 2: |
||||
if ((addr == MSG_HCA_01) || (addr == MSG_LDW_02)) { |
||||
// OP takes control of the Heading Control Assist and Lane Departure Warning messages from the camera.
|
||||
bus_fwd = -1; |
||||
} else { |
||||
// Forward all remaining traffic from Extended CAN devices to J533 gateway.
|
||||
bus_fwd = 0; |
||||
} |
||||
break; |
||||
default: |
||||
// No other buses should be in use; fallback to do-not-forward.
|
||||
bus_fwd = -1; |
||||
break; |
||||
} |
||||
|
||||
return bus_fwd; |
||||
} |
||||
|
||||
const safety_hooks volkswagen_hooks = { |
||||
.init = volkswagen_init, |
||||
.rx = volkswagen_rx_hook, |
||||
.tx = volkswagen_tx_hook, |
||||
.tx_lin = nooutput_tx_lin_hook, |
||||
.fwd = volkswagen_fwd_hook, |
||||
}; |
@ -0,0 +1,59 @@ |
||||
#!/usr/bin/env python3 |
||||
from panda import Panda |
||||
from panda.python.uds import UdsClient, NegativeResponseError, DATA_IDENTIFIER_TYPE |
||||
|
||||
if __name__ == "__main__": |
||||
address = 0x18da30f1 # Honda EPS |
||||
panda = Panda() |
||||
uds_client = UdsClient(panda, address, debug=False) |
||||
|
||||
print("tester present ...") |
||||
uds_client.tester_present() |
||||
|
||||
try: |
||||
print("") |
||||
print("read data by id: boot software id ...") |
||||
data = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.BOOT_SOFTWARE_IDENTIFICATION) |
||||
print(data.decode('utf-8')) |
||||
except NegativeResponseError as e: |
||||
print(e) |
||||
|
||||
try: |
||||
print("") |
||||
print("read data by id: application software id ...") |
||||
data = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION) |
||||
print(data.decode('utf-8')) |
||||
except NegativeResponseError as e: |
||||
print(e) |
||||
|
||||
try: |
||||
print("") |
||||
print("read data by id: application data id ...") |
||||
data = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.APPLICATION_DATA_IDENTIFICATION) |
||||
print(data.decode('utf-8')) |
||||
except NegativeResponseError as e: |
||||
print(e) |
||||
|
||||
try: |
||||
print("") |
||||
print("read data by id: boot software fingerprint ...") |
||||
data = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.BOOT_SOFTWARE_FINGERPRINT) |
||||
print(data.decode('utf-8')) |
||||
except NegativeResponseError as e: |
||||
print(e) |
||||
|
||||
try: |
||||
print("") |
||||
print("read data by id: application software fingerprint ...") |
||||
data = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_FINGERPRINT) |
||||
print(data.decode('utf-8')) |
||||
except NegativeResponseError as e: |
||||
print(e) |
||||
|
||||
try: |
||||
print("") |
||||
print("read data by id: application data fingerprint ...") |
||||
data = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.APPLICATION_DATA_FINGERPRINT) |
||||
print(data.decode('utf-8')) |
||||
except NegativeResponseError as e: |
||||
print(e) |
@ -0,0 +1,770 @@ |
||||
#!/usr/bin/env python3 |
||||
import time |
||||
import struct |
||||
from typing import NamedTuple, List |
||||
from enum import IntEnum |
||||
from queue import Queue, Empty |
||||
from threading import Thread |
||||
from binascii import hexlify |
||||
|
||||
class SERVICE_TYPE(IntEnum): |
||||
DIAGNOSTIC_SESSION_CONTROL = 0x10 |
||||
ECU_RESET = 0x11 |
||||
SECURITY_ACCESS = 0x27 |
||||
COMMUNICATION_CONTROL = 0x28 |
||||
TESTER_PRESENT = 0x3E |
||||
ACCESS_TIMING_PARAMETER = 0x83 |
||||
SECURED_DATA_TRANSMISSION = 0x84 |
||||
CONTROL_DTC_SETTING = 0x85 |
||||
RESPONSE_ON_EVENT = 0x86 |
||||
LINK_CONTROL = 0x87 |
||||
READ_DATA_BY_IDENTIFIER = 0x22 |
||||
READ_MEMORY_BY_ADDRESS = 0x23 |
||||
READ_SCALING_DATA_BY_IDENTIFIER = 0x24 |
||||
READ_DATA_BY_PERIODIC_IDENTIFIER = 0x2A |
||||
DYNAMICALLY_DEFINE_DATA_IDENTIFIER = 0x2C |
||||
WRITE_DATA_BY_IDENTIFIER = 0x2E |
||||
WRITE_MEMORY_BY_ADDRESS = 0x3D |
||||
CLEAR_DIAGNOSTIC_INFORMATION = 0x14 |
||||
READ_DTC_INFORMATION = 0x19 |
||||
INPUT_OUTPUT_CONTROL_BY_IDENTIFIER = 0x2F |
||||
ROUTINE_CONTROL = 0x31 |
||||
REQUEST_DOWNLOAD = 0x34 |
||||
REQUEST_UPLOAD = 0x35 |
||||
TRANSFER_DATA = 0x36 |
||||
REQUEST_TRANSFER_EXIT = 0x37 |
||||
|
||||
class SESSION_TYPE(IntEnum): |
||||
DEFAULT = 1 |
||||
PROGRAMMING = 2 |
||||
EXTENDED_DIAGNOSTIC = 3 |
||||
SAFETY_SYSTEM_DIAGNOSTIC = 4 |
||||
|
||||
class RESET_TYPE(IntEnum): |
||||
HARD = 1 |
||||
KEY_OFF_ON = 2 |
||||
SOFT = 3 |
||||
ENABLE_RAPID_POWER_SHUTDOWN = 4 |
||||
DISABLE_RAPID_POWER_SHUTDOWN = 5 |
||||
|
||||
class ACCESS_TYPE(IntEnum): |
||||
REQUEST_SEED = 1 |
||||
SEND_KEY = 2 |
||||
|
||||
class CONTROL_TYPE(IntEnum): |
||||
ENABLE_RX_ENABLE_TX = 0 |
||||
ENABLE_RX_DISABLE_TX = 1 |
||||
DISABLE_RX_ENABLE_TX = 2 |
||||
DISABLE_RX_DISABLE_TX = 3 |
||||
|
||||
class MESSAGE_TYPE(IntEnum): |
||||
NORMAL = 1 |
||||
NETWORK_MANAGEMENT = 2 |
||||
NORMAL_AND_NETWORK_MANAGEMENT = 3 |
||||
|
||||
class TIMING_PARAMETER_TYPE(IntEnum): |
||||
READ_EXTENDED_SET = 1 |
||||
SET_TO_DEFAULT_VALUES = 2 |
||||
READ_CURRENTLY_ACTIVE = 3 |
||||
SET_TO_GIVEN_VALUES = 4 |
||||
|
||||
class DTC_SETTING_TYPE(IntEnum): |
||||
ON = 1 |
||||
OFF = 2 |
||||
|
||||
class RESPONSE_EVENT_TYPE(IntEnum): |
||||
STOP_RESPONSE_ON_EVENT = 0 |
||||
ON_DTC_STATUS_CHANGE = 1 |
||||
ON_TIMER_INTERRUPT = 2 |
||||
ON_CHANGE_OF_DATA_IDENTIFIER = 3 |
||||
REPORT_ACTIVATED_EVENTS = 4 |
||||
START_RESPONSE_ON_EVENT = 5 |
||||
CLEAR_RESPONSE_ON_EVENT = 6 |
||||
ON_COMPARISON_OF_VALUES = 7 |
||||
|
||||
class LINK_CONTROL_TYPE(IntEnum): |
||||
VERIFY_BAUDRATE_TRANSITION_WITH_FIXED_BAUDRATE = 1 |
||||
VERIFY_BAUDRATE_TRANSITION_WITH_SPECIFIC_BAUDRATE = 2 |
||||
TRANSITION_BAUDRATE = 3 |
||||
|
||||
class BAUD_RATE_TYPE(IntEnum): |
||||
PC9600 = 1 |
||||
PC19200 = 2 |
||||
PC38400 = 3 |
||||
PC57600 = 4 |
||||
PC115200 = 5 |
||||
CAN125000 = 16 |
||||
CAN250000 = 17 |
||||
CAN500000 = 18 |
||||
CAN1000000 = 19 |
||||
|
||||
class DATA_IDENTIFIER_TYPE(IntEnum): |
||||
BOOT_SOFTWARE_IDENTIFICATION = 0xF180 |
||||
APPLICATION_SOFTWARE_IDENTIFICATION = 0xF181 |
||||
APPLICATION_DATA_IDENTIFICATION = 0xF182 |
||||
BOOT_SOFTWARE_FINGERPRINT = 0xF183 |
||||
APPLICATION_SOFTWARE_FINGERPRINT = 0xF184 |
||||
APPLICATION_DATA_FINGERPRINT = 0xF185 |
||||
ACTIVE_DIAGNOSTIC_SESSION = 0xF186 |
||||
VEHICLE_MANUFACTURER_SPARE_PART_NUMBER = 0xF187 |
||||
VEHICLE_MANUFACTURER_ECU_SOFTWARE_NUMBER = 0xF188 |
||||
VEHICLE_MANUFACTURER_ECU_SOFTWARE_VERSION_NUMBER = 0xF189 |
||||
SYSTEM_SUPPLIER_IDENTIFIER = 0xF18A |
||||
ECU_MANUFACTURING_DATE = 0xF18B |
||||
ECU_SERIAL_NUMBER = 0xF18C |
||||
SUPPORTED_FUNCTIONAL_UNITS = 0xF18D |
||||
VEHICLE_MANUFACTURER_KIT_ASSEMBLY_PART_NUMBER = 0xF18E |
||||
VIN = 0xF190 |
||||
VEHICLE_MANUFACTURER_ECU_HARDWARE_NUMBER = 0xF191 |
||||
SYSTEM_SUPPLIER_ECU_HARDWARE_NUMBER = 0xF192 |
||||
SYSTEM_SUPPLIER_ECU_HARDWARE_VERSION_NUMBER = 0xF193 |
||||
SYSTEM_SUPPLIER_ECU_SOFTWARE_NUMBER = 0xF194 |
||||
SYSTEM_SUPPLIER_ECU_SOFTWARE_VERSION_NUMBER = 0xF195 |
||||
EXHAUST_REGULATION_OR_TYPE_APPROVAL_NUMBER = 0xF196 |
||||
SYSTEM_NAME_OR_ENGINE_TYPE = 0xF197 |
||||
REPAIR_SHOP_CODE_OR_TESTER_SERIAL_NUMBER = 0xF198 |
||||
PROGRAMMING_DATE = 0xF199 |
||||
CALIBRATION_REPAIR_SHOP_CODE_OR_CALIBRATION_EQUIPMENT_SERIAL_NUMBER = 0xF19A |
||||
CALIBRATION_DATE = 0xF19B |
||||
CALIBRATION_EQUIPMENT_SOFTWARE_NUMBER = 0xF19C |
||||
ECU_INSTALLATION_DATE = 0xF19D |
||||
ODX_FILE = 0xF19E |
||||
ENTITY = 0xF19F |
||||
|
||||
class TRANSMISSION_MODE_TYPE(IntEnum): |
||||
SEND_AT_SLOW_RATE = 1 |
||||
SEND_AT_MEDIUM_RATE = 2 |
||||
SEND_AT_FAST_RATE = 3 |
||||
STOP_SENDING = 4 |
||||
|
||||
class DYNAMIC_DEFINITION_TYPE(IntEnum): |
||||
DEFINE_BY_IDENTIFIER = 1 |
||||
DEFINE_BY_MEMORY_ADDRESS = 2 |
||||
CLEAR_DYNAMICALLY_DEFINED_DATA_IDENTIFIER = 3 |
||||
|
||||
class DynamicSourceDefinition(NamedTuple): |
||||
data_identifier: int |
||||
position: int |
||||
memory_size: int |
||||
memory_address: int |
||||
|
||||
class DTC_GROUP_TYPE(IntEnum): |
||||
EMISSIONS = 0x000000 |
||||
ALL = 0xFFFFFF |
||||
|
||||
class DTC_REPORT_TYPE(IntEnum): |
||||
NUMBER_OF_DTC_BY_STATUS_MASK = 0x01 |
||||
DTC_BY_STATUS_MASK = 0x02 |
||||
DTC_SNAPSHOT_IDENTIFICATION = 0x03 |
||||
DTC_SNAPSHOT_RECORD_BY_DTC_NUMBER = 0x04 |
||||
DTC_SNAPSHOT_RECORD_BY_RECORD_NUMBER = 0x05 |
||||
DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER = 0x06 |
||||
NUMBER_OF_DTC_BY_SEVERITY_MASK_RECORD = 0x07 |
||||
DTC_BY_SEVERITY_MASK_RECORD = 0x08 |
||||
SEVERITY_INFORMATION_OF_DTC = 0x09 |
||||
SUPPORTED_DTC = 0x0A |
||||
FIRST_TEST_FAILED_DTC = 0x0B |
||||
FIRST_CONFIRMED_DTC = 0x0C |
||||
MOST_RECENT_TEST_FAILED_DTC = 0x0D |
||||
MOST_RECENT_CONFIRMED_DTC = 0x0E |
||||
MIRROR_MEMORY_DTC_BY_STATUS_MASK = 0x0F |
||||
MIRROR_MEMORY_DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER = 0x10 |
||||
NUMBER_OF_MIRROR_MEMORY_DTC_BY_STATUS_MASK = 0x11 |
||||
NUMBER_OF_EMISSIONS_RELATED_OBD_DTC_BY_STATUS_MASK = 0x12 |
||||
EMISSIONS_RELATED_OBD_DTC_BY_STATUS_MASK = 0x13 |
||||
DTC_FAULT_DETECTION_COUNTER = 0x14 |
||||
DTC_WITH_PERMANENT_STATUS = 0x15 |
||||
|
||||
class DTC_STATUS_MASK_TYPE(IntEnum): |
||||
TEST_FAILED = 0x01 |
||||
TEST_FAILED_THIS_OPERATION_CYCLE = 0x02 |
||||
PENDING_DTC = 0x04 |
||||
CONFIRMED_DTC = 0x08 |
||||
TEST_NOT_COMPLETED_SINCE_LAST_CLEAR = 0x10 |
||||
TEST_FAILED_SINCE_LAST_CLEAR = 0x20 |
||||
TEST_NOT_COMPLETED_THIS_OPERATION_CYCLE = 0x40 |
||||
WARNING_INDICATOR_REQUESTED = 0x80 |
||||
ALL = 0xFF |
||||
|
||||
class DTC_SEVERITY_MASK_TYPE(IntEnum): |
||||
MAINTENANCE_ONLY = 0x20 |
||||
CHECK_AT_NEXT_HALT = 0x40 |
||||
CHECK_IMMEDIATELY = 0x80 |
||||
ALL = 0xE0 |
||||
|
||||
class CONTROL_PARAMETER_TYPE(IntEnum): |
||||
RETURN_CONTROL_TO_ECU = 0 |
||||
RESET_TO_DEFAULT = 1 |
||||
FREEZE_CURRENT_STATE = 2 |
||||
SHORT_TERM_ADJUSTMENT = 3 |
||||
|
||||
class ROUTINE_CONTROL_TYPE(IntEnum): |
||||
START = 1 |
||||
STOP = 2 |
||||
REQUEST_RESULTS = 3 |
||||
|
||||
class ROUTINE_IDENTIFIER_TYPE(IntEnum): |
||||
ERASE_MEMORY = 0xFF00 |
||||
CHECK_PROGRAMMING_DEPENDENCIES = 0xFF01 |
||||
ERASE_MIRROR_MEMORY_DTCS = 0xFF02 |
||||
|
||||
class MessageTimeoutError(Exception): |
||||
pass |
||||
|
||||
class NegativeResponseError(Exception): |
||||
def __init__(self, message, service_id, error_code): |
||||
self.message = message |
||||
self.service_id = service_id |
||||
self.error_code = error_code |
||||
|
||||
def __str__(self): |
||||
return self.message |
||||
|
||||
class InvalidServiceIdError(Exception): |
||||
pass |
||||
|
||||
class InvalidSubFunctioneError(Exception): |
||||
pass |
||||
|
||||
_negative_response_codes = { |
||||
0x00: 'positive response', |
||||
0x10: 'general reject', |
||||
0x11: 'service not supported', |
||||
0x12: 'sub-function not supported', |
||||
0x13: 'incorrect message length or invalid format', |
||||
0x14: 'response too long', |
||||
0x21: 'busy repeat request', |
||||
0x22: 'conditions not correct', |
||||
0x24: 'request sequence error', |
||||
0x25: 'no response from subnet component', |
||||
0x26: 'failure prevents execution of requested action', |
||||
0x31: 'request out of range', |
||||
0x33: 'security access denied', |
||||
0x35: 'invalid key', |
||||
0x36: 'exceed numebr of attempts', |
||||
0x37: 'required time delay not expired', |
||||
0x70: 'upload download not accepted', |
||||
0x71: 'transfer data suspended', |
||||
0x72: 'general programming failure', |
||||
0x73: 'wrong block sequence counter', |
||||
0x78: 'request correctly received - response pending', |
||||
0x7e: 'sub-function not supported in active session', |
||||
0x7f: 'service not supported in active session', |
||||
0x81: 'rpm too high', |
||||
0x82: 'rpm too low', |
||||
0x83: 'engine is running', |
||||
0x84: 'engine is not running', |
||||
0x85: 'engine run time too low', |
||||
0x86: 'temperature too high', |
||||
0x87: 'temperature too low', |
||||
0x88: 'vehicle speed too high', |
||||
0x89: 'vehicle speed too low', |
||||
0x8a: 'throttle/pedal too high', |
||||
0x8b: 'throttle/pedal too low', |
||||
0x8c: 'transmission not in neutral', |
||||
0x8d: 'transmission not in gear', |
||||
0x8f: 'brake switch(es) not closed', |
||||
0x90: 'shifter lever not in park', |
||||
0x91: 'torque converter clutch locked', |
||||
0x92: 'voltage too high', |
||||
0x93: 'voltage too low', |
||||
} |
||||
|
||||
class IsoTpMessage(): |
||||
def __init__(self, can_tx_queue: Queue, can_rx_queue: Queue, timeout: float, debug: bool=False): |
||||
self.can_tx_queue = can_tx_queue |
||||
self.can_rx_queue = can_rx_queue |
||||
self.timeout = timeout |
||||
self.debug = debug |
||||
|
||||
def send(self, dat: bytes) -> None: |
||||
self.tx_dat = dat |
||||
self.tx_len = len(dat) |
||||
self.tx_idx = 0 |
||||
self.tx_done = False |
||||
|
||||
if self.debug: print(f"ISO-TP: REQUEST - {hexlify(self.tx_dat)}") |
||||
self._tx_first_frame() |
||||
|
||||
def _tx_first_frame(self) -> None: |
||||
if self.tx_len < 8: |
||||
# single frame (send all bytes) |
||||
if self.debug: print("ISO-TP: TX - single frame") |
||||
msg = (bytes([self.tx_len]) + self.tx_dat).ljust(8, b"\x00") |
||||
self.tx_done = True |
||||
else: |
||||
# first frame (send first 6 bytes) |
||||
if self.debug: print("ISO-TP: TX - first frame") |
||||
msg = (struct.pack("!H", 0x1000 | self.tx_len) + self.tx_dat[:6]).ljust(8, b"\x00") |
||||
self.can_tx_queue.put(msg) |
||||
|
||||
def recv(self) -> bytes: |
||||
self.rx_dat = b"" |
||||
self.rx_len = 0 |
||||
self.rx_idx = 0 |
||||
self.rx_done = False |
||||
|
||||
try: |
||||
while True: |
||||
self._isotp_rx_next() |
||||
if self.tx_done and self.rx_done: |
||||
return self.rx_dat |
||||
except Empty: |
||||
raise MessageTimeoutError("timeout waiting for response") |
||||
finally: |
||||
if self.debug: print(f"ISO-TP: RESPONSE - {hexlify(self.rx_dat)}") |
||||
|
||||
def _isotp_rx_next(self) -> None: |
||||
rx_data = self.can_rx_queue.get(block=True, timeout=self.timeout) |
||||
|
||||
# single rx_frame |
||||
if rx_data[0] >> 4 == 0x0: |
||||
self.rx_len = rx_data[0] & 0xFF |
||||
self.rx_dat = rx_data[1:1+self.rx_len] |
||||
self.rx_idx = 0 |
||||
self.rx_done = True |
||||
if self.debug: print(f"ISO-TP: RX - single frame - idx={self.rx_idx} done={self.rx_done}") |
||||
return |
||||
|
||||
# first rx_frame |
||||
if rx_data[0] >> 4 == 0x1: |
||||
self.rx_len = ((rx_data[0] & 0x0F) << 8) + rx_data[1] |
||||
self.rx_dat = rx_data[2:] |
||||
self.rx_idx = 0 |
||||
self.rx_done = False |
||||
if self.debug: print(f"ISO-TP: RX - first frame - idx={self.rx_idx} done={self.rx_done}") |
||||
if self.debug: print(f"ISO-TP: TX - flow control continue") |
||||
# send flow control message (send all bytes) |
||||
msg = b"\x30\x00\x00".ljust(8, b"\x00") |
||||
self.can_tx_queue.put(msg) |
||||
return |
||||
|
||||
# consecutive rx frame |
||||
if rx_data[0] >> 4 == 0x2: |
||||
assert self.rx_done == False, "isotp - rx: consecutive frame with no active frame" |
||||
self.rx_idx += 1 |
||||
assert self.rx_idx & 0xF == rx_data[0] & 0xF, "isotp - rx: invalid consecutive frame index" |
||||
rx_size = self.rx_len - len(self.rx_dat) |
||||
self.rx_dat += rx_data[1:1+min(rx_size, 7)] |
||||
if self.rx_len == len(self.rx_dat): |
||||
self.rx_done = True |
||||
if self.debug: print(f"ISO-TP: RX - consecutive frame - idx={self.rx_idx} done={self.rx_done}") |
||||
return |
||||
|
||||
# flow control |
||||
if rx_data[0] >> 4 == 0x3: |
||||
assert self.tx_done == False, "isotp - rx: flow control with no active frame" |
||||
assert rx_data[0] != 0x32, "isotp - rx: flow-control overflow/abort" |
||||
assert rx_data[0] == 0x30 or rx_data[0] == 0x31, "isotp - rx: flow-control transfer state indicator invalid" |
||||
if rx_data[0] == 0x30: |
||||
if self.debug: print("ISO-TP: RX - flow control continue") |
||||
delay_ts = rx_data[2] & 0x7F |
||||
# scale is 1 milliseconds if first bit == 0, 100 micro seconds if first bit == 1 |
||||
delay_div = 1000. if rx_data[2] & 0x80 == 0 else 10000. |
||||
# first frame = 6 bytes, each consecutive frame = 7 bytes |
||||
start = 6 + self.tx_idx * 7 |
||||
count = rx_data[1] |
||||
end = start + count * 7 if count > 0 else self.tx_len |
||||
for i in range(start, end, 7): |
||||
if delay_ts > 0 and i > start: |
||||
delay_s = delay_ts / delay_div |
||||
if self.debug: print(f"ISO-TP: TX - delay - seconds={delay_s}") |
||||
time.sleep(delay_s) |
||||
self.tx_idx += 1 |
||||
# consecutive tx frames |
||||
msg = (bytes([0x20 | (self.tx_idx & 0xF)]) + self.tx_dat[i:i+7]).ljust(8, b"\x00") |
||||
self.can_tx_queue.put(msg) |
||||
if end >= self.tx_len: |
||||
self.tx_done = True |
||||
if self.debug: print(f"ISO-TP: TX - consecutive frame - idx={self.tx_idx} done={self.tx_done}") |
||||
elif rx_data[0] == 0x31: |
||||
# wait (do nothing until next flow control message) |
||||
if self.debug: print("ISO-TP: TX - flow control wait") |
||||
|
||||
class UdsClient(): |
||||
def __init__(self, panda, tx_addr: int, rx_addr: int=None, bus: int=0, timeout: float=10, debug: bool=False): |
||||
self.panda = panda |
||||
self.bus = bus |
||||
self.tx_addr = tx_addr |
||||
if rx_addr == None: |
||||
if tx_addr < 0xFFF8: |
||||
# standard 11 bit response addr (add 8) |
||||
self.rx_addr = tx_addr+8 |
||||
elif tx_addr > 0x10000000 and tx_addr < 0xFFFFFFFF: |
||||
# standard 29 bit response addr (flip last two bytes) |
||||
self.rx_addr = (tx_addr & 0xFFFF0000) + (tx_addr<<8 & 0xFF00) + (tx_addr>>8 & 0xFF) |
||||
else: |
||||
raise ValueError("invalid tx_addr: {}".format(tx_addr)) |
||||
|
||||
self.can_tx_queue = Queue() |
||||
self.can_rx_queue = Queue() |
||||
self.timeout = timeout |
||||
self.debug = debug |
||||
|
||||
self.can_thread = Thread(target=self._can_thread, args=(self.debug,)) |
||||
self.can_thread.daemon = True |
||||
self.can_thread.start() |
||||
|
||||
def _can_thread(self, debug: bool=False): |
||||
try: |
||||
# allow all output |
||||
self.panda.set_safety_mode(0x1337) |
||||
# clear tx buffer |
||||
self.panda.can_clear(self.bus) |
||||
# clear rx buffer |
||||
self.panda.can_clear(0xFFFF) |
||||
|
||||
while True: |
||||
# send |
||||
while not self.can_tx_queue.empty(): |
||||
msg = self.can_tx_queue.get(block=False) |
||||
if debug: print("CAN-TX: {} - {}".format(hex(self.tx_addr), hexlify(msg))) |
||||
self.panda.can_send(self.tx_addr, msg, self.bus) |
||||
|
||||
# receive |
||||
msgs = self.panda.can_recv() |
||||
for rx_addr, rx_ts, rx_data, rx_bus in msgs: |
||||
if rx_bus != self.bus or rx_addr != self.rx_addr or len(rx_data) == 0: |
||||
continue |
||||
if debug: print("CAN-RX: {} - {}".format(hex(self.rx_addr), hexlify(rx_data))) |
||||
self.can_rx_queue.put(rx_data) |
||||
else: |
||||
time.sleep(0.01) |
||||
finally: |
||||
self.panda.close() |
||||
|
||||
# generic uds request |
||||
def _uds_request(self, service_type: SERVICE_TYPE, subfunction: int=None, data: bytes=None) -> bytes: |
||||
req = bytes([service_type]) |
||||
if subfunction is not None: |
||||
req += bytes([subfunction]) |
||||
if data is not None: |
||||
req += data |
||||
|
||||
# send request, wait for response |
||||
isotp_msg = IsoTpMessage(self.can_tx_queue, self.can_rx_queue, self.timeout, self.debug) |
||||
isotp_msg.send(req) |
||||
while True: |
||||
resp = isotp_msg.recv() |
||||
resp_sid = resp[0] if len(resp) > 0 else None |
||||
|
||||
# negative response |
||||
if resp_sid == 0x7F: |
||||
service_id = resp[1] if len(resp) > 1 else -1 |
||||
try: |
||||
service_desc = SERVICE_TYPE(service_id).name |
||||
except Exception: |
||||
service_desc = 'NON_STANDARD_SERVICE' |
||||
error_code = resp[2] if len(resp) > 2 else -1 |
||||
try: |
||||
error_desc = _negative_response_codes[error_code] |
||||
except Exception: |
||||
error_desc = resp[3:] |
||||
# wait for another message if response pending |
||||
if error_code == 0x78: |
||||
if self.debug: print("UDS-RX: response pending") |
||||
continue |
||||
raise NegativeResponseError('{} - {}'.format(service_desc, error_desc), service_id, error_code) |
||||
|
||||
# positive response |
||||
if service_type+0x40 != resp_sid: |
||||
resp_sid_hex = hex(resp_sid) if resp_sid is not None else None |
||||
raise InvalidServiceIdError('invalid response service id: {}'.format(resp_sid_hex)) |
||||
|
||||
if subfunction is not None: |
||||
resp_sfn = resp[1] if len(resp) > 1 else None |
||||
if subfunction != resp_sfn: |
||||
resp_sfn_hex = hex(resp_sfn) if resp_sfn is not None else None |
||||
raise InvalidSubFunctioneError('invalid response subfunction: {}'.format(hex(resp_sfn_hex))) |
||||
|
||||
# return data (exclude service id and sub-function id) |
||||
return resp[(1 if subfunction is None else 2):] |
||||
|
||||
# services |
||||
def diagnostic_session_control(self, session_type: SESSION_TYPE): |
||||
self._uds_request(SERVICE_TYPE.DIAGNOSTIC_SESSION_CONTROL, subfunction=session_type) |
||||
|
||||
def ecu_reset(self, reset_type: RESET_TYPE): |
||||
resp = self._uds_request(SERVICE_TYPE.ECU_RESET, subfunction=reset_type) |
||||
power_down_time = None |
||||
if reset_type == RESET_TYPE.ENABLE_RAPID_POWER_SHUTDOWN: |
||||
power_down_time = resp[0] |
||||
return power_down_time |
||||
|
||||
def security_access(self, access_type: ACCESS_TYPE, security_key: bytes=None): |
||||
request_seed = access_type % 2 != 0 |
||||
if request_seed and security_key is not None: |
||||
raise ValueError('security_key not allowed') |
||||
if not request_seed and security_key is None: |
||||
raise ValueError('security_key is missing') |
||||
resp = self._uds_request(SERVICE_TYPE.SECURITY_ACCESS, subfunction=access_type, data=security_key) |
||||
if request_seed: |
||||
security_seed = resp |
||||
return security_seed |
||||
|
||||
def communication_control(self, control_type: CONTROL_TYPE, message_type: MESSAGE_TYPE): |
||||
data = bytes([message_type]) |
||||
self._uds_request(SERVICE_TYPE.COMMUNICATION_CONTROL, subfunction=control_type, data=data) |
||||
|
||||
def tester_present(self, ): |
||||
self._uds_request(SERVICE_TYPE.TESTER_PRESENT, subfunction=0x00) |
||||
|
||||
def access_timing_parameter(self, timing_parameter_type: TIMING_PARAMETER_TYPE, parameter_values: bytes=None): |
||||
write_custom_values = timing_parameter_type == TIMING_PARAMETER_TYPE.SET_TO_GIVEN_VALUES |
||||
read_values = ( |
||||
timing_parameter_type == TIMING_PARAMETER_TYPE.READ_CURRENTLY_ACTIVE or |
||||
timing_parameter_type == TIMING_PARAMETER_TYPE.READ_EXTENDED_SET |
||||
) |
||||
if not write_custom_values and parameter_values is not None: |
||||
raise ValueError('parameter_values not allowed') |
||||
if write_custom_values and parameter_values is None: |
||||
raise ValueError('parameter_values is missing') |
||||
resp = self._uds_request(SERVICE_TYPE.ACCESS_TIMING_PARAMETER, subfunction=timing_parameter_type, data=parameter_values) |
||||
if read_values: |
||||
# TODO: parse response into values? |
||||
parameter_values = resp |
||||
return parameter_values |
||||
|
||||
def secured_data_transmission(self, data: bytes): |
||||
# TODO: split data into multiple input parameters? |
||||
resp = self._uds_request(SERVICE_TYPE.SECURED_DATA_TRANSMISSION, subfunction=None, data=data) |
||||
# TODO: parse response into multiple output values? |
||||
return resp |
||||
|
||||
def control_dtc_setting(self, dtc_setting_type: DTC_SETTING_TYPE): |
||||
self._uds_request(SERVICE_TYPE.CONTROL_DTC_SETTING, subfunction=dtc_setting_type) |
||||
|
||||
def response_on_event(self, response_event_type: RESPONSE_EVENT_TYPE, store_event: bool, window_time: int, event_type_record: int, service_response_record: int): |
||||
if store_event: |
||||
response_event_type |= 0x20 |
||||
# TODO: split record parameters into arrays |
||||
data = bytes([window_time, event_type_record, service_response_record]) |
||||
resp = self._uds_request(SERVICE_TYPE.RESPONSE_ON_EVENT, subfunction=response_event_type, data=data) |
||||
|
||||
if response_event_type == RESPONSE_EVENT_TYPE.REPORT_ACTIVATED_EVENTS: |
||||
return { |
||||
"num_of_activated_events": resp[0], |
||||
"data": resp[1:], # TODO: parse the reset of response |
||||
} |
||||
|
||||
return { |
||||
"num_of_identified_events": resp[0], |
||||
"event_window_time": resp[1], |
||||
"data": resp[2:], # TODO: parse the reset of response |
||||
} |
||||
|
||||
def link_control(self, link_control_type: LINK_CONTROL_TYPE, baud_rate_type: BAUD_RATE_TYPE=None): |
||||
if link_control_type == LINK_CONTROL_TYPE.VERIFY_BAUDRATE_TRANSITION_WITH_FIXED_BAUDRATE: |
||||
# baud_rate_type = BAUD_RATE_TYPE |
||||
data = bytes([baud_rate_type]) |
||||
elif link_control_type == LINK_CONTROL_TYPE.VERIFY_BAUDRATE_TRANSITION_WITH_SPECIFIC_BAUDRATE: |
||||
# baud_rate_type = custom value (3 bytes big-endian) |
||||
data = struct.pack('!I', baud_rate_type)[1:] |
||||
else: |
||||
data = None |
||||
self._uds_request(SERVICE_TYPE.LINK_CONTROL, subfunction=link_control_type, data=data) |
||||
|
||||
def read_data_by_identifier(self, data_identifier_type: DATA_IDENTIFIER_TYPE): |
||||
# TODO: support list of identifiers |
||||
data = struct.pack('!H', data_identifier_type) |
||||
resp = self._uds_request(SERVICE_TYPE.READ_DATA_BY_IDENTIFIER, subfunction=None, data=data) |
||||
resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None |
||||
if resp_id != data_identifier_type: |
||||
raise ValueError('invalid response data identifier: {}'.format(hex(resp_id))) |
||||
return resp[2:] |
||||
|
||||
def read_memory_by_address(self, memory_address: int, memory_size: int, memory_address_bytes: int=4, memory_size_bytes: int=1): |
||||
if memory_address_bytes < 1 or memory_address_bytes > 4: |
||||
raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) |
||||
if memory_size_bytes < 1 or memory_size_bytes > 4: |
||||
raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) |
||||
data = bytes([memory_size_bytes<<4 | memory_address_bytes]) |
||||
|
||||
if memory_address >= 1<<(memory_address_bytes*8): |
||||
raise ValueError('invalid memory_address: {}'.format(memory_address)) |
||||
data += struct.pack('!I', memory_address)[4-memory_address_bytes:] |
||||
if memory_size >= 1<<(memory_size_bytes*8): |
||||
raise ValueError('invalid memory_size: {}'.format(memory_size)) |
||||
data += struct.pack('!I', memory_size)[4-memory_size_bytes:] |
||||
|
||||
resp = self._uds_request(SERVICE_TYPE.READ_MEMORY_BY_ADDRESS, subfunction=None, data=data) |
||||
return resp |
||||
|
||||
def read_scaling_data_by_identifier(self, data_identifier_type: DATA_IDENTIFIER_TYPE): |
||||
data = struct.pack('!H', data_identifier_type) |
||||
resp = self._uds_request(SERVICE_TYPE.READ_SCALING_DATA_BY_IDENTIFIER, subfunction=None, data=data) |
||||
resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None |
||||
if resp_id != data_identifier_type: |
||||
raise ValueError('invalid response data identifier: {}'.format(hex(resp_id))) |
||||
return resp[2:] # TODO: parse the response |
||||
|
||||
def read_data_by_periodic_identifier(self, transmission_mode_type: TRANSMISSION_MODE_TYPE, periodic_data_identifier: int): |
||||
# TODO: support list of identifiers |
||||
data = bytes([transmission_mode_type, periodic_data_identifier]) |
||||
self._uds_request(SERVICE_TYPE.READ_DATA_BY_PERIODIC_IDENTIFIER, subfunction=None, data=data) |
||||
|
||||
def dynamically_define_data_identifier(self, dynamic_definition_type: DYNAMIC_DEFINITION_TYPE, dynamic_data_identifier: int, source_definitions: List[DynamicSourceDefinition], memory_address_bytes: int=4, memory_size_bytes: int=1): |
||||
if memory_address_bytes < 1 or memory_address_bytes > 4: |
||||
raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) |
||||
if memory_size_bytes < 1 or memory_size_bytes > 4: |
||||
raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) |
||||
|
||||
data = struct.pack('!H', dynamic_data_identifier) |
||||
if dynamic_definition_type == DYNAMIC_DEFINITION_TYPE.DEFINE_BY_IDENTIFIER: |
||||
for s in source_definitions: |
||||
data += struct.pack('!H', s["data_identifier"]) + bytes([s["position"], s["memory_size"]]) |
||||
elif dynamic_definition_type == DYNAMIC_DEFINITION_TYPE.DEFINE_BY_MEMORY_ADDRESS: |
||||
data += bytes([memory_size_bytes<<4 | memory_address_bytes]) |
||||
for s in source_definitions: |
||||
if s["memory_address"] >= 1<<(memory_address_bytes*8): |
||||
raise ValueError('invalid memory_address: {}'.format(s["memory_address"])) |
||||
data += struct.pack('!I', s["memory_address"])[4-memory_address_bytes:] |
||||
if s["memory_size"] >= 1<<(memory_size_bytes*8): |
||||
raise ValueError('invalid memory_size: {}'.format(s["memory_size"])) |
||||
data += struct.pack('!I', s["memory_size"])[4-memory_size_bytes:] |
||||
elif dynamic_definition_type == DYNAMIC_DEFINITION_TYPE.CLEAR_DYNAMICALLY_DEFINED_DATA_IDENTIFIER: |
||||
pass |
||||
else: |
||||
raise ValueError('invalid dynamic identifier type: {}'.format(hex(dynamic_definition_type))) |
||||
self._uds_request(SERVICE_TYPE.DYNAMICALLY_DEFINE_DATA_IDENTIFIER, subfunction=dynamic_definition_type, data=data) |
||||
|
||||
def write_data_by_identifier(self, data_identifier_type: DATA_IDENTIFIER_TYPE, data_record: bytes): |
||||
data = struct.pack('!H', data_identifier_type) + data_record |
||||
resp = self._uds_request(SERVICE_TYPE.WRITE_DATA_BY_IDENTIFIER, subfunction=None, data=data) |
||||
resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None |
||||
if resp_id != data_identifier_type: |
||||
raise ValueError('invalid response data identifier: {}'.format(hex(resp_id))) |
||||
|
||||
def write_memory_by_address(self, memory_address: int, memory_size: int, data_record: bytes, memory_address_bytes: int=4, memory_size_bytes: int=1): |
||||
if memory_address_bytes < 1 or memory_address_bytes > 4: |
||||
raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) |
||||
if memory_size_bytes < 1 or memory_size_bytes > 4: |
||||
raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) |
||||
data = bytes([memory_size_bytes<<4 | memory_address_bytes]) |
||||
|
||||
if memory_address >= 1<<(memory_address_bytes*8): |
||||
raise ValueError('invalid memory_address: {}'.format(memory_address)) |
||||
data += struct.pack('!I', memory_address)[4-memory_address_bytes:] |
||||
if memory_size >= 1<<(memory_size_bytes*8): |
||||
raise ValueError('invalid memory_size: {}'.format(memory_size)) |
||||
data += struct.pack('!I', memory_size)[4-memory_size_bytes:] |
||||
|
||||
data += data_record |
||||
self._uds_request(SERVICE_TYPE.WRITE_MEMORY_BY_ADDRESS, subfunction=0x00, data=data) |
||||
|
||||
def clear_diagnostic_information(self, dtc_group_type: DTC_GROUP_TYPE): |
||||
data = struct.pack('!I', dtc_group_type)[1:] # 3 bytes |
||||
self._uds_request(SERVICE_TYPE.CLEAR_DIAGNOSTIC_INFORMATION, subfunction=None, data=data) |
||||
|
||||
def read_dtc_information(self, dtc_report_type: DTC_REPORT_TYPE, dtc_status_mask_type: DTC_STATUS_MASK_TYPE=DTC_STATUS_MASK_TYPE.ALL, dtc_severity_mask_type: DTC_SEVERITY_MASK_TYPE=DTC_SEVERITY_MASK_TYPE.ALL, dtc_mask_record: int=0xFFFFFF, dtc_snapshot_record_num: int=0xFF, dtc_extended_record_num: int=0xFF): |
||||
data = b'' |
||||
# dtc_status_mask_type |
||||
if dtc_report_type == DTC_REPORT_TYPE.NUMBER_OF_DTC_BY_STATUS_MASK or \ |
||||
dtc_report_type == DTC_REPORT_TYPE.DTC_BY_STATUS_MASK or \ |
||||
dtc_report_type == DTC_REPORT_TYPE.MIRROR_MEMORY_DTC_BY_STATUS_MASK or \ |
||||
dtc_report_type == DTC_REPORT_TYPE.NUMBER_OF_MIRROR_MEMORY_DTC_BY_STATUS_MASK or \ |
||||
dtc_report_type == DTC_REPORT_TYPE.NUMBER_OF_EMISSIONS_RELATED_OBD_DTC_BY_STATUS_MASK or \ |
||||
dtc_report_type == DTC_REPORT_TYPE.EMISSIONS_RELATED_OBD_DTC_BY_STATUS_MASK: |
||||
data += bytes([dtc_status_mask_type]) |
||||
# dtc_mask_record |
||||
if dtc_report_type == DTC_REPORT_TYPE.DTC_SNAPSHOT_IDENTIFICATION or \ |
||||
dtc_report_type == DTC_REPORT_TYPE.DTC_SNAPSHOT_RECORD_BY_DTC_NUMBER or \ |
||||
dtc_report_type == DTC_REPORT_TYPE.DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER or \ |
||||
dtc_report_type == DTC_REPORT_TYPE.MIRROR_MEMORY_DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER or \ |
||||
dtc_report_type == DTC_REPORT_TYPE.SEVERITY_INFORMATION_OF_DTC: |
||||
data += struct.pack('!I', dtc_mask_record)[1:] # 3 bytes |
||||
# dtc_snapshot_record_num |
||||
if dtc_report_type == DTC_REPORT_TYPE.DTC_SNAPSHOT_IDENTIFICATION or \ |
||||
dtc_report_type == DTC_REPORT_TYPE.DTC_SNAPSHOT_RECORD_BY_DTC_NUMBER or \ |
||||
dtc_report_type == DTC_REPORT_TYPE.DTC_SNAPSHOT_RECORD_BY_RECORD_NUMBER: |
||||
data += ord(dtc_snapshot_record_num) |
||||
# dtc_extended_record_num |
||||
if dtc_report_type == DTC_REPORT_TYPE.DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER or \ |
||||
dtc_report_type == DTC_REPORT_TYPE.MIRROR_MEMORY_DTC_EXTENDED_DATA_RECORD_BY_DTC_NUMBER: |
||||
data += bytes([dtc_extended_record_num]) |
||||
# dtc_severity_mask_type |
||||
if dtc_report_type == DTC_REPORT_TYPE.NUMBER_OF_DTC_BY_SEVERITY_MASK_RECORD or \ |
||||
dtc_report_type == DTC_REPORT_TYPE.DTC_BY_SEVERITY_MASK_RECORD: |
||||
data += bytes([dtc_severity_mask_type, dtc_status_mask_type]) |
||||
|
||||
resp = self._uds_request(SERVICE_TYPE.READ_DTC_INFORMATION, subfunction=dtc_report_type, data=data) |
||||
|
||||
# TODO: parse response |
||||
return resp |
||||
|
||||
def input_output_control_by_identifier(self, data_identifier_type: DATA_IDENTIFIER_TYPE, control_parameter_type: CONTROL_PARAMETER_TYPE, control_option_record: bytes, control_enable_mask_record: bytes=b''): |
||||
data = struct.pack('!H', data_identifier_type) + bytes([control_parameter_type]) + control_option_record + control_enable_mask_record |
||||
resp = self._uds_request(SERVICE_TYPE.INPUT_OUTPUT_CONTROL_BY_IDENTIFIER, subfunction=None, data=data) |
||||
resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None |
||||
if resp_id != data_identifier_type: |
||||
raise ValueError('invalid response data identifier: {}'.format(hex(resp_id))) |
||||
return resp[2:] |
||||
|
||||
def routine_control(self, routine_control_type: ROUTINE_CONTROL_TYPE, routine_identifier_type: ROUTINE_IDENTIFIER_TYPE, routine_option_record: bytes=b''): |
||||
data = struct.pack('!H', routine_identifier_type) + routine_option_record |
||||
resp = self._uds_request(SERVICE_TYPE.ROUTINE_CONTROL, subfunction=routine_control_type, data=data) |
||||
resp_id = struct.unpack('!H', resp[0:2])[0] if len(resp) >= 2 else None |
||||
if resp_id != routine_identifier_type: |
||||
raise ValueError('invalid response routine identifier: {}'.format(hex(resp_id))) |
||||
return resp[2:] |
||||
|
||||
def request_download(self, memory_address: int, memory_size: int, memory_address_bytes: int=4, memory_size_bytes: int=4, data_format: int=0x00): |
||||
data = bytes([data_format]) |
||||
|
||||
if memory_address_bytes < 1 or memory_address_bytes > 4: |
||||
raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) |
||||
if memory_size_bytes < 1 or memory_size_bytes > 4: |
||||
raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) |
||||
data += bytes([memory_size_bytes<<4 | memory_address_bytes]) |
||||
|
||||
if memory_address >= 1<<(memory_address_bytes*8): |
||||
raise ValueError('invalid memory_address: {}'.format(memory_address)) |
||||
data += struct.pack('!I', memory_address)[4-memory_address_bytes:] |
||||
if memory_size >= 1<<(memory_size_bytes*8): |
||||
raise ValueError('invalid memory_size: {}'.format(memory_size)) |
||||
data += struct.pack('!I', memory_size)[4-memory_size_bytes:] |
||||
|
||||
resp = self._uds_request(SERVICE_TYPE.REQUEST_DOWNLOAD, subfunction=None, data=data) |
||||
max_num_bytes_len = resp[0] >> 4 if len(resp) > 0 else None |
||||
if max_num_bytes_len >= 1 and max_num_bytes_len <= 4: |
||||
max_num_bytes = struct.unpack('!I', (b"\x00"*(4-max_num_bytes_len))+resp[1:max_num_bytes_len+1])[0] |
||||
else: |
||||
raise ValueError('invalid max_num_bytes_len: {}'.format(max_num_bytes_len)) |
||||
|
||||
return max_num_bytes # max number of bytes per transfer data request |
||||
|
||||
def request_upload(self, memory_address: int, memory_size: int, memory_address_bytes: int=4, memory_size_bytes: int=4, data_format: int=0x00): |
||||
data = bytes([data_format]) |
||||
|
||||
if memory_address_bytes < 1 or memory_address_bytes > 4: |
||||
raise ValueError('invalid memory_address_bytes: {}'.format(memory_address_bytes)) |
||||
if memory_size_bytes < 1 or memory_size_bytes > 4: |
||||
raise ValueError('invalid memory_size_bytes: {}'.format(memory_size_bytes)) |
||||
data += bytes([memory_size_bytes<<4 | memory_address_bytes]) |
||||
|
||||
if memory_address >= 1<<(memory_address_bytes*8): |
||||
raise ValueError('invalid memory_address: {}'.format(memory_address)) |
||||
data += struct.pack('!I', memory_address)[4-memory_address_bytes:] |
||||
if memory_size >= 1<<(memory_size_bytes*8): |
||||
raise ValueError('invalid memory_size: {}'.format(memory_size)) |
||||
data += struct.pack('!I', memory_size)[4-memory_size_bytes:] |
||||
|
||||
resp = self._uds_request(SERVICE_TYPE.REQUEST_UPLOAD, subfunction=None, data=data) |
||||
max_num_bytes_len = resp[0] >> 4 if len(resp) > 0 else None |
||||
if max_num_bytes_len >= 1 and max_num_bytes_len <= 4: |
||||
max_num_bytes = struct.unpack('!I', (b"\x00"*(4-max_num_bytes_len))+resp[1:max_num_bytes_len+1])[0] |
||||
else: |
||||
raise ValueError('invalid max_num_bytes_len: {}'.format(max_num_bytes_len)) |
||||
|
||||
return max_num_bytes # max number of bytes per transfer data request |
||||
|
||||
def transfer_data(self, block_sequence_count: int, data: bytes=b''): |
||||
data = bytes([block_sequence_count]) + data |
||||
resp = self._uds_request(SERVICE_TYPE.TRANSFER_DATA, subfunction=None, data=data) |
||||
resp_id = resp[0] if len(resp) > 0 else None |
||||
if resp_id != block_sequence_count: |
||||
raise ValueError('invalid block_sequence_count: {}'.format(resp_id)) |
||||
return resp[1:] |
||||
|
||||
def request_transfer_exit(self): |
||||
self._uds_request(SERVICE_TYPE.REQUEST_TRANSFER_EXIT, subfunction=None) |
@ -1,7 +1,11 @@ |
||||
libusb1 == 1.6.6 |
||||
hexdump |
||||
pycrypto |
||||
tqdm |
||||
numpy==1.17.2 |
||||
hexdump>=3.3 |
||||
pycrypto==2.6.1 |
||||
tqdm>=4.14.0 |
||||
nose |
||||
parameterized |
||||
requests |
||||
flake8==3.7.8 |
||||
pylint==2.4.2 |
||||
cffi==1.11.4 |
||||
|
@ -0,0 +1,17 @@ |
||||
#!/usr/bin/env python |
||||
import os |
||||
import sys |
||||
import time |
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) |
||||
from panda import Panda |
||||
|
||||
power = 0 |
||||
if __name__ == "__main__": |
||||
p = Panda() |
||||
while True: |
||||
p.set_fan_power(power) |
||||
time.sleep(5) |
||||
print("Power: ", power, "RPM: ", str(p.get_fan_rpm())) |
||||
power += 10 |
||||
power %=100 |
@ -1,17 +1,16 @@ |
||||
#!/usr/bin/env python3 |
||||
import time |
||||
from panda import Panda |
||||
|
||||
p = Panda() |
||||
p.set_safety_mode(Panda.SAFETY_ALLOUTPUT) |
||||
p.set_gmlan(bus=2) |
||||
#p.can_send(0xaaa, "\x00\x00", bus=3) |
||||
#p.can_send(0xaaa, b"\x00\x00", bus=3) |
||||
last_add = None |
||||
while 1: |
||||
ret = p.can_recv() |
||||
if len(ret) > 0: |
||||
add = ret[0][0] |
||||
if last_add is not None and add != last_add+1: |
||||
print("MISS %d %d" % (last_add, add)) |
||||
if last_add is not None and add != last_add + 1: |
||||
print("MISS: ", last_add, add) |
||||
last_add = add |
||||
print(ret) |
||||
|
@ -0,0 +1,17 @@ |
||||
#!/usr/bin/env python |
||||
import os |
||||
import sys |
||||
import time |
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) |
||||
from panda import Panda |
||||
|
||||
power = 0 |
||||
if __name__ == "__main__": |
||||
p = Panda() |
||||
while True: |
||||
p.set_ir_power(power) |
||||
print("Power: ", str(power)) |
||||
time.sleep(1) |
||||
power += 10 |
||||
power %=100 |
@ -0,0 +1,585 @@ |
||||
[MASTER] |
||||
|
||||
# A comma-separated list of package or module names from where C extensions may |
||||
# be loaded. Extensions are loading into the active Python interpreter and may |
||||
# run arbitrary code |
||||
extension-pkg-whitelist=scipy |
||||
|
||||
# Add files or directories to the blacklist. They should be base names, not |
||||
# paths. |
||||
ignore=CVS |
||||
|
||||
# Add files or directories matching the regex patterns to the blacklist. The |
||||
# regex matches against base names, not paths. |
||||
ignore-patterns= |
||||
|
||||
# Python code to execute, usually for sys.path manipulation such as |
||||
# pygtk.require(). |
||||
#init-hook= |
||||
|
||||
# Use multiple processes to speed up Pylint. |
||||
jobs=4 |
||||
|
||||
# List of plugins (as comma separated values of python modules names) to load, |
||||
# usually to register additional checkers. |
||||
load-plugins= |
||||
|
||||
# Pickle collected data for later comparisons. |
||||
persistent=yes |
||||
|
||||
# Specify a configuration file. |
||||
#rcfile= |
||||
|
||||
# When enabled, pylint would attempt to guess common misconfiguration and emit |
||||
# user-friendly hints instead of false-positive error messages |
||||
suggestion-mode=yes |
||||
|
||||
# Allow loading of arbitrary C extensions. Extensions are imported into the |
||||
# active Python interpreter and may run arbitrary code. |
||||
unsafe-load-any-extension=no |
||||
|
||||
|
||||
[MESSAGES CONTROL] |
||||
|
||||
# Only show warnings with the listed confidence levels. Leave empty to show |
||||
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED |
||||
confidence= |
||||
|
||||
# Disable the message, report, category or checker with the given id(s). You |
||||
# can either give multiple identifiers separated by comma (,) or put this |
||||
# option multiple times (only on the command line, not in the configuration |
||||
# file where it should appear only once).You can also use "--disable=all" to |
||||
# disable everything first and then reenable specific checks. For example, if |
||||
# you want to run only the similarities checker, you can use "--disable=all |
||||
# --enable=similarities". If you want to run only the classes checker, but have |
||||
# no Warning level messages displayed, use"--disable=all --enable=classes |
||||
# --disable=W" |
||||
disable=print-statement, |
||||
parameter-unpacking, |
||||
unpacking-in-except, |
||||
old-raise-syntax, |
||||
backtick, |
||||
long-suffix, |
||||
old-ne-operator, |
||||
old-octal-literal, |
||||
import-star-module-level, |
||||
non-ascii-bytes-literal, |
||||
raw-checker-failed, |
||||
bad-inline-option, |
||||
locally-disabled, |
||||
locally-enabled, |
||||
file-ignored, |
||||
suppressed-message, |
||||
useless-suppression, |
||||
deprecated-pragma, |
||||
apply-builtin, |
||||
basestring-builtin, |
||||
buffer-builtin, |
||||
cmp-builtin, |
||||
coerce-builtin, |
||||
execfile-builtin, |
||||
file-builtin, |
||||
long-builtin, |
||||
raw_input-builtin, |
||||
reduce-builtin, |
||||
standarderror-builtin, |
||||
unicode-builtin, |
||||
xrange-builtin, |
||||
coerce-method, |
||||
delslice-method, |
||||
getslice-method, |
||||
setslice-method, |
||||
no-absolute-import, |
||||
old-division, |
||||
dict-iter-method, |
||||
dict-view-method, |
||||
next-method-called, |
||||
metaclass-assignment, |
||||
indexing-exception, |
||||
raising-string, |
||||
reload-builtin, |
||||
oct-method, |
||||
hex-method, |
||||
nonzero-method, |
||||
cmp-method, |
||||
input-builtin, |
||||
round-builtin, |
||||
intern-builtin, |
||||
unichr-builtin, |
||||
map-builtin-not-iterating, |
||||
zip-builtin-not-iterating, |
||||
range-builtin-not-iterating, |
||||
filter-builtin-not-iterating, |
||||
using-cmp-argument, |
||||
eq-without-hash, |
||||
div-method, |
||||
idiv-method, |
||||
rdiv-method, |
||||
exception-message-attribute, |
||||
invalid-str-codec, |
||||
sys-max-int, |
||||
bad-python3-import, |
||||
deprecated-string-function, |
||||
deprecated-str-translate-call, |
||||
deprecated-itertools-function, |
||||
deprecated-types-field, |
||||
next-method-defined, |
||||
dict-items-not-iterating, |
||||
dict-keys-not-iterating, |
||||
dict-values-not-iterating, |
||||
bad-indentation, |
||||
line-too-long, |
||||
missing-docstring, |
||||
multiple-statements, |
||||
bad-continuation, |
||||
invalid-name, |
||||
too-many-arguments, |
||||
too-many-locals, |
||||
superfluous-parens, |
||||
bad-whitespace, |
||||
too-many-instance-attributes, |
||||
wrong-import-position, |
||||
ungrouped-imports, |
||||
wrong-import-order, |
||||
protected-access, |
||||
trailing-whitespace, |
||||
too-many-branches, |
||||
too-few-public-methods, |
||||
too-many-statements, |
||||
trailing-newlines, |
||||
attribute-defined-outside-init, |
||||
too-many-return-statements, |
||||
too-many-public-methods, |
||||
unused-argument, |
||||
old-style-class, |
||||
no-init, |
||||
len-as-condition, |
||||
unneeded-not, |
||||
no-self-use, |
||||
multiple-imports, |
||||
no-else-return, |
||||
logging-not-lazy, |
||||
fixme, |
||||
redefined-outer-name, |
||||
unused-variable, |
||||
unsubscriptable-object, |
||||
expression-not-assigned, |
||||
too-many-boolean-expressions, |
||||
consider-using-ternary, |
||||
invalid-unary-operand-type, |
||||
relative-import, |
||||
deprecated-lambda |
||||
|
||||
|
||||
# Enable the message, report, category or checker with the given id(s). You can |
||||
# either give multiple identifier separated by comma (,) or put this option |
||||
# multiple time (only on the command line, not in the configuration file where |
||||
# it should appear only once). See also the "--disable" option for examples. |
||||
enable=c-extension-no-member |
||||
|
||||
|
||||
[REPORTS] |
||||
|
||||
# Python expression which should return a note less than 10 (10 is the highest |
||||
# note). You have access to the variables errors warning, statement which |
||||
# respectively contain the number of errors / warnings messages and the total |
||||
# number of statements analyzed. This is used by the global evaluation report |
||||
# (RP0004). |
||||
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) |
||||
|
||||
# Template used to display messages. This is a python new-style format string |
||||
# used to format the message information. See doc for all details |
||||
#msg-template= |
||||
|
||||
# Set the output format. Available formats are text, parseable, colorized, json |
||||
# and msvs (visual studio).You can also give a reporter class, eg |
||||
# mypackage.mymodule.MyReporterClass. |
||||
output-format=text |
||||
|
||||
# Tells whether to display a full report or only the messages |
||||
reports=no |
||||
|
||||
# Activate the evaluation score. |
||||
score=yes |
||||
|
||||
|
||||
[REFACTORING] |
||||
|
||||
# Maximum number of nested blocks for function / method body |
||||
max-nested-blocks=5 |
||||
|
||||
# Complete name of functions that never returns. When checking for |
||||
# inconsistent-return-statements if a never returning function is called then |
||||
# it will be considered as an explicit return statement and no message will be |
||||
# printed. |
||||
never-returning-functions=optparse.Values,sys.exit |
||||
|
||||
|
||||
[LOGGING] |
||||
|
||||
# Logging modules to check that the string format arguments are in logging |
||||
# function parameter format |
||||
logging-modules=logging |
||||
|
||||
|
||||
[SPELLING] |
||||
|
||||
# Limits count of emitted suggestions for spelling mistakes |
||||
max-spelling-suggestions=4 |
||||
|
||||
# Spelling dictionary name. Available dictionaries: none. To make it working |
||||
# install python-enchant package. |
||||
spelling-dict= |
||||
|
||||
# List of comma separated words that should not be checked. |
||||
spelling-ignore-words= |
||||
|
||||
# A path to a file that contains private dictionary; one word per line. |
||||
spelling-private-dict-file= |
||||
|
||||
# Tells whether to store unknown words to indicated private dictionary in |
||||
# --spelling-private-dict-file option instead of raising a message. |
||||
spelling-store-unknown-words=no |
||||
|
||||
|
||||
[MISCELLANEOUS] |
||||
|
||||
# List of note tags to take in consideration, separated by a comma. |
||||
notes=FIXME, |
||||
XXX, |
||||
TODO |
||||
|
||||
|
||||
[SIMILARITIES] |
||||
|
||||
# Ignore comments when computing similarities. |
||||
ignore-comments=yes |
||||
|
||||
# Ignore docstrings when computing similarities. |
||||
ignore-docstrings=yes |
||||
|
||||
# Ignore imports when computing similarities. |
||||
ignore-imports=no |
||||
|
||||
# Minimum lines number of a similarity. |
||||
min-similarity-lines=4 |
||||
|
||||
|
||||
[TYPECHECK] |
||||
|
||||
# List of decorators that produce context managers, such as |
||||
# contextlib.contextmanager. Add to this list to register other decorators that |
||||
# produce valid context managers. |
||||
contextmanager-decorators=contextlib.contextmanager |
||||
|
||||
# List of members which are set dynamically and missed by pylint inference |
||||
# system, and so shouldn't trigger E1101 when accessed. Python regular |
||||
# expressions are accepted. |
||||
generated-members=capnp.* cereal.* pygame.* zmq.* setproctitle.* smbus2.* usb1.* serial.* cv2.* |
||||
|
||||
# Tells whether missing members accessed in mixin class should be ignored. A |
||||
# mixin class is detected if its name ends with "mixin" (case insensitive). |
||||
ignore-mixin-members=yes |
||||
|
||||
# This flag controls whether pylint should warn about no-member and similar |
||||
# checks whenever an opaque object is returned when inferring. The inference |
||||
# can return multiple potential results while evaluating a Python object, but |
||||
# some branches might not be evaluated, which results in partial inference. In |
||||
# that case, it might be useful to still emit no-member and other checks for |
||||
# the rest of the inferred objects. |
||||
ignore-on-opaque-inference=yes |
||||
|
||||
# List of class names for which member attributes should not be checked (useful |
||||
# for classes with dynamically set attributes). This supports the use of |
||||
# qualified names. |
||||
ignored-classes=optparse.Values,thread._local,_thread._local |
||||
|
||||
# List of module names for which member attributes should not be checked |
||||
# (useful for modules/projects where namespaces are manipulated during runtime |
||||
# and thus existing member attributes cannot be deduced by static analysis. It |
||||
# supports qualified module names, as well as Unix pattern matching. |
||||
ignored-modules=flask setproctitle usb1 flask.ext.socketio smbus2 usb1.* |
||||
|
||||
# Show a hint with possible names when a member name was not found. The aspect |
||||
# of finding the hint is based on edit distance. |
||||
missing-member-hint=yes |
||||
|
||||
# The minimum edit distance a name should have in order to be considered a |
||||
# similar match for a missing member name. |
||||
missing-member-hint-distance=1 |
||||
|
||||
# The total number of similar names that should be taken in consideration when |
||||
# showing a hint for a missing member. |
||||
missing-member-max-choices=1 |
||||
|
||||
|
||||
[VARIABLES] |
||||
|
||||
# List of additional names supposed to be defined in builtins. Remember that |
||||
# you should avoid to define new builtins when possible. |
||||
additional-builtins= |
||||
|
||||
# Tells whether unused global variables should be treated as a violation. |
||||
allow-global-unused-variables=yes |
||||
|
||||
# List of strings which can identify a callback function by name. A callback |
||||
# name must start or end with one of those strings. |
||||
callbacks=cb_, |
||||
_cb |
||||
|
||||
# A regular expression matching the name of dummy variables (i.e. expectedly |
||||
# not used). |
||||
dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ |
||||
|
||||
# Argument names that match this expression will be ignored. Default to name |
||||
# with leading underscore |
||||
ignored-argument-names=_.*|^ignored_|^unused_ |
||||
|
||||
# Tells whether we should check for unused import in __init__ files. |
||||
init-import=no |
||||
|
||||
# List of qualified module names which can have objects that can redefine |
||||
# builtins. |
||||
redefining-builtins-modules=six.moves,past.builtins,future.builtins |
||||
|
||||
|
||||
[FORMAT] |
||||
|
||||
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. |
||||
expected-line-ending-format= |
||||
|
||||
# Regexp for a line that is allowed to be longer than the limit. |
||||
ignore-long-lines=^\s*(# )?<?https?://\S+>?$ |
||||
|
||||
# Number of spaces of indent required inside a hanging or continued line. |
||||
indent-after-paren=4 |
||||
|
||||
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 |
||||
# tab). |
||||
indent-string=' ' |
||||
|
||||
# Maximum number of characters on a single line. |
||||
max-line-length=100 |
||||
|
||||
# Maximum number of lines in a module |
||||
max-module-lines=1000 |
||||
|
||||
# List of optional constructs for which whitespace checking is disabled. `dict- |
||||
# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. |
||||
# `trailing-comma` allows a space between comma and closing bracket: (a, ). |
||||
# `empty-line` allows space-only lines. |
||||
no-space-check=trailing-comma, |
||||
dict-separator |
||||
|
||||
# Allow the body of a class to be on the same line as the declaration if body |
||||
# contains single statement. |
||||
single-line-class-stmt=no |
||||
|
||||
# Allow the body of an if to be on the same line as the test if there is no |
||||
# else. |
||||
single-line-if-stmt=no |
||||
|
||||
|
||||
[BASIC] |
||||
|
||||
# Naming style matching correct argument names |
||||
argument-naming-style=snake_case |
||||
|
||||
# Regular expression matching correct argument names. Overrides argument- |
||||
# naming-style |
||||
#argument-rgx= |
||||
|
||||
# Naming style matching correct attribute names |
||||
attr-naming-style=snake_case |
||||
|
||||
# Regular expression matching correct attribute names. Overrides attr-naming- |
||||
# style |
||||
#attr-rgx= |
||||
|
||||
# Bad variable names which should always be refused, separated by a comma |
||||
bad-names=foo, |
||||
bar, |
||||
baz, |
||||
toto, |
||||
tutu, |
||||
tata |
||||
|
||||
# Naming style matching correct class attribute names |
||||
class-attribute-naming-style=any |
||||
|
||||
# Regular expression matching correct class attribute names. Overrides class- |
||||
# attribute-naming-style |
||||
#class-attribute-rgx= |
||||
|
||||
# Naming style matching correct class names |
||||
class-naming-style=PascalCase |
||||
|
||||
# Regular expression matching correct class names. Overrides class-naming-style |
||||
#class-rgx= |
||||
|
||||
# Naming style matching correct constant names |
||||
const-naming-style=UPPER_CASE |
||||
|
||||
# Regular expression matching correct constant names. Overrides const-naming- |
||||
# style |
||||
#const-rgx= |
||||
|
||||
# Minimum line length for functions/classes that require docstrings, shorter |
||||
# ones are exempt. |
||||
docstring-min-length=-1 |
||||
|
||||
# Naming style matching correct function names |
||||
function-naming-style=snake_case |
||||
|
||||
# Regular expression matching correct function names. Overrides function- |
||||
# naming-style |
||||
#function-rgx= |
||||
|
||||
# Good variable names which should always be accepted, separated by a comma |
||||
good-names=i, |
||||
j, |
||||
k, |
||||
ex, |
||||
Run, |
||||
_ |
||||
|
||||
# Include a hint for the correct naming format with invalid-name |
||||
include-naming-hint=no |
||||
|
||||
# Naming style matching correct inline iteration names |
||||
inlinevar-naming-style=any |
||||
|
||||
# Regular expression matching correct inline iteration names. Overrides |
||||
# inlinevar-naming-style |
||||
#inlinevar-rgx= |
||||
|
||||
# Naming style matching correct method names |
||||
method-naming-style=snake_case |
||||
|
||||
# Regular expression matching correct method names. Overrides method-naming- |
||||
# style |
||||
#method-rgx= |
||||
|
||||
# Naming style matching correct module names |
||||
module-naming-style=snake_case |
||||
|
||||
# Regular expression matching correct module names. Overrides module-naming- |
||||
# style |
||||
#module-rgx= |
||||
|
||||
# Colon-delimited sets of names that determine each other's naming style when |
||||
# the name regexes allow several styles. |
||||
name-group= |
||||
|
||||
# Regular expression which should only match function or class names that do |
||||
# not require a docstring. |
||||
no-docstring-rgx=^_ |
||||
|
||||
# List of decorators that produce properties, such as abc.abstractproperty. Add |
||||
# to this list to register other decorators that produce valid properties. |
||||
property-classes=abc.abstractproperty |
||||
|
||||
# Naming style matching correct variable names |
||||
variable-naming-style=snake_case |
||||
|
||||
# Regular expression matching correct variable names. Overrides variable- |
||||
# naming-style |
||||
#variable-rgx= |
||||
|
||||
|
||||
[DESIGN] |
||||
|
||||
# Maximum number of arguments for function / method |
||||
max-args=5 |
||||
|
||||
# Maximum number of attributes for a class (see R0902). |
||||
max-attributes=7 |
||||
|
||||
# Maximum number of boolean expressions in a if statement |
||||
max-bool-expr=5 |
||||
|
||||
# Maximum number of branch for function / method body |
||||
max-branches=12 |
||||
|
||||
# Maximum number of locals for function / method body |
||||
max-locals=15 |
||||
|
||||
# Maximum number of parents for a class (see R0901). |
||||
max-parents=7 |
||||
|
||||
# Maximum number of public methods for a class (see R0904). |
||||
max-public-methods=20 |
||||
|
||||
# Maximum number of return / yield for function / method body |
||||
max-returns=6 |
||||
|
||||
# Maximum number of statements in function / method body |
||||
max-statements=50 |
||||
|
||||
# Minimum number of public methods for a class (see R0903). |
||||
min-public-methods=2 |
||||
|
||||
|
||||
[CLASSES] |
||||
|
||||
# List of method names used to declare (i.e. assign) instance attributes. |
||||
defining-attr-methods=__init__, |
||||
__new__, |
||||
setUp |
||||
|
||||
# List of member names, which should be excluded from the protected access |
||||
# warning. |
||||
exclude-protected=_asdict, |
||||
_fields, |
||||
_replace, |
||||
_source, |
||||
_make |
||||
|
||||
# List of valid names for the first argument in a class method. |
||||
valid-classmethod-first-arg=cls |
||||
|
||||
# List of valid names for the first argument in a metaclass class method. |
||||
valid-metaclass-classmethod-first-arg=mcs |
||||
|
||||
|
||||
[IMPORTS] |
||||
|
||||
# Allow wildcard imports from modules that define __all__. |
||||
allow-wildcard-with-all=no |
||||
|
||||
# Analyse import fallback blocks. This can be used to support both Python 2 and |
||||
# 3 compatible code, which means that the block might have code that exists |
||||
# only in one or another interpreter, leading to false positives when analysed. |
||||
analyse-fallback-blocks=no |
||||
|
||||
# Deprecated modules which should not be used, separated by a comma |
||||
deprecated-modules=regsub, |
||||
TERMIOS, |
||||
Bastion, |
||||
rexec |
||||
|
||||
# Create a graph of external dependencies in the given file (report RP0402 must |
||||
# not be disabled) |
||||
ext-import-graph= |
||||
|
||||
# Create a graph of every (i.e. internal and external) dependencies in the |
||||
# given file (report RP0402 must not be disabled) |
||||
import-graph= |
||||
|
||||
# Create a graph of internal dependencies in the given file (report RP0402 must |
||||
# not be disabled) |
||||
int-import-graph= |
||||
|
||||
# Force import order to recognize a module as part of the standard |
||||
# compatibility libraries. |
||||
known-standard-library= |
||||
|
||||
# Force import order to recognize a module as part of a third party library. |
||||
known-third-party=enchant |
||||
|
||||
|
||||
[EXCEPTIONS] |
||||
|
||||
# Exceptions that will emit a warning when being caught. Defaults to |
||||
# "Exception" |
||||
overgeneral-exceptions=Exception |
@ -0,0 +1,19 @@ |
||||
FROM ubuntu:16.04 |
||||
|
||||
RUN apt-get update && apt-get install -y make python python-pip locales curl git zlib1g-dev libffi-dev bzip2 libssl-dev |
||||
|
||||
RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen |
||||
ENV LANG en_US.UTF-8 |
||||
ENV LANGUAGE en_US:en |
||||
ENV LC_ALL en_US.UTF-8 |
||||
|
||||
RUN curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash |
||||
|
||||
ENV PATH="/root/.pyenv/bin:/root/.pyenv/shims:${PATH}" |
||||
RUN pyenv install 3.7.3 |
||||
RUN pyenv global 3.7.3 |
||||
RUN pyenv rehash |
||||
|
||||
COPY requirements.txt /tmp/ |
||||
RUN pip install -r /tmp/requirements.txt |
||||
COPY . /panda |
@ -0,0 +1,8 @@ |
||||
#!/usr/bin/env bash |
||||
|
||||
RESULT=$(python3 -m flake8 --select=F $(find ../../ -type f | grep -v "/boardesp/" | grep -v "/cppcheck/" | grep "\.py$")) |
||||
if [[ $RESULT ]]; then |
||||
echo "Pyflakes found errors in the code. Please fix and try again" |
||||
echo "$RESULT" |
||||
exit 1 |
||||
fi |
@ -0,0 +1,11 @@ |
||||
#!/usr/bin/env bash |
||||
|
||||
python3 -m pylint --disable=R,C,W $(find ../../ -type f | grep -v "/boardesp/" | grep -v "/cppcheck/" | grep "\.py$") |
||||
|
||||
exit_status=$? |
||||
(( res = exit_status & 3 )) |
||||
|
||||
if [[ $res != 0 ]]; then |
||||
echo "Pylint found errors in the code. Please fix and try again" |
||||
exit 1 |
||||
fi |
@ -1,6 +1,19 @@ |
||||
FROM ubuntu:16.04 |
||||
|
||||
RUN apt-get update && apt-get install -y make python python-pip git |
||||
COPY tests/safety/requirements.txt /panda/tests/safety/requirements.txt |
||||
RUN pip install -r /panda/tests/safety/requirements.txt |
||||
RUN apt-get update && apt-get install -y clang make python python-pip git curl locales zlib1g-dev libffi-dev bzip2 libssl-dev libbz2-dev libusb-1.0-0 |
||||
|
||||
RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen |
||||
ENV LANG en_US.UTF-8 |
||||
ENV LANGUAGE en_US:en |
||||
ENV LC_ALL en_US.UTF-8 |
||||
|
||||
RUN curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash |
||||
|
||||
ENV PATH="/root/.pyenv/bin:/root/.pyenv/shims:${PATH}" |
||||
RUN pyenv install 3.7.3 |
||||
RUN pyenv global 3.7.3 |
||||
RUN pyenv rehash |
||||
|
||||
COPY requirements.txt /tmp/ |
||||
RUN pip install -r /tmp/requirements.txt |
||||
COPY . /panda |
||||
|
@ -0,0 +1,13 @@ |
||||
#!/usr/bin/env python |
||||
import os |
||||
import sys |
||||
import datetime |
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) |
||||
from panda import Panda |
||||
|
||||
if __name__ == "__main__": |
||||
p = Panda() |
||||
|
||||
p.set_datetime(datetime.datetime.now()) |
||||
print(p.get_datetime()) |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue