mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-07-07 14:42:08 +08:00
App / Version
This commit is contained in:
@@ -7,6 +7,7 @@ from openpilot.selfdrive.ui.widgets.offroad_alerts import UpdateAlert, OffroadAl
|
||||
from openpilot.selfdrive.ui.widgets.exp_mode_button import ExperimentalModeButton
|
||||
from openpilot.selfdrive.ui.widgets.prime import PrimeWidget
|
||||
from openpilot.selfdrive.ui.widgets.setup import SetupWidget
|
||||
from openpilot.selfdrive.ui.lib.starpilot_version import starpilot_display_description
|
||||
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos
|
||||
from openpilot.system.ui.lib.multilang import tr, trn
|
||||
@@ -228,5 +229,5 @@ class HomeLayout(Widget):
|
||||
|
||||
def _get_version_text(self) -> str:
|
||||
brand = "openpilot"
|
||||
description = self.params.get("UpdaterCurrentDescription")
|
||||
description = starpilot_display_description(self.params.get("UpdaterCurrentDescription"))
|
||||
return f"{brand} {description}" if description else brand
|
||||
|
||||
@@ -3,6 +3,7 @@ import time
|
||||
import datetime
|
||||
from pathlib import Path
|
||||
from openpilot.common.time_helpers import system_time_valid
|
||||
from openpilot.selfdrive.ui.lib.starpilot_version import starpilot_display_description
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||
from openpilot.system.ui.lib.application import gui_app
|
||||
from openpilot.system.ui.lib.multilang import tr, trn
|
||||
@@ -54,7 +55,7 @@ class SoftwareLayout(Widget):
|
||||
super().__init__()
|
||||
|
||||
self._onroad_label = ListItem(lambda: tr("Updates are only downloaded while the car is off."))
|
||||
self._version_item = text_item(lambda: tr("Current Version"), ui_state.params.get("UpdaterCurrentDescription") or "")
|
||||
self._version_item = text_item(lambda: tr("Current Version"), starpilot_display_description(ui_state.params.get("UpdaterCurrentDescription")))
|
||||
self._auto_updates_toggle = toggle_item(
|
||||
lambda: tr("Automatically Install Updates"),
|
||||
lambda: tr("Automatically install updates when parked with an active internet connection."),
|
||||
@@ -103,7 +104,7 @@ class SoftwareLayout(Widget):
|
||||
self._onroad_label.set_visible(ui_state.is_onroad())
|
||||
|
||||
# Update current version and release notes
|
||||
current_desc = ui_state.params.get("UpdaterCurrentDescription") or ""
|
||||
current_desc = starpilot_display_description(ui_state.params.get("UpdaterCurrentDescription"))
|
||||
current_release_notes = (ui_state.params.get("UpdaterCurrentReleaseNotes") or b"").decode("utf-8", "replace")
|
||||
self._version_item.action_item.set_text(current_desc)
|
||||
self._version_item.set_description(current_release_notes)
|
||||
@@ -154,7 +155,7 @@ class SoftwareLayout(Widget):
|
||||
# Update install button
|
||||
self._install_btn.set_visible(ui_state.is_offroad() and update_available)
|
||||
if update_available:
|
||||
new_desc = ui_state.params.get("UpdaterNewDescription") or ""
|
||||
new_desc = starpilot_display_description(ui_state.params.get("UpdaterNewDescription"))
|
||||
new_release_notes = (ui_state.params.get("UpdaterNewReleaseNotes") or b"").decode("utf-8", "replace")
|
||||
self._install_btn.action_item.set_text(tr("INSTALL"))
|
||||
self._install_btn.action_item.set_value(new_desc)
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
STARPILOT_DISPLAY_VERSION = "7.26"
|
||||
|
||||
|
||||
def starpilot_display_description(description: str | None) -> str:
|
||||
if not description:
|
||||
return ""
|
||||
|
||||
parts = [part.strip() for part in description.split(" / ")]
|
||||
if not parts:
|
||||
return STARPILOT_DISPLAY_VERSION
|
||||
|
||||
parts[0] = STARPILOT_DISPLAY_VERSION
|
||||
return " / ".join(parts)
|
||||
@@ -10,6 +10,7 @@ from openpilot.system.ui.widgets.layouts import HBoxLayout
|
||||
from openpilot.system.ui.widgets.icon_widget import IconWidget
|
||||
from openpilot.system.ui.widgets.label import UnifiedLabel
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos
|
||||
from openpilot.selfdrive.ui.lib.starpilot_version import STARPILOT_DISPLAY_VERSION
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||
|
||||
HEAD_BUTTON_FONT_SIZE = 40
|
||||
@@ -166,11 +167,10 @@ class MiciHomeLayout(Widget):
|
||||
self._did_long_press = False
|
||||
|
||||
def _get_version_text(self) -> tuple[str, str, str, str] | None:
|
||||
version = ui_state.params.get("Version")
|
||||
branch = ui_state.params.get("GitBranch")
|
||||
commit = ui_state.params.get("GitCommit")
|
||||
|
||||
if not all((version, branch, commit)):
|
||||
if not all((branch, commit)):
|
||||
return None
|
||||
|
||||
commit_date_raw = ui_state.params.get("GitCommitDate")
|
||||
@@ -181,7 +181,7 @@ class MiciHomeLayout(Widget):
|
||||
except (ValueError, IndexError, TypeError, AttributeError):
|
||||
date_str = ""
|
||||
|
||||
return version, branch, commit[:7], date_str
|
||||
return STARPILOT_DISPLAY_VERSION, branch, commit[:7], date_str
|
||||
|
||||
def _render(self, _):
|
||||
# TODO: why is there extra space here to get it to be flush?
|
||||
|
||||
@@ -5,6 +5,7 @@ from dataclasses import dataclass
|
||||
from enum import IntEnum
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.selfdrive.selfdrived.alertmanager import OFFROAD_ALERTS
|
||||
from openpilot.selfdrive.ui.lib.starpilot_version import STARPILOT_DISPLAY_VERSION
|
||||
from openpilot.system.hardware import HARDWARE
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.system.ui.widgets.label import UnifiedLabel
|
||||
@@ -252,7 +253,8 @@ class MiciOffroadAlerts(Scroller):
|
||||
# format: "version / branch / commit / date"
|
||||
parts = new_desc.split(" / ")
|
||||
if len(parts) > 3:
|
||||
version, date = parts[0], parts[3]
|
||||
date = parts[3]
|
||||
version = STARPILOT_DISPLAY_VERSION
|
||||
version_string = f"\nopenpilot {version}, {date}\n"
|
||||
|
||||
update_alert_data.text = f"Update available {version_string}. Click to update. Read the release notes at blog.comma.ai."
|
||||
|
||||
@@ -6,6 +6,7 @@ from enum import IntEnum
|
||||
import pyray as rl
|
||||
|
||||
from openpilot.common.time_helpers import system_time_valid
|
||||
from openpilot.selfdrive.ui.lib.starpilot_version import STARPILOT_DISPLAY_VERSION
|
||||
from openpilot.selfdrive.ui.mici.layouts.settings.device import EngagedConfirmationButton
|
||||
from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigParamControl
|
||||
from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationDialog, BigDialog
|
||||
@@ -71,11 +72,11 @@ class SoftwareInfoLayoutMici(Widget):
|
||||
def _update_state(self):
|
||||
desc = _split_description(ui_state.params.get("UpdaterCurrentDescription") or "")
|
||||
if desc is not None:
|
||||
version, branch, commit, date = desc
|
||||
self._version_text_label.set_text(f"{version} ({date})")
|
||||
_, branch, commit, date = desc
|
||||
self._version_text_label.set_text(f"{STARPILOT_DISPLAY_VERSION} ({date})")
|
||||
self._branch_text_label.set_text(f"{branch} ({commit})")
|
||||
else:
|
||||
self._version_text_label.set_text(ui_state.params.get("Version") or "N/A")
|
||||
self._version_text_label.set_text(STARPILOT_DISPLAY_VERSION if ui_state.params.get("Version") else "N/A")
|
||||
self._branch_text_label.set_text(ui_state.params.get("GitBranch") or "N/A")
|
||||
|
||||
def _render(self, _):
|
||||
@@ -201,7 +202,7 @@ class InstallUpdateButton(BigButton):
|
||||
super()._update_state()
|
||||
|
||||
desc = _split_description(ui_state.params.get("UpdaterNewDescription") or "")
|
||||
value = f"{desc[0]} ({desc[1]})" if desc is not None else ""
|
||||
value = f"{STARPILOT_DISPLAY_VERSION} ({desc[1]})" if desc is not None else ""
|
||||
if self.get_value() != value:
|
||||
self.set_value(value)
|
||||
|
||||
|
||||
@@ -337,12 +337,8 @@ void OffroadHome::refresh() {
|
||||
date->setVisible(util::system_time_valid() && !simple_mode);
|
||||
|
||||
if (simple_mode) {
|
||||
version->setText(getBrand() + " " + QString::fromStdString(params.get("UpdaterCurrentDescription")));
|
||||
version->setText(getBrand() + " " + formatStarPilotDisplayVersionDescription(QString::fromStdString(params.get("UpdaterCurrentDescription"))));
|
||||
} else {
|
||||
QString versionText = getVersion().left(14).trimmed();
|
||||
if (!versionText.startsWith("v", Qt::CaseInsensitive)) {
|
||||
versionText.prepend("v");
|
||||
}
|
||||
version->setText(getBrand() + " - " + versionText + " - " + cleanModelName(starpilot_toggles.value("model_name").toString()));
|
||||
version->setText(getBrand() + " - " + getStarPilotDisplayVersion() + " - " + cleanModelName(starpilot_toggles.value("model_name").toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,11 +191,11 @@ void SoftwarePanel::updateLabels() {
|
||||
targetBranchBtn->setValue(QString::fromStdString(params.get("UpdaterTargetBranch")));
|
||||
|
||||
// current + new versions
|
||||
versionLbl->setText(QString::fromStdString(params.get("UpdaterCurrentDescription")));
|
||||
versionLbl->setText(formatStarPilotDisplayVersionDescription(QString::fromStdString(params.get("UpdaterCurrentDescription"))));
|
||||
versionLbl->setDescription(QString::fromStdString(params.get("UpdaterCurrentReleaseNotes")));
|
||||
|
||||
installBtn->setVisible((!is_onroad || parked) && params.getBool("UpdateAvailable"));
|
||||
installBtn->setValue(QString::fromStdString(params.get("UpdaterNewDescription")));
|
||||
installBtn->setValue(formatStarPilotDisplayVersionDescription(QString::fromStdString(params.get("UpdaterNewDescription"))));
|
||||
installBtn->setDescription(QString::fromStdString(params.get("UpdaterNewReleaseNotes")));
|
||||
|
||||
update();
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <QLayoutItem>
|
||||
#include <QStyleOption>
|
||||
#include <QPainterPath>
|
||||
#include <QStringList>
|
||||
#include <QTextStream>
|
||||
#include <QtXml/QDomDocument>
|
||||
|
||||
@@ -30,6 +31,24 @@ QString getBrand() {
|
||||
return QObject::tr("StarPilot");
|
||||
}
|
||||
|
||||
QString getStarPilotDisplayVersion() {
|
||||
return "7.26";
|
||||
}
|
||||
|
||||
QString formatStarPilotDisplayVersionDescription(const QString &description) {
|
||||
if (description.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
QStringList parts = description.split(" / ");
|
||||
if (parts.isEmpty()) {
|
||||
return getStarPilotDisplayVersion();
|
||||
}
|
||||
|
||||
parts[0] = getStarPilotDisplayVersion();
|
||||
return parts.join(" / ");
|
||||
}
|
||||
|
||||
QString getUserAgent() {
|
||||
return "openpilot-" + getVersion();
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
|
||||
QString getVersion();
|
||||
QString getBrand();
|
||||
QString getStarPilotDisplayVersion();
|
||||
QString formatStarPilotDisplayVersionDescription(const QString &description);
|
||||
QString getUserAgent();
|
||||
std::optional<QString> getDongleId();
|
||||
QMap<QString, QString> getSupportedLanguages();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
all: unset;
|
||||
background-color: var(--main-fg);
|
||||
border-radius: var(--border-radius-sm);
|
||||
color: var(--text-on-primary);
|
||||
cursor: pointer;
|
||||
padding: var(--padding-xs) var(--padding-xs);
|
||||
transition: background-color var(--transition-fast), transform var(--transition-fast), box-shadow var(--transition-fast);
|
||||
@@ -41,6 +42,37 @@
|
||||
transform: var(--hover-scale-sm);
|
||||
}
|
||||
|
||||
.navkeys-app-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: var(--margin-base);
|
||||
}
|
||||
|
||||
.navkeys-app-container {
|
||||
flex-basis: min(100%, calc((var(--width-lg) * 2) + var(--gap-lg)));
|
||||
max-width: calc((var(--width-lg) * 2) + var(--gap-lg));
|
||||
}
|
||||
|
||||
.navkeys-copy-btn,
|
||||
.navkeys-link-btn {
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
gap: var(--gap-xs);
|
||||
justify-content: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.navkeys-copy-btn {
|
||||
min-width: 5.5rem;
|
||||
}
|
||||
|
||||
.navkeys-icon-btn {
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
min-width: 2.25rem;
|
||||
}
|
||||
|
||||
.navkeys-error {
|
||||
color: var(--danger-bg);
|
||||
font-size: var(--font-size-sm);
|
||||
@@ -72,6 +104,7 @@
|
||||
cursor: text;
|
||||
flex: 1;
|
||||
font-size: var(--font-size-base);
|
||||
min-width: 0;
|
||||
padding: var(--padding-xs) var(--padding-sm);
|
||||
transition: border-color var(--transition-fast), box-shadow var(--transition-fast), transform var(--transition-fast);
|
||||
}
|
||||
@@ -87,6 +120,11 @@
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.navkeys-token-input {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--font-size-sm);
|
||||
}
|
||||
|
||||
.navkeys-label {
|
||||
display: block;
|
||||
font-size: var(--font-size-sm);
|
||||
@@ -94,6 +132,10 @@
|
||||
margin-bottom: var(--margin-xs);
|
||||
}
|
||||
|
||||
.navkeys-link-btn {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.navkeys-message {
|
||||
color: var(--success-bg);
|
||||
font-size: var(--font-size-sm);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { html, reactive } from "/assets/vendor/arrow-core.js"
|
||||
import { Modal } from "/assets/components/modal.js";
|
||||
|
||||
const DEFAULT_PLAY_STORE_URL = "https://play.google.com/store/apps/details?id=com.embaucha.galaxynav&hl=en-US&ah=9FldHJ99kxL8oNbSlO5F4sQqwC4"
|
||||
|
||||
export function NavKeys() {
|
||||
const state = reactive({
|
||||
@@ -20,6 +22,12 @@ export function NavKeys() {
|
||||
publicKey: "", secretKey: "",
|
||||
editPublic: false, editSecret: false,
|
||||
savedPublic: false, savedSecret: false,
|
||||
|
||||
galaxyCookieName: "galaxy_session",
|
||||
galaxySessionToken: "",
|
||||
galaxyAppUrl: DEFAULT_PLAY_STORE_URL,
|
||||
galaxyPaired: false,
|
||||
galaxySessionVisible: false,
|
||||
|
||||
showDeleteModal: false,
|
||||
keyToDelete: null,
|
||||
@@ -60,6 +68,34 @@ export function NavKeys() {
|
||||
req: async (url, opts) => {
|
||||
const response = await fetch(url, opts)
|
||||
return { ok: response.ok, data: await response.json().catch(() => ({})) }
|
||||
},
|
||||
|
||||
copyText: async (text) => {
|
||||
if (!text) {
|
||||
throw new Error("Nothing to copy")
|
||||
}
|
||||
|
||||
if (navigator.clipboard?.writeText && window.isSecureContext) {
|
||||
await navigator.clipboard.writeText(text)
|
||||
return
|
||||
}
|
||||
|
||||
const textarea = document.createElement("textarea")
|
||||
textarea.value = text
|
||||
textarea.setAttribute("readonly", "")
|
||||
textarea.style.position = "fixed"
|
||||
textarea.style.left = "-9999px"
|
||||
textarea.style.opacity = "0"
|
||||
document.body.appendChild(textarea)
|
||||
textarea.select()
|
||||
|
||||
try {
|
||||
if (!document.execCommand("copy")) {
|
||||
throw new Error("Copy command failed")
|
||||
}
|
||||
} finally {
|
||||
textarea.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +132,7 @@ export function NavKeys() {
|
||||
|
||||
const api = {
|
||||
path: {
|
||||
galaxy: "/api/galaxy/session",
|
||||
key: "/api/navigation_key",
|
||||
nav: "/api/navigation"
|
||||
},
|
||||
@@ -103,7 +140,9 @@ export function NavKeys() {
|
||||
load: async () => {
|
||||
const { ok, data } = await util.req(api.path.nav)
|
||||
if (!ok) {
|
||||
return showMessage("error", "Failed to load keys...", "")
|
||||
showMessage("error", "Failed to load keys...", "")
|
||||
await api.loadGalaxySession()
|
||||
return
|
||||
}
|
||||
|
||||
state.amap1Key = data.amap1Key ?? ""
|
||||
@@ -119,6 +158,29 @@ export function NavKeys() {
|
||||
state.initialMapboxComplete = state.savedPublic && state.savedSecret
|
||||
|
||||
bumpImageVersion()
|
||||
await api.loadGalaxySession()
|
||||
},
|
||||
|
||||
loadGalaxySession: async () => {
|
||||
const { ok, data } = await util.req(api.path.galaxy)
|
||||
if (!ok) {
|
||||
return showMessage("error", "Failed to load Galaxy session...", "app")
|
||||
}
|
||||
|
||||
state.galaxyAppUrl = data.appUrl || DEFAULT_PLAY_STORE_URL
|
||||
state.galaxyCookieName = data.cookieName || "galaxy_session"
|
||||
state.galaxyPaired = !!data.paired
|
||||
state.galaxySessionToken = data.sessionToken || ""
|
||||
state.galaxySessionVisible = false
|
||||
},
|
||||
|
||||
copyGalaxySession: async () => {
|
||||
try {
|
||||
await util.copyText(state.galaxySessionToken)
|
||||
showMessage("message", "Session token copied!", "app")
|
||||
} catch (e) {
|
||||
showMessage("error", "Copy failed...", "app")
|
||||
}
|
||||
},
|
||||
|
||||
save: (kind) => async () => {
|
||||
@@ -295,6 +357,60 @@ export function NavKeys() {
|
||||
`
|
||||
}
|
||||
|
||||
function renderAppKeys() {
|
||||
return html`
|
||||
<div class="navkeys-title">App Keys</div>
|
||||
|
||||
<div class="navkeys-app-actions">
|
||||
<a
|
||||
class="navkeys-btn navkeys-link-btn"
|
||||
href="${() => state.galaxyAppUrl}"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank">
|
||||
<i class="bi bi-google-play"></i>
|
||||
<span>Install The App</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<label class="navkeys-label" for="galaxy-cookie-name">Cookie Name</label>
|
||||
<div class="navkeys-row">
|
||||
<input
|
||||
class="navkeys-input navkeys-token-input"
|
||||
id="galaxy-cookie-name"
|
||||
readonly
|
||||
value="${() => state.galaxyCookieName}"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label class="navkeys-label" for="galaxy-session-token">Session Token</label>
|
||||
<div class="navkeys-row">
|
||||
<input
|
||||
class="navkeys-input navkeys-token-input"
|
||||
id="galaxy-session-token"
|
||||
placeholder="${() => state.galaxyPaired ? "Session token unavailable..." : "Pair Galaxy to create a session token..."}"
|
||||
readonly
|
||||
type="${() => state.galaxySessionVisible ? "text" : "password"}"
|
||||
value="${() => state.galaxySessionToken}"
|
||||
/>
|
||||
<button
|
||||
aria-label="${() => state.galaxySessionVisible ? "Hide session token" : "Show session token"}"
|
||||
class="navkeys-btn navkeys-icon-btn"
|
||||
@click="${() => { state.galaxySessionVisible = !state.galaxySessionVisible }}"
|
||||
disabled="${() => !state.galaxySessionToken}"
|
||||
title="${() => state.galaxySessionVisible ? "Hide session token" : "Show session token"}">
|
||||
<i class="${() => `bi ${state.galaxySessionVisible ? "bi-eye-slash" : "bi-eye"}`}"></i>
|
||||
</button>
|
||||
<button
|
||||
class="navkeys-btn navkeys-copy-btn"
|
||||
@click="${api.copyGalaxySession}"
|
||||
disabled="${() => !state.galaxySessionToken}">
|
||||
<i class="bi bi-copy"></i>
|
||||
<span>Copy</span>
|
||||
</button>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="navkeys-wrapper navkeys-offset-top">
|
||||
<div class="navkeys-container">
|
||||
@@ -304,6 +420,10 @@ export function NavKeys() {
|
||||
<div class="navkeys-container">
|
||||
${renderGroup("Mapbox Keys", ["public", "secret"])}
|
||||
${renderStatus("mapbox")}
|
||||
</div>
|
||||
<div class="navkeys-container navkeys-app-container">
|
||||
${renderAppKeys()}
|
||||
${renderStatus("app")}
|
||||
</div>
|
||||
</div>
|
||||
${() => state.showDeleteModal ? Modal({
|
||||
|
||||
@@ -10,11 +10,11 @@ import { LateralManeuvers } from "/assets/components/tools/lateral_maneuvers.js"
|
||||
import { LongitudinalManeuvers } from "/assets/components/tools/longitudinal_maneuvers.js"
|
||||
import { MapsManager } from "/assets/components/tools/maps.js"
|
||||
import { NavDestination } from "/assets/components/navigation/navigation_destination.js?v=nav-search-context-1"
|
||||
import { NavKeys } from "/assets/components/navigation/navigation_keys.js?v=nav-search-context-1"
|
||||
import { NavKeys } from "/assets/components/navigation/navigation_keys.js?v=app-keys-session-1"
|
||||
import { RouteRecordings } from "/assets/components/recordings/dashcam_routes.js"
|
||||
import { SettingsView } from "/assets/components/settings.js"
|
||||
import { ScreenRecordings } from "/assets/components/recordings/screen_recordings.js"
|
||||
import { Sidebar } from "/assets/components/sidebar.js"
|
||||
import { Sidebar } from "/assets/components/sidebar.js?v=app-keys-session-1"
|
||||
import { SpeedLimits } from "/assets/components/tools/speed_limits.js"
|
||||
import { ModelManager } from "/assets/components/tools/model_manager.js?v=20260303t"
|
||||
import { LivePlots } from "/assets/components/tools/plots.js"
|
||||
|
||||
@@ -18,7 +18,7 @@ const MENU_ITEMS = {
|
||||
{ name: "Long Maneuvers", link: "/longitudinal_maneuvers", icon: "bi-signpost-split" },
|
||||
{ name: "Maps", link: "/manage_maps", icon: "bi-map" },
|
||||
{ name: "Navigation", link: "/set_navigation_destination", icon: "bi-geo-alt-fill" },
|
||||
{ name: "Navigation Keys", link: "/manage_navigation_keys", icon: "bi-key-fill" },
|
||||
{ name: "App Keys", link: "/manage_navigation_keys", icon: "bi-key-fill" },
|
||||
{ name: "Model Manager", link: "/manage_models", icon: "bi-cpu" },
|
||||
{ name: "Plots", link: "/plots", icon: "bi-graph-up-arrow" },
|
||||
{ name: "Testing Ground", link: "/testing_ground", icon: "bi-bezier2" },
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<link rel="stylesheet" href="/assets/components/main.css">
|
||||
<link rel="stylesheet" href="/assets/components/modal.css">
|
||||
<link rel="stylesheet" href="/assets/components/navigation/navigation_destination.css?v=nav-search-context-1">
|
||||
<link rel="stylesheet" href="/assets/components/navigation/navigation_keys.css?v=nav-search-context-1">
|
||||
<link rel="stylesheet" href="/assets/components/navigation/navigation_keys.css?v=app-keys-session-1">
|
||||
<link rel="stylesheet" href="/assets/components/recordings/dashcam_routes.css">
|
||||
<link rel="stylesheet" href="/assets/components/recordings/screen_recordings.css">
|
||||
<link rel="stylesheet" href="/assets/components/settings.css">
|
||||
|
||||
@@ -88,3 +88,10 @@ def test_navigation_last_position_rejects_stale_persisted_fix(monkeypatch):
|
||||
monkeypatch.setattr(the_galaxy, "system_time_valid", lambda: True)
|
||||
|
||||
assert the_galaxy._get_navigation_last_position() is None
|
||||
|
||||
|
||||
def test_galaxy_session_value_matches_cookie_format():
|
||||
assert the_galaxy._build_galaxy_session_value(
|
||||
"testGalaxySlug01",
|
||||
"a" * 64,
|
||||
) == f"testGalaxySlug01%3A{'a' * 64}"
|
||||
|
||||
@@ -484,6 +484,9 @@ KEYS = {
|
||||
"secret": ("secret", "sk.", "MapboxSecretKey", "Secret key", 80),
|
||||
}
|
||||
|
||||
GALAXY_COOKIE_NAME = "galaxy_session"
|
||||
GALAXY_PLAY_STORE_URL = "https://play.google.com/store/apps/details?id=com.embaucha.galaxynav&hl=en-US&ah=9FldHJ99kxL8oNbSlO5F4sQqwC4"
|
||||
|
||||
NAVIGATION_MEMORY_LOCATION_STALE_SECONDS = 10.0
|
||||
NAVIGATION_PERSISTED_LOCATION_FUTURE_SKEW_SECONDS = 60.0
|
||||
NAVIGATION_PERSISTED_LOCATION_MAX_AGE_SECONDS = 24 * 60 * 60
|
||||
@@ -500,6 +503,23 @@ MAPS_DOWNLOAD_PARAM = "DownloadMaps"
|
||||
MAPS_CANCEL_DOWNLOAD_PARAM = "CancelDownloadMaps"
|
||||
|
||||
|
||||
def _get_galaxy_dir():
|
||||
return Path(Paths.comma_home()) / "starpilot" / "data" / "galaxy" if PC else Path("/data/galaxy")
|
||||
|
||||
|
||||
def _read_galaxy_text(path):
|
||||
try:
|
||||
return path.read_text().strip() if path.is_file() else ""
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
|
||||
def _build_galaxy_session_value(slug, token):
|
||||
if not slug or not token:
|
||||
return ""
|
||||
return quote(f"{slug}:{token}", safe="")
|
||||
|
||||
|
||||
def _parse_last_gps_position(raw_value):
|
||||
if not raw_value:
|
||||
return None
|
||||
@@ -5571,22 +5591,32 @@ def setup(app):
|
||||
}), 202
|
||||
|
||||
# ── Galaxy pairing (mirrors settings.cc L262-282) ──────────────────
|
||||
GALAXY_DIR = Path("/data/galaxy")
|
||||
GALAXY_DIR = _get_galaxy_dir()
|
||||
GALAXY_AUTH_FILE = GALAXY_DIR / "glxyauth"
|
||||
GALAXY_SESSION_FILE = GALAXY_DIR / "glxysession"
|
||||
GALAXY_SLUG_FILE = GALAXY_DIR / "glxyslug"
|
||||
|
||||
@app.route("/api/galaxy/status", methods=["GET"])
|
||||
def galaxy_status():
|
||||
try:
|
||||
paired = GALAXY_AUTH_FILE.is_file() and len(GALAXY_AUTH_FILE.read_text().strip()) == 64
|
||||
except Exception:
|
||||
paired = False
|
||||
slug_file = GALAXY_DIR / "glxyslug"
|
||||
slug = slug_file.read_text().strip() if slug_file.is_file() else ""
|
||||
paired = len(_read_galaxy_text(GALAXY_AUTH_FILE)) == 64
|
||||
slug = _read_galaxy_text(GALAXY_SLUG_FILE)
|
||||
return jsonify({
|
||||
"paired": paired,
|
||||
"url": f"https://galaxy.firestar.link/{slug}" if slug else "",
|
||||
})
|
||||
|
||||
@app.route("/api/galaxy/session", methods=["GET"])
|
||||
def galaxy_session():
|
||||
slug = _read_galaxy_text(GALAXY_SLUG_FILE)
|
||||
token = _read_galaxy_text(GALAXY_SESSION_FILE)
|
||||
paired = len(_read_galaxy_text(GALAXY_AUTH_FILE)) == 64 and bool(slug and token)
|
||||
return jsonify({
|
||||
"appUrl": GALAXY_PLAY_STORE_URL,
|
||||
"cookieName": GALAXY_COOKIE_NAME,
|
||||
"paired": paired,
|
||||
"sessionToken": _build_galaxy_session_value(slug, token),
|
||||
})
|
||||
|
||||
@app.route("/api/galaxy/pair", methods=["POST"])
|
||||
def galaxy_pair():
|
||||
data = request.get_json() or {}
|
||||
@@ -5599,12 +5629,12 @@ def setup(app):
|
||||
GALAXY_AUTH_FILE.write_text(pw_hash)
|
||||
|
||||
# Generate 256-bit secure session token
|
||||
(GALAXY_DIR / "glxysession").write_text(secrets.token_hex(32))
|
||||
GALAXY_SESSION_FILE.write_text(secrets.token_hex(32))
|
||||
|
||||
# Generate 16-character alphanumeric routing slug
|
||||
charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
slug = ''.join(secrets.choice(charset) for _ in range(16))
|
||||
(GALAXY_DIR / "glxyslug").write_text(slug)
|
||||
GALAXY_SLUG_FILE.write_text(slug)
|
||||
|
||||
return jsonify({
|
||||
"message": "Pairing successful!",
|
||||
|
||||
Reference in New Issue
Block a user