commit
						0c606ee339
					
				
				 105 changed files with 2448 additions and 1677 deletions
			
			
		| @ -1 +1 @@ | ||||
| v1.5.9 | ||||
| v1.6.9 | ||||
| @ -0,0 +1,23 @@ | ||||
| // ********************* Critical section helpers *********************
 | ||||
| volatile bool interrupts_enabled = false; | ||||
| 
 | ||||
| void enable_interrupts(void) { | ||||
|   interrupts_enabled = true; | ||||
|   __enable_irq(); | ||||
| } | ||||
| 
 | ||||
| void disable_interrupts(void) { | ||||
|   interrupts_enabled = false; | ||||
|   __disable_irq(); | ||||
| } | ||||
| 
 | ||||
| uint8_t global_critical_depth = 0U; | ||||
| #define ENTER_CRITICAL()                                      \ | ||||
|   __disable_irq();                                            \
 | ||||
|   global_critical_depth += 1U; | ||||
| 
 | ||||
| #define EXIT_CRITICAL()                                       \ | ||||
|   global_critical_depth -= 1U;                                \
 | ||||
|   if ((global_critical_depth == 0U) && interrupts_enabled) {  \
 | ||||
|     __enable_irq();                                           \
 | ||||
|   } | ||||
| @ -0,0 +1,164 @@ | ||||
| typedef struct interrupt { | ||||
|   IRQn_Type irq_type; | ||||
|   void (*handler)(void); | ||||
|   uint32_t call_counter; | ||||
|   uint32_t max_call_rate;   // Call rate is defined as the amount of calls each second
 | ||||
|   uint32_t call_rate_fault; | ||||
| } interrupt; | ||||
| 
 | ||||
| void unused_interrupt_handler(void) { | ||||
|   // Something is wrong if this handler is called!
 | ||||
|   puts("Unused interrupt handler called!\n"); | ||||
|   fault_occurred(FAULT_UNUSED_INTERRUPT_HANDLED); | ||||
| } | ||||
| 
 | ||||
| #define NUM_INTERRUPTS 102U                // There are 102 external interrupt sources (see stm32f413.h)
 | ||||
| interrupt interrupts[NUM_INTERRUPTS]; | ||||
| 
 | ||||
| #define REGISTER_INTERRUPT(irq_num, func_ptr, call_rate, rate_fault) \ | ||||
|   interrupts[irq_num].irq_type = irq_num; \
 | ||||
|   interrupts[irq_num].handler = func_ptr;  \
 | ||||
|   interrupts[irq_num].call_counter = 0U;   \
 | ||||
|   interrupts[irq_num].max_call_rate = call_rate; \
 | ||||
|   interrupts[irq_num].call_rate_fault = rate_fault; | ||||
| 
 | ||||
| bool check_interrupt_rate = false; | ||||
| 
 | ||||
