parent
cea3572b74
commit
22167461c5
23 changed files with 488 additions and 1990 deletions
@ -1,22 +0,0 @@ |
||||
#include <cassert> |
||||
|
||||
#include "selfdrive/pandad/pandad.h" |
||||
#include "common/swaglog.h" |
||||
#include "common/util.h" |
||||
#include "system/hardware/hw.h" |
||||
|
||||
int main(int argc, char *argv[]) { |
||||
LOGW("starting pandad"); |
||||
|
||||
if (!Hardware::PC()) { |
||||
int err; |
||||
err = util::set_realtime_priority(54); |
||||
assert(err == 0); |
||||
err = util::set_core_affinity({3}); |
||||
assert(err == 0); |
||||
} |
||||
|
||||
std::vector<std::string> serials(argv + 1, argv + argc); |
||||
pandad_main_thread(serials); |
||||
return 0; |
||||
} |
||||
@ -1,312 +0,0 @@ |
||||
#include "selfdrive/pandad/panda.h" |
||||
|
||||
#include <unistd.h> |
||||
|
||||
#include <cassert> |
||||
#include <stdexcept> |
||||
#include <vector> |
||||
|
||||
#include "cereal/messaging/messaging.h" |
||||
#include "common/swaglog.h" |
||||
#include "common/util.h" |
||||
|
||||
const bool PANDAD_MAXOUT = getenv("PANDAD_MAXOUT") != nullptr; |
||||
|
||||
Panda::Panda(std::string serial, uint32_t bus_offset) : bus_offset(bus_offset) { |
||||
// try USB first, then SPI
|
||||
try { |
||||
handle = std::make_unique<PandaUsbHandle>(serial); |
||||
LOGW("connected to %s over USB", serial.c_str()); |
||||
} catch (std::exception &e) { |
||||
#ifndef __APPLE__ |
||||
handle = std::make_unique<PandaSpiHandle>(serial); |
||||
LOGW("connected to %s over SPI", serial.c_str()); |
||||
#else |
||||
throw e; |
||||
#endif |
||||
} |
||||
|
||||
hw_type = get_hw_type(); |
||||
can_reset_communications(); |
||||
} |
||||
|
||||
bool Panda::connected() { |
||||
return handle->connected; |
||||
} |
||||
|
||||
bool Panda::comms_healthy() { |
||||
return handle->comms_healthy; |
||||
} |
||||
|
||||
std::string Panda::hw_serial() { |
||||
return handle->hw_serial; |
||||
} |
||||
|
||||
std::vector<std::string> Panda::list(bool usb_only) { |
||||
std::vector<std::string> serials = PandaUsbHandle::list(); |
||||
|
||||
#ifndef __APPLE__ |
||||
if (!usb_only) { |
||||
for (const auto &s : PandaSpiHandle::list()) { |
||||
if (std::find(serials.begin(), serials.end(), s) == serials.end()) { |
||||
serials.push_back(s); |
||||
} |
||||
} |
||||
} |
||||
#endif |
||||
|
||||
return serials; |
||||
} |
||||
|
||||
void Panda::set_safety_model(cereal::CarParams::SafetyModel safety_model, uint16_t safety_param) { |
||||
handle->control_write(0xdc, (uint16_t)safety_model, safety_param); |
||||
} |
||||
|
||||
void Panda::set_alternative_experience(uint16_t alternative_experience) { |
||||
handle->control_write(0xdf, alternative_experience, 0); |
||||
} |
||||
|
||||
std::string Panda::serial_read(int port_number) { |
||||
std::string ret; |
||||
char buffer[USBPACKET_MAX_SIZE] = {}; |
||||
|
||||
while (true) { |
||||
int bytes_read = handle->control_read(0xe0, port_number, 0, (unsigned char *)buffer, USBPACKET_MAX_SIZE); |
||||
if (bytes_read <= 0) { |
||||
break; |
||||
} |
||||
ret.append(buffer, bytes_read); |
||||
} |
||||
|
||||
return ret; |
||||
} |
||||
|
||||
void Panda::set_uart_baud(int uart, int rate) { |
||||
handle->control_write(0xe4, uart, int(rate / 300)); |
||||
} |
||||
|
||||
cereal::PandaState::PandaType Panda::get_hw_type() { |
||||
unsigned char hw_query[1] = {0}; |
||||
|
||||
handle->control_read(0xc1, 0, 0, hw_query, 1); |
||||
return (cereal::PandaState::PandaType)(hw_query[0]); |
||||
} |
||||
|
||||
void Panda::set_fan_speed(uint16_t fan_speed) { |
||||
handle->control_write(0xb1, fan_speed, 0); |
||||
} |
||||
|
||||
uint16_t Panda::get_fan_speed() { |
||||
uint16_t fan_speed_rpm = 0; |
||||
handle->control_read(0xb2, 0, 0, (unsigned char*)&fan_speed_rpm, sizeof(fan_speed_rpm)); |
||||
return fan_speed_rpm; |
||||
} |
||||
|
||||
void Panda::set_ir_pwr(uint16_t ir_pwr) { |
||||
handle->control_write(0xb0, ir_pwr, 0); |
||||
} |
||||
|
||||
std::optional<health_t> Panda::get_state() { |
||||
health_t health {0}; |
||||
int err = handle->control_read(0xd2, 0, 0, (unsigned char*)&health, sizeof(health)); |
||||
return err >= 0 ? std::make_optional(health) : std::nullopt; |
||||
} |
||||
|
||||
std::optional<can_health_t> Panda::get_can_state(uint16_t can_number) { |
||||
can_health_t can_health {0}; |
||||
int err = handle->control_read(0xc2, can_number, 0, (unsigned char*)&can_health, sizeof(can_health)); |
||||
return err >= 0 ? std::make_optional(can_health) : std::nullopt; |
||||
} |
||||
|
||||
void Panda::set_loopback(bool loopback) { |
||||
handle->control_write(0xe5, loopback, 0); |
||||
} |
||||
|
||||
std::optional<std::vector<uint8_t>> Panda::get_firmware_version() { |
||||
std::vector<uint8_t> fw_sig_buf(128); |
||||
int read_1 = handle->control_read(0xd3, 0, 0, &fw_sig_buf[0], 64); |
||||
int read_2 = handle->control_read(0xd4, 0, 0, &fw_sig_buf[64], 64); |
||||
return ((read_1 == 64) && (read_2 == 64)) ? std::make_optional(fw_sig_buf) : std::nullopt; |
||||
} |
||||
|
||||
std::optional<std::string> Panda::get_serial() { |
||||
char serial_buf[17] = {'\0'}; |
||||
int err = handle->control_read(0xd0, 0, 0, (uint8_t*)serial_buf, 16); |
||||
return err >= 0 ? std::make_optional(serial_buf) : std::nullopt; |
||||
} |
||||
|
||||
bool Panda::up_to_date() { |
||||
if (auto fw_sig = get_firmware_version()) { |
||||
for (auto fn : { "panda.bin.signed", "panda_h7.bin.signed" }) { |
||||
auto content = util::read_file(std::string("../../panda/board/obj/") + fn); |
||||
if (content.size() >= fw_sig->size() && |
||||
memcmp(content.data() + content.size() - fw_sig->size(), fw_sig->data(), fw_sig->size()) == 0) { |
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
void Panda::set_power_saving(bool power_saving) { |
||||
handle->control_write(0xe7, power_saving, 0); |
||||
} |
||||
|
||||
void Panda::enable_deepsleep() { |
||||
handle->control_write(0xfb, 0, 0); |
||||
} |
||||
|
||||
void Panda::send_heartbeat(bool engaged) { |
||||
handle->control_write(0xf3, engaged, 0); |
||||
} |
||||
|
||||
void Panda::set_can_speed_kbps(uint16_t bus, uint16_t speed) { |
||||
handle->control_write(0xde, bus, (speed * 10)); |
||||
} |
||||
|
||||
void Panda::set_can_fd_auto(uint16_t bus, bool enabled) { |
||||
handle->control_write(0xe8, bus, enabled); |
||||
} |
||||
|
||||
void Panda::set_data_speed_kbps(uint16_t bus, uint16_t speed) { |
||||
handle->control_write(0xf9, bus, (speed * 10)); |
||||
} |
||||
|
||||
void Panda::set_canfd_non_iso(uint16_t bus, bool non_iso) { |
||||
handle->control_write(0xfc, bus, non_iso); |
||||
} |
||||
|
||||
static uint8_t len_to_dlc(uint8_t len) { |
||||
if (len <= 8) { |
||||
return len; |
||||
} |
||||
if (len <= 24) { |
||||
return 8 + ((len - 8) / 4) + ((len % 4) ? 1 : 0); |
||||
} else { |
||||
return 11 + (len / 16) + ((len % 16) ? 1 : 0); |
||||
} |
||||
} |
||||
|
||||
void Panda::pack_can_buffer(const capnp::List<cereal::CanData>::Reader &can_data_list, |
||||
std::function<void(uint8_t *, size_t)> write_func) { |
||||
int32_t pos = 0; |
||||
uint8_t send_buf[2 * USB_TX_SOFT_LIMIT]; |
||||
|
||||
for (const auto &cmsg : can_data_list) { |
||||
// check if the message is intended for this panda
|
||||
uint8_t bus = cmsg.getSrc(); |
||||
if (bus < bus_offset || bus >= (bus_offset + PANDA_BUS_OFFSET)) { |
||||
continue; |
||||
} |
||||
auto can_data = cmsg.getDat(); |
||||
uint8_t data_len_code = len_to_dlc(can_data.size()); |
||||
assert(can_data.size() <= 64); |
||||
assert(can_data.size() == dlc_to_len[data_len_code]); |
||||
|
||||
can_header header = {}; |
||||
header.addr = cmsg.getAddress(); |
||||
header.extended = (cmsg.getAddress() >= 0x800) ? 1 : 0; |
||||
header.data_len_code = data_len_code; |
||||
header.bus = bus - bus_offset; |
||||
header.checksum = 0; |
||||
|
||||
memcpy(&send_buf[pos], (uint8_t *)&header, sizeof(can_header)); |
||||
memcpy(&send_buf[pos + sizeof(can_header)], (uint8_t *)can_data.begin(), can_data.size()); |
||||
uint32_t msg_size = sizeof(can_header) + can_data.size(); |
||||
|
||||
// set checksum
|
||||
((can_header *) &send_buf[pos])->checksum = calculate_checksum(&send_buf[pos], msg_size); |
||||
|
||||
pos += msg_size; |
||||
|
||||
if (pos >= USB_TX_SOFT_LIMIT) { |
||||
write_func(send_buf, pos); |
||||
pos = 0; |
||||
} |
||||
} |
||||
|
||||
// send remaining packets
|
||||
if (pos > 0) write_func(send_buf, pos); |
||||
} |
||||
|
||||
void Panda::can_send(const capnp::List<cereal::CanData>::Reader &can_data_list) { |
||||
pack_can_buffer(can_data_list, [=](uint8_t* data, size_t size) { |
||||
handle->bulk_write(3, data, size, 5); |
||||
}); |
||||
} |
||||
|
||||
bool Panda::can_receive(std::vector<can_frame>& out_vec) { |
||||
// Check if enough space left in buffer to store RECV_SIZE data
|
||||
assert(receive_buffer_size + RECV_SIZE <= sizeof(receive_buffer)); |
||||
|
||||
int recv = handle->bulk_read(0x81, &receive_buffer[receive_buffer_size], RECV_SIZE); |
||||
if (!comms_healthy()) { |
||||
return false; |
||||
} |
||||
|
||||
if (PANDAD_MAXOUT) { |
||||
static uint8_t junk[RECV_SIZE]; |
||||
handle->bulk_read(0xab, junk, RECV_SIZE - recv); |
||||
} |
||||
|
||||
bool ret = true; |
||||
if (recv > 0) { |
||||
receive_buffer_size += recv; |
||||
ret = unpack_can_buffer(receive_buffer, receive_buffer_size, out_vec); |
||||
} |
||||
return ret; |
||||
} |
||||
|
||||
void Panda::can_reset_communications() { |
||||
handle->control_write(0xc0, 0, 0); |
||||
} |
||||
|
||||
bool Panda::unpack_can_buffer(uint8_t *data, uint32_t &size, std::vector<can_frame> &out_vec) { |
||||
int pos = 0; |
||||
|
||||
while (pos <= size - sizeof(can_header)) { |
||||
can_header header; |
||||
memcpy(&header, &data[pos], sizeof(can_header)); |
||||
|
||||
const uint8_t data_len = dlc_to_len[header.data_len_code]; |
||||
if (pos + sizeof(can_header) + data_len > size) { |
||||
// we don't have all the data for this message yet
|
||||
break; |
||||
} |
||||
|
||||
if (calculate_checksum(&data[pos], sizeof(can_header) + data_len) != 0) { |
||||
LOGE("Panda CAN checksum failed"); |
||||
size = 0; |
||||
can_reset_communications(); |
||||
return false; |
||||
} |
||||
|
||||
can_frame &canData = out_vec.emplace_back(); |
||||
canData.address = header.addr; |
||||
canData.src = header.bus + bus_offset; |
||||
if (header.rejected) { |
||||
canData.src += CAN_REJECTED_BUS_OFFSET; |
||||
} |
||||
if (header.returned) { |
||||
canData.src += CAN_RETURNED_BUS_OFFSET; |
||||
} |
||||
|
||||
canData.dat.assign((char *)&data[pos + sizeof(can_header)], data_len); |
||||
|
||||
pos += sizeof(can_header) + data_len; |
||||
} |
||||
|
||||
// move the overflowing data to the beginning of the buffer for the next round
|
||||
memmove(data, &data[pos], size - pos); |
||||
size -= pos; |
||||
|
||||
return true; |
||||
} |
||||
|
||||
uint8_t Panda::calculate_checksum(uint8_t *data, uint32_t len) { |
||||
uint8_t checksum = 0U; |
||||
for (uint32_t i = 0U; i < len; i++) { |
||||
checksum ^= data[i]; |
||||
} |
||||
return checksum; |
||||
} |
||||
@ -1,99 +0,0 @@ |
||||
#pragma once |
||||
|
||||
#include <cstdint> |
||||
#include <ctime> |
||||
#include <functional> |
||||
#include <list> |
||||
#include <memory> |
||||
#include <optional> |
||||
#include <string> |
||||
#include <vector> |
||||
|
||||
#include "cereal/gen/cpp/car.capnp.h" |
||||
#include "cereal/gen/cpp/log.capnp.h" |
||||
#include "panda/board/health.h" |
||||
#include "panda/board/can.h" |
||||
#include "selfdrive/pandad/panda_comms.h" |
||||
|
||||
#define USB_TX_SOFT_LIMIT (0x100U) |
||||
#define USBPACKET_MAX_SIZE (0x40) |
||||
|
||||
#define RECV_SIZE (0x4000U) |
||||
|
||||
#define CAN_REJECTED_BUS_OFFSET 0xC0U |
||||
#define CAN_RETURNED_BUS_OFFSET 0x80U |
||||
|
||||
#define PANDA_BUS_OFFSET 4 |
||||
|
||||
struct __attribute__((packed)) can_header { |
||||
uint8_t reserved : 1; |
||||
uint8_t bus : 3; |
||||
uint8_t data_len_code : 4; |
||||
uint8_t rejected : 1; |
||||
uint8_t returned : 1; |
||||
uint8_t extended : 1; |
||||
uint32_t addr : 29; |
||||
uint8_t checksum : 8; |
||||
}; |
||||
|
||||
struct can_frame { |
||||
long address; |
||||
std::string dat; |
||||
long src; |
||||
}; |
||||
|
||||
|
||||
class Panda { |
||||
private: |
||||
std::unique_ptr<PandaCommsHandle> handle; |
||||
|
||||
public: |
||||
Panda(std::string serial="", uint32_t bus_offset=0); |
||||
|
||||
cereal::PandaState::PandaType hw_type = cereal::PandaState::PandaType::UNKNOWN; |
||||
const uint32_t bus_offset; |
||||
|
||||
bool connected(); |
||||
bool comms_healthy(); |
||||
std::string hw_serial(); |
||||
|
||||
// Static functions
|
||||
static std::vector<std::string> list(bool usb_only=false); |
||||
|
||||
// Panda functionality
|
||||
cereal::PandaState::PandaType get_hw_type(); |
||||
void set_safety_model(cereal::CarParams::SafetyModel safety_model, uint16_t safety_param=0U); |
||||
void set_alternative_experience(uint16_t alternative_experience); |
||||
std::string serial_read(int port_number = 0); |
||||
void set_uart_baud(int uart, int rate); |
||||
void set_fan_speed(uint16_t fan_speed); |
||||
uint16_t get_fan_speed(); |
||||
void set_ir_pwr(uint16_t ir_pwr); |
||||
std::optional<health_t> get_state(); |
||||
std::optional<can_health_t> get_can_state(uint16_t can_number); |
||||
void set_loopback(bool loopback); |
||||
std::optional<std::vector<uint8_t>> get_firmware_version(); |
||||
bool up_to_date(); |
||||
std::optional<std::string> get_serial(); |
||||
void set_power_saving(bool power_saving); |
||||
void enable_deepsleep(); |
||||
void send_heartbeat(bool engaged); |
||||
void set_can_speed_kbps(uint16_t bus, uint16_t speed); |
||||
void set_can_fd_auto(uint16_t bus, bool enabled); |
||||
void set_data_speed_kbps(uint16_t bus, uint16_t speed); |
||||
void set_canfd_non_iso(uint16_t bus, bool non_iso); |
||||
void can_send(const capnp::List<cereal::CanData>::Reader &can_data_list); |
||||
bool can_receive(std::vector<can_frame>& out_vec); |
||||
void can_reset_communications(); |
||||
|
||||
protected: |
||||
// for unit tests
|
||||
uint8_t receive_buffer[RECV_SIZE + sizeof(can_header) + 64]; |
||||
uint32_t receive_buffer_size = 0; |
||||
|
||||
Panda(uint32_t bus_offset) : bus_offset(bus_offset) {} |
||||
void pack_can_buffer(const capnp::List<cereal::CanData>::Reader &can_data_list, |
||||
std::function<void(uint8_t *, size_t)> write_func); |
||||
bool unpack_can_buffer(uint8_t *data, uint32_t &size, std::vector<can_frame> &out_vec); |
||||
uint8_t calculate_checksum(uint8_t *data, uint32_t len); |
||||
}; |
||||
@ -1,227 +0,0 @@ |
||||
#include "selfdrive/pandad/panda.h" |
||||
|
||||
#include <cassert> |
||||
#include <stdexcept> |
||||
#include <memory> |
||||
|
||||
#include "common/swaglog.h" |
||||
|
||||
static libusb_context *init_usb_ctx() { |
||||
libusb_context *context = nullptr; |
||||
int err = libusb_init(&context); |
||||
if (err != 0) { |
||||
LOGE("libusb initialization error"); |
||||
return nullptr; |
||||
} |
||||
|
||||
#if LIBUSB_API_VERSION >= 0x01000106 |
||||
libusb_set_option(context, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_INFO); |
||||
#else |
||||
libusb_set_debug(context, 3); |
||||
#endif |
||||
return context; |
||||
} |
||||
|
||||
PandaUsbHandle::PandaUsbHandle(std::string serial) : PandaCommsHandle(serial) { |
||||
// init libusb
|
||||
ssize_t num_devices; |
||||
libusb_device **dev_list = NULL; |
||||
int err = 0; |
||||
ctx = init_usb_ctx(); |
||||
if (!ctx) { goto fail; } |
||||
|
||||
// connect by serial
|
||||
num_devices = libusb_get_device_list(ctx, &dev_list); |
||||
if (num_devices < 0) { goto fail; } |
||||
for (size_t i = 0; i < num_devices; ++i) { |
||||
libusb_device_descriptor desc; |
||||
libusb_get_device_descriptor(dev_list[i], &desc); |
||||
if (desc.idVendor == 0x3801 && desc.idProduct == 0xddcc) { |
||||
int ret = libusb_open(dev_list[i], &dev_handle); |
||||
if (dev_handle == NULL || ret < 0) { goto fail; } |
||||
|
||||
unsigned char desc_serial[26] = { 0 }; |
||||
ret = libusb_get_string_descriptor_ascii(dev_handle, desc.iSerialNumber, desc_serial, std::size(desc_serial)); |
||||
if (ret < 0) { goto fail; } |
||||
|
||||
hw_serial = std::string((char *)desc_serial, ret); |
||||
if (serial.empty() || serial == hw_serial) { |
||||
break; |
||||
} |
||||
libusb_close(dev_handle); |
||||
dev_handle = NULL; |
||||
} |
||||
} |
||||
if (dev_handle == NULL) goto fail; |
||||
libusb_free_device_list(dev_list, 1); |
||||
dev_list = nullptr; |
||||
|
||||
if (libusb_kernel_driver_active(dev_handle, 0) == 1) { |
||||
libusb_detach_kernel_driver(dev_handle, 0); |
||||
} |
||||
|
||||
err = libusb_set_configuration(dev_handle, 1); |
||||
if (err != 0) { goto fail; } |
||||
|
||||
err = libusb_claim_interface(dev_handle, 0); |
||||
if (err != 0) { goto fail; } |
||||
|
||||
return; |
||||
|
||||
fail: |
||||
if (dev_list != NULL) { |
||||
libusb_free_device_list(dev_list, 1); |
||||
} |
||||
cleanup(); |
||||
throw std::runtime_error("Error connecting to panda"); |
||||
} |
||||
|
||||
PandaUsbHandle::~PandaUsbHandle() { |
||||
std::lock_guard lk(hw_lock); |
||||
cleanup(); |
||||
connected = false; |
||||
} |
||||
|
||||
void PandaUsbHandle::cleanup() { |
||||
if (dev_handle) { |
||||
libusb_release_interface(dev_handle, 0); |
||||
libusb_close(dev_handle); |
||||
} |
||||
|
||||
if (ctx) { |
||||
libusb_exit(ctx); |
||||
} |
||||
} |
||||
|
||||
std::vector<std::string> PandaUsbHandle::list() { |
||||
static std::unique_ptr<libusb_context, decltype(&libusb_exit)> context(init_usb_ctx(), libusb_exit); |
||||
// init libusb
|
||||
ssize_t num_devices; |
||||
libusb_device **dev_list = NULL; |
||||
std::vector<std::string> serials; |
||||
if (!context) { return serials; } |
||||
|
||||
num_devices = libusb_get_device_list(context.get(), &dev_list); |
||||
if (num_devices < 0) { |
||||
LOGE("libusb can't get device list"); |
||||
goto finish; |
||||
} |
||||
for (size_t i = 0; i < num_devices; ++i) { |
||||
libusb_device *device = dev_list[i]; |
||||
libusb_device_descriptor desc; |
||||
libusb_get_device_descriptor(device, &desc); |
||||
if (desc.idVendor == 0x3801 && desc.idProduct == 0xddcc) { |
||||
libusb_device_handle *handle = NULL; |
||||
int ret = libusb_open(device, &handle); |
||||
if (ret < 0) { goto finish; } |
||||
|
||||
unsigned char desc_serial[26] = { 0 }; |
||||
ret = libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber, desc_serial, std::size(desc_serial)); |
||||
libusb_close(handle); |
||||
if (ret < 0) { goto finish; } |
||||
|
||||
serials.push_back(std::string((char *)desc_serial, ret)); |
||||
} |
||||
} |
||||
|
||||
finish: |
||||
if (dev_list != NULL) { |
||||
libusb_free_device_list(dev_list, 1); |
||||
} |
||||
return serials; |
||||
} |
||||
|
||||
void PandaUsbHandle::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 == LIBUSB_ERROR_NO_DEVICE) { |
||||
LOGE("lost connection"); |
||||
connected = false; |
||||
} |
||||
// TODO: check other errors, is simply retrying okay?
|
||||
} |
||||
|
||||
int PandaUsbHandle::control_write(uint8_t bRequest, uint16_t wValue, uint16_t wIndex, unsigned int timeout) { |
||||
int err; |
||||
const uint8_t bmRequestType = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE; |
||||
|
||||
if (!connected) { |
||||
return LIBUSB_ERROR_NO_DEVICE; |
||||
} |
||||
|
||||
std::lock_guard lk(hw_lock); |
||||
do { |
||||
err = libusb_control_transfer(dev_handle, bmRequestType, bRequest, wValue, wIndex, NULL, 0, timeout); |
||||
if (err < 0) handle_usb_issue(err, __func__); |
||||
} while (err < 0 && connected); |
||||
|
||||
return err; |
||||
} |
||||
|
||||
int PandaUsbHandle::control_read(uint8_t bRequest, uint16_t wValue, uint16_t wIndex, unsigned char *data, uint16_t wLength, unsigned int timeout) { |
||||
int err; |
||||
const uint8_t bmRequestType = LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE; |
||||
|
||||
if (!connected) { |
||||
return LIBUSB_ERROR_NO_DEVICE; |
||||
} |
||||
|
||||
std::lock_guard lk(hw_lock); |
||||
do { |
||||
err = libusb_control_transfer(dev_handle, bmRequestType, bRequest, wValue, wIndex, data, wLength, timeout); |
||||
if (err < 0) handle_usb_issue(err, __func__); |
||||
} while (err < 0 && connected); |
||||
|
||||
return err; |
||||
} |
||||
|
||||
int PandaUsbHandle::bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) { |
||||
int err; |
||||
int transferred = 0; |
||||
|
||||
if (!connected) { |
||||
return 0; |
||||
} |
||||
|
||||
std::lock_guard lk(hw_lock); |
||||
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, endpoint, data, length, &transferred, timeout); |
||||
|
||||
if (err == LIBUSB_ERROR_TIMEOUT) { |
||||
LOGW("Transmit buffer full"); |
||||
break; |
||||
} else if (err != 0 || length != transferred) { |
||||
handle_usb_issue(err, __func__); |
||||
} |
||||
} while (err != 0 && connected); |
||||
|
||||
return transferred; |
||||
} |
||||
|
||||
int PandaUsbHandle::bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) { |
||||
int err; |
||||
int transferred = 0; |
||||
|
||||
if (!connected) { |
||||
return 0; |
||||
} |
||||
|
||||
std::lock_guard lk(hw_lock); |
||||
|
||||
do { |
||||
err = libusb_bulk_transfer(dev_handle, endpoint, data, length, &transferred, timeout); |
||||
|
||||
if (err == LIBUSB_ERROR_TIMEOUT) { |
||||
break; // timeout is okay to exit, recv still happened
|
||||
} else if (err == LIBUSB_ERROR_OVERFLOW) { |
||||
comms_healthy = false; |
||||
LOGE_100("overflow got 0x%x", transferred); |
||||
} else if (err != 0) { |
||||
handle_usb_issue(err, __func__); |
||||
} |
||||
|
||||
} while (err != 0 && connected); |
||||
|
||||
return transferred; |
||||
} |
||||
@ -1,93 +0,0 @@ |
||||
#pragma once |
||||
|
||||
#include <atomic> |
||||
#include <cstdint> |
||||
#include <mutex> |
||||
#include <string> |
||||
#include <vector> |
||||
|
||||
#ifndef __APPLE__ |
||||
#include <linux/spi/spidev.h> |
||||
#endif |
||||
|
||||
#include <libusb-1.0/libusb.h> |
||||
|
||||
|
||||
#define TIMEOUT 0 |
||||
#define SPI_BUF_SIZE 2048 |
||||
|
||||
|
||||
// comms base class
|
||||
class PandaCommsHandle { |
||||
public: |
||||
PandaCommsHandle(std::string serial) {} |
||||
virtual ~PandaCommsHandle() {} |
||||
virtual void cleanup() = 0; |
||||
|
||||
std::string hw_serial; |
||||
std::atomic<bool> connected = true; |
||||
std::atomic<bool> comms_healthy = true; |
||||
static std::vector<std::string> list(); |
||||
|
||||
// HW communication
|
||||
virtual int control_write(uint8_t request, uint16_t param1, uint16_t param2, unsigned int timeout=TIMEOUT) = 0; |
||||
virtual int control_read(uint8_t request, uint16_t param1, uint16_t param2, unsigned char *data, uint16_t length, unsigned int timeout=TIMEOUT) = 0; |
||||
virtual int bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT) = 0; |
||||
virtual int bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT) = 0; |
||||
}; |
||||
|
||||
class PandaUsbHandle : public PandaCommsHandle { |
||||
public: |
||||
PandaUsbHandle(std::string serial); |
||||
~PandaUsbHandle(); |
||||
int control_write(uint8_t request, uint16_t param1, uint16_t param2, unsigned int timeout=TIMEOUT); |
||||
int control_read(uint8_t request, uint16_t param1, uint16_t param2, unsigned char *data, uint16_t length, unsigned int timeout=TIMEOUT); |
||||
int bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT); |
||||
int bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT); |
||||
void cleanup(); |
||||
|
||||
static std::vector<std::string> list(); |
||||
|
||||
private: |
||||
libusb_context *ctx = NULL; |
||||
libusb_device_handle *dev_handle = NULL; |
||||
std::recursive_mutex hw_lock; |
||||
void handle_usb_issue(int err, const char func[]); |
||||
}; |
||||
|
||||
#ifndef __APPLE__ |
||||
struct __attribute__((packed)) spi_header { |
||||
uint8_t sync; |
||||
uint8_t endpoint; |
||||
uint16_t tx_len; |
||||
uint16_t max_rx_len; |
||||
}; |
||||
|
||||
class PandaSpiHandle : public PandaCommsHandle { |
||||
public: |
||||
PandaSpiHandle(std::string serial); |
||||
~PandaSpiHandle(); |
||||
int control_write(uint8_t request, uint16_t param1, uint16_t param2, unsigned int timeout=TIMEOUT); |
||||
int control_read(uint8_t request, uint16_t param1, uint16_t param2, unsigned char *data, uint16_t length, unsigned int timeout=TIMEOUT); |
||||
int bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT); |
||||
int bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT); |
||||
void cleanup(); |
||||
|
||||
static std::vector<std::string> list(); |
||||
|
||||
private: |
||||
int spi_fd = -1; |
||||
uint8_t tx_buf[SPI_BUF_SIZE]; |
||||
uint8_t rx_buf[SPI_BUF_SIZE]; |
||||
inline static std::recursive_mutex hw_lock; |
||||
|
||||
int wait_for_ack(uint8_t ack, uint8_t tx, unsigned int timeout, unsigned int length); |
||||
int bulk_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t rx_len, unsigned int timeout); |
||||
int spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len, unsigned int timeout); |
||||
int spi_transfer_retry(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len, unsigned int timeout); |
||||
int lltransfer(spi_ioc_transfer &t); |
||||
|
||||
spi_header header; |
||||
uint32_t xfer_count = 0; |
||||
}; |
||||
#endif |
||||
@ -1,81 +0,0 @@ |
||||
#include "selfdrive/pandad/pandad.h" |
||||
#include "cereal/messaging/messaging.h" |
||||
#include "common/swaglog.h" |
||||
|
||||
void PandaSafety::configureSafetyMode(bool is_onroad) { |
||||
if (is_onroad && !safety_configured_) { |
||||
updateMultiplexingMode(); |
||||
|
||||
auto car_params = fetchCarParams(); |
||||
if (!car_params.empty()) { |
||||
LOGW("got %lu bytes CarParams", car_params.size()); |
||||
setSafetyMode(car_params); |
||||
safety_configured_ = true; |
||||
} |
||||
} else if (!is_onroad) { |
||||
initialized_ = false; |
||||
safety_configured_ = false; |
||||
log_once_ = false; |
||||
} |
||||
} |
||||
|
||||
void PandaSafety::updateMultiplexingMode() { |
||||
// Initialize to ELM327 without OBD multiplexing for initial fingerprinting
|
||||
if (!initialized_) { |
||||
prev_obd_multiplexing_ = false; |
||||
for (int i = 0; i < pandas_.size(); ++i) { |
||||
pandas_[i]->set_safety_model(cereal::CarParams::SafetyModel::ELM327, 1U); |
||||
} |
||||
initialized_ = true; |
||||
} |
||||
|
||||
// Switch between multiplexing modes based on the OBD multiplexing request
|
||||
bool obd_multiplexing_requested = params_.getBool("ObdMultiplexingEnabled"); |
||||
if (obd_multiplexing_requested != prev_obd_multiplexing_) { |
||||
for (int i = 0; i < pandas_.size(); ++i) { |
||||
const uint16_t safety_param = (i > 0 || !obd_multiplexing_requested) ? 1U : 0U; |
||||
pandas_[i]->set_safety_model(cereal::CarParams::SafetyModel::ELM327, safety_param); |
||||
} |
||||
prev_obd_multiplexing_ = obd_multiplexing_requested; |
||||
params_.putBool("ObdMultiplexingChanged", true); |
||||
} |
||||
} |
||||
|
||||
std::string PandaSafety::fetchCarParams() { |
||||
if (!params_.getBool("FirmwareQueryDone")) { |
||||
return {}; |
||||
} |
||||
|
||||
if (!log_once_) { |
||||
LOGW("Finished FW query, Waiting for params to set safety model"); |
||||
log_once_ = true; |
||||
} |
||||
|
||||
if (!params_.getBool("ControlsReady")) { |
||||
return {}; |
||||
} |
||||
return params_.get("CarParams"); |
||||
} |
||||
|
||||
void PandaSafety::setSafetyMode(const std::string ¶ms_string) { |
||||
AlignedBuffer aligned_buf; |
||||
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(params_string.data(), params_string.size())); |
||||
cereal::CarParams::Reader car_params = cmsg.getRoot<cereal::CarParams>(); |
||||
|
||||
auto safety_configs = car_params.getSafetyConfigs(); |
||||
uint16_t alternative_experience = car_params.getAlternativeExperience(); |
||||
|
||||
for (int i = 0; i < pandas_.size(); ++i) { |
||||
// Default to SILENT safety model if not specified
|
||||
cereal::CarParams::SafetyModel safety_model = cereal::CarParams::SafetyModel::SILENT; |
||||
uint16_t safety_param = 0U; |
||||
if (i < safety_configs.size()) { |
||||
safety_model = safety_configs[i].getSafetyModel(); |
||||
safety_param = safety_configs[i].getSafetyParam(); |
||||
} |
||||
|
||||
LOGW("Panda %d: setting safety model: %d, param: %d, alternative experience: %d", i, (int)safety_model, safety_param, alternative_experience); |
||||
pandas_[i]->set_alternative_experience(alternative_experience); |
||||
pandas_[i]->set_safety_model(safety_model, safety_param); |
||||
} |
||||
} |
||||
@ -1,525 +0,0 @@ |
||||
#include "selfdrive/pandad/pandad.h" |
||||
|
||||
#include <algorithm> |
||||
#include <array> |
||||
#include <bitset> |
||||
#include <cassert> |
||||
#include <cerrno> |
||||
#include <memory> |
||||
#include <thread> |
||||
#include <utility> |
||||
|
||||
#include "cereal/gen/cpp/car.capnp.h" |
||||
#include "cereal/messaging/messaging.h" |
||||
#include "common/ratekeeper.h" |
||||
#include "common/swaglog.h" |
||||
#include "common/timing.h" |
||||
#include "common/util.h" |
||||
#include "system/hardware/hw.h" |
||||
|
||||
// -- Multi-panda conventions --
|
||||
// Ordering:
|
||||
// - The internal panda will always be the first panda
|
||||
// - Consecutive pandas will be sorted based on panda type, and then serial number
|
||||
// Connecting:
|
||||
// - If a panda connection is dropped, pandad will reconnect to all pandas
|
||||
// - If a panda is added, we will only reconnect when we are offroad
|
||||
// CAN buses:
|
||||
// - Each panda will have its block of 4 buses. E.g.: the second panda will use
|
||||
// bus numbers 4, 5, 6 and 7
|
||||
// - The internal panda will always be used for accessing the OBD2 port,
|
||||
// and thus firmware queries
|
||||
// Safety:
|
||||
// - SafetyConfig is a list, which is mapped to the connected pandas
|
||||
// - If there are more pandas connected than there are SafetyConfigs,
|
||||
// the excess pandas will remain in "silent" or "noOutput" mode
|
||||
// Ignition:
|
||||
// - If any of the ignition sources in any panda is high, ignition is high
|
||||
|
||||
#define MAX_IR_PANDA_VAL 50 |
||||
#define CUTOFF_IL 400 |
||||
#define SATURATE_IL 1000 |
||||
|
||||
ExitHandler do_exit; |
||||
|
||||
bool check_all_connected(const std::vector<Panda *> &pandas) { |
||||
for (const auto& panda : pandas) { |
||||
if (!panda->connected()) { |
||||
do_exit = true; |
||||
return false; |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
Panda *connect(std::string serial="", uint32_t index=0) { |
||||
std::unique_ptr<Panda> panda; |
||||
try { |
||||
panda = std::make_unique<Panda>(serial, (index * PANDA_BUS_OFFSET)); |
||||
} catch (std::exception &e) { |
||||
return nullptr; |
||||
} |
||||
|
||||
// common panda config
|
||||
if (getenv("BOARDD_LOOPBACK")) { |
||||
panda->set_loopback(true); |
||||
} |
||||
//panda->enable_deepsleep();
|
||||
|
||||
for (int i = 0; i < PANDA_BUS_CNT; i++) { |
||||
panda->set_can_fd_auto(i, true); |
||||
} |
||||
|
||||
if (!panda->up_to_date() && !getenv("BOARDD_SKIP_FW_CHECK")) { |
||||
throw std::runtime_error("Panda firmware out of date. Run pandad.py to update."); |
||||
} |
||||
|
||||
return panda.release(); |
||||
} |
||||
|
||||
void can_send_thread(std::vector<Panda *> pandas, bool fake_send) { |
||||
util::set_thread_name("pandad_can_send"); |
||||
|
||||
AlignedBuffer aligned_buf; |
||||
std::unique_ptr<Context> context(Context::create()); |
||||
std::unique_ptr<SubSocket> subscriber(SubSocket::create(context.get(), "sendcan")); |
||||
assert(subscriber != NULL); |
||||
subscriber->setTimeout(100); |
||||
|
||||
// run as fast as messages come in
|
||||
while (!do_exit && check_all_connected(pandas)) { |
||||
std::unique_ptr<Message> msg(subscriber->receive()); |
||||
if (!msg) { |
||||
continue; |
||||
} |
||||
|
||||
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(msg.get())); |
||||
cereal::Event::Reader event = cmsg.getRoot<cereal::Event>(); |
||||
|
||||
// Don't send if older than 1 second
|
||||
if ((nanos_since_boot() - event.getLogMonoTime() < 1e9) && !fake_send) { |
||||
for (const auto& panda : pandas) { |
||||
LOGT("sending sendcan to panda: %s", (panda->hw_serial()).c_str()); |
||||
panda->can_send(event.getSendcan()); |
||||
LOGT("sendcan sent to panda: %s", (panda->hw_serial()).c_str()); |
||||
} |
||||
} else { |
||||
LOGE("sendcan too old to send: %" PRIu64 ", %" PRIu64, nanos_since_boot(), event.getLogMonoTime()); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void can_recv(std::vector<Panda *> &pandas, PubMaster *pm) { |
||||
static std::vector<can_frame> raw_can_data; |
||||
{ |
||||
bool comms_healthy = true; |
||||
raw_can_data.clear(); |
||||
for (const auto& panda : pandas) { |
||||
comms_healthy &= panda->can_receive(raw_can_data); |
||||
} |
||||
|
||||
MessageBuilder msg; |
||||
auto evt = msg.initEvent(); |
||||
evt.setValid(comms_healthy); |
||||
auto canData = evt.initCan(raw_can_data.size()); |
||||
for (size_t i = 0; i < raw_can_data.size(); ++i) { |
||||
canData[i].setAddress(raw_can_data[i].address); |
||||
canData[i].setDat(kj::arrayPtr((uint8_t*)raw_can_data[i].dat.data(), raw_can_data[i].dat.size())); |
||||
canData[i].setSrc(raw_can_data[i].src); |
||||
} |
||||
pm->send("can", msg); |
||||
} |
||||
} |
||||
|
||||
void fill_panda_state(cereal::PandaState::Builder &ps, cereal::PandaState::PandaType hw_type, const health_t &health) { |
||||
ps.setVoltage(health.voltage_pkt); |
||||
ps.setCurrent(health.current_pkt); |
||||
ps.setUptime(health.uptime_pkt); |
||||
ps.setSafetyTxBlocked(health.safety_tx_blocked_pkt); |
||||
ps.setSafetyRxInvalid(health.safety_rx_invalid_pkt); |
||||
ps.setIgnitionLine(health.ignition_line_pkt); |
||||
ps.setIgnitionCan(health.ignition_can_pkt); |
||||
ps.setControlsAllowed(health.controls_allowed_pkt); |
||||
ps.setTxBufferOverflow(health.tx_buffer_overflow_pkt); |
||||
ps.setRxBufferOverflow(health.rx_buffer_overflow_pkt); |
||||
ps.setPandaType(hw_type); |
||||
ps.setSafetyModel(cereal::CarParams::SafetyModel(health.safety_mode_pkt)); |
||||
ps.setSafetyParam(health.safety_param_pkt); |
||||
ps.setFaultStatus(cereal::PandaState::FaultStatus(health.fault_status_pkt)); |
||||
ps.setPowerSaveEnabled((bool)(health.power_save_enabled_pkt)); |
||||
ps.setHeartbeatLost((bool)(health.heartbeat_lost_pkt)); |
||||
ps.setAlternativeExperience(health.alternative_experience_pkt); |
||||
ps.setHarnessStatus(cereal::PandaState::HarnessStatus(health.car_harness_status_pkt)); |
||||
ps.setInterruptLoad(health.interrupt_load_pkt); |
||||
ps.setFanPower(health.fan_power); |
||||
ps.setFanStallCount(health.fan_stall_count); |
||||
ps.setSafetyRxChecksInvalid((bool)(health.safety_rx_checks_invalid_pkt)); |
||||
ps.setSpiErrorCount(health.spi_error_count_pkt); |
||||
ps.setSbu1Voltage(health.sbu1_voltage_mV / 1000.0f); |
||||
ps.setSbu2Voltage(health.sbu2_voltage_mV / 1000.0f); |
||||
} |
||||
|
||||
void fill_panda_can_state(cereal::PandaState::PandaCanState::Builder &cs, const can_health_t &can_health) { |
||||
cs.setBusOff((bool)can_health.bus_off); |
||||
cs.setBusOffCnt(can_health.bus_off_cnt); |
||||
cs.setErrorWarning((bool)can_health.error_warning); |
||||
cs.setErrorPassive((bool)can_health.error_passive); |
||||
cs.setLastError(cereal::PandaState::PandaCanState::LecErrorCode(can_health.last_error)); |
||||
cs.setLastStoredError(cereal::PandaState::PandaCanState::LecErrorCode(can_health.last_stored_error)); |
||||
cs.setLastDataError(cereal::PandaState::PandaCanState::LecErrorCode(can_health.last_data_error)); |
||||
cs.setLastDataStoredError(cereal::PandaState::PandaCanState::LecErrorCode(can_health.last_data_stored_error)); |
||||
cs.setReceiveErrorCnt(can_health.receive_error_cnt); |
||||
cs.setTransmitErrorCnt(can_health.transmit_error_cnt); |
||||
cs.setTotalErrorCnt(can_health.total_error_cnt); |
||||
cs.setTotalTxLostCnt(can_health.total_tx_lost_cnt); |
||||
cs.setTotalRxLostCnt(can_health.total_rx_lost_cnt); |
||||
cs.setTotalTxCnt(can_health.total_tx_cnt); |
||||
cs.setTotalRxCnt(can_health.total_rx_cnt); |
||||
cs.setTotalFwdCnt(can_health.total_fwd_cnt); |
||||
cs.setCanSpeed(can_health.can_speed); |
||||
cs.setCanDataSpeed(can_health.can_data_speed); |
||||
cs.setCanfdEnabled(can_health.canfd_enabled); |
||||
cs.setBrsEnabled(can_health.brs_enabled); |
||||
cs.setCanfdNonIso(can_health.canfd_non_iso); |
||||
cs.setIrq0CallRate(can_health.irq0_call_rate); |
||||
cs.setIrq1CallRate(can_health.irq1_call_rate); |
||||
cs.setIrq2CallRate(can_health.irq2_call_rate); |
||||
cs.setCanCoreResetCnt(can_health.can_core_reset_cnt); |
||||
} |
||||
|
||||
std::optional<bool> send_panda_states(PubMaster *pm, const std::vector<Panda *> &pandas, bool is_onroad, bool spoofing_started) { |
||||
bool ignition_local = false; |
||||
const uint32_t pandas_cnt = pandas.size(); |
||||
|
||||
// build msg
|
||||
MessageBuilder msg; |
||||
auto evt = msg.initEvent(); |
||||
auto pss = evt.initPandaStates(pandas_cnt); |
||||
|
||||
std::vector<health_t> pandaStates; |
||||
pandaStates.reserve(pandas_cnt); |
||||
|
||||
std::vector<std::array<can_health_t, PANDA_CAN_CNT>> pandaCanStates; |
||||
pandaCanStates.reserve(pandas_cnt); |
||||
|
||||
const bool red_panda_comma_three = (pandas.size() == 2) && |
||||
(pandas[0]->hw_type == cereal::PandaState::PandaType::DOS) && |
||||
(pandas[1]->hw_type == cereal::PandaState::PandaType::RED_PANDA); |
||||
|
||||
for (const auto& panda : pandas){ |
||||
auto health_opt = panda->get_state(); |
||||
if (!health_opt) { |
||||
return std::nullopt; |
||||
} |
||||
|
||||
health_t health = *health_opt; |
||||
|
||||
std::array<can_health_t, PANDA_CAN_CNT> can_health{}; |
||||
for (uint32_t i = 0; i < PANDA_CAN_CNT; i++) { |
||||
auto can_health_opt = panda->get_can_state(i); |
||||
if (!can_health_opt) { |
||||
return std::nullopt; |
||||
} |
||||
can_health[i] = *can_health_opt; |
||||
} |
||||
pandaCanStates.push_back(can_health); |
||||
|
||||
if (spoofing_started) { |
||||
health.ignition_line_pkt = 1; |
||||
} |
||||
|
||||
// on comma three setups with a red panda, the dos can
|
||||
// get false positive ignitions due to the harness box
|
||||
// without a harness connector, so ignore it
|
||||
if (red_panda_comma_three && (panda->hw_type == cereal::PandaState::PandaType::DOS)) { |
||||
health.ignition_line_pkt = 0; |
||||
} |
||||
|
||||
ignition_local |= ((health.ignition_line_pkt != 0) || (health.ignition_can_pkt != 0)); |
||||
|
||||
pandaStates.push_back(health); |
||||
} |
||||
|
||||
for (uint32_t i = 0; i < pandas_cnt; i++) { |
||||
auto panda = pandas[i]; |
||||
const auto &health = pandaStates[i]; |
||||
|
||||
// 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_mode_pkt == (uint8_t)(cereal::CarParams::SafetyModel::SILENT)) { |
||||
panda->set_safety_model(cereal::CarParams::SafetyModel::NO_OUTPUT); |
||||
} |
||||
|
||||
bool power_save_desired = !ignition_local; |
||||
if (health.power_save_enabled_pkt != power_save_desired) { |
||||
panda->set_power_saving(power_save_desired); |
||||
} |
||||
|
||||
// set safety mode to NO_OUTPUT when car is off or we're not onroad. ELM327 is an alternative if we want to leverage athenad/connect
|
||||
bool should_close_relay = !ignition_local || !is_onroad; |
||||
if (should_close_relay && (health.safety_mode_pkt != (uint8_t)(cereal::CarParams::SafetyModel::NO_OUTPUT))) { |
||||
panda->set_safety_model(cereal::CarParams::SafetyModel::NO_OUTPUT); |
||||
} |
||||
|
||||
if (!panda->comms_healthy()) { |
||||
evt.setValid(false); |
||||
} |
||||
|
||||
auto ps = pss[i]; |
||||
fill_panda_state(ps, panda->hw_type, health); |
||||
|
||||
auto cs = std::array{ps.initCanState0(), ps.initCanState1(), ps.initCanState2()}; |
||||
for (uint32_t j = 0; j < PANDA_CAN_CNT; j++) { |
||||
fill_panda_can_state(cs[j], pandaCanStates[i][j]); |
||||
} |
||||
|
||||
// Convert faults bitset to capnp list
|
||||
std::bitset<sizeof(health.faults_pkt) * 8> fault_bits(health.faults_pkt); |
||||
auto faults = ps.initFaults(fault_bits.count()); |
||||
|
||||
size_t j = 0; |
||||
for (size_t f = size_t(cereal::PandaState::FaultType::RELAY_MALFUNCTION); |
||||
f <= size_t(cereal::PandaState::FaultType::HEARTBEAT_LOOP_WATCHDOG); f++) { |
||||
if (fault_bits.test(f)) { |
||||
faults.set(j, cereal::PandaState::FaultType(f)); |
||||
j++; |
||||
} |
||||
} |
||||
} |
||||
|
||||
pm->send("pandaStates", msg); |
||||
return ignition_local; |
||||
} |
||||
|
||||
void send_peripheral_state(Panda *panda, PubMaster *pm) { |
||||
// build msg
|
||||
MessageBuilder msg; |
||||
auto evt = msg.initEvent(); |
||||
evt.setValid(panda->comms_healthy()); |
||||
|
||||
auto ps = evt.initPeripheralState(); |
||||
ps.setPandaType(panda->hw_type); |
||||
|
||||
double read_time = millis_since_boot(); |
||||
ps.setVoltage(Hardware::get_voltage()); |
||||
ps.setCurrent(Hardware::get_current()); |
||||
read_time = millis_since_boot() - read_time; |
||||
if (read_time > 50) { |
||||
LOGW("reading hwmon took %lfms", read_time); |
||||
} |
||||
|
||||
// fall back to panda's voltage and current measurement
|
||||
if (ps.getVoltage() == 0 && ps.getCurrent() == 0) { |
||||
auto health_opt = panda->get_state(); |
||||
if (health_opt) { |
||||
health_t health = *health_opt; |
||||
ps.setVoltage(health.voltage_pkt); |
||||
ps.setCurrent(health.current_pkt); |
||||
} |
||||
} |
||||
|
||||
uint16_t fan_speed_rpm = panda->get_fan_speed(); |
||||
ps.setFanSpeedRpm(fan_speed_rpm); |
||||
|
||||
pm->send("peripheralState", msg); |
||||
} |
||||
|
||||
void process_panda_state(std::vector<Panda *> &pandas, PubMaster *pm, bool engaged, bool is_onroad, bool spoofing_started) { |
||||
std::vector<std::string> connected_serials; |
||||
for (Panda *p : pandas) { |
||||
connected_serials.push_back(p->hw_serial()); |
||||
} |
||||
|
||||
{ |
||||
auto ignition_opt = send_panda_states(pm, pandas, is_onroad, spoofing_started); |
||||
if (!ignition_opt) { |
||||
LOGE("Failed to get ignition_opt"); |
||||
return; |
||||
} |
||||
|
||||
// check if we should have pandad reconnect
|
||||
if (!ignition_opt.value()) { |
||||
bool comms_healthy = true; |
||||
for (const auto &panda : pandas) { |
||||
comms_healthy &= panda->comms_healthy(); |
||||
} |
||||
|
||||
if (!comms_healthy) { |
||||
LOGE("Reconnecting, communication to pandas not healthy"); |
||||
do_exit = true; |
||||
|
||||
} else { |
||||
// check for new pandas
|
||||
for (std::string &s : Panda::list(true)) { |
||||
if (!std::count(connected_serials.begin(), connected_serials.end(), s)) { |
||||
LOGW("Reconnecting to new panda: %s", s.c_str()); |
||||
do_exit = true; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
for (const auto &panda : pandas) { |
||||
panda->send_heartbeat(engaged); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void process_peripheral_state(Panda *panda, PubMaster *pm, bool no_fan_control) { |
||||
static SubMaster sm({"deviceState", "driverCameraState"}); |
||||
|
||||
static uint64_t last_driver_camera_t = 0; |
||||
static uint16_t prev_fan_speed = 999; |
||||
static int ir_pwr = 0; |
||||
static int prev_ir_pwr = 999; |
||||
|
||||
static FirstOrderFilter integ_lines_filter(0, 30.0, 0.05); |
||||
|
||||
{ |
||||
sm.update(0); |
||||
if (sm.updated("deviceState") && !no_fan_control) { |
||||
// Fan speed
|
||||
uint16_t fan_speed = sm["deviceState"].getDeviceState().getFanSpeedPercentDesired(); |
||||
if (fan_speed != prev_fan_speed || sm.frame % 100 == 0) { |
||||
panda->set_fan_speed(fan_speed); |
||||
prev_fan_speed = fan_speed; |
||||
} |
||||
} |
||||
|
||||
if (sm.updated("driverCameraState")) { |
||||
auto event = sm["driverCameraState"]; |
||||
int cur_integ_lines = event.getDriverCameraState().getIntegLines(); |
||||
|
||||
cur_integ_lines = integ_lines_filter.update(cur_integ_lines); |
||||
last_driver_camera_t = event.getLogMonoTime(); |
||||
|
||||
if (cur_integ_lines <= CUTOFF_IL) { |
||||
ir_pwr = 0; |
||||
} else if (cur_integ_lines > SATURATE_IL) { |
||||
ir_pwr = 100; |
||||
} else { |
||||
ir_pwr = 100 * (cur_integ_lines - CUTOFF_IL) / (SATURATE_IL - CUTOFF_IL); |
||||
} |
||||
} |
||||
|
||||
// Disable IR on input timeout
|
||||
if (nanos_since_boot() - last_driver_camera_t > 1e9) { |
||||
ir_pwr = 0; |
||||
} |
||||
|
||||
if (ir_pwr != prev_ir_pwr || sm.frame % 100 == 0) { |
||||
int16_t ir_panda = util::map_val(ir_pwr, 0, 100, 0, MAX_IR_PANDA_VAL);
|
||||
panda->set_ir_pwr(ir_panda); |
||||
Hardware::set_ir_power(ir_pwr);
|
||||
prev_ir_pwr = ir_pwr; |
||||
} |
||||
} |
||||
} |
||||
|
||||
void pandad_run(std::vector<Panda *> &pandas) { |
||||
const bool no_fan_control = getenv("NO_FAN_CONTROL") != nullptr; |
||||
const bool spoofing_started = getenv("STARTED") != nullptr; |
||||
const bool fake_send = getenv("FAKESEND") != nullptr; |
||||
|
||||
// Start the CAN send thread
|
||||
std::thread send_thread(can_send_thread, pandas, fake_send); |
||||
|
||||
Params params; |
||||
RateKeeper rk("pandad", 100); |
||||
SubMaster sm({"selfdriveState"}); |
||||
PubMaster pm({"can", "pandaStates", "peripheralState"}); |
||||
PandaSafety panda_safety(pandas); |
||||
Panda *peripheral_panda = pandas[0]; |
||||
bool engaged = false; |
||||
bool is_onroad = false; |
||||
|
||||
// Main loop: receive CAN data and process states
|
||||
while (!do_exit && check_all_connected(pandas)) { |
||||
can_recv(pandas, &pm); |
||||
|
||||
// Process peripheral state at 20 Hz
|
||||
if (rk.frame() % 5 == 0) { |
||||
process_peripheral_state(peripheral_panda, &pm, no_fan_control); |
||||
} |
||||
|
||||
// Process panda state at 10 Hz
|
||||
if (rk.frame() % 10 == 0) { |
||||
sm.update(0); |
||||
engaged = sm.allAliveAndValid({"selfdriveState"}) && sm["selfdriveState"].getSelfdriveState().getEnabled(); |
||||
is_onroad = params.getBool("IsOnroad"); |
||||
process_panda_state(pandas, &pm, engaged, is_onroad, spoofing_started); |
||||
panda_safety.configureSafetyMode(is_onroad); |
||||
} |
||||
|
||||
// Send out peripheralState at 2Hz
|
||||
if (rk.frame() % 50 == 0) { |
||||
send_peripheral_state(peripheral_panda, &pm); |
||||
} |
||||
|
||||
// Forward logs from pandas to cloudlog if available
|
||||
for (auto *panda : pandas) { |
||||
std::string log = panda->serial_read(); |
||||
if (!log.empty()) { |
||||
if (log.find("Register 0x") != std::string::npos) { |
||||
// Log register divergent faults as errors
|
||||
LOGE("%s", log.c_str()); |
||||
} else { |
||||
LOGD("%s", log.c_str()); |
||||
} |
||||
} |
||||
} |
||||
|
||||
rk.keepTime(); |
||||
} |
||||
|
||||
// Close relay on exit to prevent a fault
|
||||
if (is_onroad && !engaged) { |
||||
for (auto &p : pandas) { |
||||
if (p->connected()) { |
||||
p->set_safety_model(cereal::CarParams::SafetyModel::NO_OUTPUT); |
||||
} |
||||
} |
||||
} |
||||
|
||||
send_thread.join(); |
||||
} |
||||
|
||||
void pandad_main_thread(std::vector<std::string> serials) { |
||||
if (serials.size() == 0) { |
||||
serials = Panda::list(); |
||||
|
||||
if (serials.size() == 0) { |
||||
LOGW("no pandas found, exiting"); |
||||
return; |
||||
} |
||||
} |
||||
|
||||
std::string serials_str; |
||||
for (int i = 0; i < serials.size(); i++) { |
||||
serials_str += serials[i]; |
||||
if (i < serials.size() - 1) serials_str += ", "; |
||||
} |
||||
LOGW("connecting to pandas: %s", serials_str.c_str()); |
||||
|
||||
// connect to all provided serials
|
||||
std::vector<Panda *> pandas; |
||||
for (int i = 0; i < serials.size() && !do_exit; /**/) { |
||||
Panda *p = connect(serials[i], i); |
||||
if (!p) { |
||||
util::sleep_for(100); |
||||
continue; |
||||
} |
||||
|
||||
pandas.push_back(p); |
||||
++i; |
||||
} |
||||
|
||||
if (!do_exit) { |
||||
LOGW("connected to all pandas"); |
||||
pandad_run(pandas); |
||||
} |
||||
|
||||
for (Panda *panda : pandas) { |
||||
delete panda; |
||||
} |
||||
} |
||||
@ -1,27 +0,0 @@ |
||||
#pragma once |
||||
|
||||
#include <string> |
||||
#include <vector> |
||||
|
||||
#include "common/params.h" |
||||
#include "selfdrive/pandad/panda.h" |
||||
|
||||
void pandad_main_thread(std::vector<std::string> serials); |
||||
|
||||
class PandaSafety { |
||||
public: |
||||
PandaSafety(const std::vector<Panda *> &pandas) : pandas_(pandas) {} |
||||
void configureSafetyMode(bool is_onroad); |
||||
|
||||
private: |
||||
void updateMultiplexingMode(); |
||||
std::string fetchCarParams(); |
||||
void setSafetyMode(const std::string ¶ms_string); |
||||
|
||||
bool initialized_ = false; |
||||
bool log_once_ = false; |
||||
bool safety_configured_ = false; |
||||
bool prev_obd_multiplexing_ = false; |
||||
std::vector<Panda *> pandas_; |
||||
Params params_; |
||||
}; |
||||
@ -0,0 +1,101 @@ |
||||
import os |
||||
import threading |
||||
import time |
||||
import cereal.messaging as messaging |
||||
from openpilot.common.swaglog import cloudlog |
||||
from openpilot.common.filter_simple import FirstOrderFilter |
||||
from openpilot.system.hardware import HARDWARE |
||||
|
||||
NO_FAN_CONTROL = os.getenv("NO_FAN_CONTROL") == "1" |
||||
|
||||
MAX_IR_PANDA_VAL = 50 |
||||
CUTOFF_IL = 400 |
||||
SATURATE_IL = 1000 |
||||
|
||||
|
||||
class HardwareReader: |
||||
def __init__(self): |
||||
self.voltage = 0 |
||||
self.current = 0 |
||||
self.running = True |
||||
self.thread = threading.Thread(target=self._read_loop, daemon=True) |
||||
self.thread.start() |
||||
|
||||
def _read_loop(self): |
||||
while self.running: |
||||
start = time.monotonic() |
||||
try: |
||||
self.voltage = HARDWARE.get_voltage() |
||||
self.current = HARDWARE.get_current() |
||||
elapsed = (time.monotonic() - start) * 1000 |
||||
if elapsed > 50: |
||||
cloudlog.warning(f"hwmon read took {elapsed:.2f}ms") |
||||
except Exception as e: |
||||
cloudlog.error(f"Hardware read error: {e}") |
||||
time.sleep(0.5) # 500ms update rate |
||||
|
||||
def get_values(self): |
||||
return self.voltage, self.current |
||||
|
||||
def stop(self): |
||||
self.running = False |
||||
self.thread.join() |
||||
|
||||
|
||||
class PeripheralManager: |
||||
def __init__(self, panda, hw_type): |
||||
self.panda = panda |
||||
self.last_camera_t = 0 |
||||
self.prev_fan = 999 |
||||
self.prev_ir_pwr = 999 |
||||
self.ir_pwr = 0 |
||||
self.filter = FirstOrderFilter(0, 30.0, 0.05) |
||||
self.hw_type = hw_type |
||||
self.hw_reader = HardwareReader() |
||||
|
||||
def process(self, sm): |
||||
if sm.updated["deviceState"] and not NO_FAN_CONTROL: |
||||
fan = sm["deviceState"].fanSpeedPercentDesired |
||||
if fan != self.prev_fan or sm.frame % 100 == 0: |
||||
self.panda.set_fan_power(fan) |
||||
self.prev_fan = fan |
||||
|
||||
if sm.updated["driverCameraState"]: |
||||
state = sm["driverCameraState"] |
||||
lines = self.filter.update(state.integLines) |
||||
self.last_camera_t = sm.logMonoTime['driverCameraState'] |
||||
if lines <= CUTOFF_IL: |
||||
self.ir_pwr = 0 |
||||
elif lines > SATURATE_IL: |
||||
self.ir_pwr = 100 |
||||
else: |
||||
self.ir_pwr = 100 * (lines - CUTOFF_IL) / (SATURATE_IL - CUTOFF_IL) |
||||
|
||||
if time.monotonic_ns() - self.last_camera_t > 1e9: |
||||
self.ir_pwr = 0 |
||||
|
||||
if self.ir_pwr != self.prev_ir_pwr or sm.frame % 100 == 0: |
||||
ir_panda = int(self.ir_pwr * MAX_IR_PANDA_VAL / 100) |
||||
self.panda.set_ir_power(ir_panda) |
||||
|
||||
HARDWARE.set_ir_power(self.ir_pwr) |
||||
self.prev_ir_pwr = self.ir_pwr |
||||
|
||||
def send_state(self, pm): |
||||
msg = messaging.new_message('peripheralState') |
||||
msg.valid = True |
||||
ps = msg.peripheralState |
||||
ps.pandaType = self.hw_type |
||||
ps.voltage, ps.current = self.hw_reader.get_values() |
||||
|
||||
if not (ps.voltage or ps.current): |
||||
h = self.panda.health() or {} |
||||
ps.voltage = h.get("voltage", 0) |
||||
ps.current = h.get("current", 0) |
||||
|
||||
ps.fanSpeedRpm = self.panda.get_fan_rpm() |
||||
|
||||
pm.send("peripheralState", msg) |
||||
|
||||
def cleanup(self): |
||||
self.hw_reader.stop() |
||||
@ -0,0 +1,127 @@ |
||||
import os |
||||
import time |
||||
|
||||
from cereal import car |
||||
from cereal.messaging import SubMaster, PubMaster |
||||
import cereal.messaging as messaging |
||||
from panda import Panda |
||||
from openpilot.common.params import Params |
||||
from openpilot.common.realtime import Ratekeeper |
||||
from openpilot.common.swaglog import cloudlog |
||||
from openpilot.selfdrive.pandad.state_manager import PandaStateManager |
||||
from openpilot.selfdrive.pandad.peripheral import PeripheralManager |
||||
from openpilot.selfdrive.pandad.safety import PandaSafetyManager |
||||
|
||||
FAKE_SEND = os.getenv("FAKESEND") == "1" |
||||
|
||||
class PandaRunner: |
||||
def __init__(self, serials, pandas): |
||||
self.pandas = pandas |
||||
self.usb_pandas = {p.get_usb_serial() for p in pandas if not p.spi} |
||||
self.hw_types = [int.from_bytes(p.get_type(), 'big') for p in pandas] |
||||
|
||||
for panda in self.pandas: |
||||
if os.getenv("BOARDD_LOOPBACK"): |
||||
panda.set_can_loopback(True) |
||||
for i in range(3): |
||||
panda.set_canfd_auto(i, True) |
||||
|
||||
self.sm = SubMaster(["selfdriveState", "deviceState", "driverCameraState"]) |
||||
self.pm = PubMaster(["can", "pandaStates", "peripheralState"]) |
||||
self.sendcan_sock = messaging.sub_sock('sendcan', timeout=10) |
||||
self.sendcan_buffer = None |
||||
|
||||
self.state_mgr = PandaStateManager(pandas, self.hw_types) |
||||
self.periph_mgr = PeripheralManager(pandas[0], self.hw_types[0]) |
||||
self.safety_mgr = PandaSafetyManager(pandas) |
||||
|
||||
def _can_send(self): |
||||
# TODO: this needs to have a strict timeout of <10ms and handle NACKs well (buffer the data) |
||||
while (msg := messaging.recv_one_or_none(self.sendcan_sock)): |
||||
# drop msg if too old |
||||
if (time.monotonic_ns() - msg.logMonoTime) / 1e9 > 1.0: |
||||
cloudlog.warning("skipping CAN send, too old") |
||||
continue |
||||
|
||||
# Group CAN messages by panda based on bus offset |
||||
panda_msgs = [[] for _ in self.pandas] |
||||
for c in msg.sendcan: |
||||
panda_idx = c.src // 4 # Each panda handles 4 buses |
||||
if panda_idx < len(self.pandas): |
||||
# Adjust bus number for the panda (remove offset) |
||||
adjusted_bus = c.src % 4 |
||||
panda_msgs[panda_idx].append((c.address, c.dat, adjusted_bus)) |
||||
|
||||
# Send messages to each panda |
||||
for panda_idx, can_msgs in enumerate(panda_msgs): |
||||
if can_msgs: |
||||
self.pandas[panda_idx].can_send_many(can_msgs) |
||||
|
||||
def _can_recv(self): |
||||
cans = [] |
||||
for panda_idx, p in enumerate(self.pandas): |
||||
bus_offset = panda_idx * 4 # Each panda gets 4 buses |
||||
for address, dat, src in p.can_recv(): |
||||
if src >= 192: # Rejected message |
||||
base_bus = src - 192 |
||||
adjusted_src = base_bus + bus_offset + 192 |
||||
elif src >= 128: # Returned message |
||||
base_bus = src - 128 |
||||
adjusted_src = base_bus + bus_offset + 128 |
||||
else: # Normal message |
||||
adjusted_src = src + bus_offset |
||||
cans.append((address, dat, adjusted_src)) |
||||
|
||||
msg = messaging.new_message('can', len(cans) if cans else 0) |
||||
msg.valid = True |
||||
if cans: |
||||
for i, can_info in enumerate(cans): |
||||
can = msg.can[i] |
||||
can.address, can.dat, can.src = can_info |
||||
self.pm.send("can", msg) |
||||
|
||||
def run(self, evt): |
||||
rk = Ratekeeper(100, print_delay_threshold=None) |
||||
engaged = False |
||||
|
||||
try: |
||||
while not evt.is_set(): |
||||
# receive CAN messages |
||||
self._can_recv() |
||||
|
||||
# send CAN messages |
||||
self._can_send() |
||||
|
||||
# Process peripheral state at 20 Hz |
||||
if rk.frame % 5 == 0: |
||||
self.sm.update(0) |
||||
engaged = self.sm.all_checks() and self.sm["selfdriveState"].enabled |
||||
self.periph_mgr.process(self.sm) |
||||
|
||||
# Process panda state at 10 Hz |
||||
if rk.frame % 10 == 0: |
||||
ignition = self.state_mgr.process(engaged, self.pm) |
||||
if not ignition and rk.frame % 100 == 0: |
||||
if set(Panda.list(usb_only=True)) != self.usb_pandas: |
||||
cloudlog.warning("Reconnecting to new panda") |
||||
evt.set() |
||||
break |
||||
|
||||
self.safety_mgr.configure_safety_mode() |
||||
|
||||
# Send out peripheralState at 2Hz |
||||
if rk.frame % 50 == 0: |
||||
self.periph_mgr.send_state(self.pm) |
||||
|
||||
rk.keep_time() |
||||
except Exception as e: |
||||
cloudlog.error(f"Exception in main loop: {e}") |
||||
finally: |
||||
evt.set() |
||||
self.periph_mgr.cleanup() |
||||
|
||||
# Close relay on exit to prevent a fault |
||||
is_onroad = Params().get_bool("IsOnroad") |
||||
if is_onroad and not engaged: |
||||
for p in self.pandas: |
||||
p.set_safety_mode(car.CarParams.SafetyModel.noOutput) |
||||
@ -0,0 +1,76 @@ |
||||
from panda import Panda |
||||
from cereal import car |
||||
from openpilot.common.params import Params |
||||
from openpilot.common.swaglog import cloudlog |
||||
|
||||
class PandaSafetyManager: |
||||
def __init__(self, pandas: list[Panda]): |
||||
self.pandas = pandas |
||||
self.params = Params() |
||||
self.safety_configured = False |
||||
self.initialized = False |
||||
self.prev_obd_multiplexing = False |
||||
self.log_once = False |
||||
|
||||
def configure_safety_mode(self): |
||||
is_onroad = self.params.get_bool("IsOnroad") |
||||
|
||||
if is_onroad and not self.safety_configured: |
||||
self.update_multiplexing_mode() |
||||
car_params = self.fetch_car_params() |
||||
if car_params: |
||||
cloudlog.warning(f"got {len(car_params)} bytes CarParams") |
||||
self.set_safety_mode(car_params) |
||||
self.safety_configured = True |
||||
elif not is_onroad: |
||||
self.initialized = False |
||||
self.safety_configured = False |
||||
self.log_once = False |
||||
|
||||
def update_multiplexing_mode(self): |
||||
# Initialize to ELM327 without OBD multiplexing for initial fingerprinting |
||||
if not self.initialized: |
||||
self.prev_obd_multiplexing = False |
||||
for panda in self.pandas: |
||||
panda.set_safety_mode(car.CarParams.SafetyModel.elm327, 1) |
||||
self.initialized = True |
||||
|
||||
# Switch between multiplexing modes based on OBD multiplexing request |
||||
obd_multiplexing_requested = self.params.get_bool("ObdMultiplexingEnabled") |
||||
if obd_multiplexing_requested != self.prev_obd_multiplexing: |
||||
for i, panda in enumerate(self.pandas): |
||||
safety_param = 1 if i > 0 or not obd_multiplexing_requested else 0 |
||||
panda.set_safety_mode(car.CarParams.SafetyModel.elm327, safety_param) |
||||
self.prev_obd_multiplexing = obd_multiplexing_requested |
||||
self.params.put_bool("ObdMultiplexingChanged", True) |
||||
|
||||
def fetch_car_params(self) -> bytes: |
||||
if not self.params.get_bool("FirmwareQueryDone"): |
||||
return b"" |
||||
|
||||
if not self.log_once: |
||||
cloudlog.warning("Finished FW query, waiting for params to set safety model") |
||||
self.log_once = True |
||||
|
||||
if not self.params.get_bool("ControlsReady"): |
||||
return b"" |
||||
return self.params.get("CarParams") or b"" |
||||
|
||||
def set_safety_mode(self, params_bytes: bytes): |
||||
# Parse CarParams from bytes |
||||
with car.CarParams.from_bytes(params_bytes) as car_params: |
||||
safety_configs = car_params.safetyConfigs |
||||
alternative_experience = car_params.alternativeExperience |
||||
|
||||
for i, panda in enumerate(self.pandas): |
||||
# Default to SILENT if no config for this panda |
||||
safety_model = car.CarParams.SafetyModel.silent |
||||
|
||||
safety_param = 0 |
||||
if i < len(safety_configs): |
||||
safety_model = car.CarParams.SafetyModel.schema.enumerants[safety_configs[i].safetyModel] |
||||
safety_param = safety_configs[i].safetyParam |
||||
|
||||
cloudlog.warning(f"Panda {i}: setting safety model: {safety_model}, param: {safety_param}, alternative experience: {alternative_experience}") |
||||
panda._handle.controlWrite(Panda.REQUEST_OUT, 0xdf, alternative_experience, 0, b'') |
||||
panda.set_safety_mode(safety_model, safety_param) |
||||
@ -1,410 +0,0 @@ |
||||
#ifndef __APPLE__ |
||||
#include <sys/file.h> |
||||
#include <sys/ioctl.h> |
||||
#include <linux/spi/spidev.h> |
||||
|
||||
#include <cassert> |
||||
#include <cmath> |
||||
#include <cstring> |
||||
#include <iomanip> |
||||
#include <sstream> |
||||
|
||||
#include "common/util.h" |
||||
#include "common/timing.h" |
||||
#include "common/swaglog.h" |
||||
#include "panda/board/comms_definitions.h" |
||||
#include "selfdrive/pandad/panda_comms.h" |
||||
|
||||
|
||||
#define SPI_SYNC 0x5AU |
||||
#define SPI_HACK 0x79U |
||||
#define SPI_DACK 0x85U |
||||
#define SPI_NACK 0x1FU |
||||
#define SPI_CHECKSUM_START 0xABU |
||||
|
||||
|
||||
enum SpiError { |
||||
NACK = -2, |
||||
ACK_TIMEOUT = -3, |
||||
}; |
||||
|
||||
const unsigned int SPI_ACK_TIMEOUT = 500; // milliseconds
|
||||
const std::string SPI_DEVICE = "/dev/spidev0.0"; |
||||
|
||||
class LockEx { |
||||
public: |
||||
LockEx(int fd, std::recursive_mutex &m) : fd(fd), m(m) { |
||||
m.lock(); |
||||
flock(fd, LOCK_EX); |
||||
} |
||||
|
||||
~LockEx() { |
||||
flock(fd, LOCK_UN); |
||||
m.unlock(); |
||||
} |
||||
|
||||
private: |
||||
int fd; |
||||
std::recursive_mutex &m; |
||||
}; |
||||
|
||||
#define SPILOG(fn, fmt, ...) do { \ |
||||
fn(fmt, ## __VA_ARGS__); \
|
||||
fn(" %d / 0x%x / %d / %d / tx: %s", \
|
||||
xfer_count, header.endpoint, header.tx_len, header.max_rx_len, \
|
||||
util::hexdump(tx_buf, std::min((int)header.tx_len, 8)).c_str()); \
|
||||
} while (0) |
||||
|
||||
PandaSpiHandle::PandaSpiHandle(std::string serial) : PandaCommsHandle(serial) { |
||||
int ret; |
||||
const int uid_len = 12; |
||||
uint8_t uid[uid_len] = {0}; |
||||
|
||||
uint32_t spi_mode = SPI_MODE_0; |
||||
uint8_t spi_bits_per_word = 8; |
||||
|
||||
// 50MHz is the max of the 845. note that some older
|
||||
// revs of the comma three may not support this speed
|
||||
uint32_t spi_speed = 50000000; |
||||
try { |
||||
if (!util::file_exists(SPI_DEVICE)) { |
||||
throw std::runtime_error("Error connecting to panda: SPI device not found"); |
||||
} |
||||
|
||||
spi_fd = open(SPI_DEVICE.c_str(), O_RDWR); |
||||
if (spi_fd < 0) { |
||||
LOGE("failed opening SPI device %d", spi_fd); |
||||
throw std::runtime_error("Error connecting to panda: failed to open SPI device"); |
||||
} |
||||
|
||||
// SPI settings
|
||||
util::safe_ioctl(spi_fd, SPI_IOC_WR_MODE, &spi_mode, "failed setting SPI mode"); |
||||
util::safe_ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &spi_speed, "failed setting SPI speed"); |
||||
util::safe_ioctl(spi_fd, SPI_IOC_WR_BITS_PER_WORD, &spi_bits_per_word, "failed setting SPI bits per word"); |
||||
|
||||
// get hw UID/serial
|
||||
ret = control_read(0xc3, 0, 0, uid, uid_len, 100); |
||||
if (ret == uid_len) { |
||||
std::stringstream stream; |
||||
for (int i = 0; i < uid_len; i++) { |
||||
stream << std::hex << std::setw(2) << std::setfill('0') << int(uid[i]); |
||||
} |
||||
hw_serial = stream.str(); |
||||
} else { |
||||
LOGD("failed to get serial %d", ret); |
||||
throw std::runtime_error("Error connecting to panda: failed to get serial"); |
||||
} |
||||
|
||||
if (!serial.empty() && (serial != hw_serial)) { |
||||
throw std::runtime_error("Error connecting to panda: serial mismatch"); |
||||
} |
||||
|
||||
} catch (...) { |
||||
cleanup(); |
||||
throw; |
||||
} |
||||
return; |
||||
} |
||||
|
||||
PandaSpiHandle::~PandaSpiHandle() { |
||||
std::lock_guard lk(hw_lock); |
||||
cleanup(); |
||||
} |
||||
|
||||
void PandaSpiHandle::cleanup() { |
||||
if (spi_fd != -1) { |
||||
close(spi_fd); |
||||
spi_fd = -1; |
||||
} |
||||
} |
||||
|
||||
|
||||
|
||||
int PandaSpiHandle::control_write(uint8_t request, uint16_t param1, uint16_t param2, unsigned int timeout) { |
||||
ControlPacket_t packet = { |
||||
.request = request, |
||||
.param1 = param1, |
||||
.param2 = param2, |
||||
.length = 0 |
||||
}; |
||||
return spi_transfer_retry(0, (uint8_t *) &packet, sizeof(packet), NULL, 0, timeout); |
||||
} |
||||
|
||||
int PandaSpiHandle::control_read(uint8_t request, uint16_t param1, uint16_t param2, unsigned char *data, uint16_t length, unsigned int timeout) { |
||||
ControlPacket_t packet = { |
||||
.request = request, |
||||
.param1 = param1, |
||||
.param2 = param2, |
||||
.length = length |
||||
}; |
||||
return spi_transfer_retry(0, (uint8_t *) &packet, sizeof(packet), data, length, timeout); |
||||
} |
||||
|
||||
int PandaSpiHandle::bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) { |
||||
return bulk_transfer(endpoint, data, length, NULL, 0, timeout); |
||||
} |
||||
int PandaSpiHandle::bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) { |
||||
return bulk_transfer(endpoint, NULL, 0, data, length, timeout); |
||||
} |
||||
|
||||
int PandaSpiHandle::bulk_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t rx_len, unsigned int timeout) { |
||||
const int xfer_size = SPI_BUF_SIZE - 0x40; |
||||
|
||||
int ret = 0; |
||||
uint16_t length = (tx_data != NULL) ? tx_len : rx_len; |
||||
for (int i = 0; i < (int)std::ceil((float)length / xfer_size); i++) { |
||||
int d; |
||||
if (tx_data != NULL) { |
||||
int len = std::min(xfer_size, tx_len - (xfer_size * i)); |
||||
d = spi_transfer_retry(endpoint, tx_data + (xfer_size * i), len, NULL, 0, timeout); |
||||
} else { |
||||
uint16_t to_read = std::min(xfer_size, rx_len - ret); |
||||
d = spi_transfer_retry(endpoint, NULL, 0, rx_data + (xfer_size * i), to_read, timeout); |
||||
} |
||||
|
||||
if (d < 0) { |
||||
SPILOG(LOGE, "SPI: bulk transfer failed with %d", d); |
||||
comms_healthy = false; |
||||
return d; |
||||
} |
||||
|
||||
ret += d; |
||||
if ((rx_data != NULL) && d < xfer_size) { |
||||
break; |
||||
} |
||||
} |
||||
|
||||
return ret; |
||||
} |
||||
|
||||
std::vector<std::string> PandaSpiHandle::list() { |
||||
try { |
||||
PandaSpiHandle sh(""); |
||||
return {sh.hw_serial}; |
||||
} catch (std::exception &e) { |
||||
// no panda on SPI
|
||||
} |
||||
return {}; |
||||
} |
||||
|
||||
void add_checksum(uint8_t *data, int data_len) { |
||||
data[data_len] = SPI_CHECKSUM_START; |
||||
for (int i=0; i < data_len; i++) { |
||||
data[data_len] ^= data[i]; |
||||
} |
||||
} |
||||
|
||||
bool check_checksum(uint8_t *data, int data_len) { |
||||
uint8_t checksum = SPI_CHECKSUM_START; |
||||
for (uint16_t i = 0U; i < data_len; i++) { |
||||
checksum ^= data[i]; |
||||
} |
||||
return checksum == 0U; |
||||
} |
||||
|
||||
|
||||
int PandaSpiHandle::spi_transfer_retry(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len, unsigned int timeout) { |
||||
int ret; |
||||
int nack_count = 0; |
||||
int timeout_count = 0; |
||||
bool timed_out = false; |
||||
double start_time = millis_since_boot(); |
||||
|
||||
do { |
||||
ret = spi_transfer(endpoint, tx_data, tx_len, rx_data, max_rx_len, timeout); |
||||
|
||||
if (ret < 0) { |
||||
timed_out = (timeout != 0) && (timeout_count > 5); |
||||
timeout_count += ret == SpiError::ACK_TIMEOUT; |
||||
|
||||
// give other threads a chance to run
|
||||
std::this_thread::yield(); |
||||
|
||||
if (ret == SpiError::NACK) { |
||||
// prevent busy waiting while the panda is NACK'ing
|
||||
// due to full TX buffers
|
||||
nack_count += 1; |
||||
if (nack_count > 3) { |
||||
SPILOG(LOGD, "NACK sleep %d", nack_count); |
||||
usleep(std::clamp(nack_count*10, 200, 2000)); |
||||
} |
||||
} |
||||
} |
||||
} while (ret < 0 && connected && !timed_out); |
||||
|
||||
if (ret < 0) { |
||||
SPILOG(LOGE, "transfer failed, after %d tries, %.2fms", timeout_count, millis_since_boot() - start_time); |
||||
} |
||||
|
||||
return ret; |
||||
} |
||||
|
||||
int PandaSpiHandle::wait_for_ack(uint8_t ack, uint8_t tx, unsigned int timeout, unsigned int length) { |
||||
double start_millis = millis_since_boot(); |
||||
if (timeout == 0) { |
||||
timeout = SPI_ACK_TIMEOUT; |
||||
} |
||||
timeout = std::clamp(timeout, 20U, SPI_ACK_TIMEOUT); |
||||
|
||||
spi_ioc_transfer transfer = { |
||||
.tx_buf = (uint64_t)tx_buf, |
||||
.rx_buf = (uint64_t)rx_buf, |
||||
.len = length, |
||||
}; |
||||
memset(tx_buf, tx, length); |
||||
|
||||
while (true) { |
||||
int ret = lltransfer(transfer); |
||||
if (ret < 0) { |
||||
SPILOG(LOGE, "SPI: failed to send ACK request"); |
||||
return ret; |
||||
} |
||||
|
||||
if (rx_buf[0] == ack) { |
||||
break; |
||||
} else if (rx_buf[0] == SPI_NACK) { |
||||
SPILOG(LOGD, "SPI: got NACK, waiting for 0x%x", ack); |
||||
return SpiError::NACK; |
||||
} |
||||
|
||||
// handle timeout
|
||||
if (millis_since_boot() - start_millis > timeout) { |
||||
SPILOG(LOGW, "SPI: timed out waiting for ACK, waiting for 0x%x", ack); |
||||
return SpiError::ACK_TIMEOUT; |
||||
} |
||||
} |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
int PandaSpiHandle::lltransfer(spi_ioc_transfer &t) { |
||||
static const double err_prob = std::stod(util::getenv("SPI_ERR_PROB", "-1")); |
||||
|
||||
if (err_prob > 0) { |
||||
if ((static_cast<double>(rand()) / RAND_MAX) < err_prob) { |
||||
printf("transfer len error\n"); |
||||
t.len = rand() % SPI_BUF_SIZE; |
||||
} |
||||
if ((static_cast<double>(rand()) / RAND_MAX) < err_prob && t.tx_buf != (uint64_t)NULL) { |
||||
printf("corrupting TX\n"); |
||||
for (int i = 0; i < t.len; i++) { |
||||
if ((static_cast<double>(rand()) / RAND_MAX) > 0.9) { |
||||
((uint8_t*)t.tx_buf)[i] = (uint8_t)(rand() % 256); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
int ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &t); |
||||
|
||||
if (err_prob > 0) { |
||||
if ((static_cast<double>(rand()) / RAND_MAX) < err_prob && t.rx_buf != (uint64_t)NULL) { |
||||
printf("corrupting RX\n"); |
||||
for (int i = 0; i < t.len; i++) { |
||||
if ((static_cast<double>(rand()) / RAND_MAX) > 0.9) { |
||||
((uint8_t*)t.rx_buf)[i] = (uint8_t)(rand() % 256); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
return ret; |
||||
} |
||||
|
||||
int PandaSpiHandle::spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len, unsigned int timeout) { |
||||
int ret; |
||||
uint16_t rx_data_len; |
||||
LockEx lock(spi_fd, hw_lock); |
||||
|
||||
// needs to be less, since we need to have space for the checksum
|
||||
assert(tx_len < SPI_BUF_SIZE); |
||||
assert(max_rx_len < SPI_BUF_SIZE); |
||||
|
||||
xfer_count++; |
||||
header = { |
||||
.sync = SPI_SYNC, |
||||
.endpoint = endpoint, |
||||
.tx_len = tx_len, |
||||
.max_rx_len = max_rx_len |
||||
}; |
||||
|
||||
spi_ioc_transfer transfer = { |
||||
.tx_buf = (uint64_t)tx_buf, |
||||
.rx_buf = (uint64_t)rx_buf |
||||
}; |
||||
|
||||
// Send header
|
||||
memcpy(tx_buf, &header, sizeof(header)); |
||||
add_checksum(tx_buf, sizeof(header)); |
||||
transfer.len = sizeof(header) + 1; |
||||
ret = lltransfer(transfer); |
||||
if (ret < 0) { |
||||
SPILOG(LOGE, "SPI: failed to send header"); |
||||
goto fail; |
||||
} |
||||
|
||||
// Wait for (N)ACK
|
||||
ret = wait_for_ack(SPI_HACK, 0x11, timeout, 1); |
||||
if (ret < 0) { |
||||
goto fail; |
||||
} |
||||
|
||||
// Send data
|
||||
if (tx_data != NULL) { |
||||
memcpy(tx_buf, tx_data, tx_len); |
||||
} |
||||
add_checksum(tx_buf, tx_len); |
||||
transfer.len = tx_len + 1; |
||||
ret = lltransfer(transfer); |
||||
if (ret < 0) { |
||||
SPILOG(LOGE, "SPI: failed to send data"); |
||||
goto fail; |
||||
} |
||||
|
||||
// Wait for (N)ACK
|
||||
ret = wait_for_ack(SPI_DACK, 0x13, timeout, 3); |
||||
if (ret < 0) { |
||||
goto fail; |
||||
} |
||||
|
||||
// Read data
|
||||
rx_data_len = *(uint16_t *)(rx_buf+1); |
||||
if (rx_data_len >= SPI_BUF_SIZE) { |
||||
SPILOG(LOGE, "SPI: RX data len larger than buf size %d", rx_data_len); |
||||
goto fail; |
||||
} |
||||
|
||||
transfer.len = rx_data_len + 1; |
||||
transfer.rx_buf = (uint64_t)(rx_buf + 2 + 1); |
||||
ret = lltransfer(transfer); |
||||
if (ret < 0) { |
||||
SPILOG(LOGE, "SPI: failed to read rx data"); |
||||
goto fail; |
||||
} |
||||
if (!check_checksum(rx_buf, rx_data_len + 4)) { |
||||
SPILOG(LOGE, "SPI: bad checksum"); |
||||
goto fail; |
||||
} |
||||
|
||||
if (rx_data != NULL) { |
||||
memcpy(rx_data, rx_buf + 3, rx_data_len); |
||||
} |
||||
|
||||
return rx_data_len; |
||||
|
||||
fail: |
||||
// ensure slave is in a consistent state
|
||||
// and ready for the next transfer
|
||||
int nack_cnt = 0; |
||||
while (nack_cnt < 3) { |
||||
if (wait_for_ack(SPI_NACK, 0x14, 1, SPI_BUF_SIZE/2) == 0) { |
||||
nack_cnt += 1; |
||||
} else { |
||||
nack_cnt = 0; |
||||
} |
||||
} |
||||
|
||||
if (ret >= 0) ret = -1; |
||||
return ret; |
||||
} |
||||
#endif |
||||
@ -0,0 +1,126 @@ |
||||
import os |
||||
from cereal import car, log |
||||
import cereal.messaging as messaging |
||||
from panda.python import PANDA_BUS_CNT |
||||
|
||||
SPOOFING_STARTED = os.getenv("STARTED") == "1" |
||||
|
||||
LEC_ERROR_CODE = { |
||||
"No error": 0, "Stuff error": 1, "Form error": 2, "AckError": 3, |
||||
"Bit1Error": 4, "Bit0Error": 5, "CRCError": 6, "NoChange": 7, |
||||
} |
||||
|
||||
class PandaStateManager: |
||||
def __init__(self, pandas, hw_types): |
||||
self.pandas = pandas |
||||
self.hw_types = hw_types |
||||
|
||||
self.is_comma_three_red = ( |
||||
len(pandas) == 2 and |
||||
self.hw_types[0] == log.PandaState.PandaType.dos and |
||||
self.hw_types[1] == log.PandaState.PandaType.redPanda |
||||
) |
||||
|
||||
def process(self, engaged, pm) -> bool: |
||||
msg = messaging.new_message('pandaStates', len(self.pandas)) |
||||
msg.valid = True |
||||
panda_states = msg.pandaStates |
||||
ignition = False |
||||
|
||||
for i, panda in enumerate(self.pandas): |
||||
health = panda.health() or {} |
||||
|
||||
if SPOOFING_STARTED: |
||||
health['ignition_line'] = 1 |
||||
|
||||
# on comma three setups with a red panda, the dos can |
||||
# get false positive ignitions due to the harness box |
||||
# without a harness connector, so ignore it |
||||
if self.is_comma_three_red and i == 0: |
||||
health['ignition_line'] = 0 |
||||
|
||||
ignition |= bool(health['ignition_line']) or bool(health['ignition_can']) |
||||
ps = panda_states[i] |
||||
self._fill_state(ps, self.hw_types[i], health) |
||||
|
||||
# Fill can state |
||||
for j in range(PANDA_BUS_CNT): |
||||
can_health = panda.can_health(j) |
||||
can_state = ps.init(f'canState{j}') |
||||
self._fill_can_state(can_state, can_health) |
||||
|
||||
# Set faults |
||||
fault_bits = int(health.get('faults', 0)) |
||||
faults_list = [f for f in range(log.PandaState.FaultType.relayMalfunction, |
||||
log.PandaState.FaultType.heartbeatLoopWatchdog + 1) |
||||
if fault_bits & (1 << f)] |
||||
faults = ps.init('faults', len(faults_list)) |
||||
for idx, fault in enumerate(faults_list): |
||||
faults[idx] = fault |
||||
|
||||
for panda, ps in zip(self.pandas, panda_states, strict=False): |
||||
if ps.safetyModel == car.CarParams.SafetyModel.silent or ( |
||||
not ignition and ps.safetyModel != car.CarParams.SafetyModel.noOutput): |
||||
panda.set_safety_mode(car.CarParams.SafetyModel.noOutput) |
||||
|
||||
if ps.powerSaveEnabled != (not ignition): |
||||
panda.set_power_save(not ignition) |
||||
|
||||
panda.send_heartbeat(engaged) |
||||
|
||||
pm.send("pandaStates", msg) |
||||
return ignition |
||||
|
||||
def _fill_state(self, ps, hw_type, health): |
||||
ps.voltage = health['voltage'] |
||||
ps.current = health['current'] |
||||
ps.uptime = health['uptime'] |
||||
ps.safetyTxBlocked = health['safety_tx_blocked'] |
||||
ps.safetyRxInvalid = health['safety_rx_invalid'] |
||||
ps.ignitionLine = bool(health['ignition_line']) |
||||
ps.ignitionCan = bool(health['ignition_can']) |
||||
ps.controlsAllowed = bool(health['controls_allowed']) |
||||
ps.txBufferOverflow = health['tx_buffer_overflow'] |
||||
ps.rxBufferOverflow = health['rx_buffer_overflow'] |
||||
ps.pandaType = hw_type |
||||
ps.safetyModel = health['safety_mode'] |
||||
ps.safetyParam = health['safety_param'] |
||||
ps.faultStatus = health['fault_status'] |
||||
ps.powerSaveEnabled = bool(health['power_save_enabled']) |
||||
ps.heartbeatLost = bool(health['heartbeat_lost']) |
||||
ps.alternativeExperience = health['alternative_experience'] |
||||
ps.harnessStatus = health['car_harness_status'] |
||||
ps.interruptLoad = health['interrupt_load'] |
||||
ps.fanPower = health['fan_power'] |
||||
ps.fanStallCount = health['fan_stall_count'] |
||||
ps.safetyRxChecksInvalid = bool(health['safety_rx_checks_invalid']) |
||||
ps.spiErrorCount = health['spi_error_count'] |
||||
ps.sbu1Voltage = health['sbu1_voltage_mV'] / 1000.0 |
||||
ps.sbu2Voltage = health['sbu2_voltage_mV'] / 1000.0 |
||||
|
||||
def _fill_can_state(self, cs, can_health): |
||||
cs.busOff = bool(can_health['bus_off']) |
||||
cs.busOffCnt = can_health['bus_off_cnt'] |
||||
cs.errorWarning = bool(can_health['error_warning']) |
||||
cs.errorPassive = bool(can_health['error_passive']) |
||||
cs.lastError = LEC_ERROR_CODE[can_health['last_error']] |
||||
cs.lastStoredError = LEC_ERROR_CODE[can_health['last_stored_error']] |
||||
cs.lastDataError = LEC_ERROR_CODE[can_health['last_data_error']] |
||||
cs.lastDataStoredError = LEC_ERROR_CODE[can_health['last_data_stored_error']] |
||||
cs.receiveErrorCnt = can_health['receive_error_cnt'] |
||||
cs.transmitErrorCnt = can_health['transmit_error_cnt'] |
||||
cs.totalErrorCnt = can_health['total_error_cnt'] |
||||
cs.totalTxLostCnt = can_health['total_tx_lost_cnt'] |
||||
cs.totalRxLostCnt = can_health['total_rx_lost_cnt'] |
||||
cs.totalTxCnt = can_health['total_tx_cnt'] |
||||
cs.totalRxCnt = can_health['total_rx_cnt'] |
||||
cs.totalFwdCnt = can_health['total_fwd_cnt'] |
||||
cs.canSpeed = can_health['can_speed'] |
||||
cs.canDataSpeed = can_health['can_data_speed'] |
||||
cs.canfdEnabled = bool(can_health['canfd_enabled']) |
||||
cs.brsEnabled = bool(can_health['brs_enabled']) |
||||
cs.canfdNonIso = bool(can_health['canfd_non_iso']) |
||||
cs.irq0CallRate = can_health['irq0_call_rate'] |
||||
cs.irq1CallRate = can_health['irq1_call_rate'] |
||||
cs.irq2CallRate = can_health['irq2_call_rate'] |
||||
cs.canCoreResetCnt = can_health['can_core_reset_count'] |
||||
@ -1,135 +0,0 @@ |
||||
#define CATCH_CONFIG_MAIN |
||||
#define CATCH_CONFIG_ENABLE_BENCHMARKING |
||||
|
||||
#include "catch2/catch.hpp" |
||||
#include "cereal/messaging/messaging.h" |
||||
#include "common/util.h" |
||||
#include "selfdrive/pandad/panda.h" |
||||
|
||||
struct PandaTest : public Panda { |
||||
PandaTest(uint32_t bus_offset, int can_list_size, cereal::PandaState::PandaType hw_type); |
||||
void test_can_send(); |
||||
void test_can_recv(uint32_t chunk_size = 0); |
||||
void test_chunked_can_recv(); |
||||
|
||||
std::map<int, std::string> test_data; |
||||
int can_list_size = 0; |
||||
int total_pakets_size = 0; |
||||
MessageBuilder msg; |
||||
capnp::List<cereal::CanData>::Reader can_data_list; |
||||
}; |
||||
|
||||
PandaTest::PandaTest(uint32_t bus_offset_, int can_list_size, cereal::PandaState::PandaType hw_type) : can_list_size(can_list_size), Panda(bus_offset_) { |
||||
this->hw_type = hw_type; |
||||
int data_limit = ((hw_type == cereal::PandaState::PandaType::RED_PANDA) ? std::size(dlc_to_len) : 8); |
||||
// prepare test data
|
||||
for (int i = 0; i < data_limit; ++i) { |
||||
std::random_device rd; |
||||
std::independent_bits_engine<std::default_random_engine, CHAR_BIT, unsigned char> rbe(rd()); |
||||
|
||||
int data_len = dlc_to_len[i]; |
||||
std::string bytes(data_len, '\0'); |
||||
std::generate(bytes.begin(), bytes.end(), std::ref(rbe)); |
||||
test_data[data_len] = bytes; |
||||
} |
||||
|
||||
// generate can messages for this panda
|
||||
auto can_list = msg.initEvent().initSendcan(can_list_size); |
||||
for (uint8_t i = 0; i < can_list_size; ++i) { |
||||
auto can = can_list[i]; |
||||
uint32_t id = util::random_int(0, std::size(dlc_to_len) - 1); |
||||
const std::string &dat = test_data[dlc_to_len[id]]; |
||||
can.setAddress(i); |
||||
can.setSrc(util::random_int(0, 2) + bus_offset); |
||||
can.setDat(kj::ArrayPtr((uint8_t *)dat.data(), dat.size())); |
||||
total_pakets_size += sizeof(can_header) + dat.size(); |
||||
} |
||||
|
||||
can_data_list = can_list.asReader(); |
||||
INFO("test " << can_list_size << " packets, total size " << total_pakets_size); |
||||
} |
||||
|
||||
void PandaTest::test_can_send() { |
||||
std::vector<uint8_t> unpacked_data; |
||||
this->pack_can_buffer(can_data_list, [&](uint8_t *chunk, size_t size) { |
||||
unpacked_data.insert(unpacked_data.end(), chunk, &chunk[size]); |
||||
}); |
||||
REQUIRE(unpacked_data.size() == total_pakets_size); |
||||
|
||||
int cnt = 0; |
||||
INFO("test can message integrity"); |
||||
for (int pos = 0, pckt_len = 0; pos < unpacked_data.size(); pos += pckt_len) { |
||||
can_header header; |
||||
memcpy(&header, &unpacked_data[pos], sizeof(can_header)); |
||||
const uint8_t data_len = dlc_to_len[header.data_len_code]; |
||||
pckt_len = sizeof(can_header) + data_len; |
||||
|
||||
REQUIRE(header.addr == cnt); |
||||
REQUIRE(test_data.find(data_len) != test_data.end()); |
||||
const std::string &dat = test_data[data_len]; |
||||
REQUIRE(memcmp(dat.data(), &unpacked_data[pos + sizeof(can_header)], dat.size()) == 0); |
||||
++cnt; |
||||
} |
||||
REQUIRE(cnt == can_list_size); |
||||
} |
||||
|
||||
void PandaTest::test_can_recv(uint32_t rx_chunk_size) { |
||||
std::vector<can_frame> frames; |
||||
this->pack_can_buffer(can_data_list, [&](uint8_t *data, uint32_t size) { |
||||
if (rx_chunk_size == 0) { |
||||
REQUIRE(this->unpack_can_buffer(data, size, frames)); |
||||
} else { |
||||
this->receive_buffer_size = 0; |
||||
uint32_t pos = 0; |
||||
|
||||
while (pos < size) { |
||||
uint32_t chunk_size = std::min(rx_chunk_size, size - pos); |
||||
memcpy(&this->receive_buffer[this->receive_buffer_size], &data[pos], chunk_size); |
||||
this->receive_buffer_size += chunk_size; |
||||
pos += chunk_size; |
||||
|
||||
REQUIRE(this->unpack_can_buffer(this->receive_buffer, this->receive_buffer_size, frames)); |
||||
} |
||||
} |
||||
}); |
||||
|
||||
REQUIRE(frames.size() == can_list_size); |
||||
for (int i = 0; i < frames.size(); ++i) { |
||||
REQUIRE(frames[i].address == i); |
||||
REQUIRE(test_data.find(frames[i].dat.size()) != test_data.end()); |
||||
const std::string &dat = test_data[frames[i].dat.size()]; |
||||
REQUIRE(memcmp(dat.data(), frames[i].dat.data(), dat.size()) == 0); |
||||
} |
||||
} |
||||
|
||||
TEST_CASE("send/recv CAN 2.0 packets") { |
||||
auto bus_offset = GENERATE(0, 4); |
||||
auto can_list_size = GENERATE(1, 3, 5, 10, 30, 60, 100, 200); |
||||
PandaTest test(bus_offset, can_list_size, cereal::PandaState::PandaType::DOS); |
||||
|
||||
SECTION("can_send") { |
||||
test.test_can_send(); |
||||
} |
||||
SECTION("can_receive") { |
||||
test.test_can_recv(); |
||||
} |
||||
SECTION("chunked_can_receive") { |
||||
test.test_can_recv(0x40); |
||||
} |
||||
} |
||||
|
||||
TEST_CASE("send/recv CAN FD packets") { |
||||
auto bus_offset = GENERATE(0, 4); |
||||
auto can_list_size = GENERATE(1, 3, 5, 10, 30, 60, 100, 200); |
||||
PandaTest test(bus_offset, can_list_size, cereal::PandaState::PandaType::RED_PANDA); |
||||
|
||||
SECTION("can_send") { |
||||
test.test_can_send(); |
||||
} |
||||
SECTION("can_receive") { |
||||
test.test_can_recv(); |
||||
} |
||||
SECTION("chunked_can_receive") { |
||||
test.test_can_recv(0x40); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue