|  |  |  | #include <stdio.h>
 | 
					
						
							|  |  |  | #include <time.h>
 | 
					
						
							|  |  |  | #include <stdint.h>
 | 
					
						
							|  |  |  | #include <stdlib.h>
 | 
					
						
							|  |  |  | #include <string.h>
 | 
					
						
							|  |  |  | #include <signal.h>
 | 
					
						
							|  |  |  | #include <unistd.h>
 | 
					
						
							|  |  |  | #include <sched.h>
 | 
					
						
							|  |  |  | #include <string.h>
 | 
					
						
							|  |  |  | #include <sys/cdefs.h>
 | 
					
						
							|  |  |  | #include <sys/types.h>
 | 
					
						
							|  |  |  | #include <sys/time.h>
 | 
					
						
							|  |  |  | #include <sys/resource.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <assert.h>
 | 
					
						
							|  |  |  | #include <pthread.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <libusb-1.0/libusb.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <capnp/serialize.h>
 | 
					
						
							|  |  |  | #include "cereal/gen/cpp/log.capnp.h"
 | 
					
						
							|  |  |  | #include "cereal/gen/cpp/car.capnp.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "common/util.h"
 | 
					
						
							|  |  |  | #include "common/messaging.h"
 | 
					
						
							|  |  |  | #include "common/params.h"
 | 
					
						
							|  |  |  | #include "common/swaglog.h"
 | 
					
						
							|  |  |  | #include "common/timing.h"
 | 
					
						
							|  |  |  | #include "messaging.hpp"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <algorithm>
 | 
					
						
							|  |  |  | #include <bitset>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // double the FIFO size
 | 
					
						
							|  |  |  | #define RECV_SIZE (0x1000)
 | 
					
						
							|  |  |  | #define TIMEOUT 0
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define MAX_IR_POWER 0.5f
 | 
					
						
							|  |  |  | #define MIN_IR_POWER 0.0f
 | 
					
						
							|  |  |  | #define CUTOFF_GAIN 0.015625f  // iso400
 | 
					
						
							|  |  |  | #define SATURATE_GAIN 0.0625f  // iso1600
 | 
					
						
							|  |  |  | #define NIBBLE_TO_HEX(n) ((n) < 10 ? (n) + '0' : ((n) - 10) + 'a')
 | 
					
						
							|  |  |  | #define VOLTAGE_K 0.091  // LPF gain for 5s tau (dt/tau / (dt/tau + 1))
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | namespace {
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | volatile sig_atomic_t do_exit = 0;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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;
 | 
					
						
							|  |  |  | };
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | libusb_context *ctx = NULL;
 | 
					
						
							|  |  |  | libusb_device_handle *dev_handle = NULL;
 | 
					
						
							|  |  |  | pthread_mutex_t usb_lock;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool spoofing_started = false;
 | 
					
						
							|  |  |  | bool fake_send = false;
 | 
					
						
							|  |  |  | bool loopback_can = false;
 | 
					
						
							|  |  |  | cereal::HealthData::HwType hw_type = cereal::HealthData::HwType::UNKNOWN;
 | 
					
						
							|  |  |  | bool is_pigeon = false;
 | 
					
						
							|  |  |  | const uint32_t NO_IGNITION_CNT_MAX = 2 * 60 * 60 * 30;  // turn off charge after 30 hrs
 | 
					
						
							|  |  |  | const float VBATT_START_CHARGING = 11.5;
 | 
					
						
							|  |  |  | const float VBATT_PAUSE_CHARGING = 11.0;
 | 
					
						
							|  |  |  | float voltage_f = 12.5;  // filtered voltage
 | 
					
						
							|  |  |  | uint32_t no_ignition_cnt = 0;
 | 
					
						
							|  |  |  | bool connected_once = false;
 | 
					
						
							|  |  |  | bool ignition_last = false;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool safety_setter_thread_initialized = false;
 | 
					
						
							|  |  |  | pthread_t safety_setter_thread_handle;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool pigeon_thread_initialized = false;
 | 
					
						
							|  |  |  | pthread_t pigeon_thread_handle;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool pigeon_needs_init;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void pigeon_init();
 | 
					
						
							|  |  |  | void *pigeon_thread(void *crap);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void *safety_setter_thread(void *s) {
 | 
					
						
							|  |  |  |   // diagnostic only is the default, needed for VIN query
 | 
					
						
							|  |  |  |   pthread_mutex_lock(&usb_lock);
 | 
					
						
							|  |  |  |   libusb_control_transfer(dev_handle, 0x40, 0xdc, (uint16_t)(cereal::CarParams::SafetyModel::ELM327), 0, NULL, 0, TIMEOUT);
 | 
					
						
							|  |  |  |   pthread_mutex_unlock(&usb_lock);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   char *value_vin;
 | 
					
						
							|  |  |  |   size_t value_vin_sz = 0;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // switch to SILENT when CarVin param is read
 | 
					
						
							|  |  |  |   while (1) {
 | 
					
						
							|  |  |  |     if (do_exit) return NULL;
 | 
					
						
							|  |  |  |     const int result = read_db_value(NULL, "CarVin", &value_vin, &value_vin_sz);
 | 
					
						
							|  |  |  |     if (value_vin_sz > 0) {
 | 
					
						
							|  |  |  |       // sanity check VIN format
 | 
					
						
							|  |  |  |       assert(value_vin_sz == 17);
 | 
					
						
							|  |  |  |       break;
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |     usleep(100*1000);
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  |   LOGW("got CarVin %s", value_vin);
 | 
					
						
							|  |  |  |   free(value_vin);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // VIN query done, stop listening to OBDII
 | 
					
						
							|  |  |  |   pthread_mutex_lock(&usb_lock);
 | 
					
						
							|  |  |  |   libusb_control_transfer(dev_handle, 0x40, 0xdc, (uint16_t)(cereal::CarParams::SafetyModel::NO_OUTPUT), 0, NULL, 0, TIMEOUT);
 | 
					
						
							|  |  |  |   pthread_mutex_unlock(&usb_lock);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   char *value;
 | 
					
						
							|  |  |  |   size_t value_sz = 0;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   LOGW("waiting for params to set safety model");
 | 
					
						
							|  |  |  |   while (1) {
 | 
					
						
							|  |  |  |     if (do_exit) return NULL;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const int result = read_db_value(NULL, "CarParams", &value, &value_sz);
 | 
					
						
							|  |  |  |     if (value_sz > 0) break;
 | 
					
						
							|  |  |  |     usleep(100*1000);
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  |   LOGW("got %d bytes CarParams", value_sz);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // format for board, make copy due to alignment issues, will be freed on out of scope
 | 
					
						
							|  |  |  |   auto amsg = kj::heapArray<capnp::word>((value_sz / sizeof(capnp::word)) + 1);
 | 
					
						
							|  |  |  |   memcpy(amsg.begin(), value, value_sz);
 | 
					
						
							|  |  |  |   free(value);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   capnp::FlatArrayMessageReader cmsg(amsg);
 | 
					
						
							|  |  |  |   cereal::CarParams::Reader car_params = cmsg.getRoot<cereal::CarParams>();
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   int safety_model = int(car_params.getSafetyModel());
 | 
					
						
							|  |  |  |   auto safety_param = car_params.getSafetyParam();
 | 
					
						
							|  |  |  |   LOGW("setting safety model: %d with param %d", safety_model, safety_param);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   pthread_mutex_lock(&usb_lock);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // set in the mutex to avoid race
 | 
					
						
							|  |  |  |   safety_setter_thread_initialized = false;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   libusb_control_transfer(dev_handle, 0x40, 0xdc, safety_model, safety_param, NULL, 0, TIMEOUT);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   pthread_mutex_unlock(&usb_lock);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return NULL;
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // must be called before threads or with mutex
 | 
					
						
							|  |  |  | bool usb_connect() {
 | 
					
						
							|  |  |  |   int err, err2;
 | 
					
						
							|  |  |  |   unsigned char hw_query[1] = {0};
 | 
					
						
							|  |  |  |   unsigned char fw_sig_buf[128];
 | 
					
						
							|  |  |  |   unsigned char fw_sig_hex_buf[16];
 | 
					
						
							|  |  |  |   unsigned char serial_buf[16];
 | 
					
						
							|  |  |  |   const char *serial;
 | 
					
						
							|  |  |  |   int serial_sz = 0;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   ignition_last = false;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (dev_handle != NULL){
 | 
					
						
							|  |  |  |     libusb_close(dev_handle);
 | 
					
						
							|  |  |  |     dev_handle = NULL;
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   dev_handle = libusb_open_device_with_vid_pid(ctx, 0xbbaa, 0xddcc);
 | 
					
						
							|  |  |  |   if (dev_handle == NULL) { goto fail; }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   err = libusb_set_configuration(dev_handle, 1);
 | 
					
						
							|  |  |  |   if (err != 0) { goto fail; }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   err = libusb_claim_interface(dev_handle, 0);
 | 
					
						
							|  |  |  |   if (err != 0) { goto fail; }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (loopback_can) {
 | 
					
						
							|  |  |  |     libusb_control_transfer(dev_handle, 0xc0, 0xe5, 1, 0, NULL, 0, TIMEOUT);
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // get panda fw
 | 
					
						
							|  |  |  |   err = libusb_control_transfer(dev_handle, 0xc0, 0xd3, 0, 0, fw_sig_buf, 64, TIMEOUT);
 | 
					
						
							|  |  |  |   err2 = libusb_control_transfer(dev_handle, 0xc0, 0xd4, 0, 0, fw_sig_buf + 64, 64, TIMEOUT);
 | 
					
						
							|  |  |  |   if ((err == 64) && (err2 == 64)) {
 | 
					
						
							|  |  |  |     printf("FW signature read\n");
 | 
					
						
							|  |  |  |     write_db_value(NULL, "PandaFirmware", (const char *)fw_sig_buf, 128);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for (size_t i = 0; i < 8; i++){
 | 
					
						
							|  |  |  |       fw_sig_hex_buf[2*i] = NIBBLE_TO_HEX(fw_sig_buf[i] >> 4);
 | 
					
						
							|  |  |  |       fw_sig_hex_buf[2*i+1] = NIBBLE_TO_HEX(fw_sig_buf[i] & 0xF);
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |     write_db_value(NULL, "PandaFirmwareHex", (const char *)fw_sig_hex_buf, 16);
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  |   else { goto fail; }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // get panda serial
 | 
					
						
							|  |  |  |   err = libusb_control_transfer(dev_handle, 0xc0, 0xd0, 0, 0, serial_buf, 16, TIMEOUT);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (err > 0) {
 | 
					
						
							|  |  |  |     serial = (const char *)serial_buf;
 | 
					
						
							|  |  |  |     serial_sz = strnlen(serial, err);
 | 
					
						
							|  |  |  |     write_db_value(NULL, "PandaDongleId", serial, serial_sz);
 | 
					
						
							|  |  |  |     printf("panda serial: %.*s\n", serial_sz, serial);
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  |   else { goto fail; }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // power on charging, only the first time. Panda can also change mode and it causes a brief disconneciton
 | 
					
						
							|  |  |  | #ifndef __x86_64__
 | 
					
						
							|  |  |  |   if (!connected_once) {
 | 
					
						
							|  |  |  |     libusb_control_transfer(dev_handle, 0xc0, 0xe6, (uint16_t)(cereal::HealthData::UsbPowerMode::CDP), 0, NULL, 0, TIMEOUT);
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  |   connected_once = true;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   libusb_control_transfer(dev_handle, 0xc0, 0xc1, 0, 0, hw_query, 1, TIMEOUT);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   hw_type = (cereal::HealthData::HwType)(hw_query[0]);
 | 
					
						
							|  |  |  |   is_pigeon = (hw_type == cereal::HealthData::HwType::GREY_PANDA) ||
 | 
					
						
							|  |  |  |               (hw_type == cereal::HealthData::HwType::BLACK_PANDA) ||
 | 
					
						
							|  |  |  |               (hw_type == cereal::HealthData::HwType::UNO);
 | 
					
						
							|  |  |  |   if (is_pigeon) {
 | 
					
						
							|  |  |  |     LOGW("panda with gps detected");
 | 
					
						
							|  |  |  |     pigeon_needs_init = true;
 | 
					
						
							|  |  |  |     if (!pigeon_thread_initialized) {
 | 
					
						
							|  |  |  |       err = pthread_create(&pigeon_thread_handle, NULL, pigeon_thread, NULL);
 | 
					
						
							|  |  |  |       assert(err == 0);
 | 
					
						
							|  |  |  |       pigeon_thread_initialized = true;
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (hw_type == cereal::HealthData::HwType::UNO){
 | 
					
						
							|  |  |  |     // Get time from system
 | 
					
						
							|  |  |  |     time_t rawtime;
 | 
					
						
							|  |  |  |     time(&rawtime);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     struct tm * sys_time = gmtime(&rawtime);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Get time from RTC
 | 
					
						
							|  |  |  |     timestamp_t rtc_time;
 | 
					
						
							|  |  |  |     libusb_control_transfer(dev_handle, 0xc0, 0xa0, 0, 0, (unsigned char*)&rtc_time, sizeof(rtc_time), TIMEOUT);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     //printf("System: %d-%d-%d\t%d:%d:%d\n", 1900 + sys_time->tm_year, 1 + sys_time->tm_mon, sys_time->tm_mday, sys_time->tm_hour, sys_time->tm_min, sys_time->tm_sec);
 | 
					
						
							|  |  |  |     //printf("RTC: %d-%d-%d\t%d:%d:%d\n", rtc_time.year, rtc_time.month, rtc_time.day, rtc_time.hour, rtc_time.minute, rtc_time.second);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Update system time from RTC if it looks off, and RTC time is good
 | 
					
						
							|  |  |  |     if (1900 + sys_time->tm_year < 2019 && rtc_time.year >= 2019){
 | 
					
						
							|  |  |  |       LOGE("System time wrong, setting from RTC");
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       struct tm new_time = { 0 };
 | 
					
						
							|  |  |  |       new_time.tm_year = rtc_time.year - 1900;
 | 
					
						
							|  |  |  |       new_time.tm_mon  = rtc_time.month - 1;
 | 
					
						
							|  |  |  |       new_time.tm_mday = rtc_time.day;
 | 
					
						
							|  |  |  |       new_time.tm_hour = rtc_time.hour;
 | 
					
						
							|  |  |  |       new_time.tm_min  = rtc_time.minute;
 | 
					
						
							|  |  |  |       new_time.tm_sec  = rtc_time.second;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       setenv("TZ","UTC",1);
 | 
					
						
							|  |  |  |       const struct timeval tv = {mktime(&new_time), 0};
 | 
					
						
							|  |  |  |       settimeofday(&tv, 0);
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return true;
 | 
					
						
							|  |  |  | fail:
 | 
					
						
							|  |  |  |   return false;
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // must be called before threads or with mutex
 | 
					
						
							|  |  |  | void usb_retry_connect() {
 | 
					
						
							|  |  |  |   LOG("attempting to connect");
 | 
					
						
							|  |  |  |   while (!usb_connect()) { usleep(100*1000); }
 | 
					
						
							|  |  |  |   LOGW("connected to board");
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void handle_usb_issue(int err, const char func[]) {
 | 
					
						
							|  |  |  |   LOGE_100("usb error %d \"%s\" in %s", err, libusb_strerror((enum libusb_error)err), func);
 | 
					
						
							|  |  |  |   if (err == -4) {
 | 
					
						
							|  |  |  |     LOGE("lost connection");
 | 
					
						
							|  |  |  |     usb_retry_connect();
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  |   // TODO: check other errors, is simply retrying okay?
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void can_recv(PubSocket *publisher) {
 | 
					
						
							|  |  |  |   int err;
 | 
					
						
							|  |  |  |   uint32_t data[RECV_SIZE/4];
 | 
					
						
							|  |  |  |   int recv;
 | 
					
						
							|  |  |  |   uint32_t f1, f2;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   uint64_t start_time = nanos_since_boot();
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // do recv
 | 
					
						
							|  |  |  |   pthread_mutex_lock(&usb_lock);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   do {
 | 
					
						
							|  |  |  |     err = libusb_bulk_transfer(dev_handle, 0x81, (uint8_t*)data, RECV_SIZE, &recv, TIMEOUT);
 | 
					
						
							|  |  |  |     if (err != 0) { handle_usb_issue(err, __func__); }
 | 
					
						
							|  |  |  |     if (err == -8) { LOGE_100("overflow got 0x%x", recv); };
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // timeout is okay to exit, recv still happened
 | 
					
						
							|  |  |  |     if (err == -7) { break; }
 | 
					
						
							|  |  |  |   } while(err != 0);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   pthread_mutex_unlock(&usb_lock);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // return if length is 0
 | 
					
						
							|  |  |  |   if (recv <= 0) {
 | 
					
						
							|  |  |  |     return;
 | 
					
						
							|  |  |  |   } else if (recv == RECV_SIZE) {
 | 
					
						
							|  |  |  |     LOGW("Receive buffer full");
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // create message
 | 
					
						
							|  |  |  |   capnp::MallocMessageBuilder msg;
 | 
					
						
							|  |  |  |   cereal::Event::Builder event = msg.initRoot<cereal::Event>();
 | 
					
						
							|  |  |  |   event.setLogMonoTime(start_time);
 | 
					
						
							|  |  |  |   size_t num_msg = recv / 0x10;
 | 
					
						
							|  |  |  |   auto canData = event.initCan(num_msg);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // populate message
 | 
					
						
							|  |  |  |   for (int i = 0; i < num_msg; i++) {
 | 
					
						
							|  |  |  |     if (data[i*4] & 4) {
 | 
					
						
							|  |  |  |       // extended
 | 
					
						
							|  |  |  |       canData[i].setAddress(data[i*4] >> 3);
 | 
					
						
							|  |  |  |       //printf("got extended: %x\n", data[i*4] >> 3);
 | 
					
						
							|  |  |  |     } else {
 | 
					
						
							|  |  |  |       // normal
 | 
					
						
							|  |  |  |       canData[i].setAddress(data[i*4] >> 21);
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |     canData[i].setBusTime(data[i*4+1] >> 16);
 | 
					
						
							|  |  |  |     int len = data[i*4+1]&0xF;
 | 
					
						
							|  |  |  |     canData[i].setDat(kj::arrayPtr((uint8_t*)&data[i*4+2], len));
 | 
					
						
							|  |  |  |     canData[i].setSrc((data[i*4+1] >> 4) & 0xff);
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // send to can
 | 
					
						
							|  |  |  |   auto words = capnp::messageToFlatArray(msg);
 | 
					
						
							|  |  |  |   auto bytes = words.asBytes();
 | 
					
						
							|  |  |  |   publisher->send((char*)bytes.begin(), bytes.size());
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void can_health(PubSocket *publisher) {
 | 
					
						
							|  |  |  |   int cnt;
 | 
					
						
							|  |  |  |   int err;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // copied from panda/board/main.c
 | 
					
						
							|  |  |  |   struct __attribute__((packed)) health {
 | 
					
						
							|  |  |  |     uint32_t uptime;
 | 
					
						
							|  |  |  |     uint32_t voltage;
 | 
					
						
							|  |  |  |     uint32_t current;
 | 
					
						
							|  |  |  |     uint32_t can_rx_errs;
 | 
					
						
							|  |  |  |     uint32_t can_send_errs;
 | 
					
						
							|  |  |  |     uint32_t can_fwd_errs;
 | 
					
						
							|  |  |  |     uint32_t gmlan_send_errs;
 | 
					
						
							|  |  |  |     uint32_t faults;
 | 
					
						
							|  |  |  |     uint8_t ignition_line;
 | 
					
						
							|  |  |  |     uint8_t ignition_can;
 | 
					
						
							|  |  |  |     uint8_t controls_allowed;
 | 
					
						
							|  |  |  |     uint8_t gas_interceptor_detected;
 | 
					
						
							|  |  |  |     uint8_t car_harness_status;
 | 
					
						
							|  |  |  |     uint8_t usb_power_mode;
 | 
					
						
							|  |  |  |     uint8_t safety_model;
 | 
					
						
							|  |  |  |     uint8_t fault_status;
 | 
					
						
							|  |  |  |     uint8_t power_save_enabled;
 | 
					
						
							|  |  |  |   } health;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // create message
 | 
					
						
							|  |  |  |   capnp::MallocMessageBuilder msg;
 | 
					
						
							|  |  |  |   cereal::Event::Builder event = msg.initRoot<cereal::Event>();
 | 
					
						
							|  |  |  |   event.setLogMonoTime(nanos_since_boot());
 | 
					
						
							|  |  |  |   auto healthData = event.initHealth();
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   bool received = false;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // recv from board
 | 
					
						
							|  |  |  |   if (dev_handle != NULL) {
 | 
					
						
							|  |  |  |     pthread_mutex_lock(&usb_lock);
 | 
					
						
							|  |  |  |     cnt = libusb_control_transfer(dev_handle, 0xc0, 0xd2, 0, 0, (unsigned char*)&health, sizeof(health), TIMEOUT);
 | 
					
						
							|  |  |  |     pthread_mutex_unlock(&usb_lock);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     received = (cnt == sizeof(health));
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // No panda connected, send empty health packet
 | 
					
						
							|  |  |  |   if (!received){
 | 
					
						
							|  |  |  |     healthData.setHwType(cereal::HealthData::HwType::UNKNOWN);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     auto words = capnp::messageToFlatArray(msg);
 | 
					
						
							|  |  |  |     auto bytes = words.asBytes();
 | 
					
						
							|  |  |  |     publisher->send((char*)bytes.begin(), bytes.size());
 | 
					
						
							|  |  |  |     return;
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (spoofing_started) {
 | 
					
						
							|  |  |  |     health.ignition_line = 1;
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   voltage_f = VOLTAGE_K * (health.voltage / 1000.0) + (1.0 - VOLTAGE_K) * voltage_f;  // LPF
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Make sure CAN buses are live: safety_setter_thread does not work if Panda CAN are silent and there is only one other CAN node
 | 
					
						
							|  |  |  |   if (health.safety_model == (uint8_t)(cereal::CarParams::SafetyModel::SILENT)) {
 | 
					
						
							|  |  |  |     pthread_mutex_lock(&usb_lock);
 | 
					
						
							|  |  |  |     libusb_control_transfer(dev_handle, 0x40, 0xdc, (uint16_t)(cereal::CarParams::SafetyModel::NO_OUTPUT), 0, NULL, 0, TIMEOUT);
 | 
					
						
							|  |  |  |     pthread_mutex_unlock(&usb_lock);
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   bool ignition = ((health.ignition_line != 0) || (health.ignition_can != 0));
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (ignition) {
 | 
					
						
							|  |  |  |     no_ignition_cnt = 0;
 | 
					
						
							|  |  |  |   } else {
 | 
					
						
							|  |  |  |     no_ignition_cnt += 1;
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #ifndef __x86_64__
 | 
					
						
							|  |  |  |   bool cdp_mode = health.usb_power_mode == (uint8_t)(cereal::HealthData::UsbPowerMode::CDP);
 | 
					
						
							|  |  |  |   bool no_ignition_exp = no_ignition_cnt > NO_IGNITION_CNT_MAX;
 | 
					
						
							|  |  |  |   if ((no_ignition_exp || (voltage_f < VBATT_PAUSE_CHARGING)) && cdp_mode && !ignition) {
 | 
					
						
							|  |  |  |     char *disable_power_down = NULL;
 | 
					
						
							|  |  |  |     size_t disable_power_down_sz = 0;
 | 
					
						
							|  |  |  |     const int result = read_db_value(NULL, "DisablePowerDown", &disable_power_down, &disable_power_down_sz);
 | 
					
						
							|  |  |  |     if (disable_power_down_sz != 1 || disable_power_down[0] != '1') {
 | 
					
						
							|  |  |  |       printf("TURN OFF CHARGING!\n");
 | 
					
						
							|  |  |  |       pthread_mutex_lock(&usb_lock);
 | 
					
						
							|  |  |  |       libusb_control_transfer(dev_handle, 0xc0, 0xe6, (uint16_t)(cereal::HealthData::UsbPowerMode::CLIENT), 0, NULL, 0, TIMEOUT);
 | 
					
						
							|  |  |  |       pthread_mutex_unlock(&usb_lock);
 | 
					
						
							|  |  |  |       printf("POWER DOWN DEVICE\n");
 | 
					
						
							|  |  |  |       system("service call power 17 i32 0 i32 1");
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |     if (disable_power_down) free(disable_power_down);
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  |   if (!no_ignition_exp && (voltage_f > VBATT_START_CHARGING) && !cdp_mode) {
 | 
					
						
							|  |  |  |     printf("TURN ON CHARGING!\n");
 | 
					
						
							|  |  |  |     pthread_mutex_lock(&usb_lock);
 | 
					
						
							|  |  |  |     libusb_control_transfer(dev_handle, 0xc0, 0xe6, (uint16_t)(cereal::HealthData::UsbPowerMode::CDP), 0, NULL, 0, TIMEOUT);
 | 
					
						
							|  |  |  |     pthread_mutex_unlock(&usb_lock);
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  |   // set power save state enabled when car is off and viceversa when it's on
 | 
					
						
							|  |  |  |   if (ignition && (health.power_save_enabled == 1)) {
 | 
					
						
							|  |  |  |     pthread_mutex_lock(&usb_lock);
 | 
					
						
							|  |  |  |     libusb_control_transfer(dev_handle, 0xc0, 0xe7, 0, 0, NULL, 0, TIMEOUT);
 | 
					
						
							|  |  |  |     pthread_mutex_unlock(&usb_lock);
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  |   if (!ignition && (health.power_save_enabled == 0)) {
 | 
					
						
							|  |  |  |     pthread_mutex_lock(&usb_lock);
 | 
					
						
							|  |  |  |     libusb_control_transfer(dev_handle, 0xc0, 0xe7, 1, 0, NULL, 0, TIMEOUT);
 | 
					
						
							|  |  |  |     pthread_mutex_unlock(&usb_lock);
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  |   // set safety mode to NO_OUTPUT when car is off. ELM327 is an alternative if we want to leverage athenad/connect
 | 
					
						
							|  |  |  |   if (!ignition && (health.safety_model != (uint8_t)(cereal::CarParams::SafetyModel::NO_OUTPUT))) {
 | 
					
						
							|  |  |  |     pthread_mutex_lock(&usb_lock);
 | 
					
						
							|  |  |  |     libusb_control_transfer(dev_handle, 0x40, 0xdc, (uint16_t)(cereal::CarParams::SafetyModel::NO_OUTPUT), 0, NULL, 0, TIMEOUT);
 | 
					
						
							|  |  |  |     pthread_mutex_unlock(&usb_lock);
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // clear VIN, CarParams, and set new safety on car start
 | 
					
						
							|  |  |  |   if (ignition && !ignition_last) {
 | 
					
						
							|  |  |  |     int result = delete_db_value(NULL, "CarVin");
 | 
					
						
							|  |  |  |     assert((result == 0) || (result == ERR_NO_VALUE));
 | 
					
						
							|  |  |  |     result = delete_db_value(NULL, "CarParams");
 | 
					
						
							|  |  |  |     assert((result == 0) || (result == ERR_NO_VALUE));
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!safety_setter_thread_initialized) {
 | 
					
						
							|  |  |  |       err = pthread_create(&safety_setter_thread_handle, NULL, safety_setter_thread, NULL);
 | 
					
						
							|  |  |  |       assert(err == 0);
 | 
					
						
							|  |  |  |       safety_setter_thread_initialized = true;
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Get fan RPM
 | 
					
						
							|  |  |  |   uint16_t fan_speed_rpm = 0;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   pthread_mutex_lock(&usb_lock);
 | 
					
						
							|  |  |  |   int sz = libusb_control_transfer(dev_handle, 0xc0, 0xb2, 0, 0, (unsigned char*)&fan_speed_rpm, sizeof(fan_speed_rpm), TIMEOUT);
 | 
					
						
							|  |  |  |   pthread_mutex_unlock(&usb_lock);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Write to rtc once per minute when no ignition present
 | 
					
						
							|  |  |  |   if ((hw_type == cereal::HealthData::HwType::UNO) && !ignition && (no_ignition_cnt % 120 == 1)){
 | 
					
						
							|  |  |  |     // Get time from system
 | 
					
						
							|  |  |  |     time_t rawtime;
 | 
					
						
							|  |  |  |     time(&rawtime);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     struct tm * sys_time = gmtime(&rawtime);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Write time to RTC if it looks reasonable
 | 
					
						
							|  |  |  |     if (1900 + sys_time->tm_year >= 2019){
 | 
					
						
							|  |  |  |       pthread_mutex_lock(&usb_lock);
 | 
					
						
							|  |  |  |       libusb_control_transfer(dev_handle, 0x40, 0xa1, (uint16_t)(1900 + sys_time->tm_year), 0, NULL, 0, TIMEOUT);
 | 
					
						
							|  |  |  |       libusb_control_transfer(dev_handle, 0x40, 0xa2, (uint16_t)(1 + sys_time->tm_mon), 0, NULL, 0, TIMEOUT);
 | 
					
						
							|  |  |  |       libusb_control_transfer(dev_handle, 0x40, 0xa3, (uint16_t)sys_time->tm_mday, 0, NULL, 0, TIMEOUT);
 | 
					
						
							|  |  |  |       // libusb_control_transfer(dev_handle, 0x40, 0xa4, (uint16_t)(1 + sys_time->tm_wday), 0, NULL, 0, TIMEOUT);
 | 
					
						
							|  |  |  |       libusb_control_transfer(dev_handle, 0x40, 0xa5, (uint16_t)sys_time->tm_hour, 0, NULL, 0, TIMEOUT);
 | 
					
						
							|  |  |  |       libusb_control_transfer(dev_handle, 0x40, 0xa6, (uint16_t)sys_time->tm_min, 0, NULL, 0, TIMEOUT);
 | 
					
						
							|  |  |  |       libusb_control_transfer(dev_handle, 0x40, 0xa7, (uint16_t)sys_time->tm_sec, 0, NULL, 0, TIMEOUT);
 | 
					
						
							|  |  |  |       pthread_mutex_unlock(&usb_lock);
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   ignition_last = ignition;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // set fields
 | 
					
						
							|  |  |  |   healthData.setUptime(health.uptime);
 | 
					
						
							|  |  |  |   healthData.setVoltage(health.voltage);
 | 
					
						
							|  |  |  |   healthData.setCurrent(health.current);
 | 
					
						
							|  |  |  |   healthData.setIgnitionLine(health.ignition_line);
 | 
					
						
							|  |  |  |   healthData.setIgnitionCan(health.ignition_can);
 | 
					
						
							|  |  |  |   healthData.setControlsAllowed(health.controls_allowed);
 | 
					
						
							|  |  |  |   healthData.setGasInterceptorDetected(health.gas_interceptor_detected);
 | 
					
						
							|  |  |  |   healthData.setHasGps(is_pigeon);
 | 
					
						
							|  |  |  |   healthData.setCanRxErrs(health.can_rx_errs);
 | 
					
						
							|  |  |  |   healthData.setCanSendErrs(health.can_send_errs);
 | 
					
						
							|  |  |  |   healthData.setCanFwdErrs(health.can_fwd_errs);
 | 
					
						
							|  |  |  |   healthData.setGmlanSendErrs(health.gmlan_send_errs);
 | 
					
						
							|  |  |  |   healthData.setHwType(hw_type);
 | 
					
						
							|  |  |  |   healthData.setUsbPowerMode(cereal::HealthData::UsbPowerMode(health.usb_power_mode));
 | 
					
						
							|  |  |  |   healthData.setSafetyModel(cereal::CarParams::SafetyModel(health.safety_model));
 | 
					
						
							|  |  |  |   healthData.setFanSpeedRpm(fan_speed_rpm);
 | 
					
						
							|  |  |  |   healthData.setFaultStatus(cereal::HealthData::FaultStatus(health.fault_status));
 | 
					
						
							|  |  |  |   healthData.setPowerSaveEnabled((bool)(health.power_save_enabled));
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Convert faults bitset to capnp list
 | 
					
						
							|  |  |  |   std::bitset<sizeof(health.faults) * 8> fault_bits(health.faults);
 | 
					
						
							|  |  |  |   auto faults = healthData.initFaults(fault_bits.count());
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   size_t i = 0;
 | 
					
						
							|  |  |  |   for (size_t f = size_t(cereal::HealthData::FaultType::RELAY_MALFUNCTION);
 | 
					
						
							|  |  |  |        f <= size_t(cereal::HealthData::FaultType::REGISTER_DIVERGENT); f++){
 | 
					
						
							|  |  |  |     if (fault_bits.test(f)) {
 | 
					
						
							|  |  |  |       faults.set(i, cereal::HealthData::FaultType(f));
 | 
					
						
							|  |  |  |       i++;
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  |   // send to health
 | 
					
						
							|  |  |  |   auto words = capnp::messageToFlatArray(msg);
 | 
					
						
							|  |  |  |   auto bytes = words.asBytes();
 | 
					
						
							|  |  |  |   publisher->send((char*)bytes.begin(), bytes.size());
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // send heartbeat back to panda
 | 
					
						
							|  |  |  |   pthread_mutex_lock(&usb_lock);
 | 
					
						
							|  |  |  |   libusb_control_transfer(dev_handle, 0x40, 0xf3, 1, 0, NULL, 0, TIMEOUT);
 | 
					
						
							|  |  |  |   pthread_mutex_unlock(&usb_lock);
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void can_send(SubSocket *subscriber) {
 | 
					
						
							|  |  |  |   int err;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // recv from sendcan
 | 
					
						
							|  |  |  |   Message * msg = subscriber->receive();
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   auto amsg = kj::heapArray<capnp::word>((msg->getSize() / sizeof(capnp::word)) + 1);
 | 
					
						
							|  |  |  |   memcpy(amsg.begin(), msg->getData(), msg->getSize());
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   capnp::FlatArrayMessageReader cmsg(amsg);
 | 
					
						
							|  |  |  |   cereal::Event::Reader event = cmsg.getRoot<cereal::Event>();
 | 
					
						
							|  |  |  |   if (nanos_since_boot() - event.getLogMonoTime() > 1e9) {
 | 
					
						
							|  |  |  |     //Older than 1 second. Dont send.
 | 
					
						
							|  |  |  |     delete msg;
 | 
					
						
							|  |  |  |     return;
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  |   int msg_count = event.getSendcan().size();
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   uint32_t *send = (uint32_t*)malloc(msg_count*0x10);
 | 
					
						
							|  |  |  |   memset(send, 0, msg_count*0x10);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   for (int i = 0; i < msg_count; i++) {
 | 
					
						
							|  |  |  |     auto cmsg = event.getSendcan()[i];
 | 
					
						
							|  |  |  |     if (cmsg.getAddress() >= 0x800) {
 | 
					
						
							|  |  |  |       // extended
 | 
					
						
							|  |  |  |       send[i*4] = (cmsg.getAddress() << 3) | 5;
 | 
					
						
							|  |  |  |     } else {
 | 
					
						
							|  |  |  |       // normal
 | 
					
						
							|  |  |  |       send[i*4] = (cmsg.getAddress() << 21) | 1;
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |     assert(cmsg.getDat().size() <= 8);
 | 
					
						
							|  |  |  |     send[i*4+1] = cmsg.getDat().size() | (cmsg.getSrc() << 4);
 | 
					
						
							|  |  |  |     memcpy(&send[i*4+2], cmsg.getDat().begin(), cmsg.getDat().size());
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // release msg
 | 
					
						
							|  |  |  |   delete msg;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // send to board
 | 
					
						
							|  |  |  |   int sent;
 | 
					
						
							|  |  |  |   pthread_mutex_lock(&usb_lock);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (!fake_send) {
 | 
					
						
							|  |  |  |     do {
 | 
					
						
							|  |  |  |       // Try sending can messages. If the receive buffer on the panda is full it will NAK
 | 
					
						
							|  |  |  |       // and libusb will try again. After 5ms, it will time out. We will drop the messages.
 | 
					
						
							|  |  |  |       err = libusb_bulk_transfer(dev_handle, 3, (uint8_t*)send, msg_count*0x10, &sent, 5);
 | 
					
						
							|  |  |  |       if (err == LIBUSB_ERROR_TIMEOUT) {
 | 
					
						
							|  |  |  |         LOGW("Transmit buffer full");
 | 
					
						
							|  |  |  |         break;
 | 
					
						
							|  |  |  |       } else if (err != 0 || msg_count*0x10 != sent) {
 | 
					
						
							|  |  |  |         LOGW("Error");
 | 
					
						
							|  |  |  |         handle_usb_issue(err, __func__);
 | 
					
						
							|  |  |  |       }
 | 
					
						
							|  |  |  |     } while(err != 0);
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   pthread_mutex_unlock(&usb_lock);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // done
 | 
					
						
							|  |  |  |   free(send);
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // **** threads ****
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void *can_send_thread(void *crap) {
 | 
					
						
							|  |  |  |   LOGD("start send thread");
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // sendcan = 8017
 | 
					
						
							|  |  |  |   Context * context = Context::create();
 | 
					
						
							|  |  |  |   SubSocket * subscriber = SubSocket::create(context, "sendcan");
 | 
					
						
							|  |  |  |   assert(subscriber != NULL);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // drain sendcan to delete any stale messages from previous runs
 | 
					
						
							|  |  |  |   while (true){
 | 
					
						
							|  |  |  |     Message * msg = subscriber->receive(true);
 | 
					
						
							|  |  |  |     if (msg == NULL){
 | 
					
						
							|  |  |  |       break;
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |     delete msg;
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // run as fast as messages come in
 | 
					
						
							|  |  |  |   while (!do_exit) {
 | 
					
						
							|  |  |  |     can_send(subscriber);
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  |   
 | 
					
						
							|  |  |  |   delete subscriber;
 | 
					
						
							|  |  |  |   delete context;
 | 
					
						
							|  |  |  |   return NULL;
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void *can_recv_thread(void *crap) {
 | 
					
						
							|  |  |  |   LOGD("start recv thread");
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // can = 8006
 | 
					
						
							|  |  |  |   Context * c = Context::create();
 | 
					
						
							|  |  |  |   PubSocket * publisher = PubSocket::create(c, "can");
 | 
					
						
							|  |  |  |   assert(publisher != NULL);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // run at 100hz
 | 
					
						
							|  |  |  |   const uint64_t dt = 10000000ULL;
 | 
					
						
							|  |  |  |   uint64_t next_frame_time = nanos_since_boot() + dt;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   while (!do_exit) {
 | 
					
						
							|  |  |  |     can_recv(publisher);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     uint64_t cur_time = nanos_since_boot();
 | 
					
						
							|  |  |  |     int64_t remaining = next_frame_time - cur_time;
 | 
					
						
							|  |  |  |     if (remaining > 0){
 | 
					
						
							|  |  |  |       useconds_t sleep = remaining / 1000;
 | 
					
						
							|  |  |  |       usleep(sleep);
 | 
					
						
							|  |  |  |     } else {
 | 
					
						
							|  |  |  |       LOGW("missed cycle");
 | 
					
						
							|  |  |  |       next_frame_time = cur_time;
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     next_frame_time += dt;
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   delete publisher;
 | 
					
						
							|  |  |  |   delete c;
 | 
					
						
							|  |  |  |   return NULL;
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void *can_health_thread(void *crap) {
 | 
					
						
							|  |  |  |   LOGD("start health thread");
 | 
					
						
							|  |  |  |   // health = 8011
 | 
					
						
							|  |  |  |   Context * c = Context::create();
 | 
					
						
							|  |  |  |   PubSocket * publisher = PubSocket::create(c, "health");
 | 
					
						
							|  |  |  |   assert(publisher != NULL);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // run at 2hz
 | 
					
						
							|  |  |  |   while (!do_exit) {
 | 
					
						
							|  |  |  |     can_health(publisher);
 | 
					
						
							|  |  |  |     usleep(500*1000);
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   delete publisher;
 | 
					
						
							|  |  |  |   delete c;
 | 
					
						
							|  |  |  |   return NULL;
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void *hardware_control_thread(void *crap) {
 | 
					
						
							|  |  |  |   LOGD("start hardware control thread");
 | 
					
						
							|  |  |  |   Context * c = Context::create();
 | 
					
						
							|  |  |  |   SubSocket * thermal_sock = SubSocket::create(c, "thermal");
 | 
					
						
							|  |  |  |   SubSocket * front_frame_sock = SubSocket::create(c, "frontFrame");
 | 
					
						
							|  |  |  |   assert(thermal_sock != NULL);
 | 
					
						
							|  |  |  |   assert(front_frame_sock != NULL);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Poller * poller = Poller::create({thermal_sock, front_frame_sock});
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Wait for hardware type to be set.
 | 
					
						
							|  |  |  |   while (hw_type == cereal::HealthData::HwType::UNKNOWN){
 | 
					
						
							|  |  |  |     usleep(100*1000);
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  |   // Only control fan speed on UNO
 | 
					
						
							|  |  |  |   if (hw_type != cereal::HealthData::HwType::UNO) return NULL;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   uint64_t last_front_frame_t = 0;
 | 
					
						
							|  |  |  |   uint16_t prev_fan_speed = 999;
 | 
					
						
							|  |  |  |   uint16_t ir_pwr = 0;
 | 
					
						
							|  |  |  |   uint16_t prev_ir_pwr = 999;
 | 
					
						
							|  |  |  |   unsigned int cnt = 0;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   while (!do_exit) {
 | 
					
						
							|  |  |  |     cnt++;
 | 
					
						
							|  |  |  |     for (auto sock : poller->poll(1000)){
 | 
					
						
							|  |  |  |       Message * msg = sock->receive();
 | 
					
						
							|  |  |  |       if (msg == NULL) continue;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       auto amsg = kj::heapArray<capnp::word>((msg->getSize() / sizeof(capnp::word)) + 1);
 | 
					
						
							|  |  |  |       memcpy(amsg.begin(), msg->getData(), msg->getSize());
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       delete msg;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       capnp::FlatArrayMessageReader cmsg(amsg);
 | 
					
						
							|  |  |  |       cereal::Event::Reader event = cmsg.getRoot<cereal::Event>();
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       auto type = event.which();
 | 
					
						
							|  |  |  |       if(type == cereal::Event::THERMAL){
 | 
					
						
							|  |  |  |         uint16_t fan_speed = event.getThermal().getFanSpeed();
 | 
					
						
							|  |  |  |         if (fan_speed != prev_fan_speed || cnt % 100 == 0){
 | 
					
						
							|  |  |  |           pthread_mutex_lock(&usb_lock);
 | 
					
						
							|  |  |  |           libusb_control_transfer(dev_handle, 0x40, 0xb1, fan_speed, 0, NULL, 0, TIMEOUT);
 | 
					
						
							|  |  |  |           pthread_mutex_unlock(&usb_lock);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           prev_fan_speed = fan_speed;
 | 
					
						
							|  |  |  |         }
 | 
					
						
							|  |  |  |       } else if (type == cereal::Event::FRONT_FRAME){
 | 
					
						
							|  |  |  |         float cur_front_gain = event.getFrontFrame().getGainFrac();
 | 
					
						
							|  |  |  |         last_front_frame_t = event.getLogMonoTime();
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (cur_front_gain <= CUTOFF_GAIN) {
 | 
					
						
							|  |  |  |           ir_pwr = 100.0 * MIN_IR_POWER;
 | 
					
						
							|  |  |  |         } else if (cur_front_gain > SATURATE_GAIN) {
 | 
					
						
							|  |  |  |           ir_pwr = 100.0 * MAX_IR_POWER;
 | 
					
						
							|  |  |  |         } else {
 | 
					
						
							|  |  |  |           ir_pwr = 100.0 * (MIN_IR_POWER + ((cur_front_gain - CUTOFF_GAIN) * (MAX_IR_POWER - MIN_IR_POWER) / (SATURATE_GAIN - CUTOFF_GAIN)));
 | 
					
						
							|  |  |  |         }
 | 
					
						
							|  |  |  |       }
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Disable ir_pwr on front frame timeout
 | 
					
						
							|  |  |  |     uint64_t cur_t = nanos_since_boot();
 | 
					
						
							|  |  |  |     if (cur_t - last_front_frame_t > 1e9){
 | 
					
						
							|  |  |  |       ir_pwr = 0;
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (ir_pwr != prev_ir_pwr || cnt % 100 == 0 || ir_pwr >= 50.0){
 | 
					
						
							|  |  |  |       pthread_mutex_lock(&usb_lock);
 | 
					
						
							|  |  |  |       libusb_control_transfer(dev_handle, 0x40, 0xb0, ir_pwr, 0, NULL, 0, TIMEOUT);
 | 
					
						
							|  |  |  |       pthread_mutex_unlock(&usb_lock);
 | 
					
						
							|  |  |  |       prev_ir_pwr = ir_pwr;
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   delete poller;
 | 
					
						
							|  |  |  |   delete thermal_sock;
 | 
					
						
							|  |  |  |   delete c;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return NULL;
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define pigeon_send(x) _pigeon_send(x, sizeof(x)-1)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void hexdump(unsigned char *d, int l) {
 | 
					
						
							|  |  |  |   for (int i = 0; i < l; i++) {
 | 
					
						
							|  |  |  |     if (i!=0 && i%0x10 == 0) printf("\n");
 | 
					
						
							|  |  |  |     printf("%2.2X ", d[i]);
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  |   printf("\n");
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void _pigeon_send(const char *dat, int len) {
 | 
					
						
							|  |  |  |   int sent;
 | 
					
						
							|  |  |  |   unsigned char a[0x20];
 | 
					
						
							|  |  |  |   int err;
 | 
					
						
							|  |  |  |   a[0] = 1;
 | 
					
						
							|  |  |  |   for (int i=0; i<len; i+=0x20) {
 | 
					
						
							|  |  |  |     int ll = std::min(0x20, len-i);
 | 
					
						
							|  |  |  |     memcpy(&a[1], &dat[i], ll);
 | 
					
						
							|  |  |  |     pthread_mutex_lock(&usb_lock);
 | 
					
						
							|  |  |  |     err = libusb_bulk_transfer(dev_handle, 2, a, ll+1, &sent, TIMEOUT);
 | 
					
						
							|  |  |  |     if (err < 0) { handle_usb_issue(err, __func__); }
 | 
					
						
							|  |  |  |     /*assert(err == 0);
 | 
					
						
							|  |  |  |     assert(sent == ll+1);*/
 | 
					
						
							|  |  |  |     //hexdump(a, ll+1);
 | 
					
						
							|  |  |  |     pthread_mutex_unlock(&usb_lock);
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void pigeon_set_power(int power) {
 | 
					
						
							|  |  |  |   pthread_mutex_lock(&usb_lock);
 | 
					
						
							|  |  |  |   int err = libusb_control_transfer(dev_handle, 0xc0, 0xd9, power, 0, NULL, 0, TIMEOUT);
 | 
					
						
							|  |  |  |   if (err < 0) { handle_usb_issue(err, __func__); }
 | 
					
						
							|  |  |  |   pthread_mutex_unlock(&usb_lock);
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void pigeon_set_baud(int baud) {
 | 
					
						
							|  |  |  |   int err;
 | 
					
						
							|  |  |  |   pthread_mutex_lock(&usb_lock);
 | 
					
						
							|  |  |  |   err = libusb_control_transfer(dev_handle, 0xc0, 0xe2, 1, 0, NULL, 0, TIMEOUT);
 | 
					
						
							|  |  |  |   if (err < 0) { handle_usb_issue(err, __func__); }
 | 
					
						
							|  |  |  |   err = libusb_control_transfer(dev_handle, 0xc0, 0xe4, 1, baud/300, NULL, 0, TIMEOUT);
 | 
					
						
							|  |  |  |   if (err < 0) { handle_usb_issue(err, __func__); }
 | 
					
						
							|  |  |  |   pthread_mutex_unlock(&usb_lock);
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void pigeon_init() {
 | 
					
						
							|  |  |  |   usleep(1000*1000);
 | 
					
						
							|  |  |  |   LOGW("panda GPS start");
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // power off pigeon
 | 
					
						
							|  |  |  |   pigeon_set_power(0);
 | 
					
						
							|  |  |  |   usleep(100*1000);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // 9600 baud at init
 | 
					
						
							|  |  |  |   pigeon_set_baud(9600);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // power on pigeon
 | 
					
						
							|  |  |  |   pigeon_set_power(1);
 | 
					
						
							|  |  |  |   usleep(500*1000);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // baud rate upping
 | 
					
						
							|  |  |  |   pigeon_send("\x24\x50\x55\x42\x58\x2C\x34\x31\x2C\x31\x2C\x30\x30\x30\x37\x2C\x30\x30\x30\x33\x2C\x34\x36\x30\x38\x30\x30\x2C\x30\x2A\x31\x35\x0D\x0A");
 | 
					
						
							|  |  |  |   usleep(100*1000);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // set baud rate to 460800
 | 
					
						
							|  |  |  |   pigeon_set_baud(460800);
 | 
					
						
							|  |  |  |   usleep(100*1000);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // init from ubloxd
 | 
					
						
							|  |  |  |   // To generate this data, run test/ubloxd.py with the print statements enabled in the write function in panda/python/serial.py
 | 
					
						
							|  |  |  |   pigeon_send("\xB5\x62\x06\x00\x14\x00\x03\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x1E\x7F");
 | 
					
						
							|  |  |  |   pigeon_send("\xB5\x62\x06\x3E\x00\x00\x44\xD2");
 | 
					
						
							|  |  |  |   pigeon_send("\xB5\x62\x06\x00\x14\x00\x00\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x19\x35");
 | 
					
						
							|  |  |  |   pigeon_send("\xB5\x62\x06\x00\x14\x00\x01\x00\x00\x00\xC0\x08\x00\x00\x00\x08\x07\x00\x01\x00\x01\x00\x00\x00\x00\x00\xF4\x80");
 | 
					
						
							|  |  |  |   pigeon_send("\xB5\x62\x06\x00\x14\x00\x04\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1D\x85");
 | 
					
						
							|  |  |  |   pigeon_send("\xB5\x62\x06\x00\x00\x00\x06\x18");
 | 
					
						
							|  |  |  |   pigeon_send("\xB5\x62\x06\x00\x01\x00\x01\x08\x22");
 | 
					
						
							|  |  |  |   pigeon_send("\xB5\x62\x06\x00\x01\x00\x02\x09\x23");
 | 
					
						
							|  |  |  |   pigeon_send("\xB5\x62\x06\x00\x01\x00\x03\x0A\x24");
 | 
					
						
							|  |  |  |   pigeon_send("\xB5\x62\x06\x08\x06\x00\x64\x00\x01\x00\x00\x00\x79\x10");
 | 
					
						
							|  |  |  |   pigeon_send("\xB5\x62\x06\x24\x24\x00\x05\x00\x04\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x5A\x63");
 | 
					
						
							|  |  |  |   pigeon_send("\xB5\x62\x06\x1E\x14\x00\x00\x00\x00\x00\x01\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3C\x37");
 | 
					
						
							|  |  |  |   pigeon_send("\xB5\x62\x06\x24\x00\x00\x2A\x84");
 | 
					
						
							|  |  |  |   pigeon_send("\xB5\x62\x06\x23\x00\x00\x29\x81");
 | 
					
						
							|  |  |  |   pigeon_send("\xB5\x62\x06\x1E\x00\x00\x24\x72");
 | 
					
						
							|  |  |  |   pigeon_send("\xB5\x62\x06\x01\x03\x00\x01\x07\x01\x13\x51");
 | 
					
						
							|  |  |  |   pigeon_send("\xB5\x62\x06\x01\x03\x00\x02\x15\x01\x22\x70");
 | 
					
						
							|  |  |  |   pigeon_send("\xB5\x62\x06\x01\x03\x00\x02\x13\x01\x20\x6C");
 | 
					
						
							|  |  |  |   pigeon_send("\xB5\x62\x06\x01\x03\x00\x0A\x09\x01\x1E\x70");
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   LOGW("panda GPS on");
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void pigeon_publish_raw(PubSocket *publisher, unsigned char *dat, int alen) {
 | 
					
						
							|  |  |  |   // create message
 | 
					
						
							|  |  |  |   capnp::MallocMessageBuilder msg;
 | 
					
						
							|  |  |  |   cereal::Event::Builder event = msg.initRoot<cereal::Event>();
 | 
					
						
							|  |  |  |   event.setLogMonoTime(nanos_since_boot());
 | 
					
						
							|  |  |  |   auto ublox_raw = event.initUbloxRaw(alen);
 | 
					
						
							|  |  |  |   memcpy(ublox_raw.begin(), dat, alen);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // send to ubloxRaw
 | 
					
						
							|  |  |  |   auto words = capnp::messageToFlatArray(msg);
 | 
					
						
							|  |  |  |   auto bytes = words.asBytes();
 | 
					
						
							|  |  |  |   publisher->send((char*)bytes.begin(), bytes.size());
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void *pigeon_thread(void *crap) {
 | 
					
						
							|  |  |  |   // ubloxRaw = 8042
 | 
					
						
							|  |  |  |   Context * context = Context::create();
 | 
					
						
							|  |  |  |   PubSocket * publisher = PubSocket::create(context, "ubloxRaw");
 | 
					
						
							|  |  |  |   assert(publisher != NULL);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // run at ~100hz
 | 
					
						
							|  |  |  |   unsigned char dat[0x1000];
 | 
					
						
							|  |  |  |   uint64_t cnt = 0;
 | 
					
						
							|  |  |  |   while (!do_exit) {
 | 
					
						
							|  |  |  |     if (pigeon_needs_init) {
 | 
					
						
							|  |  |  |       pigeon_needs_init = false;
 | 
					
						
							|  |  |  |       pigeon_init();
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |     int alen = 0;
 | 
					
						
							|  |  |  |     while (alen < 0xfc0) {
 | 
					
						
							|  |  |  |       pthread_mutex_lock(&usb_lock);
 | 
					
						
							|  |  |  |       int len = libusb_control_transfer(dev_handle, 0xc0, 0xe0, 1, 0, dat+alen, 0x40, TIMEOUT);
 | 
					
						
							|  |  |  |       if (len < 0) { handle_usb_issue(len, __func__); }
 | 
					
						
							|  |  |  |       pthread_mutex_unlock(&usb_lock);
 | 
					
						
							|  |  |  |       if (len <= 0) break;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       //printf("got %d\n", len);
 | 
					
						
							|  |  |  |       alen += len;
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  |     if (alen > 0) {
 | 
					
						
							|  |  |  |       if (dat[0] == (char)0x00){
 | 
					
						
							|  |  |  |         LOGW("received invalid ublox message, resetting panda GPS");
 | 
					
						
							|  |  |  |         pigeon_init();
 | 
					
						
							|  |  |  |       } else {
 | 
					
						
							|  |  |  |         pigeon_publish_raw(publisher, dat, alen);
 | 
					
						
							|  |  |  |       }
 | 
					
						
							|  |  |  |     }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // 10ms
 | 
					
						
							|  |  |  |     usleep(10*1000);
 | 
					
						
							|  |  |  |     cnt++;
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   delete publisher;
 | 
					
						
							|  |  |  |   delete context;
 | 
					
						
							|  |  |  |   return NULL;
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int main() {
 | 
					
						
							|  |  |  |   int err;
 | 
					
						
							|  |  |  |   LOGW("starting boardd");
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // set process priority
 | 
					
						
							|  |  |  |   err = set_realtime_priority(4);
 | 
					
						
							|  |  |  |   LOG("setpriority returns %d", err);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // check the environment
 | 
					
						
							|  |  |  |   if (getenv("STARTED")) {
 | 
					
						
							|  |  |  |     spoofing_started = true;
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (getenv("FAKESEND")) {
 | 
					
						
							|  |  |  |     fake_send = true;
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (getenv("BOARDD_LOOPBACK")){
 | 
					
						
							|  |  |  |     loopback_can = true;
 | 
					
						
							|  |  |  |   }
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   err = pthread_mutex_init(&usb_lock, NULL);
 | 
					
						
							|  |  |  |   assert(err == 0);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // init libusb
 | 
					
						
							|  |  |  |   err = libusb_init(&ctx);
 | 
					
						
							|  |  |  |   assert(err == 0);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #if LIBUSB_API_VERSION >= 0x01000106
 | 
					
						
							|  |  |  |   libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_INFO);
 | 
					
						
							|  |  |  | #else
 | 
					
						
							|  |  |  |   libusb_set_debug(ctx, 3);
 | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   pthread_t can_health_thread_handle;
 | 
					
						
							|  |  |  |   err = pthread_create(&can_health_thread_handle, NULL,
 | 
					
						
							|  |  |  |                        can_health_thread, NULL);
 | 
					
						
							|  |  |  |   assert(err == 0);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // connect to the board
 | 
					
						
							|  |  |  |   pthread_mutex_lock(&usb_lock);
 | 
					
						
							|  |  |  |   usb_retry_connect();
 | 
					
						
							|  |  |  |   pthread_mutex_unlock(&usb_lock);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // create threads
 | 
					
						
							|  |  |  |   pthread_t can_send_thread_handle;
 | 
					
						
							|  |  |  |   err = pthread_create(&can_send_thread_handle, NULL,
 | 
					
						
							|  |  |  |                        can_send_thread, NULL);
 | 
					
						
							|  |  |  |   assert(err == 0);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   pthread_t can_recv_thread_handle;
 | 
					
						
							|  |  |  |   err = pthread_create(&can_recv_thread_handle, NULL,
 | 
					
						
							|  |  |  |                        can_recv_thread, NULL);
 | 
					
						
							|  |  |  |   assert(err == 0);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   pthread_t hardware_control_thread_handle;
 | 
					
						
							|  |  |  |   err = pthread_create(&hardware_control_thread_handle, NULL,
 | 
					
						
							|  |  |  |                        hardware_control_thread, NULL);
 | 
					
						
							|  |  |  |   assert(err == 0);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // join threads
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   err = pthread_join(can_recv_thread_handle, NULL);
 | 
					
						
							|  |  |  |   assert(err == 0);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   err = pthread_join(can_send_thread_handle, NULL);
 | 
					
						
							|  |  |  |   assert(err == 0);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   err = pthread_join(can_health_thread_handle, NULL);
 | 
					
						
							|  |  |  |   assert(err == 0);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   //while (!do_exit) usleep(1000);
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // destruct libusb
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   libusb_close(dev_handle);
 | 
					
						
							|  |  |  |   libusb_exit(ctx);
 | 
					
						
							|  |  |  | }
 |