| void handle_interrupt(IRQn_Type irq_type){ | ||||
|   interrupts[irq_type].call_counter++; | ||||
|   interrupts[irq_type].handler(); | ||||
| 
 | ||||
|   // 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"); | ||||
|     fault_occurred(interrupts[irq_type].call_rate_fault); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| // Reset interrupt counter every second
 | ||||
| void TIM6_DAC_IRQ_Handler(void) { | ||||
|   if (TIM6->SR != 0) { | ||||
|     for(uint16_t i=0U; i<NUM_INTERRUPTS; i++){ | ||||
|       interrupts[i].call_counter = 0U; | ||||
|     } | ||||
|   } | ||||
|   TIM6->SR = 0; | ||||
| } | ||||
| 
 | ||||
| void init_interrupts(bool check_rate_limit){ | ||||
|   check_interrupt_rate = check_rate_limit; | ||||
| 
 | ||||
|   for(uint16_t i=0U; i<NUM_INTERRUPTS; i++){ | ||||
|     interrupts[i].handler = unused_interrupt_handler; | ||||
|   } | ||||
| 
 | ||||
|   // Init timer 10 for a 1s interval
 | ||||
|   register_set_bits(&(RCC->APB1ENR), RCC_APB1ENR_TIM6EN);  // Enable interrupt timer peripheral
 | ||||
|   REGISTER_INTERRUPT(TIM6_DAC_IRQn, TIM6_DAC_IRQ_Handler, 1, FAULT_INTERRUPT_RATE_INTERRUPTS) | ||||
|   register_set(&(TIM6->PSC), (732-1), 0xFFFFU); | ||||
|   register_set(&(TIM6->DIER), TIM_DIER_UIE, 0x5F5FU); | ||||
|   register_set(&(TIM6->CR1), TIM_CR1_CEN, 0x3FU); | ||||
|   TIM6->SR = 0; | ||||
|   NVIC_EnableIRQ(TIM6_DAC_IRQn); | ||||
| } | ||||
| 
 | ||||
| // ********************* Bare interrupt handlers *********************
 | ||||
| // Only implemented the STM32F413 interrupts for now, the STM32F203 specific ones do not fall into the scope of SIL2
 | ||||
| 
 | ||||
| void WWDG_IRQHandler(void) {handle_interrupt(WWDG_IRQn);} | ||||
| void PVD_IRQHandler(void) {handle_interrupt(PVD_IRQn);} | ||||
| void TAMP_STAMP_IRQHandler(void) {handle_interrupt(TAMP_STAMP_IRQn);} | ||||
| void RTC_WKUP_IRQHandler(void) {handle_interrupt(RTC_WKUP_IRQn);} | ||||
| void FLASH_IRQHandler(void) {handle_interrupt(FLASH_IRQn);} | ||||
| void RCC_IRQHandler(void) {handle_interrupt(RCC_IRQn);} | ||||
| void EXTI0_IRQHandler(void) {handle_interrupt(EXTI0_IRQn);} | ||||
| void EXTI1_IRQHandler(void) {handle_interrupt(EXTI1_IRQn);} | ||||
| void EXTI2_IRQHandler(void) {handle_interrupt(EXTI2_IRQn);} | ||||
| void EXTI3_IRQHandler(void) {handle_interrupt(EXTI3_IRQn);} | ||||
| void EXTI4_IRQHandler(void) {handle_interrupt(EXTI4_IRQn);} | ||||
| void DMA1_Stream0_IRQHandler(void) {handle_interrupt(DMA1_Stream0_IRQn);} | ||||
| void DMA1_Stream1_IRQHandler(void) {handle_interrupt(DMA1_Stream1_IRQn);} | ||||
| void DMA1_Stream2_IRQHandler(void) {handle_interrupt(DMA1_Stream2_IRQn);} | ||||
| void DMA1_Stream3_IRQHandler(void) {handle_interrupt(DMA1_Stream3_IRQn);} | ||||
| void DMA1_Stream4_IRQHandler(void) {handle_interrupt(DMA1_Stream4_IRQn);} | ||||
| void DMA1_Stream5_IRQHandler(void) {handle_interrupt(DMA1_Stream5_IRQn);} | ||||
| void DMA1_Stream6_IRQHandler(void) {handle_interrupt(DMA1_Stream6_IRQn);} | ||||
| void ADC_IRQHandler(void) {handle_interrupt(ADC_IRQn);} | ||||
| void CAN1_TX_IRQHandler(void) {handle_interrupt(CAN1_TX_IRQn);} | ||||
| void CAN1_RX0_IRQHandler(void) {handle_interrupt(CAN1_RX0_IRQn);} | ||||
| void CAN1_RX1_IRQHandler(void) {handle_interrupt(CAN1_RX1_IRQn);} | ||||
| void CAN1_SCE_IRQHandler(void) {handle_interrupt(CAN1_SCE_IRQn);} | ||||
| void EXTI9_5_IRQHandler(void) {handle_interrupt(EXTI9_5_IRQn);} | ||||
| void TIM1_BRK_TIM9_IRQHandler(void) {handle_interrupt(TIM1_BRK_TIM9_IRQn);} | ||||
| void TIM1_UP_TIM10_IRQHandler(void) {handle_interrupt(TIM1_UP_TIM10_IRQn);} | ||||
| void TIM1_TRG_COM_TIM11_IRQHandler(void) {handle_interrupt(TIM1_TRG_COM_TIM11_IRQn);} | ||||
| void TIM1_CC_IRQHandler(void) {handle_interrupt(TIM1_CC_IRQn);} | ||||
| void TIM2_IRQHandler(void) {handle_interrupt(TIM2_IRQn);} | ||||
| void TIM3_IRQHandler(void) {handle_interrupt(TIM3_IRQn);} | ||||
| void TIM4_IRQHandler(void) {handle_interrupt(TIM4_IRQn);} | ||||
| void I2C1_EV_IRQHandler(void) {handle_interrupt(I2C1_EV_IRQn);} | ||||
| void I2C1_ER_IRQHandler(void) {handle_interrupt(I2C1_ER_IRQn);} | ||||
| void I2C2_EV_IRQHandler(void) {handle_interrupt(I2C2_EV_IRQn);} | ||||
| void I2C2_ER_IRQHandler(void) {handle_interrupt(I2C2_ER_IRQn);} | ||||
| void SPI1_IRQHandler(void) {handle_interrupt(SPI1_IRQn);} | ||||
| void SPI2_IRQHandler(void) {handle_interrupt(SPI2_IRQn);} | ||||
| void USART1_IRQHandler(void) {handle_interrupt(USART1_IRQn);} | ||||
| void USART2_IRQHandler(void) {handle_interrupt(USART2_IRQn);} | ||||
| void USART3_IRQHandler(void) {handle_interrupt(USART3_IRQn);} | ||||
| void EXTI15_10_IRQHandler(void) {handle_interrupt(EXTI15_10_IRQn);} | ||||
| void RTC_Alarm_IRQHandler(void) {handle_interrupt(RTC_Alarm_IRQn);} | ||||
| void OTG_FS_WKUP_IRQHandler(void) {handle_interrupt(OTG_FS_WKUP_IRQn);} | ||||
| void TIM8_BRK_TIM12_IRQHandler(void) {handle_interrupt(TIM8_BRK_TIM12_IRQn);} | ||||
| void TIM8_UP_TIM13_IRQHandler(void) {handle_interrupt(TIM8_UP_TIM13_IRQn);} | ||||
| void TIM8_TRG_COM_TIM14_IRQHandler(void) {handle_interrupt(TIM8_TRG_COM_TIM14_IRQn);} | ||||
| void TIM8_CC_IRQHandler(void) {handle_interrupt(TIM8_CC_IRQn);} | ||||
| void DMA1_Stream7_IRQHandler(void) {handle_interrupt(DMA1_Stream7_IRQn);} | ||||
| void FSMC_IRQHandler(void) {handle_interrupt(FSMC_IRQn);} | ||||
| void SDIO_IRQHandler(void) {handle_interrupt(SDIO_IRQn);} | ||||
| void TIM5_IRQHandler(void) {handle_interrupt(TIM5_IRQn);} | ||||
| void SPI3_IRQHandler(void) {handle_interrupt(SPI3_IRQn);} | ||||
| void UART4_IRQHandler(void) {handle_interrupt(UART4_IRQn);} | ||||
| void UART5_IRQHandler(void) {handle_interrupt(UART5_IRQn);} | ||||
| void TIM6_DAC_IRQHandler(void) {handle_interrupt(TIM6_DAC_IRQn);} | ||||
| void TIM7_IRQHandler(void) {handle_interrupt(TIM7_IRQn);} | ||||
| void DMA2_Stream0_IRQHandler(void) {handle_interrupt(DMA2_Stream0_IRQn);} | ||||
| void DMA2_Stream1_IRQHandler(void) {handle_interrupt(DMA2_Stream1_IRQn);} | ||||
| void DMA2_Stream2_IRQHandler(void) {handle_interrupt(DMA2_Stream2_IRQn);} | ||||
| void DMA2_Stream3_IRQHandler(void) {handle_interrupt(DMA2_Stream3_IRQn);} | ||||
| void DMA2_Stream4_IRQHandler(void) {handle_interrupt(DMA2_Stream4_IRQn);} | ||||
| void CAN2_TX_IRQHandler(void) {handle_interrupt(CAN2_TX_IRQn);} | ||||
| void CAN2_RX0_IRQHandler(void) {handle_interrupt(CAN2_RX0_IRQn);} | ||||
| void CAN2_RX1_IRQHandler(void) {handle_interrupt(CAN2_RX1_IRQn);} | ||||
| void CAN2_SCE_IRQHandler(void) {handle_interrupt(CAN2_SCE_IRQn);} | ||||
| void OTG_FS_IRQHandler(void) {handle_interrupt(OTG_FS_IRQn);} | ||||
| void DMA2_Stream5_IRQHandler(void) {handle_interrupt(DMA2_Stream5_IRQn);} | ||||
| void DMA2_Stream6_IRQHandler(void) {handle_interrupt(DMA2_Stream6_IRQn);} | ||||
| void DMA2_Stream7_IRQHandler(void) {handle_interrupt(DMA2_Stream7_IRQn);} | ||||
| void USART6_IRQHandler(void) {handle_interrupt(USART6_IRQn);} | ||||
| void I2C3_EV_IRQHandler(void) {handle_interrupt(I2C3_EV_IRQn);} | ||||
| void I2C3_ER_IRQHandler(void) {handle_interrupt(I2C3_ER_IRQn);} | ||||
| #ifdef STM32F4 | ||||
|   void DFSDM1_FLT0_IRQHandler(void) {handle_interrupt(DFSDM1_FLT0_IRQn);} | ||||
|   void DFSDM1_FLT1_IRQHandler(void) {handle_interrupt(DFSDM1_FLT1_IRQn);} | ||||
|   void CAN3_TX_IRQHandler(void) {handle_interrupt(CAN3_TX_IRQn);} | ||||
|   void CAN3_RX0_IRQHandler(void) {handle_interrupt(CAN3_RX0_IRQn);} | ||||
|   void CAN3_RX1_IRQHandler(void) {handle_interrupt(CAN3_RX1_IRQn);} | ||||
|   void CAN3_SCE_IRQHandler(void) {handle_interrupt(CAN3_SCE_IRQn);} | ||||
|   void RNG_IRQHandler(void) {handle_interrupt(RNG_IRQn);} | ||||
|   void FPU_IRQHandler(void) {handle_interrupt(FPU_IRQn);} | ||||
|   void UART7_IRQHandler(void) {handle_interrupt(UART7_IRQn);} | ||||
|   void UART8_IRQHandler(void) {handle_interrupt(UART8_IRQn);} | ||||
|   void SPI4_IRQHandler(void) {handle_interrupt(SPI4_IRQn);} | ||||
|   void SPI5_IRQHandler(void) {handle_interrupt(SPI5_IRQn);} | ||||
|   void SAI1_IRQHandler(void) {handle_interrupt(SAI1_IRQn);} | ||||
|   void UART9_IRQHandler(void) {handle_interrupt(UART9_IRQn);} | ||||
|   void UART10_IRQHandler(void) {handle_interrupt(UART10_IRQn);} | ||||
|   void QUADSPI_IRQHandler(void) {handle_interrupt(QUADSPI_IRQn);} | ||||
|   void FMPI2C1_EV_IRQHandler(void) {handle_interrupt(FMPI2C1_EV_IRQn);} | ||||
|   void FMPI2C1_ER_IRQHandler(void) {handle_interrupt(FMPI2C1_ER_IRQn);} | ||||
|   void LPTIM1_IRQHandler(void) {handle_interrupt(LPTIM1_IRQn);} | ||||
|   void DFSDM2_FLT0_IRQHandler(void) {handle_interrupt(DFSDM2_FLT0_IRQn);} | ||||
|   void DFSDM2_FLT1_IRQHandler(void) {handle_interrupt(DFSDM2_FLT1_IRQn);} | ||||
|   void DFSDM2_FLT2_IRQHandler(void) {handle_interrupt(DFSDM2_FLT2_IRQn);} | ||||
|   void DFSDM2_FLT3_IRQHandler(void) {handle_interrupt(DFSDM2_FLT3_IRQn);} | ||||
| #endif | ||||
| @ -0,0 +1,81 @@ | ||||
| 
 | ||||
| typedef struct reg { | ||||
|   volatile uint32_t *address; | ||||
|   uint32_t value; | ||||
|   uint32_t check_mask; | ||||
| } reg; | ||||
| 
 | ||||
| // 10 bit hash with 23 as a prime
 | ||||
| #define REGISTER_MAP_SIZE 0x3FFU | ||||
| #define HASHING_PRIME 23U | ||||
| #define CHECK_COLLISION(hash, addr) (((uint32_t) register_map[hash].address != 0U) && (register_map[hash].address != addr)) | ||||
| 
 | ||||
| reg register_map[REGISTER_MAP_SIZE]; | ||||
| 
 | ||||
| // Hash spread in first and second iterations seems to be reasonable.
 | ||||
| // See: tests/development/register_hashmap_spread.py
 | ||||
| // Also, check the collision warnings in the debug output, and minimize those.
 | ||||
| uint16_t hash_addr(uint32_t input){ | ||||
|   return (((input >> 16U) ^ ((((input + 1U) & 0xFFFFU) * HASHING_PRIME) & 0xFFFFU)) & REGISTER_MAP_SIZE); | ||||
| } | ||||
| 
 | ||||
| // Do not put bits in the check mask that get changed by the hardware
 | ||||
| void register_set(volatile uint32_t *addr, uint32_t val, uint32_t mask){ | ||||
|   ENTER_CRITICAL() | ||||
|   // Set bits in register that are also in the mask
 | ||||
|   (*addr) = ((*addr) & (~mask)) | (val & mask); | ||||
| 
 | ||||
|   // Add these values to the map
 | ||||
|   uint16_t hash = hash_addr((uint32_t) addr); | ||||
|   uint16_t tries = REGISTER_MAP_SIZE; | ||||
|   while(CHECK_COLLISION(hash, addr) && (tries > 0U)) { hash = hash_addr((uint32_t) hash); tries--;} | ||||
|   if (tries != 0U){ | ||||
|     register_map[hash].address = addr; | ||||
|     register_map[hash].value = (register_map[hash].value & (~mask)) | (val & mask); | ||||
|     register_map[hash].check_mask |= mask; | ||||
|   } else { | ||||
|     #ifdef DEBUG_FAULTS | ||||
|       puts("Hash collision: address 0x"); puth((uint32_t) addr); puts("!\n"); | ||||
|     #endif | ||||
|   } | ||||
|   EXIT_CRITICAL() | ||||
| } | ||||
| 
 | ||||
| // Set individual bits. Also add them to the check_mask.
 | ||||
| // Do not use this to change bits that get reset by the hardware
 | ||||
| void register_set_bits(volatile uint32_t *addr, uint32_t val) { | ||||
|   return register_set(addr, val, val); | ||||
| } | ||||
| 
 | ||||
| // Clear individual bits. Also add them to the check_mask.
 | ||||
| // Do not use this to clear bits that get set by the hardware
 | ||||
| void register_clear_bits(volatile uint32_t *addr, uint32_t val) { | ||||
|   return register_set(addr, (~val), val); | ||||
| } | ||||
| 
 | ||||
| // To be called periodically
 | ||||
| void check_registers(void){ | ||||
|   for(uint16_t i=0U; i<REGISTER_MAP_SIZE; i++){ | ||||
|     if((uint32_t) register_map[i].address != 0U){ | ||||
|       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"); | ||||
|         #endif | ||||
|         fault_occurred(FAULT_REGISTER_DIVERGENT); | ||||
|       } | ||||
|       EXIT_CRITICAL() | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void init_registers(void) { | ||||
|   for(uint16_t i=0U; i<REGISTER_MAP_SIZE; i++){ | ||||
|     register_map[i].address = (volatile uint32_t *) 0U; | ||||
|     register_map[i].check_mask = 0U; | ||||
|   } | ||||
| } | ||||
| @ -1,7 +1,7 @@ | ||||
| void timer_init(TIM_TypeDef *TIM, int psc) { | ||||
|   TIM->PSC = psc-1; | ||||
|   TIM->DIER = TIM_DIER_UIE; | ||||
|   TIM->CR1 = TIM_CR1_CEN; | ||||
|   register_set(&(TIM->PSC), (psc-1), 0xFFFFU); | ||||
|   register_set(&(TIM->DIER), TIM_DIER_UIE, 0x5F5FU); | ||||
|   register_set(&(TIM->CR1), TIM_CR1_CEN, 0x3FU); | ||||
|   TIM->SR = 0; | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -0,0 +1,49 @@ | ||||
| #define FAULT_STATUS_NONE 0U | ||||
| #define FAULT_STATUS_TEMPORARY 1U | ||||
| #define FAULT_STATUS_PERMANENT 2U | ||||
| 
 | ||||
| // Fault types
 | ||||
| #define FAULT_RELAY_MALFUNCTION         (1U << 0) | ||||
| #define FAULT_UNUSED_INTERRUPT_HANDLED  (1U << 1) | ||||
| #define FAULT_INTERRUPT_RATE_CAN_1      (1U << 2) | ||||
| #define FAULT_INTERRUPT_RATE_CAN_2      (1U << 3) | ||||
| #define FAULT_INTERRUPT_RATE_CAN_3      (1U << 4) | ||||
| #define FAULT_INTERRUPT_RATE_TACH       (1U << 5) | ||||
| #define FAULT_INTERRUPT_RATE_GMLAN      (1U << 6) | ||||
| #define FAULT_INTERRUPT_RATE_INTERRUPTS (1U << 7) | ||||
| #define FAULT_INTERRUPT_RATE_SPI_DMA    (1U << 8) | ||||
| #define FAULT_INTERRUPT_RATE_SPI_CS     (1U << 9) | ||||
| #define FAULT_INTERRUPT_RATE_UART_1     (1U << 10) | ||||
| #define FAULT_INTERRUPT_RATE_UART_2     (1U << 11) | ||||
| #define FAULT_INTERRUPT_RATE_UART_3     (1U << 12) | ||||
| #define FAULT_INTERRUPT_RATE_UART_5     (1U << 13) | ||||
| #define FAULT_INTERRUPT_RATE_UART_DMA   (1U << 14) | ||||
| #define FAULT_INTERRUPT_RATE_USB        (1U << 15) | ||||
| #define FAULT_INTERRUPT_RATE_TIM1       (1U << 16) | ||||
| #define FAULT_INTERRUPT_RATE_TIM3       (1U << 17) | ||||
| #define FAULT_REGISTER_DIVERGENT        (1U << 18) | ||||
| 
 | ||||
| // Permanent faults
 | ||||
| #define PERMANENT_FAULTS 0U | ||||
| 
 | ||||
| uint8_t fault_status = FAULT_STATUS_NONE; | ||||
| 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"); | ||||
|     fault_status = FAULT_STATUS_PERMANENT; | ||||
|   } else { | ||||
|     puts("Temporary fault occurred: 0x"); puth(fault); puts("\n"); | ||||
|     fault_status = FAULT_STATUS_TEMPORARY; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void fault_recovered(uint32_t fault) { | ||||
|   if((PERMANENT_FAULTS & fault) == 0U){ | ||||
|     faults &= ~fault; | ||||
|   } else { | ||||
|     puts("Cannot recover from a permanent fault!\n"); | ||||
|   } | ||||
| } | ||||
| @ -1,3 +1,3 @@ | ||||
| version https://git-lfs.github.com/spec/v1
 | ||||
| oid sha256:d0d192a64b7d859a76a631cf9bfdb865825ca83172a455eff661217c56392638 | ||||
| size 1145211 | ||||
| oid sha256:7c2cb1deecee360e4e8069c4215f05f652e14c70bd88f9bd072dc5502a5fd530 | ||||
| size 1145325 | ||||
|  | ||||
| @ -1,59 +0,0 @@ | ||||
| #!/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,77 @@ | ||||
| #!/usr/bin/env python3 | ||||
| from tqdm import tqdm | ||||
| from panda import Panda | ||||
| from panda.python.uds import UdsClient, MessageTimeoutError, NegativeResponseError, SESSION_TYPE, DATA_IDENTIFIER_TYPE | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|   addrs = [0x700 + i for i in range(256)] | ||||
|   addrs += [0x18da0000 + (i<<8) + 0xf1 for i in range(256)] | ||||
|   results = {} | ||||
| 
 | ||||
|   panda = Panda() | ||||
|   panda.set_safety_mode(Panda.SAFETY_ELM327) | ||||
|   print("querying addresses ...") | ||||
|   with tqdm(addrs) as t: | ||||
|     for addr in t: | ||||
|       # skip functional broadcast addrs | ||||
|       if addr == 0x7df or addr == 0x18db33f1: | ||||
|         continue | ||||
|       t.set_description(hex(addr)) | ||||
| 
 | ||||
|       uds_client = UdsClient(panda, addr, bus=1 if panda.has_obd() else 0, timeout=0.1, debug=False) | ||||
|       try: | ||||
|         uds_client.tester_present() | ||||
|         uds_client.diagnostic_session_control(SESSION_TYPE.EXTENDED_DIAGNOSTIC) | ||||
|       except NegativeResponseError: | ||||
|         pass | ||||
|       except MessageTimeoutError: | ||||
|         continue | ||||
| 
 | ||||
|       resp = {} | ||||
| 
 | ||||
|       try: | ||||
|         data = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.BOOT_SOFTWARE_IDENTIFICATION) | ||||
|         if data: resp[DATA_IDENTIFIER_TYPE.BOOT_SOFTWARE_IDENTIFICATION] = data | ||||
|       except NegativeResponseError: | ||||
|         pass | ||||
| 
 | ||||
|       try: | ||||
|         data = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION) | ||||
|         if data: resp[DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION] = data | ||||
|       except NegativeResponseError: | ||||
|         pass | ||||
| 
 | ||||
|       try: | ||||
|         data = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.APPLICATION_DATA_IDENTIFICATION) | ||||
|         if data: resp[DATA_IDENTIFIER_TYPE.APPLICATION_DATA_IDENTIFICATION] = data | ||||
|       except NegativeResponseError: | ||||
|         pass | ||||
| 
 | ||||
|       try: | ||||
|         data = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.BOOT_SOFTWARE_FINGERPRINT) | ||||
|         if data: resp[DATA_IDENTIFIER_TYPE.BOOT_SOFTWARE_FINGERPRINT] = data | ||||
|       except NegativeResponseError: | ||||
|         pass | ||||
| 
 | ||||
