commit
						7834995df4
					
				
				 37 changed files with 947 additions and 107 deletions
			
			
		| @ -0,0 +1,3 @@ | ||||
| .git | ||||
| .DS_Store | ||||
| boardesp/esp-open-sdk | ||||
| @ -0,0 +1,64 @@ | ||||
| FROM ubuntu:16.04 | ||||
| ENV PYTHONUNBUFFERED 1 | ||||
| 
 | ||||
| RUN apt-get update && apt-get install -y \ | ||||
|     autoconf \ | ||||
|     automake \ | ||||
|     bash \ | ||||
|     bison \ | ||||
|     bzip2 \ | ||||
|     curl \ | ||||
|     dfu-util \ | ||||
|     flex \ | ||||
|     g++ \ | ||||
|     gawk \ | ||||
|     gcc \ | ||||
|     git \ | ||||
|     gperf \ | ||||
|     help2man \ | ||||
|     iputils-ping \ | ||||
|     libexpat-dev \ | ||||
|     libstdc++-arm-none-eabi-newlib \ | ||||
|     libtool \ | ||||
|     libtool-bin \ | ||||
|     libusb-1.0-0 \ | ||||
|     make \ | ||||
|     ncurses-dev \ | ||||
|     network-manager \ | ||||
|     python-dev \ | ||||
|     python-serial \ | ||||
|     sed \ | ||||
|     texinfo \ | ||||
|     unrar-free \ | ||||
|     unzip \ | ||||
|     wget \ | ||||
|     build-essential \ | ||||
|     python-dev \ | ||||
|     python-pip \ | ||||
|     screen \ | ||||
|     vim \ | ||||
|     wget \ | ||||
|     wireless-tools | ||||
| 
 | ||||
| RUN pip install --upgrade pip==18.0 | ||||
| 
 | ||||
| COPY requirements.txt /tmp/ | ||||
| RUN pip install -r /tmp/requirements.txt | ||||
| 
 | ||||
| RUN mkdir -p /home/batman | ||||
| ENV HOME /home/batman | ||||
| 
 | ||||
| ENV PYTHONPATH /tmp:$PYTHONPATH | ||||
| 
 | ||||
| COPY ./boardesp/get_sdk_ci.sh /tmp/panda/boardesp/ | ||||
| 
 | ||||
| RUN useradd --system -s /sbin/nologin pandauser | ||||
| RUN mkdir -p /tmp/panda/boardesp/esp-open-sdk | ||||
| RUN chown pandauser /tmp/panda/boardesp/esp-open-sdk | ||||
| USER pandauser | ||||
| RUN cd /tmp/panda/boardesp && ./get_sdk_ci.sh | ||||
| USER root | ||||
| 
 | ||||
| COPY ./xx/pandaextra /tmp/pandaextra | ||||
| 
 | ||||
