Pigeon abstraction layer (#1977)

* pigeon abstraction layer

* Fix string literals

* more generic pigeon class

* add TTYpigon

* nicer tty error handling

* close tty fd on pigeon delete

* pigeon receive return std::string

* use sizeof

* max receive size to prevent infinite loop

* remove namespace

* add unistd include for usleep

* fix is pigeon

* Handle tty error in opening

* fix printing binary strings with dump.py

* fix pigeon build on macos

* Handle errors seperately

Co-authored-by: Comma Device <device@comma.ai>
albatross
Willem Melching 2020-08-13 10:10:50 +02:00 committed by GitHub
parent 9bc0b350fd
commit 502cc665e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 443 additions and 99 deletions

View File

@ -91,6 +91,8 @@ selfdrive/boardd/boardd_setup.py
selfdrive/boardd/can_list_to_can_capnp.cc
selfdrive/boardd/panda.cc
selfdrive/boardd/panda.h
selfdrive/boardd/pigeon.cc
selfdrive/boardd/pigeon.h
selfdrive/car/__init__.py
selfdrive/car/car_helpers.py
@ -211,6 +213,8 @@ selfdrive/common/visionimg.cc
selfdrive/common/visionimg.h
selfdrive/common/spinner.c
selfdrive/common/spinner.h
selfdrive/common/gpio.cc
selfdrive/common/gpio.h
selfdrive/controls/__init__.py

View File

@ -1,6 +1,6 @@
Import('env', 'common', 'cereal', 'messaging', 'cython_dependencies')
env.Program('boardd', ['boardd.cc', 'panda.cc'], LIBS=['usb-1.0', common, cereal, messaging, 'pthread', 'zmq', 'capnp', 'kj'])
env.Program('boardd', ['boardd.cc', 'panda.cc', 'pigeon.cc'], LIBS=['usb-1.0', common, cereal, messaging, 'pthread', 'zmq', 'capnp', 'kj'])
env.Library('libcan_list_to_can_capnp', ['can_list_to_can_capnp.cc'])
env.Command(['boardd_api_impl.so', 'boardd_api_impl.cpp'],

View File

@ -28,6 +28,7 @@
#include "messaging.hpp"
#include "panda.h"
#include "pigeon.h"
#define MAX_IR_POWER 0.5f
@ -43,8 +44,6 @@ const float VBATT_START_CHARGING = 11.5;
const float VBATT_PAUSE_CHARGING = 11.0;
#endif
namespace {
Panda * panda = NULL;
std::atomic<bool> safety_setter_thread_running(false);
volatile sig_atomic_t do_exit = 0;
@ -468,82 +467,13 @@ void hardware_control_thread() {
}
}
#define pigeon_send(x) _pigeon_send(x, sizeof(x)-1)
void _pigeon_send(const char *dat, int len) {
unsigned char a[0x20+1];
a[0] = 1;
for (int i=0; i<len; i+=0x20) {
int ll = std::min(0x20, len-i);
memcpy(&a[1], &dat[i], ll);
panda->usb_bulk_write(2, a, ll+1);
}
}
void pigeon_set_power(int power) {
panda->usb_write(0xd9, power, 0);
}
void pigeon_set_baud(int baud) {
panda->usb_write(0xe2, 1, 0);
panda->usb_write(0xe4, 1, baud/300);
}
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(PubMaster &pm, unsigned char *dat, int alen) {
static void pigeon_publish_raw(PubMaster &pm, std::string dat) {
// 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);
auto ublox_raw = event.initUbloxRaw(dat.length());
memcpy(ublox_raw.begin(), dat.data(), dat.length());
pm.send("ubloxRaw", msg);
}
@ -555,37 +485,32 @@ void pigeon_thread() {
// ubloxRaw = 8042
PubMaster pm({"ubloxRaw"});
// run at ~100hz
unsigned char dat[0x1000];
uint64_t cnt = 0;
#ifdef QCOM2
Pigeon * pigeon = Pigeon::connect("/dev/ttyHS0");
#else
Pigeon * pigeon = Pigeon::connect(panda);
#endif
pigeon_init();
pigeon->init();
while (!do_exit && panda->connected) {
int alen = 0;
while (alen < 0xfc0) {
int len = panda->usb_read(0xe0, 1, 0, dat+alen, 0x40);
if (len <= 0) break;
//printf("got %d\n", len);
alen += len;
}
if (alen > 0) {
if (dat[0] == (char)0x00){
std::string recv = pigeon->receive();
if (recv.length() > 0) {
if (recv[0] == (char)0x00){
LOGW("received invalid ublox message, resetting panda GPS");
pigeon_init();
pigeon->init();
} else {
pigeon_publish_raw(pm, dat, alen);
pigeon_publish_raw(pm, recv);
}
}
// 10ms
// 10ms - 100 Hz
usleep(10*1000);
cnt++;
}
delete pigeon;
}
}
int main() {
int err;
@ -606,6 +531,8 @@ int main() {
fake_send = true;
}
panda_set_power(true);
while (!do_exit){
std::vector<std::thread> threads;
threads.push_back(std::thread(can_health_thread));

View File

@ -2,10 +2,33 @@
#include <cassert>
#include <iostream>
#include <unistd.h>
#include "common/swaglog.h"
#include "common/gpio.h"
#include "panda.h"
void panda_set_power(bool power){
#ifdef QCOM2
int err = 0;
err += gpio_init(GPIO_STM_RST_N, true);
err += gpio_init(GPIO_STM_BOOT0, true);
err += gpio_init(GPIO_HUB_RST_N, true);
err += gpio_set(GPIO_STM_RST_N, false);
// TODO: set hub somewhere else
err += gpio_set(GPIO_HUB_RST_N, true);
err += gpio_set(GPIO_STM_BOOT0, false);
usleep(100*1000); // 100 ms
err += gpio_set(GPIO_STM_RST_N, power);
assert(err == 0);
#endif
}
Panda::Panda(){
int err;
@ -39,8 +62,10 @@ Panda::Panda(){
is_pigeon =
(hw_type == cereal::HealthData::HwType::GREY_PANDA) ||
(hw_type == cereal::HealthData::HwType::BLACK_PANDA) ||
(hw_type == cereal::HealthData::HwType::UNO);
has_rtc = (hw_type == cereal::HealthData::HwType::UNO);
(hw_type == cereal::HealthData::HwType::UNO) ||
(hw_type == cereal::HealthData::HwType::DOS);
has_rtc = (hw_type == cereal::HealthData::HwType::UNO) ||
(hw_type == cereal::HealthData::HwType::DOS);
return;

View File

@ -34,6 +34,9 @@ struct __attribute__((packed)) health_t {
uint8_t power_save_enabled;
};
void panda_set_power(bool power);
class Panda {
private:
libusb_context *ctx = NULL;

View File

@ -0,0 +1,226 @@
#include <cassert>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>
#include "common/swaglog.h"
#include "common/gpio.h"
#include "pigeon.h"
// Termios on macos doesn't define all baud rate constants
#ifndef B460800
#define B460800 0010004
#endif
using namespace std::string_literals;
Pigeon * Pigeon::connect(Panda * p){
PandaPigeon * pigeon = new PandaPigeon();
pigeon->connect(p);
return pigeon;
}
Pigeon * Pigeon::connect(const char * tty){
TTYPigeon * pigeon = new TTYPigeon();
pigeon->connect(tty);
return pigeon;
}
void Pigeon::init() {
usleep(1000*1000);
LOGW("panda GPS start");
// power off pigeon
set_power(0);
usleep(100*1000);
// 9600 baud at init
set_baud(9600);
// power on pigeon
set_power(1);
usleep(500*1000);
// baud rate upping
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"s);
usleep(100*1000);
// set baud rate to 460800
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
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"s);
send("\xB5\x62\x06\x3E\x00\x00\x44\xD2"s);
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"s);
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"s);
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"s);
send("\xB5\x62\x06\x00\x00\x00\x06\x18"s);
send("\xB5\x62\x06\x00\x01\x00\x01\x08\x22"s);
send("\xB5\x62\x06\x00\x01\x00\x02\x09\x23"s);
send("\xB5\x62\x06\x00\x01\x00\x03\x0A\x24"s);
send("\xB5\x62\x06\x08\x06\x00\x64\x00\x01\x00\x00\x00\x79\x10"s);
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"s);
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"s);
send("\xB5\x62\x06\x24\x00\x00\x2A\x84"s);
send("\xB5\x62\x06\x23\x00\x00\x29\x81"s);
send("\xB5\x62\x06\x1E\x00\x00\x24\x72"s);
send("\xB5\x62\x06\x01\x03\x00\x01\x07\x01\x13\x51"s);
send("\xB5\x62\x06\x01\x03\x00\x02\x15\x01\x22\x70"s);
send("\xB5\x62\x06\x01\x03\x00\x02\x13\x01\x20\x6C"s);
send("\xB5\x62\x06\x01\x03\x00\x0A\x09\x01\x1E\x70"s);
LOGW("panda GPS on");
}
void PandaPigeon::connect(Panda * p) {
panda = p;
}
void PandaPigeon::set_baud(int baud) {
panda->usb_write(0xe2, 1, 0);
panda->usb_write(0xe4, 1, baud/300);
}
void PandaPigeon::send(std::string s) {
int len = s.length();
const char * dat = s.data();
unsigned char a[0x20+1];
a[0] = 1;
for (int i=0; i<len; i+=0x20) {
int ll = std::min(0x20, len-i);
memcpy(&a[1], &dat[i], ll);
panda->usb_bulk_write(2, a, ll+1);
}
}
std::string PandaPigeon::receive() {
std::string r;
while (true){
unsigned char dat[0x40];
int len = panda->usb_read(0xe0, 1, 0, dat, sizeof(dat));
if (len <= 0 || r.length() > 0x1000) break;
r.append((char*)dat, len);
}
return r;
}
void PandaPigeon::set_power(bool power) {
panda->usb_write(0xd9, power, 0);
}
PandaPigeon::~PandaPigeon(){
}
void handle_tty_issue(int err, const char func[]) {
LOGE_100("tty error %d \"%s\" in %s", err, strerror(err), func);
}
void TTYPigeon::connect(const char * tty) {
pigeon_tty_fd = open(tty, O_RDWR);
if (pigeon_tty_fd < 0){
handle_tty_issue(errno, __func__);
assert(pigeon_tty_fd >= 0);
}
assert(tcgetattr(pigeon_tty_fd, &pigeon_tty) == 0);
// configure tty
pigeon_tty.c_cflag &= ~PARENB; // disable parity
pigeon_tty.c_cflag &= ~CSTOPB; // single stop bit
pigeon_tty.c_cflag |= CS8; // 8 bits per byte
pigeon_tty.c_cflag &= ~CRTSCTS; // no RTS/CTS flow control
pigeon_tty.c_cflag |= CREAD | CLOCAL; // turn on READ & ignore ctrl lines
pigeon_tty.c_lflag &= ~ICANON; // disable canonical mode
pigeon_tty.c_lflag &= ~ISIG; // disable interpretation of INTR, QUIT and SUSP
pigeon_tty.c_iflag &= ~(IXON | IXOFF | IXANY); // turn off software flow ctrl
pigeon_tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL); // disable any special handling of received bytes
pigeon_tty.c_oflag &= ~OPOST; // prevent special interpretation of output bytes
pigeon_tty.c_oflag &= ~ONLCR; // prevent conversion of newline to carriage return/line feed
// configure blocking behavior
pigeon_tty.c_cc[VMIN] = 0; // min amount of characters returned
pigeon_tty.c_cc[VTIME] = 0; // max blocking time in s/10 (0=inf)
assert(tcsetattr(pigeon_tty_fd, TCSANOW, &pigeon_tty) == 0);
}
void TTYPigeon::set_baud(int baud){
speed_t baud_const = 0;
switch(baud){
case 9600:
baud_const = B9600;
break;
case 460800:
baud_const = B460800;
break;
default:
assert(false);
}
// make sure everything is tx'ed before changing baud
assert(tcdrain(pigeon_tty_fd) == 0);
// change baud
assert(tcgetattr(pigeon_tty_fd, &pigeon_tty) == 0);
assert(cfsetspeed(&pigeon_tty, baud_const) == 0);
assert(tcsetattr(pigeon_tty_fd, TCSANOW, &pigeon_tty) == 0);
// flush
assert(tcflush(pigeon_tty_fd, TCIOFLUSH) == 0);
}
void TTYPigeon::send(std::string s) {
int len = s.length();
const char * dat = s.data();
int err = write(pigeon_tty_fd, dat, len);
if(err < 0) { handle_tty_issue(err, __func__); }
err = tcdrain(pigeon_tty_fd);
if(err < 0) { handle_tty_issue(err, __func__); }
}
std::string TTYPigeon::receive() {
std::string r;
while (true){
char dat[0x40];
int len = read(pigeon_tty_fd, dat, sizeof(dat));
if(len < 0) {
handle_tty_issue(len, __func__);
} else if (len == 0 || r.length() > 0x1000){
break;
} else {
r.append(dat, len);
}
}
return r;
}
void TTYPigeon::set_power(bool power){
#ifdef QCOM2
int err = 0;
err += gpio_init(GPIO_UBLOX_RST_N, true);
err += gpio_init(GPIO_UBLOX_SAFEBOOT_N, true);
err += gpio_init(GPIO_UBLOX_PWR_EN, true);
err += gpio_set(GPIO_UBLOX_RST_N, power);
err += gpio_set(GPIO_UBLOX_SAFEBOOT_N, power);
err += gpio_set(GPIO_UBLOX_PWR_EN, power);
assert(err == 0);
#endif
}
TTYPigeon::~TTYPigeon(){
close(pigeon_tty_fd);
}

View File

@ -0,0 +1,43 @@
#pragma once
#include <string>
#include <termios.h>
#include "panda.h"
class Pigeon {
public:
static Pigeon* connect(Panda * p);
static Pigeon* connect(const char * tty);
virtual ~Pigeon(){};
void init();
virtual void set_baud(int baud) = 0;
virtual void send(std::string s) = 0;
virtual std::string receive() = 0;
virtual void set_power(bool power) = 0;
};
class PandaPigeon : public Pigeon {
Panda * panda = NULL;
public:
~PandaPigeon();
void connect(Panda * p);
void set_baud(int baud);
void send(std::string s);
std::string receive();
void set_power(bool power);
};
class TTYPigeon : public Pigeon {
int pigeon_tty_fd = -1;
struct termios pigeon_tty;
public:
~TTYPigeon();
void connect(const char* tty);
void set_baud(int baud);
void send(std::string s);
std::string receive();
void set_power(bool power);
};

View File

@ -5,7 +5,7 @@ if SHARED:
else:
fxn = env.Library
_common = fxn('common', ['params.cc', 'swaglog.cc', 'util.c', 'cqueue.c'], LIBS="json11")
_common = fxn('common', ['params.cc', 'swaglog.cc', 'util.c', 'cqueue.c', 'gpio.cc'], LIBS="json11")
_visionipc = fxn('visionipc', ['visionipc.c', 'ipc.c'])
files = [
@ -42,4 +42,3 @@ else:
_gpucommon = fxn('gpucommon', files, CPPDEFINES=defines, LIBS=_gpu_libs)
Export('_common', '_visionipc', '_gpucommon', '_gpu_libs')

View File

@ -0,0 +1,75 @@
#include "gpio.h"
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
// We assume that all pins have already been exported on boot,
// and that we have permission to write to them.
int gpio_init(int pin_nr, bool output){
int ret = 0;
int fd, tmp;
char pin_dir_path[50];
int pin_dir_path_len = snprintf(pin_dir_path, sizeof(pin_dir_path),
"/sys/class/gpio/gpio%d/direction", pin_nr);
if(pin_dir_path_len <= 0){
ret = -1;
goto cleanup;
}
fd = open(pin_dir_path, O_WRONLY);
if(fd == -1){
ret = -1;
goto cleanup;
}
if(output){
tmp = write(fd, "out", 3);
if(tmp != 3){
ret = -1;
goto cleanup;
}
} else {
tmp = write(fd, "in", 2);
if(tmp != 2){
ret = -1;
goto cleanup;
}
}
cleanup:
if(fd >= 0){
close(fd);
}
return ret;
}
int gpio_set(int pin_nr, bool high){
int ret = 0;
int fd, tmp;
char pin_val_path[50];
int pin_val_path_len = snprintf(pin_val_path, sizeof(pin_val_path),
"/sys/class/gpio/gpio%d/value", pin_nr);
if(pin_val_path_len <= 0){
ret = -1;
goto cleanup;
}
fd = open(pin_val_path, O_WRONLY);
if(fd == -1){
ret = -1;
goto cleanup;
}
tmp = write(fd, high ? "1" : "0", 1);
if(tmp != 1){
ret = -1;
goto cleanup;
}
cleanup:
if(fd >= 0){
close(fd);
}
return ret;
}

View File

@ -0,0 +1,35 @@
#ifndef GPIO_H
#define GPIO_H
#include <stdbool.h>
// Pin definitions
#ifdef QCOM2
#define GPIO_HUB_RST_N 30
#define GPIO_UBLOX_RST_N 32
#define GPIO_UBLOX_SAFEBOOT_N 33
#define GPIO_UBLOX_PWR_EN 34
#define GPIO_STM_RST_N 124
#define GPIO_STM_BOOT0 134
#else
#define GPIO_HUB_RST_N 0
#define GPIO_UBLOX_RST_N 0
#define GPIO_UBLOX_SAFEBOOT_N 0
#define GPIO_UBLOX_PWR_EN 0
#define GPIO_STM_RST_N 0
#define GPIO_STM_BOOT0 0
#endif
#ifdef __cplusplus
extern "C" {
#endif
int gpio_init(int pin_nr, bool output);
int gpio_set(int pin_nr, bool high);
#ifdef __cplusplus
} // extern "C"
#endif
#endif

View File

@ -61,4 +61,11 @@ if __name__ == "__main__":
print("{} = {}".format(".".join(value), item))
print("")
else:
print(evt)
try:
print(evt)
except UnicodeDecodeError:
w = evt.which()
s = f"( logMonoTime {evt.logMonoTime} \n {w} = "
s += str(evt.__getattr__(w))
s += f"\n valid = {evt.valid} )"
print(s)