|       try: | ||||
|         data = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_FINGERPRINT) | ||||
|         if data: resp[DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_FINGERPRINT] = data | ||||
|       except NegativeResponseError: | ||||
|         pass | ||||
| 
 | ||||
|       try: | ||||
|         data = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.APPLICATION_DATA_FINGERPRINT) | ||||
|         if data: resp[DATA_IDENTIFIER_TYPE.APPLICATION_DATA_FINGERPRINT] = data | ||||
|       except NegativeResponseError: | ||||
|         pass | ||||
| 
 | ||||
|       if resp.keys(): | ||||
|         results[addr] = resp | ||||
| 
 | ||||
|     print("results:") | ||||
|     if len(results.items()): | ||||
|       for addr, resp in results.items(): | ||||
|         for rid, dat in resp.items(): | ||||
|           print(hex(addr), hex(rid), dat.decode()) | ||||
|     else: | ||||
|       print("no fw versions found!") | ||||
| @ -1,11 +1,34 @@ | ||||
| from .helpers import test_all_pandas, panda_connect_and_init | ||||
| import os | ||||
| 
 | ||||
| from nose.tools import assert_equal | ||||
| 
 | ||||
| from panda import Panda, BASEDIR | ||||
| from .helpers import reset_pandas, test_all_pandas, panda_connect_and_init | ||||
| 
 | ||||
