From f4708c153ef1acf2bbce55c97dfeadd9c4f848c5 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 29 Jul 2021 15:57:58 -0700 Subject: [PATCH] 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 old-commit-hash: 14d26d6d891865ca14a5242edce4b14f0912854e --- launch_chffrplus.sh | 5 +- release/files_tici | 1 + selfdrive/hardware/tici/agnos.py | 28 ++--- selfdrive/hardware/tici/updater | 3 + selfdrive/ui/.gitignore | 1 + selfdrive/ui/SConscript | 9 +- selfdrive/ui/qt/setup/updater.cc | 175 +++++++++++++++++++++++++++++++ selfdrive/ui/qt/setup/updater.h | 30 ++++++ 8 files changed, 225 insertions(+), 27 deletions(-) create mode 100755 selfdrive/hardware/tici/updater create mode 100644 selfdrive/ui/qt/setup/updater.cc create mode 100644 selfdrive/ui/qt/setup/updater.h diff --git a/launch_chffrplus.sh b/launch_chffrplus.sh index 9abf85a524..8e270276fc 100755 --- a/launch_chffrplus.sh +++ b/launch_chffrplus.sh @@ -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 } diff --git a/release/files_tici b/release/files_tici index 69d4b121cf..f3037a1d4e 100644 --- a/release/files_tici +++ b/release/files_tici @@ -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 diff --git a/selfdrive/hardware/tici/agnos.py b/selfdrive/hardware/tici/agnos.py index 54edede39b..340e0887e4 100755 --- a/selfdrive/hardware/tici/agnos.py +++ b/selfdrive/hardware/tici/agnos.py @@ -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) diff --git a/selfdrive/hardware/tici/updater b/selfdrive/hardware/tici/updater new file mode 100755 index 0000000000..2d589b83c3 --- /dev/null +++ b/selfdrive/hardware/tici/updater @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a242a48497a398be53dee2cbe7f1f11f37c0b3ad28c5bee51b5ab16ef0de1019 +size 7820552 diff --git a/selfdrive/ui/.gitignore b/selfdrive/ui/.gitignore index 3f5116f24b..4525455912 100644 --- a/selfdrive/ui/.gitignore +++ b/selfdrive/ui/.gitignore @@ -7,4 +7,5 @@ qt/spinner qt/setup/setup qt/setup/reset qt/setup/wifi +qt/setup/updater qt/setup/installer_* diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index a283abbb16..61ca8c8687 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -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() diff --git a/selfdrive/ui/qt/setup/updater.cc b/selfdrive/ui/qt/setup/updater.cc new file mode 100644 index 0000000000..f35a062c90 --- /dev/null +++ b/selfdrive/ui/qt/setup/updater.cc @@ -0,0 +1,175 @@ +#include +#include +#include + +#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::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(); +} diff --git a/selfdrive/ui/qt/setup/updater.h b/selfdrive/ui/qt/setup/updater.h new file mode 100644 index 0000000000..af7373aaee --- /dev/null +++ b/selfdrive/ui/qt/setup/updater.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +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; +};