Merge remote-tracking branch 'refs/remotes/openpilot/master' into small-sync

# Conflicts:
#	cereal
#	opendbc
#	panda
#	selfdrive/car/__init__.py
#	selfdrive/monitoring/dmonitoringd.py
This commit is contained in:
Miguel Fernandez
2024-05-09 15:13:00 +02:00
88 changed files with 1032 additions and 1551 deletions
+1 -1
View File
@@ -26,7 +26,7 @@ cabana_env.Command(assets, assets_src, f"rcc $SOURCES -o $TARGET")
cabana_env.Depends(assets, Glob('/assets/*', exclude=[assets, assets_src, "assets/assets.o"]))
cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'streams/socketcanstream.cc', 'streams/pandastream.cc', 'streams/devicestream.cc', 'streams/livestream.cc', 'streams/abstractstream.cc', 'streams/replaystream.cc', 'binaryview.cc', 'historylog.cc', 'videowidget.cc', 'signalview.cc',
'dbc/dbc.cc', 'dbc/dbcfile.cc', 'dbc/dbcmanager.cc',
'streams/routes.cc', 'dbc/dbc.cc', 'dbc/dbcfile.cc', 'dbc/dbcmanager.cc',
'utils/export.cc', 'utils/util.cc',
'chart/chartswidget.cc', 'chart/chart.cc', 'chart/signalselector.cc', 'chart/tiplabel.cc', 'chart/sparkline.cc',
'commands.cc', 'messageswidget.cc', 'streamselector.cc', 'settings.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc', 'tools/findsignal.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks)
+10 -22
View File
@@ -3,7 +3,6 @@
#include "selfdrive/ui/qt/util.h"
#include "tools/cabana/mainwin.h"
#include "tools/cabana/streamselector.h"
#include "tools/cabana/streams/devicestream.h"
#include "tools/cabana/streams/pandastream.h"
#include "tools/cabana/streams/replaystream.h"
@@ -82,28 +81,17 @@ int main(int argc, char *argv[]) {
}
}
int ret = 0;
{
MainWindow w;
QTimer::singleShot(0, [&]() {
if (!stream) {
StreamSelector dlg(&stream);
dlg.exec();
dbc_file = dlg.dbcFile();
}
if (!stream) {
stream = new DummyStream(&app);
}
stream->start();
if (!dbc_file.isEmpty()) {
w.loadFile(dbc_file);
}
w.show();
});
ret = app.exec();
MainWindow w;
if (stream) {
stream->start();
if (!dbc_file.isEmpty()) {
w.loadFile(dbc_file);
}
} else {
w.openStream();
}
w.show();
int ret = app.exec();
delete can;
return ret;
}
+2 -1
View File
@@ -22,6 +22,7 @@
// ChartAxisElement's padding is 4 (https://codebrowser.dev/qt5/qtcharts/src/charts/axis/chartaxiselement_p.h.html)
const int AXIS_X_TOP_MARGIN = 4;
const double MIN_ZOOM_SECONDS = 0.01; // 10ms
// Define a small value of epsilon to compare double values
const float EPSILON = 0.000001;
static inline bool xLessThan(const QPointF &p, float x) { return p.x() < (x - EPSILON); }
@@ -511,7 +512,7 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) {
if (rubber->width() <= 0) {
// no rubber dragged, seek to mouse position
can->seekTo(min);
} else if (rubber->width() > 10 && (max - min) > 0.01) { // Minimum range is 10 milliseconds.
} else if (rubber->width() > 10 && (max - min) > MIN_ZOOM_SECONDS) {
charts_widget->zoom_undo_stack->push(new ZoomCommand(charts_widget, {min, max}));
} else {
viewport()->update();
+2 -1
View File
@@ -197,7 +197,8 @@ void ChartsWidget::updateState() {
display_range.second = display_range.first + max_chart_range;
} else if (cur_sec < (zoomed_range.first - 0.1) || cur_sec >= zoomed_range.second) {
// loop in zoomed range
can->seekTo(zoomed_range.first);
QTimer::singleShot(0, [ts = zoomed_range.first]() { can->seekTo(ts);});
return;
}
const auto &range = is_zoomed ? zoomed_range : display_range;
+3
View File
@@ -262,6 +262,9 @@ void MainWindow::openStream() {
}
stream->start();
statusBar()->showMessage(tr("Route %1 loaded").arg(can->routeName()), 2000);
} else if (!can) {
stream = new DummyStream(this);
stream->start();
}
}
+4 -1
View File
@@ -138,13 +138,16 @@ void AbstractStream::updateLastMsgsTo(double sec) {
auto prev = std::prev(it);
double ts = (*prev)->mono_time / 1e9 - routeStartTime();
auto &m = msgs[id];
double freq = 0;
// Keep suppressed bits.
if (auto old_m = messages_.find(id); old_m != messages_.end()) {
freq = old_m->second.freq;
m.last_changes.reserve(old_m->second.last_changes.size());
std::transform(old_m->second.last_changes.cbegin(), old_m->second.last_changes.cend(),
std::back_inserter(m.last_changes),
[](const auto &change) { return CanData::ByteLastChange{.suppressed = change.suppressed}; });
}
m.compute(id, (*prev)->dat, (*prev)->size, ts, getSpeed(), {});
m.compute(id, (*prev)->dat, (*prev)->size, ts, getSpeed(), {}, freq);
m.count = std::distance(ev.begin(), prev) + 1;
}
}
+2 -1
View File
@@ -90,6 +90,7 @@ public:
signals:
void paused();
void resume();
void seekingTo(double sec);
void seekedTo(double sec);
void streamStarted();
void eventsMerged(const MessageEventsMap &events_map);
@@ -107,6 +108,7 @@ protected:
uint64_t lastEventMonoTime() const { return lastest_event_ts; }
std::vector<const CanEvent *> all_events_;
double current_sec_ = 0;
uint64_t lastest_event_ts = 0;
private:
@@ -114,7 +116,6 @@ private:
void updateLastMsgsTo(double sec);
void updateMasks();
double current_sec_ = 0;
MessageEventsMap events_;
std::unordered_map<MessageId, CanData> last_msgs;
std::unique_ptr<MonotonicBuffer> event_buffer_;
+20 -58
View File
@@ -6,39 +6,12 @@
#include <QMessageBox>
#include <QPushButton>
#include <QThread>
#include <QVBoxLayout>
// TODO: remove clearLayout
static void clearLayout(QLayout* layout) {
while (layout->count() > 0) {
QLayoutItem* item = layout->takeAt(0);
if (QWidget* widget = item->widget()) {
widget->deleteLater();
}
if (QLayout* childLayout = item->layout()) {
clearLayout(childLayout);
}
delete item;
}
}
PandaStream::PandaStream(QObject *parent, PandaStreamConfig config_) : config(config_), LiveStream(parent) {
if (config.serial.isEmpty()) {
auto serials = Panda::list();
if (serials.size() == 0) {
throw std::runtime_error("No panda found");
}
config.serial = QString::fromStdString(serials[0]);
}
qDebug() << "Connecting to panda with serial" << config.serial;
if (!connect()) {
throw std::runtime_error("Failed to connect to panda");
}
}
PandaStream::PandaStream(QObject *parent, PandaStreamConfig config_) : config(config_), LiveStream(parent) {}
bool PandaStream::connect() {
try {
qDebug() << "Connecting to panda with serial" << config.serial;
panda.reset(new Panda(config.serial.toStdString()));
config.bus_config.resize(3);
qDebug() << "Connected";
@@ -47,7 +20,6 @@ bool PandaStream::connect() {
}
panda->set_safety_model(cereal::CarParams::SafetyModel::SILENT);
for (int bus = 0; bus < config.bus_config.size(); bus++) {
panda->set_can_speed_kbps(bus, config.bus_config[bus].can_speed_kbps);
@@ -60,7 +32,6 @@ bool PandaStream::connect() {
panda->set_data_speed_kbps(bus, 10);
}
}
}
return true;
}
@@ -108,26 +79,14 @@ AbstractOpenStreamWidget *PandaStream::widget(AbstractStream **stream) {
// OpenPandaWidget
OpenPandaWidget::OpenPandaWidget(AbstractStream **stream) : AbstractOpenStreamWidget(stream) {
QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->addStretch(1);
QFormLayout *form_layout = new QFormLayout();
form_layout = new QFormLayout(this);
QHBoxLayout *serial_layout = new QHBoxLayout();
serial_edit = new QComboBox();
serial_edit->setFixedWidth(300);
serial_layout->addWidget(serial_edit);
serial_layout->addWidget(serial_edit = new QComboBox());
QPushButton *refresh = new QPushButton(tr("Refresh"));
refresh->setFixedWidth(100);
refresh->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
serial_layout->addWidget(refresh);
form_layout->addRow(tr("Serial"), serial_layout);
main_layout->addLayout(form_layout);
config_layout = new QFormLayout();
main_layout->addLayout(config_layout);
main_layout->addStretch(1);
QObject::connect(refresh, &QPushButton::clicked, this, &OpenPandaWidget::refreshSerials);
QObject::connect(serial_edit, &QComboBox::currentTextChanged, this, &OpenPandaWidget::buildConfigForm);
@@ -145,15 +104,16 @@ void OpenPandaWidget::refreshSerials() {
}
void OpenPandaWidget::buildConfigForm() {
clearLayout(config_layout);
QString serial = serial_edit->currentText();
for (int i = form_layout->rowCount() - 1; i > 0; --i) {
form_layout->removeRow(i);
}
QString serial = serial_edit->currentText();
bool has_fd = false;
bool has_panda = !serial.isEmpty();
if (has_panda) {
try {
Panda panda = Panda(serial.toStdString());
Panda panda(serial.toStdString());
has_fd = (panda.hw_type == cereal::PandaState::PandaType::RED_PANDA) || (panda.hw_type == cereal::PandaState::PandaType::RED_PANDA_V2);
} catch (const std::exception& e) {
has_panda = false;
@@ -201,20 +161,22 @@ void OpenPandaWidget::buildConfigForm() {
QObject::connect(enable_fd, &QCheckBox::stateChanged, [=](int state) {config.bus_config[i].can_fd = (bool)state;});
}
config_layout->addRow(tr("Bus %1:").arg(i), bus_layout);
form_layout->addRow(tr("Bus %1:").arg(i), bus_layout);
}
} else {
config.serial = "";
config_layout->addWidget(new QLabel(tr("No panda found")));
form_layout->addWidget(new QLabel(tr("No panda found")));
}
}
bool OpenPandaWidget::open() {
try {
*stream = new PandaStream(qApp, config);
} catch (std::exception &e) {
QMessageBox::warning(nullptr, tr("Warning"), tr("Failed to connect to panda: '%1'").arg(e.what()));
return false;
if (!config.serial.isEmpty()) {
auto panda_stream = std::make_unique<PandaStream>(qApp, config);
if (panda_stream->connect()) {
*stream = panda_stream.release();
return true;
}
}
return true;
QMessageBox::warning(nullptr, tr("Warning"), tr("Failed to connect to panda"));
return false;
}
+2 -2
View File
@@ -21,6 +21,7 @@ class PandaStream : public LiveStream {
Q_OBJECT
public:
PandaStream(QObject *parent, PandaStreamConfig config_ = {});
bool connect();
static AbstractOpenStreamWidget *widget(AbstractStream **stream);
inline QString routeName() const override {
return QString("Live Streaming From Panda %1").arg(config.serial);
@@ -28,7 +29,6 @@ public:
protected:
void streamThread() override;
bool connect();
std::unique_ptr<Panda> panda;
PandaStreamConfig config = {};
@@ -47,6 +47,6 @@ private:
void buildConfigForm();
QComboBox *serial_edit;
QFormLayout *config_layout;
QFormLayout *form_layout;
PandaStreamConfig config = {};
};
+24 -6
View File
@@ -7,6 +7,7 @@
#include <QPushButton>
#include "common/timing.h"
#include "tools/cabana/streams/routes.h"
ReplayStream::ReplayStream(QObject *parent) : AbstractStream(parent) {
unsetenv("ZMQ");
@@ -84,6 +85,16 @@ bool ReplayStream::eventFilter(const Event *event) {
return true;
}
void ReplayStream::seekTo(double ts) {
// Update timestamp and notify receivers of the time change.
current_sec_ = ts;
std::set<MessageId> new_msgs;
msgsReceived(&new_msgs, false);
// Seek to the specified timestamp
replay->seekTo(std::max(double(0), ts), false);
}
void ReplayStream::pause(bool pause) {
replay->pause(pause);
emit(pause ? paused() : resume());
@@ -97,29 +108,36 @@ AbstractOpenStreamWidget *ReplayStream::widget(AbstractStream **stream) {
// OpenReplayWidget
OpenReplayWidget::OpenReplayWidget(AbstractStream **stream) : AbstractOpenStreamWidget(stream) {
// TODO: get route list from api.comma.ai
QGridLayout *grid_layout = new QGridLayout(this);
grid_layout->addWidget(new QLabel(tr("Route")), 0, 0);
grid_layout->addWidget(route_edit = new QLineEdit(this), 0, 1);
route_edit->setPlaceholderText(tr("Enter remote route name or click browse to select a local route"));
auto file_btn = new QPushButton(tr("Browse..."), this);
grid_layout->addWidget(file_btn, 0, 2);
route_edit->setPlaceholderText(tr("Enter route name or browse for local/remote route"));
auto browse_remote_btn = new QPushButton(tr("Remote route..."), this);
grid_layout->addWidget(browse_remote_btn, 0, 2);
auto browse_local_btn = new QPushButton(tr("Local route..."), this);
grid_layout->addWidget(browse_local_btn, 0, 3);
grid_layout->addWidget(new QLabel(tr("Camera")), 1, 0);
QHBoxLayout *camera_layout = new QHBoxLayout();
for (auto c : {tr("Road camera"), tr("Driver camera"), tr("Wide road camera")})
camera_layout->addWidget(cameras.emplace_back(new QCheckBox(c, this)));
cameras[0]->setChecked(true);
camera_layout->addStretch(1);
grid_layout->addItem(camera_layout, 1, 1);
setMinimumWidth(550);
QObject::connect(file_btn, &QPushButton::clicked, [=]() {
QObject::connect(browse_local_btn, &QPushButton::clicked, [=]() {
QString dir = QFileDialog::getExistingDirectory(this, tr("Open Local Route"), settings.last_route_dir);
if (!dir.isEmpty()) {
route_edit->setText(dir);
settings.last_route_dir = QFileInfo(dir).absolutePath();
}
});
QObject::connect(browse_remote_btn, &QPushButton::clicked, [this]() {
RoutesDialog route_dlg(this);
if (route_dlg.exec()) {
route_edit->setText(route_dlg.route());
}
});
}
bool OpenReplayWidget::open() {
+1 -1
View File
@@ -18,7 +18,7 @@ public:
void start() override;
bool loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags = REPLAY_FLAG_NONE);
bool eventFilter(const Event *event);
void seekTo(double ts) override { replay->seekTo(std::max(double(0), ts), false); }
void seekTo(double ts) override;
bool liveStreaming() const override { return false; }
inline QString routeName() const override { return replay->route()->name(); }
inline QString carFingerprint() const override { return replay->carFingerprint().c_str(); }
+123
View File
@@ -0,0 +1,123 @@
#include "tools/cabana/streams/routes.h"
#include <QDateTime>
#include <QDialogButtonBox>
#include <QFormLayout>
#include <QJsonArray>
#include <QJsonDocument>
#include <QListWidget>
#include <QMessageBox>
#include <QPainter>
#include "system/hardware/hw.h"
// The RouteListWidget class extends QListWidget to display a custom message when empty
class RouteListWidget : public QListWidget {
public:
RouteListWidget(QWidget *parent = nullptr) : QListWidget(parent) {}
void setEmptyText(const QString &text) {
empty_text_ = text;
viewport()->update();
}
void paintEvent(QPaintEvent *event) override {
QListWidget::paintEvent(event);
if (count() == 0) {
QPainter painter(viewport());
painter.drawText(viewport()->rect(), Qt::AlignCenter, empty_text_);
}
}
QString empty_text_ = tr("No items");
};
RoutesDialog::RoutesDialog(QWidget *parent) : QDialog(parent) {
setWindowTitle(tr("Remote routes"));
QFormLayout *layout = new QFormLayout(this);
layout->addRow(tr("Device"), device_list_ = new QComboBox(this));
layout->addRow(tr("Duration"), period_selector_ = new QComboBox(this));
layout->addRow(route_list_ = new RouteListWidget(this));
auto button_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
layout->addRow(button_box);
device_list_->addItem(tr("Loading..."));
// Populate period selector with predefined durations
period_selector_->addItem(tr("Last week"), 7);
period_selector_->addItem(tr("Last 2 weeks"), 14);
period_selector_->addItem(tr("Last month"), 30);
period_selector_->addItem(tr("Last 6 months"), 180);
// Connect signals and slots
connect(device_list_, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RoutesDialog::fetchRoutes);
connect(period_selector_, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &RoutesDialog::fetchRoutes);
connect(route_list_, &QListWidget::itemDoubleClicked, this, &QDialog::accept);
QObject::connect(button_box, &QDialogButtonBox::accepted, this, &QDialog::accept);
QObject::connect(button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
// Send request to fetch devices
HttpRequest *http = new HttpRequest(this, !Hardware::PC());
QObject::connect(http, &HttpRequest::requestDone, this, &RoutesDialog::parseDeviceList);
http->sendRequest(CommaApi::BASE_URL + "/v1/me/devices/");
}
void RoutesDialog::parseDeviceList(const QString &json, bool success, QNetworkReply::NetworkError err) {
if (success) {
device_list_->clear();
auto devices = QJsonDocument::fromJson(json.toUtf8()).array();
for (const QJsonValue &device : devices) {
QString dongle_id = device["dongle_id"].toString();
device_list_->addItem(dongle_id, dongle_id);
}
} else {
bool unauthorized = (err == QNetworkReply::ContentAccessDenied || err == QNetworkReply::AuthenticationRequiredError);
QMessageBox::warning(this, tr("Error"), unauthorized ? tr("Unauthorized, Authenticate with tools/lib/auth.py") : tr("Network error"));
reject();
}
sender()->deleteLater();
}
void RoutesDialog::fetchRoutes() {
if (device_list_->currentIndex() == -1 || device_list_->currentData().isNull())
return;
route_list_->clear();
route_list_->setEmptyText(tr("Loading..."));
HttpRequest *http = new HttpRequest(this, !Hardware::PC());
QObject::connect(http, &HttpRequest::requestDone, this, &RoutesDialog::parseRouteList);
// Construct URL with selected device and date range
auto dongle_id = device_list_->currentData().toString();
QDateTime current = QDateTime::currentDateTime();
QString url = QString("%1/v1/devices/%2/routes_segments?start=%3&end=%4")
.arg(CommaApi::BASE_URL).arg(dongle_id)
.arg(current.addDays(-(period_selector_->currentData().toInt())).toMSecsSinceEpoch())
.arg(current.toMSecsSinceEpoch());
http->sendRequest(url);
}
void RoutesDialog::parseRouteList(const QString &json, bool success, QNetworkReply::NetworkError err) {
if (success) {
for (const QJsonValue &route : QJsonDocument::fromJson(json.toUtf8()).array()) {
uint64_t start_time = route["start_time_utc_millis"].toDouble();
uint64_t end_time = route["end_time_utc_millis"].toDouble();
auto datetime = QDateTime::fromMSecsSinceEpoch(start_time);
auto item = new QListWidgetItem(QString("%1 %2min").arg(datetime.toString()).arg((end_time - start_time) / (1000 * 60)));
item->setData(Qt::UserRole, route["fullname"].toString());
route_list_->addItem(item);
}
// Select first route if available
if (route_list_->count() > 0) route_list_->setCurrentRow(0);
} else {
QMessageBox::warning(this, tr("Error"), tr("Failed to fetch routes. Check your network connection."));
reject();
}
route_list_->setEmptyText(tr("No items"));
sender()->deleteLater();
}
void RoutesDialog::accept() {
if (auto current_item = route_list_->currentItem()) {
route_ = current_item->data(Qt::UserRole).toString();
}
QDialog::accept();
}
+26
View File
@@ -0,0 +1,26 @@
#pragma once
#include <QComboBox>
#include <QDialog>
#include "selfdrive/ui/qt/api.h"
class RouteListWidget;
class RoutesDialog : public QDialog {
Q_OBJECT
public:
RoutesDialog(QWidget *parent);
QString route() const { return route_; }
protected:
void accept() override;
void parseDeviceList(const QString &json, bool success, QNetworkReply::NetworkError err);
void parseRouteList(const QString &json, bool success, QNetworkReply::NetworkError err);
void fetchRoutes();
QComboBox *device_list_;
QComboBox *period_selector_;
RouteListWidget *route_list_;
QString route_;
};
+4 -12
View File
@@ -13,12 +13,8 @@
StreamSelector::StreamSelector(AbstractStream **stream, QWidget *parent) : QDialog(parent) {
setWindowTitle(tr("Open stream"));
QVBoxLayout *main_layout = new QVBoxLayout(this);
QWidget *w = new QWidget(this);
QVBoxLayout *layout = new QVBoxLayout(w);
QVBoxLayout *layout = new QVBoxLayout(this);
tab = new QTabWidget(this);
tab->setTabBarAutoHide(true);
layout->addWidget(tab);
QHBoxLayout *dbc_layout = new QHBoxLayout();
@@ -35,9 +31,8 @@ StreamSelector::StreamSelector(AbstractStream **stream, QWidget *parent) : QDial
line->setFrameStyle(QFrame::HLine | QFrame::Sunken);
layout->addWidget(line);
main_layout->addWidget(w);
auto btn_box = new QDialogButtonBox(QDialogButtonBox::Open | QDialogButtonBox::Cancel);
main_layout->addWidget(btn_box);
layout->addWidget(btn_box);
addStreamWidget(ReplayStream::widget(stream));
addStreamWidget(PandaStream::widget(stream));
@@ -48,14 +43,11 @@ StreamSelector::StreamSelector(AbstractStream **stream, QWidget *parent) : QDial
QObject::connect(btn_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
QObject::connect(btn_box, &QDialogButtonBox::accepted, [=]() {
btn_box->button(QDialogButtonBox::Open)->setEnabled(false);
w->setEnabled(false);
setEnabled(false);
if (((AbstractOpenStreamWidget *)tab->currentWidget())->open()) {
accept();
} else {
btn_box->button(QDialogButtonBox::Open)->setEnabled(true);
w->setEnabled(true);
}
setEnabled(true);
});
QObject::connect(file_btn, &QPushButton::clicked, [this]() {
QString fn = QFileDialog::getOpenFileName(this, tr("Open File"), settings.last_dir, "DBC (*.dbc)");
+15 -45
View File
@@ -4,67 +4,37 @@ import argparse
from collections import defaultdict
from openpilot.selfdrive.debug.format_fingerprints import format_brand_fw_versions
from openpilot.selfdrive.car.fw_versions import match_fw_to_car
from openpilot.selfdrive.car.interfaces import get_interface_attr
from openpilot.selfdrive.car.fingerprints import MIGRATION
from openpilot.selfdrive.car.fw_versions import MODEL_TO_BRAND, match_fw_to_car
from openpilot.tools.lib.logreader import LogReader, ReadMode
ALL_FW_VERSIONS = get_interface_attr("FW_VERSIONS")
ALL_CARS = get_interface_attr("CAR")
PLATFORM_TO_PYTHON_CAR_NAME = {brand: {car.value: car.name for car in ALL_CARS[brand]} for brand in ALL_CARS}
BRAND_TO_PLATFORMS = {brand: [car.value for car in ALL_CARS[brand]] for brand in ALL_CARS}
PLATFORM_TO_BRAND = dict(sum([[(platform, brand) for platform in BRAND_TO_PLATFORMS[brand]] for brand in BRAND_TO_PLATFORMS], []))
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Auto fingerprint from a route")
parser.add_argument("route", help="The route name to use")
parser.add_argument("platform", help="The platform, or leave empty to auto-determine using fuzzy", default=None, nargs='?')
parser.add_argument("platform", help="The platform, or leave empty to auto-determine using fuzzy", default=None, nargs="?")
args = parser.parse_args()
lr = LogReader(args.route, ReadMode.QLOG)
carFw = None
carVin = None
carPlatform = None
platform: str | None = None
CP = lr.first("carParams")
assert CP is not None, "No carParams in route"
if CP is None:
raise Exception("No fw versions in the provided route...")
carPlatform = MIGRATION.get(CP.carFingerprint, CP.carFingerprint)
carFw = CP.carFw
carVin = CP.carVin
carPlatform = CP.carFingerprint
if args.platform is None: # attempt to auto-determine platform with other fuzzy fingerprints
_, possible_platforms = match_fw_to_car(carFw, carVin, log=False)
if len(possible_platforms) != 1:
print(f"Unable to auto-determine platform, possible platforms: {possible_platforms}")
if carPlatform != "MOCK":
print("Using platform from route")
platform = carPlatform
else:
platform = None
else:
platform = list(possible_platforms)[0]
else:
if args.platform is not None:
platform = args.platform
elif carPlatform != "MOCK":
platform = carPlatform
else:
_, matches = match_fw_to_car(CP.carFw, CP.carVin, log=False)
assert len(matches) == 1, f"Unable to auto-determine platform, matches: {matches}"
platform = list(matches)[0]
if platform is None:
raise Exception("unable to determine platform, try manually specifying the fingerprint.")
print("Attempting to add fw version for: ", platform)
print("Attempting to add fw version for:", platform)
fw_versions: dict[str, dict[tuple, list[bytes]]] = defaultdict(lambda: defaultdict(list))
brand = PLATFORM_TO_BRAND[platform]
brand = MODEL_TO_BRAND[platform]
for fw in carFw:
for fw in CP.carFw:
if fw.brand == brand and not fw.logging:
addr = fw.address
subAddr = None if fw.subAddress == 0 else fw.subAddress
@@ -18,7 +18,7 @@
"from openpilot.selfdrive.car.subaru.values import CAR, SubaruFlags\n",
"from openpilot.selfdrive.car.subaru.fingerprints import FW_VERSIONS\n",
"\n",
"TEST_PLATFORMS = CAR.without_flags(SubaruFlags.PREGLOBAL)\n",
"TEST_PLATFORMS = set(CAR) - CAR.with_flags(SubaruFlags.PREGLOBAL)\n",
"\n",
"Ecu = car.CarParams.Ecu\n",
"\n",
+1 -1
View File
@@ -12,7 +12,7 @@ def create_test_models_suite(routes: list[CarTestRoute], ci=False) -> unittest.T
test_suite = unittest.TestSuite()
for test_route in routes:
# create new test case and discover tests
test_case_args = {"car_model": test_route.car_model, "test_route": test_route, "ci": ci}
test_case_args = {"platform": test_route.car_model, "test_route": test_route, "test_route_on_bucket": ci}
CarModelTestCase = type("CarModelTestCase", (TestCarModel,), test_case_args)
test_suite.addTest(unittest.TestLoader().loadTestsFromTestCase(CarModelTestCase))
return test_suite
-304
View File
@@ -1,304 +0,0 @@
#!/usr/bin/env python3
import sys
import json
import base64
import os
import subprocess
from multiprocessing import Pool
from openpilot.tools.lib.route import Route
from openpilot.tools.lib.logreader import LogReader
try:
from mcap.writer import Writer, CompressionType
except ImportError:
print("mcap module not found. Attempting to install...")
subprocess.run([sys.executable, "-m", "pip", "install", "mcap"])
# Attempt to import again after installation
try:
from mcap.writer import Writer, CompressionType
except ImportError:
print("Failed to install mcap module. Exiting.")
sys.exit(1)
FOXGLOVE_IMAGE_SCHEME_TITLE = "foxglove.CompressedImage"
FOXGLOVE_GEOJSON_TITLE = "foxglove.GeoJSON"
FOXGLOVE_IMAGE_ENCODING = "base64"
OUT_MCAP_FILE_NAME = "json_log.mcap"
RLOG_FOLDER = "rlogs"
SCHEMAS_FOLDER = "schemas"
SCHEMA_EXTENSION = ".json"
schemas: dict[str, int] = {}
channels: dict[str, int] = {}
writer: Writer
def convertBytesToString(data):
if isinstance(data, bytes):
return data.decode('latin-1') # Assuming UTF-8 encoding, adjust if needed
elif isinstance(data, list):
return [convertBytesToString(item) for item in data]
elif isinstance(data, dict):
return {key: convertBytesToString(value) for key, value in data.items()}
else:
return data
# Load jsonscheme for every Event
def loadSchema(schemaName):
with open(os.path.join(SCHEMAS_FOLDER, schemaName + SCHEMA_EXTENSION), "r") as file:
return json.loads(file.read())
# Foxglove creates one graph of an array, and not one for each item of an array
# This can be avoided by transforming array to separate objects
def transformListsToJsonDict(json_data):
def convert_array_to_dict(array):
new_dict = {}
for index, item in enumerate(array):
if isinstance(item, dict):
new_dict[index] = transformListsToJsonDict(item)
else:
new_dict[index] = item
return new_dict
new_data = {}
for key, value in json_data.items():
if isinstance(value, list):
new_data[key] = convert_array_to_dict(value)
elif isinstance(value, dict):
new_data[key] = transformListsToJsonDict(value)
else:
new_data[key] = value
return new_data
# Transform openpilot thumbnail to foxglove compressedImage
def transformToFoxgloveSchema(jsonMsg):
bytesImgData = jsonMsg.get("thumbnail").get("thumbnail").encode('latin1')
base64ImgData = base64.b64encode(bytesImgData)
base64_string = base64ImgData.decode('utf-8')
foxMsg = {
"timestamp": {"sec": "0", "nsec": jsonMsg.get("logMonoTime")},
"frame_id": str(jsonMsg.get("thumbnail").get("frameId")),
"data": base64_string,
"format": "jpeg",
}
return foxMsg
# TODO: Check if there is a tool to build GEOJson
def transformMapCoordinates(jsonMsg):
coordinates = []
for jsonCoords in jsonMsg.get("navRoute").get("coordinates"):
coordinates.append([jsonCoords.get("longitude"), jsonCoords.get("latitude")])
# Define the GeoJSON
geojson_data = {
"type": "FeatureCollection",
"features": [{"type": "Feature", "geometry": {"type": "LineString", "coordinates": coordinates}, "logMonoTime": jsonMsg.get("logMonoTime")}],
}
# Create the final JSON with the GeoJSON data encoded as a string
geoJson = {"geojson": json.dumps(geojson_data)}
return geoJson
def jsonToScheme(jsonData):
zeroArray = False
schema = {"type": "object", "properties": {}, "required": []}
for key, value in jsonData.items():
if isinstance(value, dict):
tempScheme, zeroArray = jsonToScheme(value)
if tempScheme == 0:
return 0
schema["properties"][key] = tempScheme
schema["required"].append(key)
elif isinstance(value, list):
if all(isinstance(item, dict) for item in value) and len(value) > 0: # Handle zero value arrays
# Handle array of objects
tempScheme, zeroArray = jsonToScheme(value[0])
schema["properties"][key] = {"type": "array", "items": tempScheme if value else {}}
schema["required"].append(key)
else:
if len(value) == 0:
zeroArray = True
# Handle array of primitive types
schema["properties"][key] = {"type": "array", "items": {"type": "string"}}
schema["required"].append(key)
else:
typeName = type(value).__name__
if typeName == "str":
typeName = "string"
elif typeName == "bool":
typeName = "boolean"
elif typeName == "float":
typeName = "number"
elif typeName == "int":
typeName = "integer"
schema["properties"][key] = {"type": typeName}
schema["required"].append(key)
return schema, zeroArray
def saveScheme(scheme, schemaFileName):
schemaFileName = schemaFileName + SCHEMA_EXTENSION
# Create the new schemas folder
os.makedirs(SCHEMAS_FOLDER, exist_ok=True)
with open(os.path.join(SCHEMAS_FOLDER, schemaFileName), 'w') as json_file:
json.dump(convertBytesToString(scheme), json_file)
def convertToFoxGloveFormat(jsonData, rlogTopic):
jsonData["title"] = rlogTopic
if rlogTopic == "thumbnail":
jsonData = transformToFoxgloveSchema(jsonData)
jsonData["title"] = FOXGLOVE_IMAGE_SCHEME_TITLE
elif rlogTopic == "navRoute":
jsonData = transformMapCoordinates(jsonData)
jsonData["title"] = FOXGLOVE_GEOJSON_TITLE
else:
jsonData = transformListsToJsonDict(jsonData)
return jsonData
def generateSchemas():
listOfDirs = os.listdir(RLOG_FOLDER)
# Open every dir in rlogs
for directory in listOfDirs:
# List every file in every rlog dir
dirPath = os.path.join(RLOG_FOLDER, directory)
listOfFiles = os.listdir(dirPath)
lastIteration = len(listOfFiles)
for iteration, file in enumerate(listOfFiles):
# Load json data from every file until found one without empty arrays
filePath = os.path.join(dirPath, file)
with open(filePath, 'r') as jsonFile:
jsonData = json.load(jsonFile)
scheme, zerroArray = jsonToScheme(jsonData)
# If array of len 0 has been found, type of its data can not be parsed, skip to the next log
# in search for a non empty array. If there is not an non empty array in logs, put a dummy string type
if zerroArray and not iteration == lastIteration - 1:
continue
title = jsonData.get("title")
scheme["title"] = title
# Add contentEncoding type, hardcoded in foxglove format
if title == FOXGLOVE_IMAGE_SCHEME_TITLE:
scheme["properties"]["data"]["contentEncoding"] = FOXGLOVE_IMAGE_ENCODING
saveScheme(scheme, directory)
break
def downloadLogs(logPaths):
segment_counter = 0
for logPath in logPaths:
segment_counter += 1
msg_counter = 1
print(segment_counter)
rlog = LogReader(logPath)
for msg in rlog:
jsonMsg = json.loads(json.dumps(convertBytesToString(msg.to_dict())))
jsonMsg = convertToFoxGloveFormat(jsonMsg, msg.which())
rlog_dir_path = os.path.join(RLOG_FOLDER, msg.which())
if not os.path.exists(rlog_dir_path):
os.makedirs(rlog_dir_path)
file_path = os.path.join(rlog_dir_path, str(segment_counter) + "," + str(msg_counter))
with open(file_path, 'w') as json_file:
json.dump(jsonMsg, json_file)
msg_counter += 1
def getLogMonoTime(jsonMsg):
if jsonMsg.get("title") == FOXGLOVE_IMAGE_SCHEME_TITLE:
logMonoTime = jsonMsg.get("timestamp").get("nsec")
elif jsonMsg.get("title") == FOXGLOVE_GEOJSON_TITLE:
logMonoTime = json.loads(jsonMsg.get("geojson")).get("features")[0].get("logMonoTime")
else:
logMonoTime = jsonMsg.get("logMonoTime")
return logMonoTime
def processMsgs(args):
msgFile, rlogTopicPath, rlogTopic = args
msgFilePath = os.path.join(rlogTopicPath, msgFile)
with open(msgFilePath, "r") as file:
jsonMsg = json.load(file)
logMonoTime = getLogMonoTime(jsonMsg)
return {'channel_id': channels[rlogTopic], 'log_time': logMonoTime, 'data': json.dumps(jsonMsg).encode("utf-8"), 'publish_time': logMonoTime}
# Get logs from a path, and convert them into mcap
def createMcap(logPaths):
print(f"Downloading logs [{len(logPaths)}]")
downloadLogs(logPaths)
print("Creating schemas")
generateSchemas()
print("Creating mcap file")
listOfRlogTopics = os.listdir(RLOG_FOLDER)
print(f"Registering schemas and channels [{len(listOfRlogTopics)}]")
for counter, rlogTopic in enumerate(listOfRlogTopics):
print(counter)
schema = loadSchema(rlogTopic)
schema_id = writer.register_schema(name=schema.get("title"), encoding="jsonschema", data=json.dumps(schema).encode())
schemas[rlogTopic] = schema_id
channel_id = writer.register_channel(schema_id=schemas[rlogTopic], topic=rlogTopic, message_encoding="json")
channels[rlogTopic] = channel_id
rlogTopicPath = os.path.join(RLOG_FOLDER, rlogTopic)
msgFiles = os.listdir(rlogTopicPath)
pool = Pool()
results = pool.map(processMsgs, [(msgFile, rlogTopicPath, rlogTopic) for msgFile in msgFiles])
pool.close()
pool.join()
for result in results:
writer.add_message(channel_id=result['channel_id'], log_time=result['log_time'], data=result['data'], publish_time=result['publish_time'])
def is_program_installed(program_name):
try:
# Check if the program is installed using dpkg (for traditional Debian packages)
subprocess.run(["dpkg", "-l", program_name], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return True
except subprocess.CalledProcessError:
# Check if the program is installed using snap
try:
subprocess.run(["snap", "list", program_name], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return True
except subprocess.CalledProcessError:
return False
if __name__ == '__main__':
# Example usage:
program_name = "foxglove-studio" # Change this to the program you want to check
if is_program_installed(program_name):
print(f"{program_name} detected.")
else:
print(f"{program_name} could not be detected.")
installFoxglove = input("Would you like to install it? YES/NO? - ")
if installFoxglove.lower() == "yes":
try:
subprocess.run(['./install_foxglove.sh'], check=True)
print("Installation completed successfully.")
except subprocess.CalledProcessError as e:
print(f"Installation failed with return code {e.returncode}.")
# Get a route
if len(sys.argv) == 1:
route_name = "a2a0ccea32023010|2023-07-27--13-01-19"
print("No route was provided, using demo route")
else:
route_name = sys.argv[1]
# Get logs for a route
print("Getting route log paths")
route = Route(route_name)
logPaths = route.log_paths()
# Start mcap writer
with open(OUT_MCAP_FILE_NAME, "wb") as stream:
writer = Writer(stream, compression=CompressionType.NONE)
writer.start()
createMcap(logPaths)
writer.finish()
print(f"File {OUT_MCAP_FILE_NAME} has been successfully created. Please import it into foxglove studio to continue.")
-3
View File
@@ -1,3 +0,0 @@
#!/bin/bash
echo "Installing foxglvoe studio..."
sudo snap install foxglove-studio
+27 -19
View File
@@ -1,12 +1,13 @@
#include "tools/replay/camera.h"
#include <capnp/dynamic.h>
#include <cassert>
#include "third_party/linux/include/msm_media_info.h"
#include "tools/replay/util.h"
const int BUFFER_COUNT = 40;
std::tuple<size_t, size_t, size_t> get_nv12_info(int width, int height) {
int nv12_width = VENUS_Y_STRIDE(COLOR_FMT_NV12, width);
int nv12_height = VENUS_Y_SCANLINES(COLOR_FMT_NV12, height);
@@ -36,10 +37,12 @@ CameraServer::~CameraServer() {
void CameraServer::startVipcServer() {
vipc_server_.reset(new VisionIpcServer("camerad"));
for (auto &cam : cameras_) {
cam.cached_buf.clear();
if (cam.width > 0 && cam.height > 0) {
rInfo("camera[%d] frame size %dx%d", cam.type, cam.width, cam.height);
auto [nv12_width, nv12_height, nv12_buffer_size] = get_nv12_info(cam.width, cam.height);
vipc_server_->create_buffers_with_sizes(cam.stream_type, YUV_BUFFER_COUNT, false, cam.width, cam.height,
vipc_server_->create_buffers_with_sizes(cam.stream_type, BUFFER_COUNT, false, cam.width, cam.height,
nv12_buffer_size, nv12_width, nv12_width * nv12_height);
if (!cam.thread.joinable()) {
cam.thread = std::thread(&CameraServer::cameraThread, this, std::ref(cam));
@@ -50,13 +53,6 @@ void CameraServer::startVipcServer() {
}
void CameraServer::cameraThread(Camera &cam) {
auto read_frame = [&](FrameReader *fr, int frame_id) {
VisionBuf *yuv_buf = vipc_server_->get_buffer(cam.stream_type);
assert(yuv_buf);
bool ret = fr->get(frame_id, yuv_buf);
return ret ? yuv_buf : nullptr;
};
while (true) {
const auto [fr, event] = cam.queue.pop();
if (!fr) break;
@@ -66,29 +62,41 @@ void CameraServer::cameraThread(Camera &cam) {
auto eidx = capnp::AnyStruct::Reader(evt).getPointerSection()[0].getAs<cereal::EncodeIndex>();
if (eidx.getType() != cereal::EncodeIndex::Type::FULL_H_E_V_C) continue;
const int id = eidx.getSegmentId();
bool prefetched = (id == cam.cached_id && eidx.getSegmentNum() == cam.cached_seg);
auto yuv = prefetched ? cam.cached_buf : read_frame(fr, id);
if (yuv) {
int segment_id = eidx.getSegmentId();
uint32_t frame_id = eidx.getFrameId();
if (auto yuv = getFrame(cam, fr, segment_id, frame_id)) {
VisionIpcBufExtra extra = {
.frame_id = eidx.getFrameId(),
.frame_id = frame_id,
.timestamp_sof = eidx.getTimestampSof(),
.timestamp_eof = eidx.getTimestampEof(),
};
yuv->set_frame_id(eidx.getFrameId());
vipc_server_->send(yuv, &extra);
} else {
rError("camera[%d] failed to get frame: %lu", cam.type, eidx.getSegmentId());
rError("camera[%d] failed to get frame: %lu", cam.type, segment_id);
}
cam.cached_id = id + 1;
cam.cached_seg = eidx.getSegmentNum();
cam.cached_buf = read_frame(fr, cam.cached_id);
// Prefetch the next frame
getFrame(cam, fr, segment_id + 1, frame_id + 1);
--publishing_;
}
}
VisionBuf *CameraServer::getFrame(Camera &cam, FrameReader *fr, int32_t segment_id, uint32_t frame_id) {
// Check if the frame is cached
auto buf_it = std::find_if(cam.cached_buf.begin(), cam.cached_buf.end(),
[frame_id](VisionBuf *buf) { return buf->get_frame_id() == frame_id; });
if (buf_it != cam.cached_buf.end()) return *buf_it;
VisionBuf *yuv_buf = vipc_server_->get_buffer(cam.stream_type);
if (fr->get(segment_id, yuv_buf)) {
yuv_buf->set_frame_id(frame_id);
cam.cached_buf.insert(yuv_buf);
return yuv_buf;
}
return nullptr;
}
void CameraServer::pushFrame(CameraType type, FrameReader *fr, const Event *event) {
auto &cam = cameras_[type];
if (cam.width != fr->width || cam.height != fr->height) {
+3 -3
View File
@@ -1,6 +1,7 @@
#pragma once
#include <memory>
#include <set>
#include <tuple>
#include <utility>
@@ -26,12 +27,11 @@ protected:
int height;
std::thread thread;
SafeQueue<std::pair<FrameReader*, const Event *>> queue;
int cached_id = -1;
int cached_seg = -1;
VisionBuf * cached_buf;
std::set<VisionBuf *> cached_buf;
};
void startVipcServer();
void cameraThread(Camera &cam);
VisionBuf *getFrame(Camera &cam, FrameReader *fr, int32_t segment_id, uint32_t frame_id);
Camera cameras_[MAX_CAMERAS] = {
{.type = RoadCam, .stream_type = VISION_STREAM_ROAD},
+8 -5
View File
@@ -468,12 +468,15 @@ std::vector<Event>::const_iterator Replay::publishEvents(std::vector<Event>::con
// Skip events if socket is not present
if (!sockets_[evt.which]) continue;
int64_t time_diff = (evt.mono_time - evt_start_ts) / speed_ - (nanos_since_boot() - loop_start_ts);
// if time_diff is greater than 1 second, it means that an invalid segment is skipped
if (time_diff >= 1e9 || speed_ != prev_replay_speed) {
// reset event start times
const uint64_t current_nanos = nanos_since_boot();
const int64_t time_diff = (evt.mono_time - evt_start_ts) / speed_ - (current_nanos - loop_start_ts);
// Reset timestamps for potential synchronization issues:
// - A negative time_diff may indicate slow execution or system wake-up,
// - A time_diff exceeding 1 second suggests a skipped segment.
if ((time_diff < -1e9 || time_diff >= 1e9) || speed_ != prev_replay_speed) {
evt_start_ts = evt.mono_time;
loop_start_ts = nanos_since_boot();
loop_start_ts = current_nanos;
prev_replay_speed = speed_;
} else if (time_diff > 0) {
precise_nano_sleep(time_diff);
+3 -1
View File
@@ -46,6 +46,7 @@ class SimulatorBridge(ABC):
self.world: World | None = None
self.past_startup_engaged = False
self.startup_button_prev = True
def _on_shutdown(self, signal, frame):
self.shutdown()
@@ -161,7 +162,8 @@ Ignition: {self.simulator_state.ignition} Engaged: {self.simulator_state.is_enga
self.past_startup_engaged = True
elif not self.past_startup_engaged and controlsState.engageable:
self.simulator_state.cruise_button = CruiseButtons.DECEL_SET # force engagement on startup
self.simulator_state.cruise_button = CruiseButtons.DECEL_SET if self.startup_button_prev else CruiseButtons.MAIN # force engagement on startup
self.startup_button_prev = not self.startup_button_prev
throttle_out = throttle_op if self.simulator_state.is_engaged else throttle_manual
brake_out = brake_op if self.simulator_state.is_engaged else brake_manual
-2
View File
@@ -62,8 +62,6 @@ class TestSimBridgeBase(unittest.TestCase):
while time.monotonic() < start_time + max_time_per_step:
sm.update()
q.put("cruise_down") # Try engaging
if sm.all_alive() and sm['controlsState'].active:
control_active += 1
-78
View File
@@ -1,78 +0,0 @@
import ft4222
import ft4222.I2CMaster
DEBUG = False
INA231_ADDR = 0x40
INA231_REG_CONFIG = 0x00
INA231_REG_SHUNT_VOLTAGE = 0x01
INA231_REG_BUS_VOLTAGE = 0x02
INA231_REG_POWER = 0x03
INA231_REG_CURRENT = 0x04
INA231_REG_CALIBRATION = 0x05
INA231_BUS_LSB = 1.25e-3
INA231_SHUNT_LSB = 2.5e-6
SHUNT_RESISTOR = 30e-3
CURRENT_LSB = 1e-5
class Zookeeper:
def __init__(self):
if ft4222.createDeviceInfoList() < 2:
raise Exception("No connected zookeeper found!")
self.dev_a = ft4222.openByDescription("FT4222 A")
self.dev_b = ft4222.openByDescription("FT4222 B")
if DEBUG:
for i in range(ft4222.createDeviceInfoList()):
print(f"Device {i}: {ft4222.getDeviceInfoDetail(i, False)}")
# Setup GPIO
self.dev_b.gpio_Init(gpio2=ft4222.Dir.OUTPUT, gpio3=ft4222.Dir.OUTPUT)
self.dev_b.setSuspendOut(False)
self.dev_b.setWakeUpInterrut(False)
# Setup I2C
self.dev_a.i2cMaster_Init(kbps=400)
self._initialize_ina()
# Helper functions
def _read_ina_register(self, register, length):
self.dev_a.i2cMaster_WriteEx(INA231_ADDR, data=register, flag=ft4222.I2CMaster.Flag.REPEATED_START)
return self.dev_a.i2cMaster_Read(INA231_ADDR, bytesToRead=length)
def _write_ina_register(self, register, data):
msg = register.to_bytes(1, byteorder="big") + data.to_bytes(2, byteorder="big")
self.dev_a.i2cMaster_Write(INA231_ADDR, data=msg)
def _initialize_ina(self):
# Config
self._write_ina_register(INA231_REG_CONFIG, 0x4127)
# Calibration
CAL_VALUE = int(0.00512 / (CURRENT_LSB * SHUNT_RESISTOR))
if DEBUG:
print(f"Calibration value: {hex(CAL_VALUE)}")
self._write_ina_register(INA231_REG_CALIBRATION, CAL_VALUE)
def _set_gpio(self, number, enabled):
self.dev_b.gpio_Write(portNum=number, value=enabled)
# Public API functions
def set_device_power(self, enabled):
self._set_gpio(2, enabled)
def set_device_ignition(self, enabled):
self._set_gpio(3, enabled)
def read_current(self):
# Returns in A
return int.from_bytes(self._read_ina_register(INA231_REG_CURRENT, 2), byteorder="big") * CURRENT_LSB
def read_power(self):
# Returns in W
return int.from_bytes(self._read_ina_register(INA231_REG_POWER, 2), byteorder="big") * CURRENT_LSB * 25
def read_voltage(self):
# Returns in V
return int.from_bytes(self._read_ina_register(INA231_REG_BUS_VOLTAGE, 2), byteorder="big") * INA231_BUS_LSB
-27
View File
@@ -1,27 +0,0 @@
#!/usr/bin/env python3
import sys
import time
from openpilot.tools.zookeeper import Zookeeper
# Usage: check_consumption.py <averaging_time_sec> <max_average_power_W>
# Exit code: 0 -> passed
# 1 -> failed
if __name__ == "__main__":
z = Zookeeper()
averaging_time_s = int(sys.argv[1])
max_average_power = float(sys.argv[2])
start_time = time.time()
measurements = []
while time.time() - start_time < averaging_time_s:
measurements.append(z.read_power())
time.sleep(0.1)
average_power = sum(measurements)/len(measurements)
print(f"Average power: {round(average_power, 4)}W")
if average_power > max_average_power:
exit(1)
-8
View File
@@ -1,8 +0,0 @@
#!/usr/bin/env python3
from openpilot.tools.zookeeper import Zookeeper
if __name__ == "__main__":
z = Zookeeper()
z.set_device_power(False)
-31
View File
@@ -1,31 +0,0 @@
#!/usr/bin/env python3
import os
import sys
import time
from socket import gethostbyname, gaierror
from openpilot.tools.zookeeper import Zookeeper
def is_online(ip):
try:
addr = gethostbyname(ip)
return (os.system(f"ping -c 1 {addr} > /dev/null") == 0)
except gaierror:
return False
if __name__ == "__main__":
z = Zookeeper()
z.set_device_power(True)
ip = str(sys.argv[1])
timeout = int(sys.argv[2])
start_time = time.time()
while not is_online(ip):
print(f"{ip} not online yet!")
if time.time() - start_time > timeout:
print("Timed out!")
raise TimeoutError()
time.sleep(1)
-10
View File
@@ -1,10 +0,0 @@
#!/usr/bin/env python3
import sys
from openpilot.tools.zookeeper import Zookeeper
if __name__ == "__main__":
z = Zookeeper()
z.set_device_ignition(1 if int(sys.argv[1]) > 0 else 0)
-40
View File
@@ -1,40 +0,0 @@
#!/usr/bin/env python3
import sys
import time
import datetime
from openpilot.common.realtime import Ratekeeper
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.tools.zookeeper import Zookeeper
if __name__ == "__main__":
z = Zookeeper()
z.set_device_power(True)
z.set_device_ignition(False)
duration = None
if len(sys.argv) > 1:
duration = int(sys.argv[1])
rate = 123
rk = Ratekeeper(rate, print_delay_threshold=None)
fltr = FirstOrderFilter(0, 5, 1. / rate, initialized=False)
measurements = []
start_time = time.monotonic()
try:
while duration is None or time.monotonic() - start_time < duration:
fltr.update(z.read_power())
if rk.frame % rate == 0:
measurements.append(fltr.x)
t = datetime.timedelta(seconds=time.monotonic() - start_time)
avg = sum(measurements) / len(measurements)
print(f"Now: {fltr.x:.2f} W, Avg: {avg:.2f} W over {t}")
rk.keep_time()
except KeyboardInterrupt:
pass
t = datetime.timedelta(seconds=time.monotonic() - start_time)
avg = sum(measurements) / len(measurements)
print(f"\nAverage power: {avg:.2f}W over {t}")
-25
View File
@@ -1,25 +0,0 @@
#!/usr/bin/env python3
import time
from openpilot.tools.zookeeper import Zookeeper
if __name__ == "__main__":
z = Zookeeper()
z.set_device_power(True)
i = 0
ign = False
while 1:
voltage = round(z.read_voltage(), 2)
current = round(z.read_current(), 3)
power = round(z.read_power(), 2)
z.set_device_ignition(ign)
print(f"Voltage: {voltage}V, Current: {current}A, Power: {power}W, Ignition: {ign}")
if i > 200:
ign = not ign
i = 0
i += 1
time.sleep(0.1)