| 
 | ||||
| # Reset the pandas before flashing them | ||||
| def aaaa_reset_before_tests(): | ||||
|   reset_pandas() | ||||
| 
 | ||||
| 
 | ||||
| @test_all_pandas | ||||
| @panda_connect_and_init | ||||
| def test_recover(p): | ||||
|   assert p.recover(timeout=30) | ||||
| 
 | ||||
| 
 | ||||
| @test_all_pandas | ||||
| @panda_connect_and_init | ||||
| def test_flash(p): | ||||
|   p.flash() | ||||
| 
 | ||||
| 
 | ||||
| @test_all_pandas | ||||
| @panda_connect_and_init | ||||
| def test_get_signature(p): | ||||
|   fn = os.path.join(BASEDIR, "board/obj/panda.bin") | ||||
| 
 | ||||
|   firmware_sig = Panda.get_signature_from_firmware(fn) | ||||
|   panda_sig = p.get_signature() | ||||
| 
 | ||||
|   assert_equal(panda_sig, firmware_sig) | ||||
|  | ||||
| @ -0,0 +1,44 @@ | ||||
| import time | ||||
| from panda_jungle import PandaJungle  # pylint: disable=import-error | ||||
| from .helpers import panda_jungle, reset_pandas, test_all_pandas, test_all_gen2_pandas, panda_connect_and_init | ||||
| 
 | ||||
| # Reset the pandas before running tests | ||||
| def aaaa_reset_before_tests(): | ||||
|   reset_pandas() | ||||
| 
 | ||||
| @test_all_pandas | ||||
| @panda_connect_and_init | ||||
| def test_ignition(p): | ||||
|   try: | ||||
|     # Set harness orientation to #2, since the ignition line is on the wrong SBU bus :/ | ||||
|     panda_jungle.set_harness_orientation(PandaJungle.HARNESS_ORIENTATION_2) | ||||
|     reset_pandas() | ||||
|     p.reconnect() | ||||
|     panda_jungle.set_ignition(False) | ||||
|     time.sleep(2) | ||||
|     assert p.health()['ignition_line'] == False | ||||
|     panda_jungle.set_ignition(True) | ||||
|     time.sleep(2) | ||||
|     assert p.health()['ignition_line'] == True | ||||
|   finally: | ||||
|     panda_jungle.set_harness_orientation(PandaJungle.HARNESS_ORIENTATION_1) | ||||
| 
 | ||||
| @test_all_gen2_pandas | ||||
| @panda_connect_and_init | ||||
| def test_orientation_detection(p): | ||||
|   seen_orientations = [] | ||||
|   for i in range(3): | ||||
|     panda_jungle.set_harness_orientation(i) | ||||
|     reset_pandas() | ||||
|     p.reconnect() | ||||
|     detected_harness_orientation = p.health()['car_harness_status'] | ||||
|     if (i == 0 and detected_harness_orientation != 0) or detected_harness_orientation in seen_orientations: | ||||
|       assert False | ||||
|     seen_orientations.append(detected_harness_orientation) | ||||
| 
 | ||||
| 
 | ||||
| @test_all_pandas | ||||
| @panda_connect_and_init | ||||
| def test_voltage(p): | ||||
|   voltage = p.health()['voltage'] | ||||
|   assert ((voltage > 10000) and (voltage < 14000)) | ||||
| @ -1,8 +1,12 @@ | ||||
| import time | ||||
| from panda import Panda | ||||
| from .helpers import connect_wifi, test_white, test_all_pandas, panda_type_to_serial, panda_connect_and_init | ||||
| from .helpers import reset_pandas, connect_wifi, test_white, test_all_pandas, panda_type_to_serial, panda_connect_and_init | ||||
| import requests | ||||
| 
 | ||||
| # Reset the pandas before running tests | ||||
| def aaaa_reset_before_tests(): | ||||
|   reset_pandas() | ||||
| 
 | ||||
| @test_all_pandas | ||||
| @panda_connect_and_init | ||||
| def test_get_serial(p): | ||||
| @ -1,195 +0,0 @@ | ||||
| 
 | ||||