| ADD ./panda.tar.gz /tmp/panda | ||||
| @ -0,0 +1,55 @@ | ||||
| pipeline { | ||||
|   agent any | ||||
|   environment { | ||||
|     AUTHOR = """${sh( | ||||
|                 returnStdout: true, | ||||
|                 script: "git --no-pager show -s --format='%an' ${GIT_COMMIT}" | ||||
|              ).trim()}""" | ||||
| 
 | ||||
|     DOCKER_IMAGE_TAG = "panda:build-${env.BUILD_ID}" | ||||
|     DOCKER_NAME = "panda-test-${env.BUILD_ID}" | ||||
|   } | ||||
|   stages { | ||||
|     stage('Build Docker Image') { | ||||
|       steps { | ||||
|         timeout(time: 60, unit: 'MINUTES') { | ||||
|           script { | ||||
|             sh 'git clone --no-checkout --depth 1 git@github.com:commaai/xx.git || true' | ||||
|             sh 'cd xx && git fetch origin && git checkout origin/master -- pandaextra && cd ..' // Needed for certs for panda flashing | ||||
|             sh 'git archive -v -o panda.tar.gz --format=tar.gz HEAD' | ||||
|             dockerImage = docker.build("${env.DOCKER_IMAGE_TAG}") | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     stage('Test Dev Build') { | ||||
|       steps { | ||||
|         lock(resource: "Pandas", inversePrecedence: true, quantity:1){ | ||||
|           timeout(time: 60, unit: 'MINUTES') { | ||||
|             sh "docker run --name ${env.DOCKER_NAME} --privileged --volume /dev/bus/usb:/dev/bus/usb --volume /var/run/dbus:/var/run/dbus --net host ${env.DOCKER_IMAGE_TAG} bash -c 'cd /tmp/panda; ./run_automated_tests.sh '" | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     stage('Test EON Build') { | ||||
|       steps { | ||||
|         lock(resource: "Pandas", inversePrecedence: true, quantity:1){ | ||||
|           timeout(time: 60, unit: 'MINUTES') { | ||||
|             sh "docker cp ${env.DOCKER_NAME}:/tmp/panda/nosetests.xml test_results_dev.xml" | ||||
|             sh "touch EON && docker cp EON ${env.DOCKER_NAME}:/EON" | ||||
|             sh "docker start -a ${env.DOCKER_NAME}" | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   post { | ||||
|     always { | ||||
|       script { | ||||
|         sh "docker cp ${env.DOCKER_NAME}:/tmp/panda/nosetests.xml test_results_EON.xml" | ||||
|         sh "docker rm ${env.DOCKER_NAME}" | ||||
|       } | ||||
|       junit "test_results*.xml" | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -1 +1 @@ | ||||
| v1.2.0 | ||||
| v1.2.1 | ||||
| @ -0,0 +1,88 @@ | ||||
| #ifdef PANDA | ||||
| 
 | ||||
| int relay_control = 0;  // True if relay is controlled through l-line
 | ||||
| 
 | ||||
| /* Conrol a relay connected to l-line pin */ | ||||
| 
 | ||||
| // 160us cycles, 1 high, 25 low
 | ||||
| 
 | ||||
| volatile int turn_on_relay = 0; | ||||
| volatile int on_cycles = 25; | ||||
| 
 | ||||
| //5s timeout
 | ||||
| #define LLINE_TIMEOUT_CYCLES 31250 | ||||
| volatile int timeout_cycles = LLINE_TIMEOUT_CYCLES; | ||||
| 
 | ||||
| void TIM5_IRQHandler(void) { | ||||
|   if (TIM5->SR & TIM_SR_UIF) { | ||||
|     on_cycles--; | ||||
|     timeout_cycles--; | ||||
|     if (timeout_cycles == 0) { | ||||
|       turn_on_relay = 0; | ||||
|     } | ||||
|     if (on_cycles > 0) { | ||||
|       if (turn_on_relay) { | ||||
|         set_gpio_output(GPIOC, 10, 0); | ||||
|       } | ||||
|     } | ||||
|     else { | ||||
|       set_gpio_output(GPIOC, 10, 1); | ||||
|       on_cycles = 25; | ||||
|     } | ||||
|   } | ||||
|   TIM5->ARR = 160-1; | ||||
|   TIM5->SR = 0; | ||||
| } | ||||
| 
 | ||||
| void lline_relay_init (void) { | ||||
|   set_lline_output(0); | ||||
|   relay_control = 1; | ||||
|   set_gpio_output(GPIOC, 10, 1); | ||||
| 
 | ||||
|   // setup
 | ||||
|   TIM5->PSC = 48-1; // tick on 1 us
 | ||||
|   TIM5->CR1 = TIM_CR1_CEN;   // enable
 | ||||
|   TIM5->ARR = 50-1;         // 50 us
 | ||||
|   TIM5->DIER = TIM_DIER_UIE; // update interrupt
 | ||||
|   TIM5->CNT = 0; | ||||
| 
 | ||||
|   NVIC_EnableIRQ(TIM5_IRQn); | ||||
| 
 | ||||
| #ifdef DEBUG | ||||
|   puts("INIT LLINE\n"); | ||||
|   puts(" SR "); | ||||
|   putui(TIM5->SR); | ||||
|   puts(" PSC "); | ||||
|   putui(TIM5->PSC); | ||||
|   puts(" CR1 "); | ||||
|   putui(TIM5->CR1); | ||||
|   puts(" ARR "); | ||||
|   putui(TIM5->ARR); | ||||
|   puts(" DIER "); | ||||
|   putui(TIM5->DIER); | ||||
|   puts(" SR "); | ||||
|   putui(TIM5->SR); | ||||
|   puts(" CNT "); | ||||
|   putui(TIM5->CNT); | ||||
|   puts("\n"); | ||||
| #endif | ||||
| } | ||||
| 
 | ||||
| void lline_relay_release (void) { | ||||
|   set_lline_output(0); | ||||
|   relay_control = 0; | ||||
|   puts("RELEASE LLINE\n"); | ||||
|   set_gpio_alternate(GPIOC, 10, GPIO_AF7_USART3); | ||||
|   NVIC_DisableIRQ(TIM5_IRQn); | ||||
| } | ||||
| 
 | ||||
| void set_lline_output(int to_set) { | ||||
|   timeout_cycles = LLINE_TIMEOUT_CYCLES; | ||||
|   turn_on_relay = to_set; | ||||
| } | ||||
| 
 | ||||
| int get_lline_status() { | ||||
|   return turn_on_relay; | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
| @ -0,0 +1,157 @@ | ||||
| #define POWER_SAVE_STATUS_DISABLED 0 | ||||
| //Moving to enabled, but can wakeup not yet enabled
 | ||||
| #define POWER_SAVE_STATUS_SWITCHING 1 | ||||
| #define POWER_SAVE_STATUS_ENABLED 2 | ||||
| 
 | ||||
| volatile int power_save_status = POWER_SAVE_STATUS_DISABLED; | ||||
| 
 | ||||
| void power_save_enable(void) { | ||||
|   power_save_status = POWER_SAVE_STATUS_SWITCHING; | ||||
|   puts("Saving power\n"); | ||||
|   //Turn off can transciever
 | ||||
|   set_can_enable(CAN1, 0); | ||||
|   set_can_enable(CAN2, 0); | ||||
| #ifdef PANDA | ||||
|   set_can_enable(CAN3, 0); | ||||
| #endif | ||||
| 
 | ||||
|   //Turn off GMLAN
 | ||||
|   set_gpio_output(GPIOB, 14, 0); | ||||
|   set_gpio_output(GPIOB, 15, 0); | ||||
| 
 | ||||
| #ifdef PANDA | ||||
|   //Turn off LIN K
 | ||||
|   if (revision == PANDA_REV_C) { | ||||
|     set_gpio_output(GPIOB, 7, 0); // REV C
 | ||||
|   } else { | ||||
|     set_gpio_output(GPIOB, 4, 0); // REV AB
 | ||||
|   } | ||||
|   // LIN L
 | ||||
|   set_gpio_output(GPIOA, 14, 0); | ||||
| #endif | ||||
| 
 | ||||
|   if (is_grey_panda) { | ||||
|     char* UBLOX_SLEEP_MSG = "\xb5\x62\x06\x04\x04\x00\x01\x00\x08\x00\x17\x78"; | ||||
|     int len = 12; | ||||
|     uart_ring *ur = get_ring_by_number(1); | ||||
|     for (int i = 0; i < len; i++) while (!putc(ur, UBLOX_SLEEP_MSG[i])); | ||||
|   } | ||||
| 
 | ||||
|   //Setup timer for can enable
 | ||||
|   TIM6->PSC = 48-1; // tick on 1 us
 | ||||
| 
 | ||||
|   TIM6->ARR = 12; // 12us
 | ||||
|   // Enable, One-Pulse Mode, Only overflow interrupt
 | ||||
|   TIM6->CR1 = TIM_CR1_CEN | TIM_CR1_OPM | TIM_CR1_URS; | ||||
|   TIM6->EGR = TIM_EGR_UG; | ||||
|   TIM6->CR1 |= TIM_CR1_CEN; | ||||
| } | ||||
| 
 | ||||
| void power_save_enable_can_wake(void) { | ||||
|   // CAN Automatic Wake must be done a little while after the sleep
 | ||||
|   // On some cars turning off the can transciver can trigger the wakeup
 | ||||
|   power_save_status = POWER_SAVE_STATUS_ENABLED; | ||||
|   puts("Turning can off\n"); | ||||
|   CAN1->MCR |= CAN_MCR_SLEEP; | ||||
|   CAN1->MCR |= CAN_MCR_AWUM; | ||||
| 
 | ||||
|   CAN2->MCR |= CAN_MCR_SLEEP; | ||||
|   CAN2->MCR |= CAN_MCR_AWUM; | ||||
| #ifdef PANDA | ||||
|   CAN3->MCR |= CAN_MCR_SLEEP; | ||||
|   CAN3->MCR |= CAN_MCR_AWUM; | ||||
| #endif | ||||
| 
 | ||||
|   //set timer back
 | ||||
|   TIM6->PSC = 48000-1; // tick on 1 ms
 | ||||
|   TIM6->ARR = 10000; // 10s
 | ||||
|   // Enable, One-Pulse Mode, Only overflow interrupt
 | ||||
|   TIM6->CR1 = TIM_CR1_OPM | TIM_CR1_URS; | ||||
|   TIM6->EGR = TIM_EGR_UG; | ||||
| } | ||||
| 
 | ||||
| void power_save_disable(void) { | ||||
|   power_save_status = POWER_SAVE_STATUS_DISABLED; | ||||
|   puts("not Saving power\n"); | ||||
|   TIM6->CR1 |= TIM_CR1_CEN; //Restart timer
 | ||||
|   TIM6->CNT = 0; | ||||
| 
 | ||||
|   //Turn on can
 | ||||
|   set_can_enable(CAN1, 1); | ||||
|   set_can_enable(CAN2, 1); | ||||
| 
 | ||||
| #ifdef PANDA | ||||
|   set_can_enable(CAN3, 1); | ||||
| #endif | ||||
| 
 | ||||
|   //Turn on GMLAN
 | ||||
|   set_gpio_output(GPIOB, 14, 1); | ||||
|   set_gpio_output(GPIOB, 15, 1); | ||||
| 
 | ||||
| #ifdef PANDA | ||||
|   //Turn on LIN K
 | ||||
|   if (revision == PANDA_REV_C) { | ||||
|     set_gpio_output(GPIOB, 7, 1); // REV C
 | ||||
|   } else { | ||||
|     set_gpio_output(GPIOB, 4, 1); // REV AB
 | ||||
|   } | ||||
|   // LIN L
 | ||||
|   set_gpio_output(GPIOA, 14, 1); | ||||
| #endif | ||||
| 
 | ||||
|   if (is_grey_panda) { | ||||
|     char* UBLOX_WAKE_MSG = "\xb5\x62\x06\x04\x04\x00\x01\x00\x09\x00\x18\x7a"; | ||||
|     int len = 12; | ||||
|     uart_ring *ur = get_ring_by_number(1); | ||||
|     for (int i = 0; i < len; i++) while (!putc(ur, UBLOX_WAKE_MSG[i])); | ||||
|   } | ||||
| 
 | ||||
|   //set timer back
 | ||||
|   TIM6->PSC = 48000-1; // tick on 1 ms
 | ||||
|   TIM6->ARR = 10000; // 10s
 | ||||
|   // Enable, One-Pulse Mode, Only overflow interrupt
 | ||||
|   TIM6->CR1 = TIM_CR1_CEN | TIM_CR1_OPM | TIM_CR1_URS; | ||||
|   TIM6->EGR = TIM_EGR_UG; | ||||
|   TIM6->CR1 |= TIM_CR1_CEN; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| // Reset timer when activity
 | ||||
| void power_save_reset_timer() { | ||||
|   TIM6->CNT = 0; | ||||
|   if (power_save_status != POWER_SAVE_STATUS_DISABLED){ | ||||
|     power_save_disable(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void power_save_init(void) { | ||||
|   puts("Saving power init\n"); | ||||
|   TIM6->PSC = 48000-1; // tick on 1 ms
 | ||||
| 
 | ||||
| 
 | ||||
|   TIM6->ARR = 10000; // 10s
 | ||||
|   // Enable, One-Pulse Mode, Only overflow interrupt
 | ||||
|   TIM6->CR1 = TIM_CR1_CEN | TIM_CR1_OPM | TIM_CR1_URS; | ||||
|   TIM6->EGR = TIM_EGR_UG; | ||||
|   NVIC_EnableIRQ(TIM6_DAC_IRQn); | ||||
|   puts("Saving power init done\n"); | ||||
|   TIM6->DIER = TIM_DIER_UIE; | ||||
|   TIM6->CR1 |= TIM_CR1_CEN; | ||||
| } | ||||
| 
 | ||||
| void TIM6_DAC_IRQHandler(void) { | ||||
|   //Timeout switch to power saving mode.
 | ||||
|   if (TIM6->SR & TIM_SR_UIF) { | ||||
|     TIM6->SR = 0; | ||||
| #ifdef EON | ||||
|     if (power_save_status == POWER_SAVE_STATUS_DISABLED) { | ||||
|       power_save_enable(); | ||||
|     } else if (power_save_status == POWER_SAVE_STATUS_SWITCHING) { | ||||
|       power_save_enable_can_wake(); | ||||
|     } | ||||
| #endif | ||||
|   } else { | ||||
|     TIM6->CR1 |= TIM_CR1_CEN; | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,5 @@ | ||||
| #!/bin/bash | ||||
| git clone --recursive https://github.com/pfalcon/esp-open-sdk.git | ||||
| cd esp-open-sdk | ||||
| git checkout 03f5e898a059451ec5f3de30e7feff30455f7cec | ||||
| LD_LIBRARY_PATH="" make STANDALONE=y | ||||
| @ -1,4 +1,7 @@ | ||||
| libusb1 | ||||
| libusb1 == 1.6.6 | ||||
| hexdump | ||||
| pycrypto | ||||
| tqdm | ||||
| nose | ||||
| parameterized | ||||
| requests | ||||
|  | ||||
| @ -1,3 +1,9 @@ | ||||
| #!/bin/bash | ||||
| PYTHONPATH="." nosetests -x -s tests/automated/$1*.py | ||||
| TEST_FILENAME=${TEST_FILENAME:-nosetests.xml} | ||||
| if [ ! -f "/EON" ]; then | ||||
|   TESTSUITE_NAME="Panda_Test-EON" | ||||
| else | ||||
|   TESTSUITE_NAME="Panda_Test-DEV" | ||||
| fi | ||||
| 
 | ||||
| PYTHONPATH="." nosetests -v --with-xunit --xunit-file=./$TEST_FILENAME --xunit-testsuite-name=$TESTSUITE_NAME -s tests/automated/$1*.py | ||||
|  | ||||
| @ -0,0 +1,121 @@ | ||||
| from __future__ import print_function | ||||
| import time | ||||
| from panda import Panda | ||||
| from nose.tools import assert_equal, assert_less, assert_greater | ||||
| from helpers import time_many_sends, test_two_panda, panda_color_to_serial | ||||
| 
 | ||||
| @test_two_panda | ||||
| @panda_color_to_serial | ||||
| def test_send_recv(serial_sender=None, serial_reciever=None): | ||||
|   p_send = Panda(serial_sender) | ||||
|   p_recv = Panda(serial_reciever) | ||||
| 
 | ||||
|   p_send.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, "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_color_to_serial | ||||
| def test_latency(serial_sender=None, serial_reciever=None): | ||||
|   p_send = Panda(serial_sender) | ||||
|   p_recv = Panda(serial_reciever) | ||||
| 
 | ||||
|   p_send.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, "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 | ||||
|       r = [1] | ||||
|       while len(r) > 0: | ||||
|         r = p_send.can_recv() | ||||
|       r = [1] | ||||
|       while len(r) > 0: | ||||
|         r = p_recv.can_recv() | ||||
|       time.sleep(0.05) | ||||
| 
 | ||||
|       latencies = [] | ||||
|       comp_kbps_list = [] | ||||
|       saturation_pcts = [] | ||||
| 
 | ||||
|       num_messages = 100 | ||||
| 
 | ||||
|       for i in range(num_messages): | ||||
|         st = time.time() | ||||
|         p_send.can_send(0x1ab, "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)) | ||||
					Loading…
					
					
				
		Reference in new issue