commit
d3bdf6c345
56 changed files with 502 additions and 454 deletions
@ -0,0 +1,30 @@ |
|||||||
|
#!/usr/bin/env python3 |
||||||
|
from collections import defaultdict |
||||||
|
|
||||||
|
from cereal import car |
||||||
|
from openpilot.selfdrive.car.ford.values import get_platform_codes |
||||||
|
from openpilot.selfdrive.car.ford.fingerprints import FW_VERSIONS |
||||||
|
|
||||||
|
Ecu = car.CarParams.Ecu |
||||||
|
ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()} |
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
cars_for_code: defaultdict = defaultdict(lambda: defaultdict(set)) |
||||||
|
|
||||||
|
for car_model, ecus in FW_VERSIONS.items(): |
||||||
|
print(car_model) |
||||||
|
for ecu in sorted(ecus, key=lambda x: int(x[0])): |
||||||
|
platform_codes = get_platform_codes(ecus[ecu]) |
||||||
|
for code in platform_codes: |
||||||
|
cars_for_code[ecu][code].add(car_model) |
||||||
|
|
||||||
|
print(f' (Ecu.{ECU_NAME[ecu[0]]}, {hex(ecu[1])}, {ecu[2]}):') |
||||||
|
print(f' Codes: {sorted(platform_codes)}') |
||||||
|
print() |
||||||
|
|
||||||
|
print('\nCar models vs. platform codes:') |
||||||
|
for ecu, codes in cars_for_code.items(): |
||||||
|
print(f' (Ecu.{ECU_NAME[ecu[0]]}, {hex(ecu[1])}, {ecu[2]}):') |
||||||
|
for code, cars in codes.items(): |
||||||
|
print(f' {code!r}: {sorted(map(str, cars))}') |
@ -0,0 +1,14 @@ |
|||||||
|
#!/usr/bin/env python3 |
||||||
|
from openpilot.selfdrive.car.values import BRANDS |
||||||
|
|
||||||
|
for brand in BRANDS: |
||||||
|
all_flags = set() |
||||||
|
for platform in brand: |
||||||
|
if platform.config.flags != 0: |
||||||
|
all_flags |= set(platform.config.flags) |
||||||
|
|
||||||
|
if len(all_flags): |
||||||
|
print(brand.__module__.split('.')[-2].upper() + ':') |
||||||
|
for flag in sorted(all_flags): |
||||||
|
print(f' {flag.name:<24}:', {platform.name for platform in brand.with_flags(flag)}) |
||||||
|
print() |
@ -1 +1 @@ |
|||||||
692a21e4a722d91086998b532ca6759a3f85c345 |
685a2bb9aacdd790e26d14aa49d3792c3ed65125 |
@ -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(); |
||||||
|
} |
@ -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_; |
||||||
|
}; |
@ -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 |
|
@ -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) |
|
@ -1,8 +0,0 @@ |
|||||||
#!/usr/bin/env python3 |
|
||||||
|
|
||||||
from openpilot.tools.zookeeper import Zookeeper |
|
||||||
|
|
||||||
if __name__ == "__main__": |
|
||||||
z = Zookeeper() |
|
||||||
z.set_device_power(False) |
|
||||||
|
|
@ -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) |
|
||||||
|
|
@ -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) |
|
||||||
|
|
@ -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}") |
|
@ -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) |
|
Loading…
Reference in new issue