| import os | ||||
| import time | ||||
| import random | ||||
| from panda import Panda | ||||
| from nose.tools import assert_equal, assert_less, assert_greater | ||||
| from .helpers import time_many_sends, test_two_panda, test_two_black_panda, panda_type_to_serial, clear_can_buffers, panda_connect_and_init | ||||
| 
 | ||||
| @test_two_panda | ||||
| @panda_type_to_serial | ||||
| @panda_connect_and_init | ||||
| def test_send_recv(p_send, p_recv): | ||||
|   p_send.set_safety_mode(Panda.SAFETY_ALLOUTPUT) | ||||
|   p_recv.set_safety_mode(Panda.SAFETY_ALLOUTPUT) | ||||
|   p_send.set_can_loopback(False) | ||||
|   p_recv.set_can_loopback(False) | ||||
| 
 | ||||
|   assert not p_send.legacy | ||||
|   assert not p_recv.legacy | ||||
| 
 | ||||
|   p_send.can_send_many([(0x1ba, 0, b"message", 0)]*2) | ||||
|   time.sleep(0.05) | ||||
|   p_recv.can_recv() | ||||
|   p_send.can_recv() | ||||
| 
 | ||||
|   busses = [0,1,2] | ||||
| 
 | ||||
|   for bus in busses: | ||||
|     for speed in [100, 250, 500, 750, 1000]: | ||||
|       p_send.set_can_speed_kbps(bus, speed) | ||||
|       p_recv.set_can_speed_kbps(bus, speed) | ||||
|       time.sleep(0.05) | ||||
| 
 | ||||
|       comp_kbps = time_many_sends(p_send, bus, p_recv, two_pandas=True) | ||||
| 
 | ||||
|       saturation_pct = (comp_kbps/speed) * 100.0 | ||||
|       assert_greater(saturation_pct, 80) | ||||
|       assert_less(saturation_pct, 100) | ||||
| 
 | ||||
|       print("two pandas bus {}, 100 messages at speed {:4d}, comp speed is {:7.2f}, percent {:6.2f}".format(bus, speed, comp_kbps, saturation_pct)) | ||||
| 
 | ||||
| @test_two_panda | ||||
| @panda_type_to_serial | ||||
| @panda_connect_and_init | ||||
| def test_latency(p_send, p_recv): | ||||
|   p_send.set_safety_mode(Panda.SAFETY_ALLOUTPUT) | ||||
|   p_recv.set_safety_mode(Panda.SAFETY_ALLOUTPUT) | ||||
|   p_send.set_can_loopback(False) | ||||
|   p_recv.set_can_loopback(False) | ||||
| 
 | ||||
|   assert not p_send.legacy | ||||
|   assert not p_recv.legacy | ||||
| 
 | ||||
|   p_send.set_can_speed_kbps(0, 100) | ||||
|   p_recv.set_can_speed_kbps(0, 100) | ||||
|   time.sleep(0.05) | ||||
| 
 | ||||
|   p_send.can_send_many([(0x1ba, 0, b"testmsg", 0)]*10) | ||||
|   time.sleep(0.05) | ||||
|   p_recv.can_recv() | ||||
|   p_send.can_recv() | ||||
| 
 | ||||
|   busses = [0,1,2] | ||||
| 
 | ||||
|   for bus in busses: | ||||
|     for speed in [100, 250, 500, 750, 1000]: | ||||
|       p_send.set_can_speed_kbps(bus, speed) | ||||
|       p_recv.set_can_speed_kbps(bus, speed) | ||||
|       time.sleep(0.1) | ||||
| 
 | ||||
|       #clear can buffers | ||||
|       clear_can_buffers(p_send) | ||||
|       clear_can_buffers(p_recv) | ||||
| 
 | ||||
|       latencies = [] | ||||
|       comp_kbps_list = [] | ||||
|       saturation_pcts = [] | ||||
| 
 | ||||
|       num_messages = 100 | ||||
| 
 | ||||
|       for i in range(num_messages): | ||||
|         st = time.time() | ||||
|         p_send.can_send(0x1ab, b"message", bus) | ||||
|         r = [] | ||||
|         while len(r) < 1 and (time.time() - st) < 5: | ||||
|           r = p_recv.can_recv() | ||||
|         et = time.time() | ||||
|         r_echo = [] | ||||
|         while len(r_echo) < 1 and (time.time() - st) < 10: | ||||
|           r_echo = p_send.can_recv() | ||||
| 
 | ||||
|         if len(r) == 0 or len(r_echo) == 0: | ||||
|           print("r: {}, r_echo: {}".format(r, r_echo)) | ||||
| 
 | ||||
|         assert_equal(len(r),1) | ||||
|         assert_equal(len(r_echo),1) | ||||
| 
 | ||||
|         et = (et - st)*1000.0 | ||||
|         comp_kbps = (1+11+1+1+1+4+8*8+15+1+1+1+7) / et | ||||
|         latency = et - ((1+11+1+1+1+4+8*8+15+1+1+1+7) / speed) | ||||
| 
 | ||||
|         assert_less(latency, 5.0) | ||||
| 
 | ||||
|         saturation_pct = (comp_kbps/speed) * 100.0 | ||||
|         latencies.append(latency) | ||||
|         comp_kbps_list.append(comp_kbps) | ||||
|         saturation_pcts.append(saturation_pct) | ||||
| 
 | ||||
|       average_latency = sum(latencies)/num_messages | ||||
|       assert_less(average_latency, 1.0) | ||||
|       average_comp_kbps = sum(comp_kbps_list)/num_messages | ||||
|       average_saturation_pct = sum(saturation_pcts)/num_messages | ||||
| 
 | ||||
|       print("two pandas bus {}, {} message average at speed {:4d}, latency is {:5.3f}ms, comp speed is {:7.2f}, percent {:6.2f}"\ | ||||
|             .format(bus, num_messages, speed, average_latency, average_comp_kbps, average_saturation_pct)) | ||||
| 
 | ||||
| @test_two_black_panda | ||||
| @panda_type_to_serial | ||||
| @panda_connect_and_init | ||||
| def test_black_loopback(panda0, panda1): | ||||
|   # disable safety modes | ||||
|   panda0.set_safety_mode(Panda.SAFETY_ALLOUTPUT) | ||||
|   panda1.set_safety_mode(Panda.SAFETY_ALLOUTPUT) | ||||
| 
 | ||||
|   # disable loopback | ||||
|   panda0.set_can_loopback(False) | ||||
|   panda1.set_can_loopback(False) | ||||
| 
 | ||||
|   # clear stuff | ||||
|   panda0.can_send_many([(0x1ba, 0, b"testmsg", 0)]*10) | ||||
|   time.sleep(0.05) | ||||
|   panda0.can_recv() | ||||
|   panda1.can_recv() | ||||
| 
 | ||||
|   # test array (send bus, sender obd, reciever obd, expected busses) | ||||
|   test_array = [ | ||||
|     (0, False, False, [0]), | ||||
|     (1, False, False, [1]), | ||||
|     (2, False, False, [2]), | ||||
|     (0, False, True, [0, 1]), | ||||
|     (1, False, True, []), | ||||
|     (2, False, True, [2]), | ||||
|     (0, True, False, [0]), | ||||
|     (1, True, False, [0]), | ||||
|     (2, True, False, [2]), | ||||
|     (0, True, True, [0, 1]), | ||||
|     (1, True, True, [0, 1]), | ||||
|     (2, True, True, [2]) | ||||
|   ] | ||||
| 
 | ||||
|   # test functions | ||||
|   def get_test_string(): | ||||
|       return b"test"+os.urandom(10) | ||||
| 
 | ||||
|   def _test_buses(send_panda, recv_panda, _test_array): | ||||
|     for send_bus, send_obd, recv_obd, recv_buses in _test_array: | ||||
|       print("\nSend bus:", send_bus, " Send OBD:", send_obd, " Recv OBD:", recv_obd) | ||||
| 
 | ||||
