Konik.ai server support

Co-Authored-By: MoreTorque <104254025+moretore@users.noreply.github.com>
This commit is contained in:
James
2025-12-06 18:45:09 -07:00
parent cc73376f75
commit 518da86590
20 changed files with 93 additions and 25 deletions
+3 -1
View File
@@ -5,7 +5,9 @@ from datetime import datetime, timedelta, UTC
from openpilot.system.hardware.hw import Paths
from openpilot.system.version import get_version
API_HOST = os.getenv('API_HOST', 'https://api.commadotai.com')
from openpilot.frogpilot.common.frogpilot_utilities import use_konik_server
API_HOST = os.getenv('API_HOST', f'https://api.{'konik.ai' if use_konik_server() else 'commadotai.com'}')
# name: jwt signature algorithm
KEYS = {"id_rsa": "RS256",
+13 -2
View File
@@ -9,13 +9,14 @@ from pathlib import Path
from openpilot.common.basedir import BASEDIR
from openpilot.common.params import Params
from openpilot.common.time_helpers import system_time_valid
from openpilot.system.athena.registration import register
from openpilot.system.hardware import HARDWARE
from openpilot.frogpilot.assets.theme_manager import ThemeManager
from openpilot.frogpilot.common.frogpilot_backups import backup_frogpilot
from openpilot.frogpilot.common.frogpilot_utilities import is_FrogsGoMoo, run_cmd
from openpilot.frogpilot.common.frogpilot_utilities import is_FrogsGoMoo, run_cmd, use_konik_server
from openpilot.frogpilot.common.frogpilot_variables import (
ERROR_LOGS_PATH, FROGS_GO_MOO_PATH, HD_LOGS_PATH, THEME_SAVE_PATH,
ERROR_LOGS_PATH, FROGS_GO_MOO_PATH, HD_LOGS_PATH, KONIK_LOGS_PATH, THEME_SAVE_PATH,
FrogPilotVariables, get_frogpilot_toggles
)
@@ -26,6 +27,15 @@ def frogpilot_boot_functions(build_metadata, params):
FrogPilotVariables()
ThemeManager(params, params_memory, boot_run=True).update_active_theme(time_validated=system_time_valid(), frogpilot_toggles=get_frogpilot_toggles(), boot_run=True)
if use_konik_server():
if params.get("KonikDongleId") is not None:
params.put("DongleId", params.get("KonikDongleId"))
else:
params.put("KonikDongleId", register(show_spinner=True, register_konik=True))
params.put("DongleId", params.get("KonikDongleId"))
elif params.get("DongleId") == params.get("KonikDongleId"):
params.put("DongleId", params.get("StockDongleId"))
def boot_thread():
while not system_time_valid():
print("Waiting for system time to become valid...")
@@ -40,6 +50,7 @@ def install_frogpilot(build_metadata, params):
paths = [
ERROR_LOGS_PATH,
HD_LOGS_PATH,
KONIK_LOGS_PATH,
THEME_SAVE_PATH
]
for path in paths:
+7 -1
View File
@@ -9,6 +9,7 @@ import threading
import time
import zipfile
from functools import cache
from pathlib import Path
import openpilot.system.sentry as sentry
@@ -17,7 +18,7 @@ from cereal import log, messaging
from openpilot.common.realtime import DT_DMON, DT_HW
from panda import Panda
from openpilot.frogpilot.common.frogpilot_variables import EARTH_RADIUS, FROGS_GO_MOO_PATH
from openpilot.frogpilot.common.frogpilot_variables import EARTH_RADIUS, FROGS_GO_MOO_PATH, KONIK_PATH
class ThreadManager:
def __init__(self):
@@ -211,6 +212,11 @@ def update_json_file(path, data):
os.replace(temp_path, path)
@cache
def use_konik_server():
return KONIK_PATH.is_file()
def wait_for_no_driver(params, sm, time_threshold=60):
while sm["deviceState"].screenBrightnessPercent != 0 or any(proc.name == "dmonitoringd" and proc.running for proc in sm["managerState"].processes):
sm.update()
+14
View File
@@ -67,6 +67,9 @@ FROGS_GO_MOO_PATH = Path("/persist/frogsgomoo.py")
HD_LOGS_PATH = Path("/data/media/0/realdata_HD")
HD_PATH = Path("/cache/use_HD")
KONIK_LOGS_PATH = Path("/data/media/0/realdata_konik")
KONIK_PATH = Path("/cache/use_konik")
MAPD_PATH = Path("/data/media/0/osm/mapd")
MAPS_PATH = Path("/data/media/0/osm/offline")
@@ -225,6 +228,17 @@ class FrogPilotVariables:
HD_PATH.unlink()
HARDWARE.reboot()
toggle.use_konik_server = device_management
toggle.use_konik_server &= self.get_value("UseKonikServer")
toggle.use_konik_server |= Path("/data/openpilot/not_vetted").is_file()
if not KONIK_PATH.is_file() and toggle.use_konik_server:
KONIK_PATH.touch()
HARDWARE.reboot()
elif KONIK_PATH.is_file() and not toggle.use_konik_server:
KONIK_PATH.unlink()
HARDWARE.reboot()
stock_colors_json = (STOCK_THEME_PATH / "colors/colors.json")
self.stock_colors = json.loads(stock_colors_json.read_text()) if stock_colors_json.is_file() else {}
+8 -1
View File
@@ -29,6 +29,7 @@ FrogPilotDevicePanel::FrogPilotDevicePanel(FrogPilotSettingsWindow *parent, bool
{"HigherBitrate", tr("High-Quality Recording"), tr("<b>Save drive footage in higher video quality.</b>"), ""},
{"LowVoltageShutdown", tr("Low-Voltage Cutoff"), tr("<b>While parked, if the battery voltage falls below the set level, the device shuts down</b> to prevent excessive battery drain."), ""},
{"IncreaseThermalLimits", tr("Raise Temperature Limits"), QString("<b>%1</b><br><br>%2").arg(tr("WARNING: Running at higher temperatures may damage your device!")).arg(tr("<b>Allow the device to run at higher temperatures</b> before throttling or shutting down. Use only if you understand the risks!")), ""},
{"UseKonikServer", tr("Use Konik Server"), tr("<b>Upload driving data to \"stable.konik.ai\" instead of \"connect.comma.ai\".</b>"), ""},
{"ScreenManagement", tr("Screen Settings"), tr("<b>Settings that control screen brightness, screen recording, and timeout duration.</b>"), "../../frogpilot/assets/toggle_icons/icon_light.png"},
{"ScreenBrightness", tr("Screen Brightness (Offroad)"), tr("<b>The screen brightness while not driving.</b>"), ""},
@@ -136,12 +137,14 @@ FrogPilotDevicePanel::FrogPilotDevicePanel(FrogPilotSettingsWindow *parent, bool
QObject::connect(static_cast<ToggleControl*>(toggles[key]), &ToggleControl::toggleFlipped, this, &FrogPilotDevicePanel::updateToggles);
}
QSet<QString> rebootKeys = {"HigherBitrate"};
QSet<QString> rebootKeys = {"HigherBitrate", "UseKonikServer"};
for (const QString &key : rebootKeys) {
QObject::connect(static_cast<ToggleControl*>(toggles[key]), &ToggleControl::toggleFlipped, [key, this](bool state) {
QString filePath;
if (key == "HigherBitrate") {
filePath = "/cache/use_HD";
} else if (key == "UseKonikServer") {
filePath = "/cache/use_konik";
}
if (!filePath.isEmpty()) {
@@ -203,6 +206,10 @@ void FrogPilotDevicePanel::updateToggles() {
setVisible &= params.getBool("DeviceManagement") && params.getBool("NoUploads") && !params.getBool("DisableOnroadUploads");
}
else if (key == "UseKonikServer" && QFile("/data/not_vetted").exists()) {
static_cast<ToggleControl*>(toggle)->forceOn(true);
}
toggle->setVisible(setVisible);
if (setVisible) {
+1 -1
View File
@@ -23,7 +23,7 @@ private:
std::map<QString, AbstractControl*> toggles;
QSet<QString> deviceManagementKeys = {"DeviceShutdown", "HigherBitrate", "IncreaseThermalLimits", "LowVoltageShutdown", "NoLogging", "NoUploads"};
QSet<QString> deviceManagementKeys = {"DeviceShutdown", "HigherBitrate", "IncreaseThermalLimits", "LowVoltageShutdown", "NoLogging", "NoUploads", "UseKonikServer"};
QSet<QString> screenKeys = {"ScreenBrightness", "ScreenBrightnessOnroad", "ScreenRecorder", "ScreenTimeout", "ScreenTimeoutOnroad", "StandbyMode"};
QSet<QString> parentKeys;
+4 -3
View File
@@ -10,12 +10,13 @@ static QLabel *newLabel(const QString &text, const QString &type) {
DriveStats::DriveStats(QWidget *parent) : QFrame(parent) {
isMetric = params.getBool("IsMetric");
konik = useKonikServer();
QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(50, 25, 50, 20);
addStatsLayouts(tr("ALL TIME"), all);
addStatsLayouts(tr("PAST WEEK"), week);
addStatsLayouts(konik ? tr("ALL TIME (KONIK)") : tr("ALL TIME"), all);
addStatsLayouts(konik ? tr("PAST WEEK (KONIK)") : tr("PAST WEEK"), week);
addStatsLayouts(tr("FROGPILOT"), frogPilot, true);
std::optional<QString> dongleId = getDongleId();
@@ -103,5 +104,5 @@ void DriveStats::updateStats() {
updateStatsForLabel(json["week"].toObject(), week);
updateFrogPilotStatsForLabel(frogPilot);
params.putIntNonBlocking("openpilotMinutes", json["all"].toObject()["minutes"].toDouble());
params.putIntNonBlocking(konik ? "KonikMinutes" : "openpilotMinutes", json["all"].toObject()["minutes"].toDouble());
}
+1
View File
@@ -23,6 +23,7 @@ private:
void updateStatsForLabel(const QJsonObject &obj, StatsLabels &labels);
bool isMetric;
bool konik;
Params params;
@@ -17,6 +17,11 @@ bool isFrogsGoMoo() {
return is_FrogsGoMoo;
}
bool useKonikServer() {
static bool use_konik = QFile::exists("/cache/use_konik");
return use_konik;
}
void clearMovie(QSharedPointer<QMovie> &movie, QWidget *parent) {
if (!movie) {
return;
@@ -16,6 +16,7 @@
#include "selfdrive/ui/qt/widgets/controls.h"
bool isFrogsGoMoo();
bool useKonikServer();
void loadGif(const QString &gifPath, QSharedPointer<QMovie> &movie, const QSize &size, QWidget *parent);
void loadImage(const QString &basePath, QPixmap &pixmap, QSharedPointer<QMovie> &movie, const QSize &size, QWidget *parent);
+3 -1
View File
@@ -7,9 +7,11 @@
#include "common/util.h"
#include "frogpilot/ui/qt/widgets/frogpilot_controls.h"
namespace CommaApi {
const QString BASE_URL = util::getenv("API_HOST", "https://api.commadotai.com").c_str();
const QString BASE_URL = util::getenv("API_HOST", useKonikServer() ? "https://api.konik.ai" : "https://api.commadotai.com").c_str();
QByteArray rsa_sign(const QByteArray &data);
QString create_jwt(const QJsonObject &payloads = {}, int expiry = 3600);
+2 -2
View File
@@ -229,7 +229,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
addItem(new LabelControl(tr("Serial"), params.get("HardwareSerial").c_str()));
pair_device = new ButtonControl(tr("Pair Device"), tr("PAIR"),
tr("Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer."));
useKonikServer() ? tr("Pair your device with Konik connect (stable.konik.ai).") : tr("Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer."));
connect(pair_device, &ButtonControl::clicked, [=]() {
PairingPopup popup(this);
popup.exec();
@@ -577,7 +577,7 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) {
if (!tuningLevelConfirmed) {
int frogpilotHours = QJsonDocument::fromJson(QString::fromStdString(params.get("FrogPilotStats")).toUtf8()).object().value("FrogPilotSeconds").toInt() / (60 * 60);
int openpilotHours = params.getInt("openpilotMinutes") / 60;
int openpilotHours = params.getInt("KonikMinutes") / 60 + params.getInt("openpilotMinutes") / 60;
if (frogpilotHours < 1 && openpilotHours < 100) {
if (openpilotHours < 10) {
+5 -5
View File
@@ -36,7 +36,7 @@ void PairingQRWidget::hideEvent(QHideEvent *event) {
void PairingQRWidget::refresh() {
QString pairToken = CommaApi::create_jwt({{"pair", true}});
QString qrString = "https://connect.comma.ai/?pair=" + pairToken;
QString qrString = (useKonikServer() ? "https://stable.konik.ai/?pair=" : "https://connect.comma.ai/?pair=") + pairToken;
this->updateQrCode(qrString);
update();
}
@@ -89,7 +89,7 @@ PairingPopup::PairingPopup(QWidget *parent) : DialogBase(parent) {
vlayout->addSpacing(30);
QLabel *title = new QLabel(tr("Pair your device to your comma account"), this);
QLabel *title = new QLabel(tr("Pair your device to your %1 account").arg(useKonikServer() ? "Konik" : "comma"), this);
title->setStyleSheet("font-size: 75px; color: black;");
title->setWordWrap(true);
vlayout->addWidget(title);
@@ -100,9 +100,9 @@ PairingPopup::PairingPopup(QWidget *parent) : DialogBase(parent) {
<li style='margin-bottom: 50px;'>%2</li>
<li style='margin-bottom: 50px;'>%3</li>
</ol>
)").arg(tr("Go to https://connect.comma.ai on your phone"))
)").arg(tr("Go to https://%1 on your phone").arg(useKonikServer() ? "stable.konik.ai" : "connect.comma.ai"))
.arg(tr("Click \"add new device\" and scan the QR code on the right"))
.arg(tr("Bookmark connect.comma.ai to your home screen to use it like an app")), this);
.arg(tr("Bookmark %1 to your home screen to use it like an app").arg(useKonikServer() ? "stable.konik.ai" : "connect.comma.ai")), this);
instructions->setStyleSheet("font-size: 47px; font-weight: bold; color: black;");
instructions->setWordWrap(true);
@@ -196,7 +196,7 @@ SetupWidget::SetupWidget(QWidget* parent) : QFrame(parent) {
registrationTitle->setStyleSheet("font-size: 75px; font-weight: bold;");
finishRegistrationLayout->addWidget(registrationTitle);
QLabel* registrationDescription = new QLabel(tr("Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer."));
QLabel* registrationDescription = new QLabel(useKonikServer() ? tr("Pair your device with Konik connect (stable.konik.ai).") : tr("Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer."));
registrationDescription->setWordWrap(true);
registrationDescription->setStyleSheet("font-size: 50px; font-weight: light;");
finishRegistrationLayout->addWidget(registrationDescription);
+3 -1
View File
@@ -40,8 +40,10 @@ from openpilot.common.swaglog import cloudlog
from openpilot.system.version import get_build_metadata
from openpilot.system.hardware.hw import Paths
from openpilot.frogpilot.common.frogpilot_utilities import use_konik_server
ATHENA_HOST = os.getenv('ATHENA_HOST', 'wss://athena.comma.ai')
ATHENA_HOST = os.getenv('ATHENA_HOST', f"wss://athena.{'konik.ai' if use_konik_server() else 'comma.ai'}")
HANDLER_THREADS = int(os.getenv('HANDLER_THREADS', "4"))
LOCAL_PORT_WHITELIST = {22, } # SSH
+5 -4
View File
@@ -24,7 +24,7 @@ def is_registered_device() -> bool:
return dongle not in (None, UNREGISTERED_DONGLE_ID)
def register(show_spinner=False) -> str | None:
def register(show_spinner=False, register_konik=False) -> str | None:
"""
All devices built since March 2024 come with all
info stored in /persist/. This is kept around
@@ -45,10 +45,10 @@ def register(show_spinner=False) -> str | None:
# Create registration token, in the future, this key will make JWTs directly
jwt_algo, private_key, public_key = get_key_pair()
if not public_key:
if not public_key and not register_konik:
dongle_id = UNREGISTERED_DONGLE_ID
cloudlog.warning("missing public key")
elif dongle_id is None:
elif dongle_id is None or register_konik:
if show_spinner:
spinner = Spinner()
spinner.update("registering device")
@@ -96,8 +96,9 @@ def register(show_spinner=False) -> str | None:
if show_spinner:
spinner.close()
if dongle_id:
if not register_konik and dongle_id != params.get("KonikDongleId"):
params.put("DongleId", dongle_id)
params.put("StockDongleId", dongle_id)
set_offroad_alert("Offroad_UnregisteredHardware", (dongle_id == UNREGISTERED_DONGLE_ID) and not PC)
return dongle_id
+4
View File
@@ -37,6 +37,10 @@ namespace Path {
return "/data/media/0/realdata_HD/";
}
if (std::filesystem::exists("/cache/use_konik")) {
return "/data/media/0/realdata_konik/";
}
return "/data/media/0/realdata";
}
+2
View File
@@ -19,6 +19,8 @@ class Paths:
return str(Path(Paths.comma_home()) / "media" / "0" / "realdata")
elif Path("/cache/use_HD").is_file():
return '/data/media/0/realdata_HD/'
elif Path("/cache/use_konik").is_file():
return '/data/media/0/realdata_konik/'
else:
return '/data/media/0/realdata/'
+4 -1
View File
@@ -1,6 +1,9 @@
import os
import requests
API_HOST = os.getenv('API_HOST', 'https://api.commadotai.com')
from openpilot.frogpilot.common.frogpilot_utilities import use_konik_server
API_HOST = os.getenv('API_HOST', f'https://api.{'konik.ai' if use_konik_server() else 'commadotai.com'}')
# TODO: this should be merged into common.api
+5 -1
View File
@@ -22,6 +22,7 @@ Examples::
"""
import argparse
import os
import sys
import pprint
import webbrowser
@@ -32,6 +33,9 @@ from urllib.parse import parse_qs, urlencode
from openpilot.tools.lib.api import APIError, CommaApi, UnauthorizedError
from openpilot.tools.lib.auth_config import set_token, get_token
from openpilot.frogpilot.common.frogpilot_utilities import use_konik_server
API_HOST = os.getenv('API_HOST', f'https://api.{'konik' if use_konik_server() else 'comma'}.ai')
PORT = 3000
@@ -66,7 +70,7 @@ def auth_redirect_link(method):
}[method]
params = {
'redirect_uri': f"https://api.comma.ai/v2/auth/{provider_id}/redirect/",
'redirect_uri': f"{API_HOST}/v2/auth/{provider_id}/redirect/",
'state': f'service,localhost:{PORT}',
}
+3 -1
View File
@@ -1,6 +1,8 @@
#!/usr/bin/env python3
import sys
from openpilot.frogpilot.common.frogpilot_utilities import use_konik_server
if len(sys.argv) < 4:
print(f"{sys.argv[0]} <route> <segment> <frame number> [front|wide|driver]")
print('example: ./fetch_image_from_route.py "02c45f73a2e5c6e9|2020-06-01--18-03-08" 3 500 driver')
@@ -24,7 +26,7 @@ segment = int(sys.argv[2])
frame = int(sys.argv[3])
camera = cameras[sys.argv[4]] if len(sys.argv) > 4 and sys.argv[4] in cameras else "cameras"
url = f'https://api.commadotai.com/v1/route/{route}/files'
url = f'https://api.{'konik.ai' if use_konik_server() else 'commadotai.com'}/v1/route/{route}/files'
r = requests.get(url, headers={"Authorization": f"JWT {jwt}"}, timeout=10)
assert r.status_code == 200
print("got api response")