agnos updater UI (#21776)
* start agnos updater UI * wifi * progress * sometimes things fail * fix wifi * in launch script * fwd * fwd stderr * update that * release files Co-authored-by: Comma Device <device@comma.ai> old-commit-hash: 14d26d6d891865ca14a5242edce4b14f0912854e
This commit is contained in:
@@ -109,10 +109,7 @@ function tici_init {
|
||||
# Check if AGNOS update is required
|
||||
if [ $(< /VERSION) != "$AGNOS_VERSION" ]; then
|
||||
MANIFEST="$DIR/selfdrive/hardware/tici/agnos.json"
|
||||
$DIR/selfdrive/hardware/tici/agnos.py --swap $MANIFEST
|
||||
|
||||
sleep 1
|
||||
sudo reboot
|
||||
$DIR/selfdrive/hardware/tici/updater $DIR/selfdrive/hardware/tici/agnos.py $MANIFEST
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ selfdrive/hardware/tici/pins.py
|
||||
selfdrive/hardware/tici/agnos.py
|
||||
selfdrive/hardware/tici/agnos.json
|
||||
selfdrive/hardware/tici/amplifier.py
|
||||
selfdrive/hardware/tici/updater
|
||||
|
||||
selfdrive/ui/qt/spinner_larch64
|
||||
selfdrive/ui/qt/text_larch64
|
||||
|
||||
@@ -5,10 +5,9 @@ import hashlib
|
||||
import requests
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
from typing import Generator, Optional
|
||||
|
||||
from common.spinner import Spinner
|
||||
from typing import Generator
|
||||
|
||||
SPARSE_CHUNK_FMT = struct.Struct('H2xI4x')
|
||||
|
||||
@@ -127,7 +126,7 @@ def clear_partition_hash(target_slot_number: int, partition: dict) -> None:
|
||||
os.sync()
|
||||
|
||||
|
||||
def flash_partition(target_slot_number: int, partition: dict, cloudlog, spinner: Optional[Spinner] = None):
|
||||
def flash_partition(target_slot_number: int, partition: dict, cloudlog):
|
||||
cloudlog.info(f"Downloading and writing {partition['name']}")
|
||||
|
||||
if verify_partition(target_slot_number, partition):
|
||||
@@ -151,9 +150,7 @@ def flash_partition(target_slot_number: int, partition: dict, cloudlog, spinner:
|
||||
for chunk in unsparsify(downloader):
|
||||
raw_hash.update(chunk)
|
||||
out.write(chunk)
|
||||
|
||||
if spinner is not None:
|
||||
spinner.update_progress(out.tell(), partition_size)
|
||||
print(f"Installing {partition['name']}: {out.tell() / partition_size * 100}", file=sys.stderr)
|
||||
|
||||
if raw_hash.hexdigest().lower() != partition['hash_raw'].lower():
|
||||
raise Exception(f"Unsparse hash mismatch '{raw_hash.hexdigest().lower()}'")
|
||||
@@ -161,9 +158,6 @@ def flash_partition(target_slot_number: int, partition: dict, cloudlog, spinner:
|
||||
while not downloader.eof:
|
||||
out.write(downloader.read(1024 * 1024))
|
||||
|
||||
if spinner is not None:
|
||||
spinner.update_progress(out.tell(), partition_size)
|
||||
|
||||
if downloader.sha256.hexdigest().lower() != partition['hash'].lower():
|
||||
raise Exception("Uncompressed hash mismatch")
|
||||
|
||||
@@ -191,7 +185,7 @@ def swap(manifest_path: str, target_slot_number: int, cloudlog) -> None:
|
||||
cloudlog.error(f"Swap failed {out}")
|
||||
|
||||
|
||||
def flash_agnos_update(manifest_path: str, target_slot_number: int, cloudlog, spinner: Optional[Spinner] = None) -> None:
|
||||
def flash_agnos_update(manifest_path: str, target_slot_number: int, cloudlog) -> None:
|
||||
update = json.load(open(manifest_path))
|
||||
|
||||
cloudlog.info(f"Target slot {target_slot_number}")
|
||||
@@ -204,14 +198,12 @@ def flash_agnos_update(manifest_path: str, target_slot_number: int, cloudlog, sp
|
||||
|
||||
for retries in range(10):
|
||||
try:
|
||||
flash_partition(target_slot_number, partition, cloudlog, spinner)
|
||||
flash_partition(target_slot_number, partition, cloudlog)
|
||||
success = True
|
||||
break
|
||||
|
||||
except requests.exceptions.RequestException:
|
||||
cloudlog.exception("Failed")
|
||||
if spinner is not None:
|
||||
spinner.update("Waiting for internet...")
|
||||
cloudlog.info(f"Failed to download {partition['name']}, retrying ({retries})")
|
||||
time.sleep(10)
|
||||
|
||||
@@ -239,19 +231,15 @@ if __name__ == "__main__":
|
||||
parser.add_argument("manifest", help="Manifest json")
|
||||
args = parser.parse_args()
|
||||
|
||||
spinner = Spinner()
|
||||
spinner.update("Updating AGNOS")
|
||||
time.sleep(5)
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
target_slot_number = get_target_slot_number()
|
||||
if args.swap:
|
||||
while not verify_agnos_update(args.manifest, target_slot_number):
|
||||
logging.error("Verification failed. Flashing AGNOS")
|
||||
flash_agnos_update(args.manifest, target_slot_number, logging, spinner)
|
||||
flash_agnos_update(args.manifest, target_slot_number, logging)
|
||||
|
||||
logging.warning(f"Verification succeeded. Swapping to slot {target_slot_number}")
|
||||
swap(args.manifest, target_slot_number, logging)
|
||||
else:
|
||||
flash_agnos_update(args.manifest, target_slot_number, logging, spinner)
|
||||
flash_agnos_update(args.manifest, target_slot_number, logging)
|
||||
|
||||
3
selfdrive/hardware/tici/updater
Executable file
3
selfdrive/hardware/tici/updater
Executable file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:a242a48497a398be53dee2cbe7f1f11f37c0b3ad28c5bee51b5ab16ef0de1019
|
||||
size 7820552
|
||||
1
selfdrive/ui/.gitignore
vendored
1
selfdrive/ui/.gitignore
vendored
@@ -7,4 +7,5 @@ qt/spinner
|
||||
qt/setup/setup
|
||||
qt/setup/reset
|
||||
qt/setup/wifi
|
||||
qt/setup/updater
|
||||
qt/setup/installer_*
|
||||
|
||||
@@ -54,15 +54,18 @@ qt_env.Program("_ui", qt_src, LIBS=qt_libs)
|
||||
|
||||
# setup, factory resetter, and installer
|
||||
if arch != 'aarch64' and GetOption('setup'):
|
||||
qt_env.Program("qt/setup/reset", ["qt/setup/reset.cc"], LIBS=qt_libs)
|
||||
qt_env.Program("qt/setup/wifi", ["qt/setup/wifi.cc"], LIBS=qt_libs + ['common', 'json11'])
|
||||
|
||||
# TODO: do this for all resources once NEOS has rcc
|
||||
assets = "#selfdrive/assets/assets.cc"
|
||||
assets_src = "#selfdrive/assets/assets.qrc"
|
||||
qt_env.Command(assets, assets_src, f"rcc $SOURCES -o $TARGET")
|
||||
qt_env.Depends(assets, Glob('#selfdrive/assets/*', exclude=[assets, assets_src, "#selfdrive/assets/assets.o"]))
|
||||
qt_env.Program("qt/setup/setup", ["qt/setup/setup.cc", assets],
|
||||
asset_obj = qt_env.Object("assets", assets)
|
||||
|
||||
qt_env.Program("qt/setup/reset", ["qt/setup/reset.cc"], LIBS=qt_libs)
|
||||
qt_env.Program("qt/setup/wifi", ["qt/setup/wifi.cc"], LIBS=qt_libs + ['common', 'json11'])
|
||||
qt_env.Program("qt/setup/updater", ["qt/setup/updater.cc", asset_obj], LIBS=qt_libs)
|
||||
qt_env.Program("qt/setup/setup", ["qt/setup/setup.cc", asset_obj],
|
||||
LIBS=qt_libs + ['curl', 'common', 'json11'])
|
||||
|
||||
senv = qt_env.Clone()
|
||||
|
||||
175
selfdrive/ui/qt/setup/updater.cc
Normal file
175
selfdrive/ui/qt/setup/updater.cc
Normal file
@@ -0,0 +1,175 @@
|
||||
#include <QDebug>
|
||||
#include <QTimer>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "selfdrive/hardware/hw.h"
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/qt/qt_window.h"
|
||||
#include "selfdrive/ui/qt/offroad/networking.h"
|
||||
#include "selfdrive/ui/qt/setup/updater.h"
|
||||
|
||||
|
||||
Updater::Updater(const QString &updater_path, const QString &manifest_path, QWidget *parent)
|
||||
: updater(updater_path), manifest(manifest_path), QStackedWidget(parent) {
|
||||
|
||||
assert(updater.size());
|
||||
assert(manifest.size());
|
||||
|
||||
// initial prompt screen
|
||||
prompt = new QWidget;
|
||||
{
|
||||
QVBoxLayout *layout = new QVBoxLayout(prompt);
|
||||
layout->setContentsMargins(100, 250, 100, 100);
|
||||
|
||||
QLabel *title = new QLabel("Update Required");
|
||||
title->setStyleSheet("font-size: 80px; font-weight: bold;");
|
||||
layout->addWidget(title);
|
||||
|
||||
layout->addSpacing(75);
|
||||
|
||||
QLabel *desc = new QLabel("An operating system update is required. Connect your device to WiFi for the fastest update experience. The download size is approximately 1GB.");
|
||||
desc->setWordWrap(true);
|
||||
desc->setStyleSheet("font-size: 65px;");
|
||||
layout->addWidget(desc);
|
||||
|
||||
layout->addStretch();
|
||||
|
||||
QHBoxLayout *hlayout = new QHBoxLayout;
|
||||
hlayout->setSpacing(30);
|
||||
layout->addLayout(hlayout);
|
||||
|
||||
QPushButton *connect = new QPushButton("Connect to WiFi");
|
||||
connect->setObjectName("navBtn");
|
||||
QObject::connect(connect, &QPushButton::clicked, [=]() {
|
||||
setCurrentWidget(wifi);
|
||||
});
|
||||
hlayout->addWidget(connect);
|
||||
|
||||
QPushButton *install = new QPushButton("Install");
|
||||
install->setObjectName("navBtn");
|
||||
install->setStyleSheet("background-color: #465BEA;");
|
||||
QObject::connect(install, &QPushButton::clicked, this, &Updater::installUpdate);
|
||||
hlayout->addWidget(install);
|
||||
}
|
||||
|
||||
// wifi connection screen
|
||||
wifi = new QWidget;
|
||||
{
|
||||
QVBoxLayout *layout = new QVBoxLayout(wifi);
|
||||
layout->setContentsMargins(100, 100, 100, 100);
|
||||
|
||||
Networking *networking = new Networking(this, false);
|
||||
networking->setStyleSheet("Networking { background-color: #292929; border-radius: 13px; }");
|
||||
layout->addWidget(networking, 1);
|
||||
|
||||
QPushButton *back = new QPushButton("Back");
|
||||
back->setObjectName("navBtn");
|
||||
back->setStyleSheet("padding-left: 60px; padding-right: 60px;");
|
||||
QObject::connect(back, &QPushButton::clicked, [=]() {
|
||||
setCurrentWidget(prompt);
|
||||
});
|
||||
layout->addWidget(back, 0, Qt::AlignLeft);
|
||||
}
|
||||
|
||||
// progress screen
|
||||
progress = new QWidget;
|
||||
{
|
||||
QVBoxLayout *layout = new QVBoxLayout(progress);
|
||||
layout->setContentsMargins(150, 330, 150, 150);
|
||||
layout->setSpacing(0);
|
||||
|
||||
text = new QLabel("Installing...");
|
||||
text->setStyleSheet("font-size: 90px; font-weight: 600;");
|
||||
layout->addWidget(text, 0, Qt::AlignTop);
|
||||
|
||||
layout->addSpacing(100);
|
||||
|
||||
bar = new QProgressBar();
|
||||
bar->setRange(0, 100);
|
||||
bar->setTextVisible(false);
|
||||
bar->setFixedHeight(72);
|
||||
layout->addWidget(bar, 0, Qt::AlignTop);
|
||||
|
||||
layout->addStretch();
|
||||
|
||||
reboot = new QPushButton("Reboot");
|
||||
reboot->setObjectName("navBtn");
|
||||
reboot->setStyleSheet("padding-left: 60px; padding-right: 60px;");
|
||||
QObject::connect(reboot, &QPushButton::clicked, [=]() {
|
||||
Hardware::reboot();
|
||||
});
|
||||
layout->addWidget(reboot, 0, Qt::AlignLeft);
|
||||
reboot->hide();
|
||||
|
||||
layout->addStretch();
|
||||
}
|
||||
|
||||
addWidget(prompt);
|
||||
addWidget(wifi);
|
||||
addWidget(progress);
|
||||
|
||||
setStyleSheet(R"(
|
||||
* {
|
||||
color: white;
|
||||
font-family: Inter;
|
||||
}
|
||||
Updater {
|
||||
color: white;
|
||||
background-color: black;
|
||||
}
|
||||
QPushButton#navBtn {
|
||||
height: 160;
|
||||
font-size: 55px;
|
||||
font-weight: 400;
|
||||
border-radius: 10px;
|
||||
background-color: #333333;
|
||||
}
|
||||
QProgressBar {
|
||||
border: none;
|
||||
background-color: #292929;
|
||||
}
|
||||
QProgressBar::chunk {
|
||||
background-color: #364DEF;
|
||||
}
|
||||
)");
|
||||
}
|
||||
|
||||
void Updater::installUpdate() {
|
||||
setCurrentWidget(progress);
|
||||
QObject::connect(&proc, &QProcess::readyReadStandardError, this, &Updater::readProgress);
|
||||
QObject::connect(&proc, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &Updater::updateFinished);
|
||||
proc.setProcessChannelMode(QProcess::ForwardedOutputChannel);
|
||||
proc.start(updater, {"--swap", manifest});
|
||||
}
|
||||
|
||||
void Updater::readProgress() {
|
||||
auto lines = QString(proc.readAllStandardError());
|
||||
for (const QString &line : lines.trimmed().split("\n")) {
|
||||
auto parts = line.split(":");
|
||||
if (parts.size() == 2) {
|
||||
text->setText(parts[0]);
|
||||
bar->setValue((int)parts[1].toDouble());
|
||||
repaint();
|
||||
} else {
|
||||
qDebug() << line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Updater::updateFinished(int exitCode, QProcess::ExitStatus exitStatus) {
|
||||
qDebug() << "finished with " << exitCode;
|
||||
if (exitCode == 0) {
|
||||
Hardware::reboot();
|
||||
} else {
|
||||
text->setText("Update failed");
|
||||
reboot->show();
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
initApp();
|
||||
QApplication a(argc, argv);
|
||||
Updater updater(argv[1], argv[2]);
|
||||
setMainWindow(&updater);
|
||||
return a.exec();
|
||||
}
|
||||
30
selfdrive/ui/qt/setup/updater.h
Normal file
30
selfdrive/ui/qt/setup/updater.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include <QLabel>
|
||||
#include <QProcess>
|
||||
#include <QPushButton>
|
||||
#include <QProgressBar>
|
||||
#include <QStackedWidget>
|
||||
#include <QWidget>
|
||||
|
||||
class Updater : public QStackedWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Updater(const QString &updater_path, const QString &manifest_path, QWidget *parent = 0);
|
||||
|
||||
private slots:
|
||||
void installUpdate();
|
||||
void readProgress();
|
||||
void updateFinished(int exitCode, QProcess::ExitStatus exitStatus);
|
||||
|
||||
private:
|
||||
QString updater, manifest;
|
||||
|
||||
QLabel *text;
|
||||
QProgressBar *bar;
|
||||
QPushButton *reboot;
|
||||
QProcess proc;
|
||||
|
||||
QWidget *prompt, *wifi, *progress;
|
||||
};
|
||||
Reference in New Issue
Block a user