|       # set OBD on pandas | ||||
|       send_panda.set_gmlan(True if send_obd else None) | ||||
|       recv_panda.set_gmlan(True if recv_obd else None) | ||||
| 
 | ||||
|       # clear buffers | ||||
|       clear_can_buffers(send_panda) | ||||
|       clear_can_buffers(recv_panda) | ||||
| 
 | ||||
|       # send the characters | ||||
|       at = random.randint(1, 2000) | ||||
|       st = get_test_string()[0:8] | ||||
|       send_panda.can_send(at, st, send_bus) | ||||
|       time.sleep(0.1) | ||||
| 
 | ||||
|       # check for receive | ||||
|       _ = send_panda.can_recv()  # cans echo | ||||
|       cans_loop = recv_panda.can_recv() | ||||
| 
 | ||||
|       loop_buses = [] | ||||
|       for loop in cans_loop: | ||||
|         print("  Loop on bus", str(loop[3])) | ||||
|         loop_buses.append(loop[3]) | ||||
|       if len(cans_loop) == 0: | ||||
|         print("  No loop") | ||||
| 
 | ||||
|       # test loop buses | ||||
|       recv_buses.sort() | ||||
|       loop_buses.sort() | ||||
|       assert recv_buses == loop_buses | ||||
|       print("  TEST PASSED") | ||||
|     print("\n") | ||||
| 
 | ||||
|   # test both orientations | ||||
|   print("***************** TESTING (0 --> 1) *****************") | ||||
|   _test_buses(panda0, panda1, test_array) | ||||
|   print("***************** TESTING (1 --> 0) *****************") | ||||
|   _test_buses(panda1, panda0, test_array) | ||||
| @ -1,16 +1,23 @@ | ||||
| 
 | ||||
| import sys | ||||
| import time | ||||
| from .helpers import time_many_sends, connect_wifi, test_white, panda_type_to_serial | ||||
| from .helpers import start_heartbeat_thread, reset_pandas, time_many_sends, connect_wifi, test_white, panda_type_to_serial | ||||
| from panda import Panda, PandaWifiStreaming | ||||
| from nose.tools import assert_less, assert_greater | ||||
| 
 | ||||
| # Reset the pandas before running tests | ||||
| def aaaa_reset_before_tests(): | ||||
|   reset_pandas() | ||||
| 
 | ||||
| @test_white | ||||
| @panda_type_to_serial | ||||
| def test_udp_doesnt_drop(serials=None): | ||||
|   connect_wifi(serials[0]) | ||||
| 
 | ||||
|   p = Panda(serials[0]) | ||||
| 
 | ||||
|   # Start heartbeat | ||||
|   start_heartbeat_thread(p) | ||||
| 
 | ||||
|   p.set_safety_mode(Panda.SAFETY_ALLOUTPUT) | ||||
|   p.set_can_loopback(True) | ||||
| 
 | ||||
| @ -0,0 +1,202 @@ | ||||
| import os | ||||
| import time | ||||
| import random | ||||
| from panda import Panda | ||||
| from nose.tools import assert_equal, assert_less, assert_greater | ||||
| from .helpers import panda_jungle, start_heartbeat_thread, reset_pandas, time_many_sends, test_all_pandas, test_all_gen2_pandas, clear_can_buffers, panda_connect_and_init | ||||
| 
 | ||||
| # Reset the pandas before running tests | ||||
| def aaaa_reset_before_tests(): | ||||
|   reset_pandas() | ||||
| 
 | ||||
| @test_all_pandas | ||||
| @panda_connect_and_init | ||||
| def test_send_recv(p): | ||||
|   def test(p_send, p_recv): | ||||
|     p_send.set_can_loopback(False) | ||||
|     p_recv.set_can_loopback(False) | ||||
| 
 | ||||
|     p_send.can_send_many([(0x1ba, 0, b"message", 0)]*2) | ||||
|     time.sleep(0.05) | ||||
|     p_recv.can_recv() | ||||
|     p_send.can_recv() | ||||
| 
 | ||||
|     busses = [0,1,2] | ||||
| 
 | ||||
|     for bus in busses: | ||||
|       for speed in [100, 250, 500, 750, 1000]: | ||||
|         p_send.set_can_speed_kbps(bus, speed) | ||||
|         p_recv.set_can_speed_kbps(bus, speed) | ||||
|         time.sleep(0.05) | ||||
| 
 | ||||
|         clear_can_buffers(p_send) | ||||
|         clear_can_buffers(p_recv) | ||||
| 
 | ||||
|         comp_kbps = time_many_sends(p_send, bus, p_recv, two_pandas=True) | ||||
| 
 | ||||
|         saturation_pct = (comp_kbps/speed) * 100.0 | ||||
|         assert_greater(saturation_pct, 80) | ||||
|         assert_less(saturation_pct, 100) | ||||
| 
 | ||||
|         print("two pandas bus {}, 100 messages at speed {:4d}, comp speed is {:7.2f}, percent {:6.2f}".format(bus, speed, comp_kbps, saturation_pct)) | ||||
| 
 | ||||
|   # Start heartbeat | ||||
|   start_heartbeat_thread(p) | ||||
| 
 | ||||
|   # Set safety mode and power saving | ||||
|   p.set_safety_mode(Panda.SAFETY_ALLOUTPUT) | ||||
|   p.set_power_save(False) | ||||
| 
 | ||||
|   try: | ||||
|     # Run tests in both directions | ||||
|     test(p, panda_jungle) | ||||
|     test(panda_jungle, p) | ||||
|   except Exception as e: | ||||
|     # Raise errors again, we don't want them to get lost | ||||
|     raise e | ||||
|   finally: | ||||
|     # Set back to silent mode | ||||
|     p.set_safety_mode(Panda.SAFETY_SILENT) | ||||
| 
 | ||||
| @test_all_pandas | ||||
| @panda_connect_and_init | ||||
| def test_latency(p): | ||||
|   def test(p_send, p_recv): | ||||
|     p_send.set_can_loopback(False) | ||||
|     p_recv.set_can_loopback(False) | ||||
| 
 | ||||
|     p_send.set_can_speed_kbps(0, 100) | ||||
|     p_recv.set_can_speed_kbps(0, 100) | ||||
|     time.sleep(0.05) | ||||
| 
 | ||||
|     p_send.can_send_many([(0x1ba, 0, b"testmsg", 0)]*10) | ||||
|     time.sleep(0.05) | ||||
|     p_recv.can_recv() | ||||
|     p_send.can_recv() | ||||
| 
 | ||||
|     busses = [0,1,2] | ||||
| 
 | ||||
|     for bus in busses: | ||||
|       for speed in [100, 250, 500, 750, 1000]: | ||||
|         p_send.set_can_speed_kbps(bus, speed) | ||||
|         p_recv.set_can_speed_kbps(bus, speed) | ||||
|         time.sleep(0.1) | ||||
| 
 | ||||
|         # clear can buffers | ||||
|         clear_can_buffers(p_send) | ||||
|         clear_can_buffers(p_recv) | ||||
| 
 | ||||
|         latencies = [] | ||||
|         comp_kbps_list = [] | ||||
|         saturation_pcts = [] | ||||
| 
 | ||||
|         num_messages = 100 | ||||
| 
 | ||||
|         for i in range(num_messages): | ||||
|           st = time.time() | ||||
|           p_send.can_send(0x1ab, b"message", bus) | ||||
|           r = [] | ||||
|           while len(r) < 1 and (time.time() - st) < 5: | ||||
|             r = p_recv.can_recv() | ||||
|           et = time.time() | ||||
|           r_echo = [] | ||||
|           while len(r_echo) < 1 and (time.time() - st) < 10: | ||||
|             r_echo = p_send.can_recv() | ||||
| 
 | ||||
|           if len(r) == 0 or len(r_echo) == 0: | ||||
|             print("r: {}, r_echo: {}".format(r, r_echo)) | ||||
| 
 | ||||
|           assert_equal(len(r),1) | ||||
|           assert_equal(len(r_echo),1) | ||||
| 
 | ||||
