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: 14d26d6d89
commatwo_master
Adeeb Shihadeh 4 years ago committed by GitHub
parent 5d94b9978f
commit f4708c153e
  1. 5
      launch_chffrplus.sh
  2. 1
      release/files_tici
  3. 28
      selfdrive/hardware/tici/agnos.py
  4. 3
      selfdrive/hardware/tici/updater
  5. 1
      selfdrive/ui/.gitignore
  6. 9
      selfdrive/ui/SConscript
  7. 175
      selfdrive/ui/qt/setup/updater.cc
  8. 30
      selfdrive/ui/qt/setup/updater.h

@ -109,10 +109,7 @@ function tici_init {
# Check if AGNOS update is required # Check if AGNOS update is required
if [ $(< /VERSION) != "$AGNOS_VERSION" ]; then if [ $(< /VERSION) != "$AGNOS_VERSION" ]; then
MANIFEST="$DIR/selfdrive/hardware/tici/agnos.json" MANIFEST="$DIR/selfdrive/hardware/tici/agnos.json"
$DIR/selfdrive/hardware/tici/agnos.py --swap $MANIFEST $DIR/selfdrive/hardware/tici/updater $DIR/selfdrive/hardware/tici/agnos.py $MANIFEST
sleep 1
sudo reboot
fi fi
} }

@ -19,6 +19,7 @@ selfdrive/hardware/tici/pins.py
selfdrive/hardware/tici/agnos.py selfdrive/hardware/tici/agnos.py
selfdrive/hardware/tici/agnos.json selfdrive/hardware/tici/agnos.json
selfdrive/hardware/tici/amplifier.py selfdrive/hardware/tici/amplifier.py
selfdrive/hardware/tici/updater
selfdrive/ui/qt/spinner_larch64 selfdrive/ui/qt/spinner_larch64
selfdrive/ui/qt/text_larch64 selfdrive/ui/qt/text_larch64

@ -5,10 +5,9 @@ import hashlib
import requests import requests
import struct import struct
import subprocess import subprocess
import sys
import os import os
from typing import Generator, Optional from typing import Generator
from common.spinner import Spinner
SPARSE_CHUNK_FMT = struct.Struct('H2xI4x') SPARSE_CHUNK_FMT = struct.Struct('H2xI4x')
@ -127,7 +126,7 @@ def clear_partition_hash(target_slot_number: int, partition: dict) -> None:
os.sync() 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']}") cloudlog.info(f"Downloading and writing {partition['name']}")
if verify_partition(target_slot_number, partition): 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): for chunk in unsparsify(downloader):
raw_hash.update(chunk) raw_hash.update(chunk)
out.write(chunk) out.write(chunk)
print(f"Installing {partition['name']}: {out.tell() / partition_size * 100}", file=sys.stderr)
if spinner is not None:
spinner.update_progress(out.tell(), partition_size)
if raw_hash.hexdigest().lower() != partition['hash_raw'].lower(): if raw_hash.hexdigest().lower() != partition['hash_raw'].lower():
raise Exception(f"Unsparse hash mismatch '{raw_hash.hexdigest().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: while not downloader.eof:
out.write(downloader.read(1024 * 1024)) 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(): if downloader.sha256.hexdigest().lower() != partition['hash'].lower():
raise Exception("Uncompressed hash mismatch") 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}") 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)) update = json.load(open(manifest_path))
cloudlog.info(f"Target slot {target_slot_number}") 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): for retries in range(10):
try: try:
flash_partition(target_slot_number, partition, cloudlog, spinner) flash_partition(target_slot_number, partition, cloudlog)
success = True success = True
break break
except requests.exceptions.RequestException: except requests.exceptions.RequestException:
cloudlog.exception("Failed") cloudlog.exception("Failed")
if spinner is not None:
spinner.update("Waiting for internet...")
cloudlog.info(f"Failed to download {partition['name']}, retrying ({retries})") cloudlog.info(f"Failed to download {partition['name']}, retrying ({retries})")
time.sleep(10) time.sleep(10)
@ -239,19 +231,15 @@ if __name__ == "__main__":
parser.add_argument("manifest", help="Manifest json") parser.add_argument("manifest", help="Manifest json")
args = parser.parse_args() args = parser.parse_args()
spinner = Spinner()
spinner.update("Updating AGNOS")
time.sleep(5)
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
target_slot_number = get_target_slot_number() target_slot_number = get_target_slot_number()
if args.swap: if args.swap:
while not verify_agnos_update(args.manifest, target_slot_number): while not verify_agnos_update(args.manifest, target_slot_number):
logging.error("Verification failed. Flashing AGNOS") 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}") logging.warning(f"Verification succeeded. Swapping to slot {target_slot_number}")
swap(args.manifest, target_slot_number, logging) swap(args.manifest, target_slot_number, logging)
else: else:
flash_agnos_update(args.manifest, target_slot_number, logging, spinner) flash_agnos_update(args.manifest, target_slot_number, logging)

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a242a48497a398be53dee2cbe7f1f11f37c0b3ad28c5bee51b5ab16ef0de1019
size 7820552

@ -7,4 +7,5 @@ qt/spinner
qt/setup/setup qt/setup/setup
qt/setup/reset qt/setup/reset
qt/setup/wifi qt/setup/wifi
qt/setup/updater
qt/setup/installer_* qt/setup/installer_*

@ -54,15 +54,18 @@ qt_env.Program("_ui", qt_src, LIBS=qt_libs)
# setup, factory resetter, and installer # setup, factory resetter, and installer
if arch != 'aarch64' and GetOption('setup'): 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 # TODO: do this for all resources once NEOS has rcc
assets = "#selfdrive/assets/assets.cc" assets = "#selfdrive/assets/assets.cc"
assets_src = "#selfdrive/assets/assets.qrc" assets_src = "#selfdrive/assets/assets.qrc"
qt_env.Command(assets, assets_src, f"rcc $SOURCES -o $TARGET") 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.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']) LIBS=qt_libs + ['curl', 'common', 'json11'])
senv = qt_env.Clone() senv = qt_env.Clone()

@ -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();
}

@ -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;
};
Loading…
Cancel
Save