|           et = (et - st)*1000.0 | ||||
|           comp_kbps = (1+11+1+1+1+4+8*8+15+1+1+1+7) / et | ||||
|           latency = et - ((1+11+1+1+1+4+8*8+15+1+1+1+7) / speed) | ||||
| 
 | ||||
|           assert_less(latency, 5.0) | ||||
| 
 | ||||
|           saturation_pct = (comp_kbps/speed) * 100.0 | ||||
|           latencies.append(latency) | ||||
|           comp_kbps_list.append(comp_kbps) | ||||
|           saturation_pcts.append(saturation_pct) | ||||
| 
 | ||||
|         average_latency = sum(latencies)/num_messages | ||||
|         assert_less(average_latency, 1.0) | ||||
|         average_comp_kbps = sum(comp_kbps_list)/num_messages | ||||
|         average_saturation_pct = sum(saturation_pcts)/num_messages | ||||
| 
 | ||||
|         print("two pandas bus {}, {} message average at speed {:4d}, latency is {:5.3f}ms, comp speed is {:7.2f}, percent {:6.2f}"\ | ||||
|               .format(bus, num_messages, speed, average_latency, average_comp_kbps, average_saturation_pct)) | ||||
| 
 | ||||
|   # Start heartbeat | ||||
|   start_heartbeat_thread(p) | ||||
| 
 | ||||
|   # Set safety mode and power saving | ||||
|   p.set_safety_mode(Panda.SAFETY_ALLOUTPUT) | ||||
|   p.set_power_save(False) | ||||
| 
 | ||||
|   try: | ||||
|     # Run tests in both directions | ||||
|     test(p, panda_jungle) | ||||
|     test(panda_jungle, p) | ||||
|   except Exception as e: | ||||
|     # Raise errors again, we don't want them to get lost | ||||
|     raise e | ||||
|   finally: | ||||
|     # Set back to silent mode | ||||
|     p.set_safety_mode(Panda.SAFETY_SILENT) | ||||
| 
 | ||||
| 
 | ||||
| @test_all_gen2_pandas | ||||
| @panda_connect_and_init | ||||
| def test_gen2_loopback(p): | ||||
|   def test(p_send, p_recv): | ||||
|     for bus in range(4): | ||||
|       obd = False | ||||
|       if bus == 3: | ||||
|         obd = True | ||||
|         bus = 1 | ||||
|        | ||||
|       # Clear buses | ||||
|       clear_can_buffers(p_send) | ||||
|       clear_can_buffers(p_recv) | ||||
| 
 | ||||
|       # Send a random string | ||||
|       addr = random.randint(1, 2000) | ||||
|       string = b"test"+os.urandom(4) | ||||
|       p_send.set_obd(obd) | ||||
|       p_recv.set_obd(obd) | ||||
|       time.sleep(0.2) | ||||
|       p_send.can_send(addr, string, bus) | ||||
|       time.sleep(0.2) | ||||
| 
 | ||||
|       content = p_recv.can_recv() | ||||
| 
 | ||||
|       # Check amount of messages | ||||
|       assert len(content) == 1 | ||||
| 
 | ||||
|       # Check content | ||||
|       assert content[0][0] == addr and content[0][2] == string | ||||
|        | ||||
|       # Check bus | ||||
|       assert content[0][3] == bus | ||||
| 
 | ||||
|       print("Bus:", bus, "OBD:", obd, "OK") | ||||
| 
 | ||||
|   # Start heartbeat | ||||
|   start_heartbeat_thread(p) | ||||
| 
 | ||||
|   # Set safety mode and power saving | ||||
|   p.set_safety_mode(Panda.SAFETY_ALLOUTPUT) | ||||
|   p.set_power_save(False) | ||||
| 
 | ||||
|   try: | ||||
|     # Run tests in both directions | ||||
|     test(p, panda_jungle) | ||||
|     test(panda_jungle, p) | ||||
|   except Exception as e: | ||||
|     # Raise errors again, we don't want them to get lost | ||||
|     raise e | ||||
|   finally: | ||||
|     # Set back to silent mode | ||||
|     p.set_safety_mode(Panda.SAFETY_SILENT) | ||||
| @ -0,0 +1,25 @@ | ||||
| import time | ||||
| from multiprocessing import Process | ||||
| 
 | ||||
| # Note: this does not return any return values of the function, just the exit status | ||||
| INTERVAL = 0.1 | ||||
| def run_with_timeout(timeout, fn, *kwargs): | ||||
|   def runner(fn, kwargs): | ||||
|     try: | ||||
|       fn(*kwargs) | ||||
|     except Exception as e: | ||||
|       print(e) | ||||
|       raise e | ||||
|    | ||||
|   process = Process(target=runner, args=(fn, kwargs)) | ||||
|   process.start() | ||||
| 
 | ||||
|   counter = 0 | ||||
|   while process.is_alive(): | ||||
|     time.sleep(INTERVAL) | ||||
|     counter+=1 | ||||
|     if (counter * INTERVAL) > timeout: | ||||
|       process.terminate() | ||||
|       raise TimeoutError("Function timed out!") | ||||
|   if process.exitcode != 0: | ||||
|     raise RuntimeError("Test failed with exit code: ", str(process.exitcode)) | ||||
| @ -0,0 +1,85 @@ | ||||
| import os | ||||
| FNULL = open(os.devnull, 'w') | ||||
| def _connect_wifi(dongle_id, pw, insecure_okay=False): | ||||
|   ssid = "panda-" + dongle_id | ||||
| 
 | ||||
|   r = subprocess.call(["ping", "-W", "4", "-c", "1", "192.168.0.10"], stdout=FNULL, stderr=subprocess.STDOUT) | ||||
|   if not r: | ||||
|     # Can already ping, try connecting on wifi | ||||
|     try: | ||||
|       p = Panda("WIFI") | ||||
|       p.get_serial() | ||||
|       print("Already connected") | ||||
|       return | ||||
|     except: | ||||
|       pass | ||||
| 
 | ||||
|   print("WIFI: connecting to %s" % ssid) | ||||
| 
 | ||||
|   while 1: | ||||
|     if sys.platform == "darwin": | ||||
|       os.system("networksetup -setairportnetwork en0 %s %s" % (ssid, pw)) | ||||
|     else: | ||||
|       wlan_interface = subprocess.check_output(["sh", "-c", "iw dev | awk '/Interface/ {print $2}'"]).strip().decode('utf8') | ||||
|       cnt = 0 | ||||
|       MAX_TRIES = 10 | ||||
|       while cnt < MAX_TRIES: | ||||
|         print("WIFI: scanning %d" % cnt) | ||||
|         os.system("iwlist %s scanning > /dev/null" % wlan_interface) | ||||
|         os.system("nmcli device wifi rescan") | ||||
|         wifi_networks = [x.decode("utf8") for x in subprocess.check_output(["nmcli","dev", "wifi", "list"]).split(b"\n")] | ||||
|         wifi_scan = [x for x in wifi_networks if ssid in x] | ||||
|         if len(wifi_scan) != 0: | ||||
|           break | ||||
|         time.sleep(0.1) | ||||
|         # MAX_TRIES tries, ~10 seconds max | ||||
|         cnt += 1 | ||||
|       assert cnt < MAX_TRIES | ||||
|       if "-pair" in wifi_scan[0]: | ||||
|         os.system("nmcli d wifi connect %s-pair" % (ssid)) | ||||
|         connect_cnt = 0 | ||||
|         MAX_TRIES = 100 | ||||
|         while connect_cnt < MAX_TRIES: | ||||
|           connect_cnt += 1 | ||||
|           r = subprocess.call(["ping", "-W", "4", "-c", "1", "192.168.0.10"], stdout=FNULL, stderr=subprocess.STDOUT) | ||||
|           if r: | ||||
|             print("Waiting for panda to ping...") | ||||
|             time.sleep(0.5) | ||||
|           else: | ||||
|             break | ||||
|         if insecure_okay: | ||||
|           break | ||||
|         # fetch webpage | ||||
|         print("connecting to insecure network to secure") | ||||
|         try: | ||||
|           r = requests.get("http://192.168.0.10/") | ||||
|         except requests.ConnectionError: | ||||
|           r = requests.get("http://192.168.0.10/") | ||||
|         assert r.status_code==200 | ||||
| 
 | ||||
|         print("securing") | ||||
|         try: | ||||
|           r = requests.get("http://192.168.0.10/secure", timeout=0.01) | ||||
|         except requests.exceptions.Timeout: | ||||
|           print("timeout http request to secure") | ||||
|           pass | ||||
|       else: | ||||
|         ret = os.system("nmcli d wifi connect %s password %s" % (ssid, pw)) | ||||
|         if os.WEXITSTATUS(ret) == 0: | ||||
|           #check ping too | ||||
|           ping_ok = False | ||||
|           connect_cnt = 0 | ||||
|           MAX_TRIES = 10 | ||||
|           while connect_cnt < MAX_TRIES: | ||||
|             connect_cnt += 1 | ||||
|             r = subprocess.call(["ping", "-W", "4", "-c", "1", "192.168.0.10"], stdout=FNULL, stderr=subprocess.STDOUT) | ||||
|             if r: | ||||
|               print("Waiting for panda to ping...") | ||||
|               time.sleep(0.1) | ||||
|             else: | ||||
|               ping_ok = True | ||||
|               break | ||||
|           if ping_ok: | ||||
|             break | ||||
| 
 | ||||
|   # TODO: confirm that it's connected to the right panda | ||||
| @ -0,0 +1,50 @@ | ||||
| #!/usr/bin/env python3 | ||||
| import matplotlib.pyplot as plt # pylint: disable=import-error | ||||
| 
 | ||||
| HASHING_PRIME = 23 | ||||
| REGISTER_MAP_SIZE = 0x3FF | ||||
| BYTES_PER_REG = 4 | ||||
| 
 | ||||
| # From ST32F413 datasheet | ||||
| REGISTER_ADDRESS_REGIONS = [ | ||||
|   (0x40000000, 0x40007FFF), | ||||
|   (0x40010000, 0x400107FF), | ||||
|   (0x40011000, 0x400123FF), | ||||
|   (0x40012C00, 0x40014BFF), | ||||
|   (0x40015000, 0x400153FF), | ||||
|   (0x40015800, 0x40015BFF), | ||||
|   (0x40016000, 0x400167FF), | ||||
|   (0x40020000, 0x40021FFF), | ||||
|   (0x40023000, 0x400233FF), | ||||
|   (0x40023800, 0x40023FFF), | ||||
|   (0x40026000, 0x400267FF), | ||||
|   (0x50000000, 0x5003FFFF), | ||||
|   (0x50060000, 0x500603FF), | ||||
|   (0x50060800, 0x50060BFF), | ||||
|   (0x50060800, 0x50060BFF), | ||||
|   (0xE0000000, 0xE00FFFFF) | ||||
| ] | ||||
| 
 | ||||
| def hash(reg_addr): | ||||
|   return (((reg_addr >> 16) ^ ((((reg_addr + 1) & 0xFFFF) * HASHING_PRIME) & 0xFFFF)) & REGISTER_MAP_SIZE) | ||||
| 
 | ||||
| # Calculate hash for each address | ||||
| hashes = [] | ||||
| double_hashes = [] | ||||
| for (start_addr, stop_addr) in REGISTER_ADDRESS_REGIONS: | ||||
|   for addr in range(start_addr, stop_addr+1, BYTES_PER_REG): | ||||
|     h = hash(addr) | ||||
|     hashes.append(h) | ||||
|     double_hashes.append(hash(h)) | ||||
| 
 | ||||
| # Make histograms | ||||
| plt.subplot(2, 1, 1) | ||||
| plt.hist(hashes, bins=REGISTER_MAP_SIZE) | ||||
| plt.title("Number of collisions per hash") | ||||
| plt.xlabel("Address") | ||||
| 
 | ||||
| plt.subplot(2, 1, 2) | ||||
| plt.hist(double_hashes, bins=REGISTER_MAP_SIZE) | ||||
| plt.title("Number of collisions per double hash") | ||||
| plt.xlabel("Address") | ||||
| plt.show() | ||||
| @ -0,0 +1,35 @@ | ||||
| #!/usr/bin/env python3 | ||||
| import os | ||||
| import sys | ||||
| import time | ||||
| import _thread | ||||
| 
 | ||||
| sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..")) | ||||
| from panda import Panda | ||||
| 
 | ||||
| # This script is intended to be used in conjunction with the echo_loopback_test.py test script from panda jungle. | ||||
| # It sends a reversed response back for every message received containing b"test". | ||||
| 
 | ||||
| def heartbeat_thread(p): | ||||
|   while True: | ||||
|     try: | ||||
|       p.send_heartbeat() | ||||
|       time.sleep(1) | ||||
|     except: | ||||
|       break | ||||
| 
 | ||||
| # Resend every CAN message that has been received on the same bus, but with the data reversed | ||||
| if __name__ == "__main__": | ||||
|   p = Panda() | ||||
|   _thread.start_new_thread(heartbeat_thread, (p,)) | ||||
|   p.set_safety_mode(Panda.SAFETY_ALLOUTPUT) | ||||
|   p.set_power_save(False) | ||||
| 
 | ||||
|   while True: | ||||
|     incoming = p.can_recv() | ||||
|     for message in incoming: | ||||
|       address, notused, data, bus = message | ||||
|       if b'test' in data: | ||||
|         p.can_send(address, data[::-1], bus) | ||||
| 
 | ||||
| 
 | ||||
| @ -0,0 +1 @@ | ||||
| cppcheck/ | ||||
| @ -0,0 +1,36 @@ | ||||
| from panda.tests.safety import libpandasafety_py | ||||
| 
 | ||||
| def make_msg(bus, addr, length=8): | ||||
|   to_send = libpandasafety_py.ffi.new('CAN_FIFOMailBox_TypeDef *') | ||||
|   if addr >= 0x800: | ||||
|     to_send[0].RIR = (addr << 3) | 5 | ||||
|   else: | ||||
|     to_send[0].RIR = (addr << 21) | 1 | ||||
|   to_send[0].RDTR = length | ||||
|   to_send[0].RDTR |= bus << 4 | ||||
| 
 | ||||
|   return to_send | ||||
| 
 | ||||
| def test_relay_malfunction(test, addr): | ||||
|   # input is a test class and the address that, if seen on bus 0, triggers | ||||
|   # the relay_malfunction protection logic: both tx_hook and fwd_hook are | ||||
|   # expected to return failure | ||||
|   test.assertFalse(test.safety.get_relay_malfunction()) | ||||
|   test.safety.safety_rx_hook(make_msg(0, addr, 8)) | ||||
|   test.assertTrue(test.safety.get_relay_malfunction()) | ||||
|   for a in range(1, 0x800): | ||||
|     for b in range(0, 3): | ||||
|       test.assertFalse(test.safety.safety_tx_hook(make_msg(b, a, 8))) | ||||
|       test.assertEqual(-1, test.safety.safety_fwd_hook(b, make_msg(b, a, 8))) | ||||
| 
 | ||||
| def test_manually_enable_controls_allowed(test): | ||||
|   test.safety.set_controls_allowed(1) | ||||
|   test.assertTrue(test.safety.get_controls_allowed()) | ||||
|   test.safety.set_controls_allowed(0) | ||||
|   test.assertFalse(test.safety.get_controls_allowed()) | ||||
| 
 | ||||
| def test_spam_can_buses(test, TX_MSGS): | ||||
|   for addr in range(1, 0x800): | ||||
|     for bus in range(0, 4): | ||||
|       if all(addr != m[0] or bus != m[1] for m in TX_MSGS): | ||||
|         test.assertFalse(test.safety.safety_tx_hook(make_msg(bus, addr, 8))) | ||||
Some files were not shown because too many files have changed in this diff Show More
					Loading…
					
					
				
		Reference in new issue