Unify neos/agnos updaters (#22109)
* start moving neos updater * downloading * ui * move recovery * resuming * add verification * fix up launch * test * update updater * fix mypy * fake updater * review suggestions * more tests * abc * update bin * raise Co-authored-by: Comma Device <device@comma.ai>pull/22196/head
parent
d5e6dd3d5b
commit
b3705ede5e
|
@ -176,7 +176,7 @@ pipeline {
|
|||
["test loggerd", "python selfdrive/loggerd/tests/test_loggerd.py"],
|
||||
["test encoder", "python selfdrive/loggerd/tests/test_encoder.py"],
|
||||
["test logcatd", "python selfdrive/logcatd/tests/test_logcatd_android.py"],
|
||||
//["test updater", "python installer/updater/test_updater.py"],
|
||||
["test updater", "python selfdrive/hardware/eon/test_neos_updater.py"],
|
||||
])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -354,7 +354,6 @@ Directory Structure
|
|||
.
|
||||
├── cereal # The messaging spec and libs used for all logs
|
||||
├── common # Library like functionality we've developed here
|
||||
├── installer/updater # Manages updates of NEOS
|
||||
├── opendbc # Files showing how to interpret data from cars
|
||||
├── panda # Code used to communicate on CAN
|
||||
├── phonelibs # External libraries
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
local/
|
||||
prod/
|
||||
staging/
|
|
@ -1,99 +0,0 @@
|
|||
CC = clang
|
||||
CXX = clang++
|
||||
|
||||
PHONELIBS = ../../phonelibs
|
||||
|
||||
WARN_FLAGS = -Werror=implicit-function-declaration \
|
||||
-Werror=incompatible-pointer-types \
|
||||
-Werror=int-conversion \
|
||||
-Werror=return-type \
|
||||
-Werror=format-extra-args
|
||||
|
||||
CFLAGS = -std=gnu11 -g -fPIC -O2 $(WARN_FLAGS)
|
||||
CXXFLAGS = -std=c++1z -g -fPIC -O2 $(WARN_FLAGS)
|
||||
|
||||
CURL_FLAGS = -I$(PHONELIBS)/curl/include
|
||||
CURL_LIBS = $(PHONELIBS)/curl/lib/libcurl.a \
|
||||
$(PHONELIBS)/zlib/lib/libz.a
|
||||
|
||||
BORINGSSL_FLAGS = -I$(PHONELIBS)/boringssl/include
|
||||
BORINGSSL_LIBS = $(PHONELIBS)/boringssl/lib/libssl_static.a \
|
||||
$(PHONELIBS)/boringssl/lib/libcrypto_static.a \
|
||||
|
||||
NANOVG_FLAGS = -I$(PHONELIBS)/nanovg
|
||||
|
||||
JSON11_FLAGS = -I$(PHONELIBS)/json11
|
||||
|
||||
OPENGL_LIBS = -lGLESv3
|
||||
|
||||
FRAMEBUFFER_LIBS = -lutils -lgui -lEGL
|
||||
|
||||
.PHONY: all
|
||||
all: updater
|
||||
|
||||
OBJS = opensans_regular.ttf.o \
|
||||
opensans_semibold.ttf.o \
|
||||
opensans_bold.ttf.o \
|
||||
../../selfdrive/common/util.o \
|
||||
../../selfdrive/common/touch.o \
|
||||
../../selfdrive/common/framebuffer.o \
|
||||
$(PHONELIBS)/json11/json11.o \
|
||||
$(PHONELIBS)/nanovg/nanovg.o
|
||||
|
||||
DEPS := $(OBJS:.o=.d)
|
||||
|
||||
updater: updater.o $(OBJS)
|
||||
@echo "[ LINK ] $@"
|
||||
$(CXX) $(CPPFLAGS) -fPIC -o 'updater' $^ \
|
||||
$(FRAMEBUFFER_LIBS) \
|
||||
$(CURL_LIBS) \
|
||||
$(BORINGSSL_LIBS) \
|
||||
-L/system/vendor/lib64 \
|
||||
$(OPENGL_LIBS) \
|
||||
-lcutils -lm -llog
|
||||
strip updater
|
||||
|
||||
opensans_regular.ttf.o: ../../selfdrive/assets/fonts/opensans_regular.ttf
|
||||
@echo "[ bin2o ] $@"
|
||||
cd '$(dir $<)' && ld -r -b binary '$(notdir $<)' -o '$(abspath $@)'
|
||||
|
||||
opensans_bold.ttf.o: ../../selfdrive/assets/fonts/opensans_bold.ttf
|
||||
@echo "[ bin2o ] $@"
|
||||
cd '$(dir $<)' && ld -r -b binary '$(notdir $<)' -o '$(abspath $@)'
|
||||
|
||||
opensans_semibold.ttf.o: ../../selfdrive/assets/fonts/opensans_semibold.ttf
|
||||
@echo "[ bin2o ] $@"
|
||||
cd '$(dir $<)' && ld -r -b binary '$(notdir $<)' -o '$(abspath $@)'
|
||||
|
||||
%.o: %.c
|
||||
mkdir -p $(@D)
|
||||
@echo "[ CC ] $@"
|
||||
$(CC) $(CPPFLAGS) $(CFLAGS) \
|
||||
-I../.. \
|
||||
-I$(PHONELIBS)/android_frameworks_native/include \
|
||||
-I$(PHONELIBS)/android_system_core/include \
|
||||
-I$(PHONELIBS)/android_hardware_libhardware/include \
|
||||
$(NANOVG_FLAGS) \
|
||||
-c -o '$@' '$<'
|
||||
|
||||
%.o: %.cc
|
||||
mkdir -p $(@D)
|
||||
@echo "[ CXX ] $@"
|
||||
$(CXX) $(CPPFLAGS) $(CXXFLAGS) \
|
||||
-I../../selfdrive \
|
||||
-I../../ \
|
||||
-I$(PHONELIBS)/android_frameworks_native/include \
|
||||
-I$(PHONELIBS)/android_system_core/include \
|
||||
-I$(PHONELIBS)/android_hardware_libhardware/include \
|
||||
$(NANOVG_FLAGS) \
|
||||
$(JSON11_FLAGS) \
|
||||
$(CURL_FLAGS) \
|
||||
$(BORINGSSL_FLAGS) \
|
||||
-c -o '$@' '$<'
|
||||
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -f $(OBJS) $(DEPS)
|
||||
|
||||
-include $(DEPS)
|
|
@ -1,82 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import time
|
||||
import unittest
|
||||
|
||||
from common.basedir import BASEDIR
|
||||
|
||||
UPDATER_PATH = os.path.join(BASEDIR, "installer/updater")
|
||||
UPDATER = os.path.join(UPDATER_PATH, "updater")
|
||||
UPDATE_MANIFEST = os.path.join(UPDATER_PATH, "update.json")
|
||||
|
||||
|
||||
class TestUpdater(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
# test that the updater builds
|
||||
cls.assertTrue(f"cd {UPDATER_PATH} && make clean && make", "updater failed to build")
|
||||
|
||||
# restore the checked-in version, since that's what actually runs on devices
|
||||
os.system(f"git reset --hard {UPDATER_PATH}")
|
||||
|
||||
def setUp(self):
|
||||
self._clear_dir()
|
||||
|
||||
def tearDown(self):
|
||||
self._clear_dir()
|
||||
|
||||
def _clear_dir(self):
|
||||
if os.path.isdir("/data/neoupdate"):
|
||||
shutil.rmtree("/data/neoupdate")
|
||||
|
||||
def _assert_ok(self, cmd, msg=None):
|
||||
self.assertTrue(os.system(cmd) == 0, msg)
|
||||
|
||||
def _assert_fails(self, cmd):
|
||||
self.assertFalse(os.system(cmd) == 0)
|
||||
|
||||
def test_background_download(self):
|
||||
self._assert_ok(f"{UPDATER} bgcache 'file://{UPDATE_MANIFEST}'")
|
||||
|
||||
def test_background_download_bad_manifest(self):
|
||||
# update with bad manifest should fail
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".json") as f:
|
||||
f.write("{}")
|
||||
self._assert_fails(f"{UPDATER} bgcache 'file://{f.name}'")
|
||||
|
||||
def test_cache_resume(self):
|
||||
self._assert_ok(f"{UPDATER} bgcache 'file://{UPDATE_MANIFEST}'")
|
||||
# a full download takes >1m, but resuming from fully cached should only be a few seconds
|
||||
start_time = time.monotonic()
|
||||
self._assert_ok(f"{UPDATER} bgcache 'file://{UPDATE_MANIFEST}'")
|
||||
self.assertLess(time.monotonic() - start_time, 10)
|
||||
|
||||
# make sure we can recover from corrupt downloads
|
||||
def test_recover_from_corrupt(self):
|
||||
# download the whole update
|
||||
self._assert_ok(f"{UPDATER} bgcache 'file://{UPDATE_MANIFEST}'")
|
||||
|
||||
# write some random bytes
|
||||
for f in os.listdir("/data/neoupdate"):
|
||||
with open(os.path.join("/data/neoupdate", f), "ab") as f:
|
||||
f.write(b"\xab"*20)
|
||||
|
||||
# this attempt should fail, then it unlinks
|
||||
self._assert_fails(f"{UPDATER} bgcache 'file://{UPDATE_MANIFEST}'")
|
||||
|
||||
# now it should pass
|
||||
self._assert_ok(f"{UPDATER} bgcache 'file://{UPDATE_MANIFEST}'")
|
||||
|
||||
# simple test that the updater doesn't crash in UI mode
|
||||
def test_ui_init(self):
|
||||
with subprocess.Popen(UPDATER) as proc:
|
||||
time.sleep(5)
|
||||
self.assertTrue(proc.poll() is None)
|
||||
proc.terminate()
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
Binary file not shown.
|
@ -1,803 +0,0 @@
|
|||
#include <sys/stat.h>
|
||||
#include <sys/statvfs.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include <curl/curl.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
#include <GLES3/gl3.h>
|
||||
#include "nanovg.h"
|
||||
#define NANOVG_GLES3_IMPLEMENTATION
|
||||
#include "json11.hpp"
|
||||
#include "nanovg_gl.h"
|
||||
#include "nanovg_gl_utils.h"
|
||||
|
||||
#include "selfdrive/common/framebuffer.h"
|
||||
#include "selfdrive/common/touch.h"
|
||||
#include "selfdrive/common/util.h"
|
||||
|
||||
#define USER_AGENT "NEOSUpdater-0.2"
|
||||
|
||||
#define MANIFEST_URL_NEOS_STAGING "https://github.com/commaai/eon-neos/raw/master/update.staging.json"
|
||||
#define MANIFEST_URL_NEOS_LOCAL "http://192.168.5.1:8000/neosupdate/update.local.json"
|
||||
#define MANIFEST_URL_NEOS "https://github.com/commaai/eon-neos/raw/master/update.json"
|
||||
const char *manifest_url = MANIFEST_URL_NEOS;
|
||||
|
||||
#define RECOVERY_DEV "/dev/block/bootdevice/by-name/recovery"
|
||||
#define RECOVERY_COMMAND "/cache/recovery/command"
|
||||
|
||||
#define UPDATE_DIR "/data/neoupdate"
|
||||
|
||||
extern const uint8_t bin_opensans_regular[] asm("_binary_opensans_regular_ttf_start");
|
||||
extern const uint8_t bin_opensans_regular_end[] asm("_binary_opensans_regular_ttf_end");
|
||||
extern const uint8_t bin_opensans_semibold[] asm("_binary_opensans_semibold_ttf_start");
|
||||
extern const uint8_t bin_opensans_semibold_end[] asm("_binary_opensans_semibold_ttf_end");
|
||||
extern const uint8_t bin_opensans_bold[] asm("_binary_opensans_bold_ttf_start");
|
||||
extern const uint8_t bin_opensans_bold_end[] asm("_binary_opensans_bold_ttf_end");
|
||||
|
||||
namespace {
|
||||
|
||||
std::string sha256_file(std::string fn, size_t limit=0) {
|
||||
SHA256_CTX ctx;
|
||||
SHA256_Init(&ctx);
|
||||
|
||||
FILE *file = fopen(fn.c_str(), "rb");
|
||||
if (!file) return "";
|
||||
|
||||
const size_t buf_size = 8192;
|
||||
std::unique_ptr<char[]> buffer( new char[ buf_size ] );
|
||||
|
||||
bool read_limit = (limit != 0);
|
||||
while (true) {
|
||||
size_t read_size = buf_size;
|
||||
if (read_limit) read_size = std::min(read_size, limit);
|
||||
size_t bytes_read = fread(buffer.get(), 1, read_size, file);
|
||||
if (!bytes_read) break;
|
||||
|
||||
SHA256_Update(&ctx, buffer.get(), bytes_read);
|
||||
|
||||
if (read_limit) {
|
||||
limit -= bytes_read;
|
||||
if (limit == 0) break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t hash[SHA256_DIGEST_LENGTH];
|
||||
SHA256_Final(hash, &ctx);
|
||||
|
||||
fclose(file);
|
||||
|
||||
return util::tohex(hash, sizeof(hash));
|
||||
}
|
||||
|
||||
size_t download_string_write(void *ptr, size_t size, size_t nmeb, void *up) {
|
||||
size_t sz = size * nmeb;
|
||||
((std::string*)up)->append((char*)ptr, sz);
|
||||
return sz;
|
||||
}
|
||||
|
||||
std::string download_string(CURL *curl, std::string url) {
|
||||
std::string os;
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
|
||||
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 0);
|
||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, USER_AGENT);
|
||||
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
|
||||
curl_easy_setopt(curl, CURLOPT_RESUME_FROM, 0);
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1);
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, download_string_write);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &os);
|
||||
CURLcode res = curl_easy_perform(curl);
|
||||
if (res != CURLE_OK) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
size_t download_file_write(void *ptr, size_t size, size_t nmeb, void *up) {
|
||||
return fwrite(ptr, size, nmeb, (FILE*)up);
|
||||
}
|
||||
|
||||
int battery_capacity() {
|
||||
std::string bat_cap_s = util::read_file("/sys/class/power_supply/battery/capacity");
|
||||
return atoi(bat_cap_s.c_str());
|
||||
}
|
||||
|
||||
int battery_current() {
|
||||
std::string current_now_s = util::read_file("/sys/class/power_supply/battery/current_now");
|
||||
return atoi(current_now_s.c_str());
|
||||
}
|
||||
|
||||
bool check_battery() {
|
||||
int bat_cap = battery_capacity();
|
||||
int current_now = battery_current();
|
||||
return bat_cap > 35 || (current_now < 0 && bat_cap > 10);
|
||||
}
|
||||
|
||||
bool check_space() {
|
||||
struct statvfs stat;
|
||||
if (statvfs("/data/", &stat) != 0) {
|
||||
return false;
|
||||
}
|
||||
size_t space = stat.f_bsize * stat.f_bavail;
|
||||
return space > 2000000000ULL; // 2GB
|
||||
}
|
||||
|
||||
static void start_settings_activity(const char* name) {
|
||||
char launch_cmd[1024];
|
||||
snprintf(launch_cmd, sizeof(launch_cmd),
|
||||
"am start -W --ez :settings:show_fragment_as_subsetting true -n 'com.android.settings/.%s'", name);
|
||||
system(launch_cmd);
|
||||
}
|
||||
|
||||
bool is_settings_active() {
|
||||
FILE *fp;
|
||||
char sys_output[4096];
|
||||
|
||||
fp = popen("/bin/dumpsys window windows", "r");
|
||||
if (fp == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool active = false;
|
||||
while (fgets(sys_output, sizeof(sys_output), fp) != NULL) {
|
||||
if (strstr(sys_output, "mCurrentFocus=null") != NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (strstr(sys_output, "mCurrentFocus=Window") != NULL) {
|
||||
active = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pclose(fp);
|
||||
|
||||
return active;
|
||||
}
|
||||
|
||||
struct Updater {
|
||||
bool do_exit = false;
|
||||
|
||||
TouchState touch;
|
||||
|
||||
int fb_w, fb_h;
|
||||
|
||||
std::unique_ptr<FrameBuffer> fb;
|
||||
NVGcontext *vg = NULL;
|
||||
int font_regular;
|
||||
int font_semibold;
|
||||
int font_bold;
|
||||
|
||||
std::thread update_thread_handle;
|
||||
|
||||
std::mutex lock;
|
||||
|
||||
enum UpdateState {
|
||||
CONFIRMATION,
|
||||
LOW_BATTERY,
|
||||
RUNNING,
|
||||
ERROR,
|
||||
};
|
||||
UpdateState state;
|
||||
|
||||
std::string progress_text;
|
||||
float progress_frac;
|
||||
|
||||
std::string error_text;
|
||||
|
||||
std::string low_battery_text;
|
||||
std::string low_battery_title;
|
||||
std::string low_battery_context;
|
||||
std::string battery_cap_text;
|
||||
int min_battery_cap = 35;
|
||||
|
||||
// button
|
||||
int b_x, b_w, b_y, b_h;
|
||||
int balt_x;
|
||||
|
||||
// download stage writes these for the installation stage
|
||||
int recovery_len;
|
||||
std::string recovery_hash;
|
||||
std::string recovery_fn;
|
||||
std::string ota_fn;
|
||||
|
||||
CURL *curl = NULL;
|
||||
|
||||
void ui_init() {
|
||||
touch_init(&touch);
|
||||
|
||||
fb = std::make_unique<FrameBuffer>("updater", 0x00001000, false, &fb_w, &fb_h);
|
||||
|
||||
fb->set_power(HWC_POWER_MODE_NORMAL);
|
||||
|
||||
vg = nvgCreateGLES3(NVG_ANTIALIAS | NVG_STENCIL_STROKES | NVG_DEBUG);
|
||||
assert(vg);
|
||||
|
||||
font_regular = nvgCreateFontMem(vg, "opensans_regular", (unsigned char*)bin_opensans_regular, (bin_opensans_regular_end - bin_opensans_regular), 0);
|
||||
assert(font_regular >= 0);
|
||||
|
||||
font_semibold = nvgCreateFontMem(vg, "opensans_semibold", (unsigned char*)bin_opensans_semibold, (bin_opensans_semibold_end - bin_opensans_semibold), 0);
|
||||
assert(font_semibold >= 0);
|
||||
|
||||
font_bold = nvgCreateFontMem(vg, "opensans_bold", (unsigned char*)bin_opensans_bold, (bin_opensans_bold_end - bin_opensans_bold), 0);
|
||||
assert(font_bold >= 0);
|
||||
|
||||
b_w = 640;
|
||||
balt_x = 200;
|
||||
b_x = fb_w-b_w-200;
|
||||
b_y = 720;
|
||||
b_h = 220;
|
||||
|
||||
if (download_stage(true)) {
|
||||
state = RUNNING;
|
||||
update_thread_handle = std::thread(&Updater::run_stages, this);
|
||||
} else {
|
||||
state = CONFIRMATION;
|
||||
}
|
||||
}
|
||||
|
||||
int download_file_xferinfo(curl_off_t dltotal, curl_off_t dlno,
|
||||
curl_off_t ultotal, curl_off_t ulnow) {
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(lock);
|
||||
if (dltotal != 0) {
|
||||
progress_frac = (float) dlno / dltotal;
|
||||
}
|
||||
}
|
||||
// printf("info: %ld %ld %f\n", dltotal, dlno, progress_frac);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool download_file(std::string url, std::string out_fn) {
|
||||
FILE *of = fopen(out_fn.c_str(), "ab");
|
||||
assert(of);
|
||||
|
||||
CURLcode res;
|
||||
long last_resume_from = 0;
|
||||
|
||||
fseek(of, 0, SEEK_END);
|
||||
|
||||
int tries = 4;
|
||||
|
||||
bool ret = false;
|
||||
|
||||
while (true) {
|
||||
long resume_from = ftell(of);
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
|
||||
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 0);
|
||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, USER_AGENT);
|
||||
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
|
||||
curl_easy_setopt(curl, CURLOPT_RESUME_FROM, resume_from);
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, download_file_write);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, of);
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_XFERINFODATA, this);
|
||||
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, &Updater::download_file_xferinfo);
|
||||
|
||||
CURLcode res = curl_easy_perform(curl);
|
||||
|
||||
long response_code = 0;
|
||||
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
|
||||
|
||||
// double content_length = 0.0;
|
||||
// curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &content_length);
|
||||
|
||||
printf("download %s res %d, code %ld, resume from %ld\n", url.c_str(), res, response_code, resume_from);
|
||||
if (res == CURLE_OK) {
|
||||
ret = true;
|
||||
break;
|
||||
} else if (res == CURLE_HTTP_RETURNED_ERROR && response_code == 416) {
|
||||
// failed because the file is already complete?
|
||||
ret = true;
|
||||
break;
|
||||
} else if (resume_from == last_resume_from) {
|
||||
// failed and dind't make make forward progress. only retry a couple times
|
||||
tries--;
|
||||
if (tries <= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
last_resume_from = resume_from;
|
||||
}
|
||||
// printf("res %d\n", res);
|
||||
|
||||
// printf("- %ld %f\n", response_code, content_length);
|
||||
|
||||
fclose(of);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void set_progress(std::string text) {
|
||||
std::lock_guard<std::mutex> guard(lock);
|
||||
progress_text = text;
|
||||
}
|
||||
|
||||
void set_error(std::string text) {
|
||||
std::lock_guard<std::mutex> guard(lock);
|
||||
error_text = text;
|
||||
state = ERROR;
|
||||
}
|
||||
|
||||
void set_battery_low() {
|
||||
std::lock_guard<std::mutex> guard(lock);
|
||||
state = LOW_BATTERY;
|
||||
}
|
||||
|
||||
void set_running() {
|
||||
std::lock_guard<std::mutex> guard(lock);
|
||||
state = RUNNING;
|
||||
}
|
||||
|
||||
std::string download(std::string url, std::string hash, std::string name, bool dry_run) {
|
||||
std::string out_fn = UPDATE_DIR "/" + util::base_name(url);
|
||||
|
||||
std::string fn_hash = sha256_file(out_fn);
|
||||
if (dry_run) {
|
||||
return (hash.compare(fn_hash) != 0) ? "" : out_fn;
|
||||
}
|
||||
|
||||
// start or resume downloading if hash doesn't match
|
||||
if (hash.compare(fn_hash) != 0) {
|
||||
set_progress("Downloading " + name + "...");
|
||||
bool r = download_file(url, out_fn);
|
||||
if (!r) {
|
||||
set_error("failed to download " + name);
|
||||
unlink(out_fn.c_str());
|
||||
return "";
|
||||
}
|
||||
fn_hash = sha256_file(out_fn);
|
||||
}
|
||||
|
||||
set_progress("Verifying " + name + "...");
|
||||
printf("got %s hash: %s\n", name.c_str(), hash.c_str());
|
||||
if (fn_hash != hash) {
|
||||
set_error(name + " was corrupt");
|
||||
unlink(out_fn.c_str());
|
||||
return "";
|
||||
}
|
||||
return out_fn;
|
||||
}
|
||||
|
||||
bool download_stage(bool dry_run = false) {
|
||||
curl = curl_easy_init();
|
||||
assert(curl);
|
||||
|
||||
// ** quick checks before download **
|
||||
|
||||
if (!check_space()) {
|
||||
if (!dry_run) set_error("2GB of free space required to update");
|
||||
return false;
|
||||
}
|
||||
|
||||
mkdir(UPDATE_DIR, 0777);
|
||||
|
||||
set_progress("Finding latest version...");
|
||||
std::string manifest_s = download_string(curl, manifest_url);
|
||||
printf("manifest: %s\n", manifest_s.c_str());
|
||||
|
||||
std::string err;
|
||||
auto manifest = json11::Json::parse(manifest_s, err);
|
||||
if (manifest.is_null() || !err.empty()) {
|
||||
set_error("failed to load update manifest");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string ota_url = manifest["ota_url"].string_value();
|
||||
std::string ota_hash = manifest["ota_hash"].string_value();
|
||||
|
||||
std::string recovery_url = manifest["recovery_url"].string_value();
|
||||
recovery_hash = manifest["recovery_hash"].string_value();
|
||||
recovery_len = manifest["recovery_len"].int_value();
|
||||
|
||||
// std::string installer_url = manifest["installer_url"].string_value();
|
||||
// std::string installer_hash = manifest["installer_hash"].string_value();
|
||||
|
||||
if (ota_url.empty() || ota_hash.empty()) {
|
||||
set_error("invalid update manifest");
|
||||
return false;
|
||||
}
|
||||
|
||||
// std::string installer_fn = download(installer_url, installer_hash, "installer");
|
||||
// if (installer_fn.empty()) {
|
||||
// //error'd
|
||||
// return;
|
||||
// }
|
||||
|
||||
// ** handle recovery download **
|
||||
if (recovery_url.empty() || recovery_hash.empty() || recovery_len == 0) {
|
||||
set_progress("Skipping recovery flash...");
|
||||
} else {
|
||||
// only download the recovery if it differs from what's flashed
|
||||
set_progress("Checking recovery...");
|
||||
std::string existing_recovery_hash = sha256_file(RECOVERY_DEV, recovery_len);
|
||||
printf("existing recovery hash: %s\n", existing_recovery_hash.c_str());
|
||||
|
||||
if (existing_recovery_hash != recovery_hash) {
|
||||
recovery_fn = download(recovery_url, recovery_hash, "recovery", dry_run);
|
||||
if (recovery_fn.empty()) {
|
||||
// error'd
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ** handle ota download **
|
||||
ota_fn = download(ota_url, ota_hash, "update", dry_run);
|
||||
if (ota_fn.empty()) {
|
||||
//error'd
|
||||
return false;
|
||||
}
|
||||
|
||||
// download sucessful
|
||||
return true;
|
||||
}
|
||||
|
||||
// thread that handles downloading and installing the update
|
||||
void run_stages() {
|
||||
printf("run_stages start\n");
|
||||
|
||||
|
||||
// ** download update **
|
||||
|
||||
if (!check_battery()) {
|
||||
set_battery_low();
|
||||
int battery_cap = battery_capacity();
|
||||
while(battery_cap < min_battery_cap) {
|
||||
battery_cap = battery_capacity();
|
||||
battery_cap_text = std::to_string(battery_cap);
|
||||
util::sleep_for(1000);
|
||||
}
|
||||
set_running();
|
||||
}
|
||||
|
||||
bool sucess = download_stage();
|
||||
if (!sucess) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ** install update **
|
||||
|
||||
if (!check_battery()) {
|
||||
set_battery_low();
|
||||
int battery_cap = battery_capacity();
|
||||
while(battery_cap < min_battery_cap) {
|
||||
battery_cap = battery_capacity();
|
||||
battery_cap_text = std::to_string(battery_cap);
|
||||
util::sleep_for(1000);
|
||||
}
|
||||
set_running();
|
||||
}
|
||||
|
||||
if (!recovery_fn.empty()) {
|
||||
// flash recovery
|
||||
set_progress("Flashing recovery...");
|
||||
|
||||
FILE *flash_file = fopen(recovery_fn.c_str(), "rb");
|
||||
if (!flash_file) {
|
||||
set_error("failed to flash recovery");
|
||||
return;
|
||||
}
|
||||
|
||||
FILE *recovery_dev = fopen(RECOVERY_DEV, "w+b");
|
||||
if (!recovery_dev) {
|
||||
fclose(flash_file);
|
||||
set_error("failed to flash recovery");
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t buf_size = 4096;
|
||||
std::unique_ptr<char[]> buffer( new char[ buf_size ] );
|
||||
|
||||
while (true) {
|
||||
size_t bytes_read = fread(buffer.get(), 1, buf_size, flash_file);
|
||||
if (!bytes_read) break;
|
||||
|
||||
size_t bytes_written = fwrite(buffer.get(), 1, bytes_read, recovery_dev);
|
||||
if (bytes_read != bytes_written) {
|
||||
fclose(recovery_dev);
|
||||
fclose(flash_file);
|
||||
set_error("failed to flash recovery: write failed");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(recovery_dev);
|
||||
fclose(flash_file);
|
||||
|
||||
set_progress("Verifying flash...");
|
||||
std::string new_recovery_hash = sha256_file(RECOVERY_DEV, recovery_len);
|
||||
printf("new recovery hash: %s\n", new_recovery_hash.c_str());
|
||||
|
||||
if (new_recovery_hash != recovery_hash) {
|
||||
set_error("recovery flash corrupted");
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// write arguments to recovery
|
||||
FILE *cmd_file = fopen(RECOVERY_COMMAND, "wb");
|
||||
if (!cmd_file) {
|
||||
set_error("failed to reboot into recovery");
|
||||
return;
|
||||
}
|
||||
fprintf(cmd_file, "--update_package=%s\n", ota_fn.c_str());
|
||||
fclose(cmd_file);
|
||||
|
||||
set_progress("Rebooting");
|
||||
|
||||
// remove the continue.sh so we come back into the setup.
|
||||
// maybe we should go directly into the installer, but what if we don't come back with internet? :/
|
||||
//unlink("/data/data/com.termux/files/continue.sh");
|
||||
|
||||
// TODO: this should be generic between android versions
|
||||
// IPowerManager.reboot(confirm=false, reason="recovery", wait=true)
|
||||
system("service call power 16 i32 0 s16 recovery i32 1");
|
||||
while (true) pause();
|
||||
|
||||
// execl("/system/bin/reboot", "recovery");
|
||||
// set_error("failed to reboot into recovery");
|
||||
}
|
||||
|
||||
void draw_ack_screen(const char *title, const char *message, const char *button, const char *altbutton) {
|
||||
nvgFillColor(vg, nvgRGBA(255,255,255,255));
|
||||
nvgTextAlign(vg, NVG_ALIGN_CENTER | NVG_ALIGN_BASELINE);
|
||||
|
||||
nvgFontFace(vg, "opensans_bold");
|
||||
nvgFontSize(vg, 120.0f);
|
||||
nvgTextBox(vg, 110, 220, fb_w-240, title, NULL);
|
||||
|
||||
nvgFontFace(vg, "opensans_regular");
|
||||
nvgFontSize(vg, 86.0f);
|
||||
nvgTextBox(vg, 130, 380, fb_w-260, message, NULL);
|
||||
|
||||
// draw button
|
||||
if (button) {
|
||||
nvgBeginPath(vg);
|
||||
nvgFillColor(vg, nvgRGBA(8, 8, 8, 255));
|
||||
nvgRoundedRect(vg, b_x, b_y, b_w, b_h, 20);
|
||||
nvgFill(vg);
|
||||
|
||||
nvgFillColor(vg, nvgRGBA(255, 255, 255, 255));
|
||||
nvgFontFace(vg, "opensans_semibold");
|
||||
nvgTextAlign(vg, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE);
|
||||
nvgText(vg, b_x+b_w/2, b_y+b_h/2, button, NULL);
|
||||
|
||||
nvgBeginPath(vg);
|
||||
nvgStrokeColor(vg, nvgRGBA(255, 255, 255, 50));
|
||||
nvgStrokeWidth(vg, 5);
|
||||
nvgRoundedRect(vg, b_x, b_y, b_w, b_h, 20);
|
||||
nvgStroke(vg);
|
||||
}
|
||||
|
||||
// draw button
|
||||
if (altbutton) {
|
||||
nvgBeginPath(vg);
|
||||
nvgFillColor(vg, nvgRGBA(8, 8, 8, 255));
|
||||
nvgRoundedRect(vg, balt_x, b_y, b_w, b_h, 20);
|
||||
nvgFill(vg);
|
||||
|
||||
nvgFillColor(vg, nvgRGBA(255, 255, 255, 255));
|
||||
nvgFontFace(vg, "opensans_semibold");
|
||||
nvgTextAlign(vg, NVG_ALIGN_CENTER | NVG_ALIGN_MIDDLE);
|
||||
nvgText(vg, balt_x+b_w/2, b_y+b_h/2, altbutton, NULL);
|
||||
|
||||
nvgBeginPath(vg);
|
||||
nvgStrokeColor(vg, nvgRGBA(255, 255, 255, 50));
|
||||
nvgStrokeWidth(vg, 5);
|
||||
nvgRoundedRect(vg, balt_x, b_y, b_w, b_h, 20);
|
||||
nvgStroke(vg);
|
||||
}
|
||||
}
|
||||
|
||||
void draw_battery_screen() {
|
||||
low_battery_title = "Low Battery";
|
||||
low_battery_text = "Please connect EON to your charger. Update will continue once EON battery reaches 35%.";
|
||||
low_battery_context = "Current battery charge: " + battery_cap_text + "%";
|
||||
|
||||
nvgFillColor(vg, nvgRGBA(255,255,255,255));
|
||||
nvgTextAlign(vg, NVG_ALIGN_CENTER | NVG_ALIGN_BASELINE);
|
||||
|
||||
nvgFontFace(vg, "opensans_bold");
|
||||
nvgFontSize(vg, 120.0f);
|
||||
nvgTextBox(vg, 110, 220, fb_w-240, low_battery_title.c_str(), NULL);
|
||||
|
||||
nvgFontFace(vg, "opensans_regular");
|
||||
nvgFontSize(vg, 86.0f);
|
||||
nvgTextBox(vg, 130, 380, fb_w-260, low_battery_text.c_str(), NULL);
|
||||
|
||||
nvgFontFace(vg, "opensans_bold");
|
||||
nvgFontSize(vg, 86.0f);
|
||||
nvgTextBox(vg, 130, 700, fb_w-260, low_battery_context.c_str(), NULL);
|
||||
}
|
||||
|
||||
void draw_progress_screen() {
|
||||
// draw progress message
|
||||
nvgFontSize(vg, 64.0f);
|
||||
nvgFillColor(vg, nvgRGBA(255,255,255,255));
|
||||
nvgTextAlign(vg, NVG_ALIGN_CENTER | NVG_ALIGN_BASELINE);
|
||||
nvgFontFace(vg, "opensans_bold");
|
||||
nvgFontSize(vg, 86.0f);
|
||||
nvgTextBox(vg, 0, 380, fb_w, progress_text.c_str(), NULL);
|
||||
|
||||
// draw progress bar
|
||||
{
|
||||
int progress_width = 1000;
|
||||
int progress_x = fb_w/2-progress_width/2;
|
||||
int progress_y = 520;
|
||||
int progress_height = 50;
|
||||
|
||||
int powerprompt_y = 312;
|
||||
nvgFontFace(vg, "opensans_regular");
|
||||
nvgFontSize(vg, 64.0f);
|
||||
nvgText(vg, fb_w/2, 740, "Ensure your device remains connected to a power source.", NULL);
|
||||
|
||||
NVGpaint paint = nvgBoxGradient(
|
||||
vg, progress_x + 1, progress_y + 1,
|
||||
progress_width - 2, progress_height, 3, 4, nvgRGB(27, 27, 27), nvgRGB(27, 27, 27));
|
||||
nvgBeginPath(vg);
|
||||
nvgRoundedRect(vg, progress_x, progress_y, progress_width, progress_height, 12);
|
||||
nvgFillPaint(vg, paint);
|
||||
nvgFill(vg);
|
||||
|
||||
float value = std::min(std::max(0.0f, progress_frac), 1.0f);
|
||||
int bar_pos = ((progress_width - 2) * value);
|
||||
|
||||
paint = nvgBoxGradient(
|
||||
vg, progress_x, progress_y,
|
||||
bar_pos+1.5f, progress_height-1, 3, 4,
|
||||
nvgRGB(245, 245, 245), nvgRGB(105, 105, 105));
|
||||
|
||||
nvgBeginPath(vg);
|
||||
nvgRoundedRect(
|
||||
vg, progress_x+1, progress_y+1,
|
||||
bar_pos, progress_height-2, 12);
|
||||
nvgFillPaint(vg, paint);
|
||||
nvgFill(vg);
|
||||
}
|
||||
}
|
||||
|
||||
void ui_draw() {
|
||||
std::lock_guard<std::mutex> guard(lock);
|
||||
|
||||
nvgBeginFrame(vg, fb_w, fb_h, 1.0f);
|
||||
|
||||
switch (state) {
|
||||
case CONFIRMATION:
|
||||
draw_ack_screen("An update to NEOS is required.",
|
||||
"Your device will now be reset and upgraded. You may want to connect to wifi as download is around 1 GB. Existing data on device should not be lost.",
|
||||
"Continue",
|
||||
"Connect to WiFi");
|
||||
break;
|
||||
case LOW_BATTERY:
|
||||
draw_battery_screen();
|
||||
break;
|
||||
case RUNNING:
|
||||
draw_progress_screen();
|
||||
break;
|
||||
case ERROR:
|
||||
draw_ack_screen("There was an error", (error_text).c_str(), NULL, "Reboot");
|
||||
break;
|
||||
}
|
||||
|
||||
nvgEndFrame(vg);
|
||||
}
|
||||
|
||||
void ui_update() {
|
||||
std::lock_guard<std::mutex> guard(lock);
|
||||
|
||||
if (state == ERROR || state == CONFIRMATION) {
|
||||
int touch_x = -1, touch_y = -1;
|
||||
int res = touch_poll(&touch, &touch_x, &touch_y, 0);
|
||||
if (res == 1 && !is_settings_active()) {
|
||||
if (touch_x >= b_x && touch_x < b_x+b_w && touch_y >= b_y && touch_y < b_y+b_h) {
|
||||
if (state == CONFIRMATION) {
|
||||
state = RUNNING;
|
||||
update_thread_handle = std::thread(&Updater::run_stages, this);
|
||||
}
|
||||
}
|
||||
if (touch_x >= balt_x && touch_x < balt_x+b_w && touch_y >= b_y && touch_y < b_y+b_h) {
|
||||
if (state == CONFIRMATION) {
|
||||
start_settings_activity("Settings$WifiSettingsActivity");
|
||||
} else if (state == ERROR) {
|
||||
do_exit = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void go() {
|
||||
ui_init();
|
||||
|
||||
while (!do_exit) {
|
||||
ui_update();
|
||||
|
||||
glClearColor(0.08, 0.08, 0.08, 1.0);
|
||||
glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
|
||||
|
||||
// background
|
||||
nvgBeginPath(vg);
|
||||
NVGpaint bg = nvgLinearGradient(vg, fb_w, 0, fb_w, fb_h,
|
||||
nvgRGBA(0, 0, 0, 0), nvgRGBA(0, 0, 0, 255));
|
||||
nvgFillPaint(vg, bg);
|
||||
nvgRect(vg, 0, 0, fb_w, fb_h);
|
||||
nvgFill(vg);
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
ui_draw();
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
|
||||
fb->swap();
|
||||
|
||||
assert(glGetError() == GL_NO_ERROR);
|
||||
|
||||
// no simple way to do 30fps vsync with surfaceflinger...
|
||||
util::sleep_for(30);
|
||||
}
|
||||
|
||||
if (update_thread_handle.joinable()) {
|
||||
update_thread_handle.join();
|
||||
}
|
||||
|
||||
// reboot
|
||||
system("service call power 16 i32 0 i32 0 i32 1");
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
bool background_cache = false;
|
||||
if (argc > 1) {
|
||||
if (strcmp(argv[1], "local") == 0) {
|
||||
manifest_url = MANIFEST_URL_NEOS_LOCAL;
|
||||
} else if (strcmp(argv[1], "staging") == 0) {
|
||||
manifest_url = MANIFEST_URL_NEOS_STAGING;
|
||||
} else if (strcmp(argv[1], "bgcache") == 0) {
|
||||
manifest_url = argv[2];
|
||||
background_cache = true;
|
||||
} else {
|
||||
manifest_url = argv[1];
|
||||
}
|
||||
}
|
||||
|
||||
printf("updating from %s\n", manifest_url);
|
||||
Updater updater;
|
||||
|
||||
int err = 0;
|
||||
if (background_cache) {
|
||||
err = !updater.download_stage();
|
||||
} else {
|
||||
updater.go();
|
||||
}
|
||||
return err;
|
||||
}
|
|
@ -90,7 +90,11 @@ function two_init {
|
|||
|
||||
# Check for NEOS update
|
||||
if [ $(< /VERSION) != "$REQUIRED_NEOS_VERSION" ]; then
|
||||
"$DIR/installer/updater/updater" "file://$DIR/installer/updater/update.json"
|
||||
echo "Installing NEOS update"
|
||||
NEOS_PY="$DIR/selfdrive/hardware/eon/neos.py"
|
||||
MANIFEST="$DIR/selfdrive/hardware/eon/neos.json"
|
||||
$NEOS_PY --swap-if-ready $MANIFEST
|
||||
$DIR/selfdrive/hardware/eon/updater $NEOS_PY $MANIFEST
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
|
@ -188,9 +188,6 @@ selfdrive/debug/*.py
|
|||
selfdrive/common/SConscript
|
||||
selfdrive/common/version.h
|
||||
|
||||
selfdrive/common/framebuffer.h
|
||||
selfdrive/common/framebuffer.cc
|
||||
selfdrive/common/touch.[c,h]
|
||||
selfdrive/common/swaglog.h
|
||||
selfdrive/common/swaglog.cc
|
||||
selfdrive/common/util.cc
|
||||
|
@ -271,9 +268,12 @@ selfdrive/hardware/base.h
|
|||
selfdrive/hardware/base.py
|
||||
selfdrive/hardware/hw.h
|
||||
selfdrive/hardware/eon/__init__.py
|
||||
selfdrive/hardware/eon/androidd.py
|
||||
selfdrive/hardware/eon/hardware.h
|
||||
selfdrive/hardware/eon/hardware.py
|
||||
selfdrive/hardware/eon/androidd.py
|
||||
selfdrive/hardware/eon/neos.py
|
||||
selfdrive/hardware/eon/neos.json
|
||||
selfdrive/hardware/eon/updater
|
||||
selfdrive/hardware/tici/__init__.py
|
||||
selfdrive/hardware/tici/hardware.py
|
||||
selfdrive/hardware/tici/amplifier.py
|
||||
|
@ -483,11 +483,6 @@ phonelibs/android_frameworks_native/**
|
|||
phonelibs/android_hardware_libhardware/**
|
||||
phonelibs/android_system_core/**
|
||||
|
||||
installer/updater/updater
|
||||
installer/updater/updater.cc
|
||||
installer/updater/update.json
|
||||
installer/updater/Makefile
|
||||
|
||||
scripts/update_now.sh
|
||||
scripts/stop_updater.sh
|
||||
|
||||
|
|
|
@ -22,10 +22,6 @@ files = [
|
|||
]
|
||||
|
||||
if arch == "aarch64":
|
||||
files += [
|
||||
'framebuffer.cc',
|
||||
'touch.c',
|
||||
]
|
||||
_gpu_libs = ['gui', 'adreno_utils']
|
||||
elif arch == "larch64":
|
||||
_gpu_libs = ["GLESv2"]
|
||||
|
|
|
@ -1,146 +0,0 @@
|
|||
#include "selfdrive/common/framebuffer.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cassert>
|
||||
|
||||
#include "selfdrive/common/util.h"
|
||||
|
||||
#include <ui/DisplayInfo.h>
|
||||
|
||||
#include <gui/ISurfaceComposer.h>
|
||||
#include <gui/Surface.h>
|
||||
#include <gui/SurfaceComposerClient.h>
|
||||
#include <GLES2/gl2.h>
|
||||
#include <EGL/eglext.h>
|
||||
|
||||
#define BACKLIGHT_LEVEL 205
|
||||
|
||||
using namespace android;
|
||||
|
||||
struct FramebufferState {
|
||||
sp<SurfaceComposerClient> session;
|
||||
sp<IBinder> dtoken;
|
||||
DisplayInfo dinfo;
|
||||
sp<SurfaceControl> control;
|
||||
|
||||
sp<Surface> s;
|
||||
EGLDisplay display;
|
||||
|
||||
EGLint egl_major, egl_minor;
|
||||
EGLConfig config;
|
||||
EGLSurface surface;
|
||||
EGLContext context;
|
||||
};
|
||||
|
||||
void FrameBuffer::swap() {
|
||||
eglSwapBuffers(s->display, s->surface);
|
||||
assert(glGetError() == GL_NO_ERROR);
|
||||
}
|
||||
|
||||
bool set_brightness(int brightness) {
|
||||
char bright[64];
|
||||
snprintf(bright, sizeof(bright), "%d", brightness);
|
||||
return 0 == util::write_file("/sys/class/leds/lcd-backlight/brightness", bright, strlen(bright));
|
||||
}
|
||||
|
||||
void FrameBuffer::set_power(int mode) {
|
||||
SurfaceComposerClient::setDisplayPowerMode(s->dtoken, mode);
|
||||
}
|
||||
|
||||
FrameBuffer::FrameBuffer(const char *name, uint32_t layer, int alpha, int *out_w, int *out_h) {
|
||||
s = new FramebufferState;
|
||||
|
||||
s->session = new SurfaceComposerClient();
|
||||
assert(s->session != NULL);
|
||||
|
||||
s->dtoken = SurfaceComposerClient::getBuiltInDisplay(
|
||||
ISurfaceComposer::eDisplayIdMain);
|
||||
assert(s->dtoken != NULL);
|
||||
|
||||
status_t status = SurfaceComposerClient::getDisplayInfo(s->dtoken, &s->dinfo);
|
||||
assert(status == 0);
|
||||
|
||||
//int orientation = 3; // rotate framebuffer 270 degrees
|
||||
int orientation = 1; // rotate framebuffer 90 degrees
|
||||
if(orientation == 1 || orientation == 3) {
|
||||
int temp = s->dinfo.h;
|
||||
s->dinfo.h = s->dinfo.w;
|
||||
s->dinfo.w = temp;
|
||||
}
|
||||
|
||||
printf("dinfo %dx%d\n", s->dinfo.w, s->dinfo.h);
|
||||
|
||||
Rect destRect(s->dinfo.w, s->dinfo.h);
|
||||
s->session->setDisplayProjection(s->dtoken, orientation, destRect, destRect);
|
||||
|
||||
s->control = s->session->createSurface(String8(name),
|
||||
s->dinfo.w, s->dinfo.h, PIXEL_FORMAT_RGBX_8888);
|
||||
assert(s->control != NULL);
|
||||
|
||||
SurfaceComposerClient::openGlobalTransaction();
|
||||
status = s->control->setLayer(layer);
|
||||
SurfaceComposerClient::closeGlobalTransaction();
|
||||
assert(status == 0);
|
||||
|
||||
s->s = s->control->getSurface();
|
||||
assert(s->s != NULL);
|
||||
|
||||
// init opengl and egl
|
||||
const EGLint attribs[] = {
|
||||
EGL_RED_SIZE, 8,
|
||||
EGL_GREEN_SIZE, 8,
|
||||
EGL_BLUE_SIZE, 8,
|
||||
EGL_ALPHA_SIZE, alpha ? 8 : 0,
|
||||
EGL_DEPTH_SIZE, 0,
|
||||
EGL_STENCIL_SIZE, 8,
|
||||
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT_KHR,
|
||||
// enable MSAA
|
||||
EGL_SAMPLE_BUFFERS, 1,
|
||||
EGL_SAMPLES, 4,
|
||||
EGL_NONE,
|
||||
};
|
||||
|
||||
s->display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||
assert(s->display != EGL_NO_DISPLAY);
|
||||
|
||||
int success = eglInitialize(s->display, &s->egl_major, &s->egl_minor);
|
||||
assert(success);
|
||||
|
||||
printf("egl version %d.%d\n", s->egl_major, s->egl_minor);
|
||||
|
||||
EGLint num_configs;
|
||||
success = eglChooseConfig(s->display, attribs, &s->config, 1, &num_configs);
|
||||
assert(success);
|
||||
|
||||
s->surface = eglCreateWindowSurface(s->display, s->config, s->s.get(), NULL);
|
||||
assert(s->surface != EGL_NO_SURFACE);
|
||||
|
||||
const EGLint context_attribs[] = {
|
||||
EGL_CONTEXT_CLIENT_VERSION, 3,
|
||||
EGL_NONE,
|
||||
};
|
||||
s->context = eglCreateContext(s->display, s->config, NULL, context_attribs);
|
||||
assert(s->context != EGL_NO_CONTEXT);
|
||||
|
||||
EGLint w, h;
|
||||
eglQuerySurface(s->display, s->surface, EGL_WIDTH, &w);
|
||||
eglQuerySurface(s->display, s->surface, EGL_HEIGHT, &h);
|
||||
printf("egl w %d h %d\n", w, h);
|
||||
|
||||
success = eglMakeCurrent(s->display, s->surface, s->surface, s->context);
|
||||
assert(success);
|
||||
|
||||
printf("gl version %s\n", glGetString(GL_VERSION));
|
||||
|
||||
set_brightness(BACKLIGHT_LEVEL);
|
||||
|
||||
if (out_w) *out_w = w;
|
||||
if (out_h) *out_h = h;
|
||||
}
|
||||
|
||||
FrameBuffer::~FrameBuffer() {
|
||||
eglDestroyContext(s->display, s->context);
|
||||
eglDestroySurface(s->display, s->surface);
|
||||
eglTerminate(s->display);
|
||||
delete s;
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "hardware/hwcomposer_defs.h"
|
||||
|
||||
bool set_brightness(int brightness);
|
||||
|
||||
struct FramebufferState;
|
||||
class FrameBuffer {
|
||||
public:
|
||||
FrameBuffer(const char *name, uint32_t layer, int alpha, int *out_w, int *out_h);
|
||||
~FrameBuffer();
|
||||
void set_power(int mode);
|
||||
void swap();
|
||||
private:
|
||||
FramebufferState *s;
|
||||
};
|
|
@ -1,96 +0,0 @@
|
|||
#include "selfdrive/common/touch.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/input.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/poll.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* this macro is used to tell if "bit" is set in "array"
|
||||
* it selects a byte from the array, and does a boolean AND
|
||||
* operation with a byte that only has the relevant bit set.
|
||||
* eg. to check for the 12th bit, we do (array[1] & 1<<4)
|
||||
*/
|
||||
#define test_bit(bit, array) (array[bit/8] & (1<<(bit%8)))
|
||||
|
||||
static int find_dev() {
|
||||
int err;
|
||||
|
||||
int ret = -1;
|
||||
|
||||
DIR *dir = opendir("/dev/input");
|
||||
assert(dir);
|
||||
struct dirent* de = NULL;
|
||||
while ((de = readdir(dir))) {
|
||||
if (strncmp(de->d_name, "event", 5)) continue;
|
||||
|
||||
int fd = openat(dirfd(dir), de->d_name, O_RDONLY);
|
||||
assert(fd >= 0);
|
||||
|
||||
unsigned char ev_bits[KEY_MAX / 8 + 1];
|
||||
err = ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(ev_bits)), ev_bits);
|
||||
assert(err >= 0);
|
||||
|
||||
if (test_bit(ABS_MT_POSITION_X, ev_bits) && test_bit(ABS_MT_POSITION_Y, ev_bits)) {
|
||||
ret = fd;
|
||||
break;
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
closedir(dir);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void touch_init(TouchState *s) {
|
||||
s->fd = find_dev();
|
||||
assert(s->fd >= 0);
|
||||
}
|
||||
|
||||
int touch_poll(TouchState *s, int* out_x, int* out_y, int timeout) {
|
||||
assert(out_x && out_y);
|
||||
bool up = false;
|
||||
while (true) {
|
||||
struct pollfd polls[] = {{
|
||||
.fd = s->fd,
|
||||
.events = POLLIN,
|
||||
}};
|
||||
int err = poll(polls, 1, timeout);
|
||||
if (err < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (!(polls[0].revents & POLLIN)) {
|
||||
break;
|
||||
}
|
||||
|
||||
struct input_event event;
|
||||
err = read(polls[0].fd, &event, sizeof(event));
|
||||
if (err < sizeof(event)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch (event.type) {
|
||||
case EV_ABS:
|
||||
if (event.code == ABS_MT_POSITION_X) {
|
||||
s->last_x = event.value;
|
||||
} else if (event.code == ABS_MT_POSITION_Y) {
|
||||
s->last_y = event.value;
|
||||
} else if (event.code == ABS_MT_TRACKING_ID && event.value != -1) {
|
||||
up = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (up) {
|
||||
// adjust for flippening
|
||||
*out_x = s->last_y;
|
||||
*out_y = 1080 - s->last_x;
|
||||
}
|
||||
return up;
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct TouchState {
|
||||
int fd;
|
||||
int last_x, last_y;
|
||||
} TouchState;
|
||||
|
||||
void touch_init(TouchState *s);
|
||||
int touch_poll(TouchState *s, int *out_x, int *out_y, int timeout);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,130 @@
|
|||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import requests
|
||||
|
||||
NEOSUPDATE_DIR = "/data/neoupdate"
|
||||
|
||||
RECOVERY_DEV = "/dev/block/bootdevice/by-name/recovery"
|
||||
RECOVERY_COMMAND = "/cache/recovery/command"
|
||||
|
||||
|
||||
def get_fn(url: str):
|
||||
return os.path.join(NEOSUPDATE_DIR, os.path.basename(url))
|
||||
|
||||
|
||||
def download_file(url: str, fn: str, sha256: str, display_name: str, cloudlog=logging) -> None:
|
||||
# check if already downloaded
|
||||
if check_hash(fn, sha256):
|
||||
cloudlog.info(f"{display_name} already cached")
|
||||
return
|
||||
|
||||
try:
|
||||
with open(fn, "ab+") as f:
|
||||
headers = {"Range": f"bytes={f.tell()}-"}
|
||||
r = requests.get(url, stream=True, allow_redirects=True, headers=headers)
|
||||
r.raise_for_status()
|
||||
|
||||
total = int(r.headers['Content-Length'])
|
||||
if 'Content-Range' in r.headers:
|
||||
total = int(r.headers['Content-Range'].split('/')[-1])
|
||||
|
||||
for chunk in r.iter_content(chunk_size=1024 * 1024):
|
||||
f.write(chunk)
|
||||
print(f"Downloading {display_name}: {f.tell() / total * 100}")
|
||||
except Exception:
|
||||
cloudlog.error("download error")
|
||||
if os.path.isfile(fn):
|
||||
os.unlink(fn)
|
||||
raise
|
||||
|
||||
if not check_hash(fn, sha256):
|
||||
if os.path.isfile(fn):
|
||||
os.unlink(fn)
|
||||
raise Exception("downloaded update failed hash check")
|
||||
|
||||
|
||||
def check_hash(fn: str, sha256: str, length: int = -1) -> bool:
|
||||
if not os.path.exists(fn):
|
||||
return False
|
||||
|
||||
h = hashlib.sha256()
|
||||
with open(fn, "rb") as f:
|
||||
while f.tell() != length:
|
||||
r = min(max(0, length - f.tell()), 1024 * 1024) if length > 0 else 1024 * 1024
|
||||
dat = f.read(r)
|
||||
if not dat:
|
||||
break
|
||||
h.update(dat)
|
||||
return h.hexdigest().lower() == sha256.lower()
|
||||
|
||||
|
||||
def flash_update(update_fn: str, out_path: str) -> None:
|
||||
with open(update_fn, "rb") as update, open(out_path, "w+b") as out:
|
||||
while True:
|
||||
dat = update.read(8192)
|
||||
if len(dat) == 0:
|
||||
break
|
||||
out.write(dat)
|
||||
|
||||
|
||||
def download_neos_update(manifest_path: str, cloudlog=logging) -> None:
|
||||
with open(manifest_path) as f:
|
||||
m = json.load(f)
|
||||
|
||||
os.makedirs(NEOSUPDATE_DIR, exist_ok=True)
|
||||
|
||||
# handle recovery updates
|
||||
if not check_hash(RECOVERY_DEV, m['recovery_hash'], m['recovery_len']):
|
||||
cloudlog.info("recovery needs update")
|
||||
recovery_fn = os.path.join(NEOSUPDATE_DIR, os.path.basename(m['recovery_url']))
|
||||
download_file(m['recovery_url'], recovery_fn, m['recovery_hash'], "recovery", cloudlog)
|
||||
|
||||
flash_update(recovery_fn, RECOVERY_DEV)
|
||||
assert check_hash(RECOVERY_DEV, m['recovery_hash'], m['recovery_len']), "recovery flash corrupted"
|
||||
cloudlog.info("recovery successfully flashed")
|
||||
|
||||
# download OTA update
|
||||
download_file(m['ota_url'], get_fn(m['ota_url']), m['ota_hash'], "system", cloudlog)
|
||||
|
||||
|
||||
def verify_update_ready(manifest_path: str) -> bool:
|
||||
with open(manifest_path) as f:
|
||||
m = json.load(f)
|
||||
|
||||
ota_downloaded = check_hash(get_fn(m['ota_url']), m['ota_hash'])
|
||||
recovery_flashed = check_hash(RECOVERY_DEV, m['recovery_hash'], m['recovery_len'])
|
||||
return ota_downloaded and recovery_flashed
|
||||
|
||||
|
||||
def perform_ota_update(manifest_path: str) -> None:
|
||||
with open(manifest_path) as f:
|
||||
m = json.load(f)
|
||||
|
||||
# reboot into recovery
|
||||
ota_fn = get_fn(m['ota_url'])
|
||||
with open(RECOVERY_COMMAND, "w") as rf:
|
||||
rf.write(f"--update_package={ota_fn}\n")
|
||||
os.system("service call power 16 i32 0 s16 recovery i32 1")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="NEOS update utility",
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument("--swap", action="store_true", help="Peform update after downloading")
|
||||
parser.add_argument("--swap-if-ready", action="store_true", help="Perform update if already downloaded")
|
||||
parser.add_argument("manifest", help="Manifest json")
|
||||
args = parser.parse_args()
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
if args.swap_if_ready:
|
||||
if verify_update_ready(args.manifest):
|
||||
perform_ota_update(args.manifest)
|
||||
else:
|
||||
download_neos_update(args.manifest, logging)
|
||||
if args.swap:
|
||||
perform_ota_update(args.manifest)
|
|
@ -0,0 +1,145 @@
|
|||
#!/usr/bin/env python3
|
||||
import hashlib
|
||||
import http.server
|
||||
import json
|
||||
import os
|
||||
import unittest
|
||||
import random
|
||||
import requests
|
||||
import shutil
|
||||
import socketserver
|
||||
import tempfile
|
||||
import multiprocessing
|
||||
from pathlib import Path
|
||||
|
||||
from selfdrive.hardware.eon.neos import RECOVERY_DEV, NEOSUPDATE_DIR, get_fn, download_file, \
|
||||
verify_update_ready, download_neos_update
|
||||
|
||||
EON_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)))
|
||||
MANIFEST = os.path.join(EON_DIR, "neos.json")
|
||||
|
||||
PORT = 8000
|
||||
|
||||
def server_thread(port):
|
||||
socketserver.TCPServer.allow_reuse_address = True
|
||||
httpd = socketserver.TCPServer(("", port), http.server.SimpleHTTPRequestHandler)
|
||||
httpd.serve_forever()
|
||||
|
||||
|
||||
class TestNeosUpdater(unittest.TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
# generate a fake manifest
|
||||
cls.manifest = {}
|
||||
for i in ('ota', 'recovery'):
|
||||
with tempfile.NamedTemporaryFile(delete=False, dir=os.getcwd()) as f:
|
||||
dat = os.urandom(random.randint(1000, 100000))
|
||||
f.write(dat)
|
||||
cls.manifest[f"{i}_url"] = f"http://localhost:{PORT}/" + os.path.relpath(f.name)
|
||||
cls.manifest[F"{i}_hash"] = hashlib.sha256(dat).hexdigest()
|
||||
if i == "recovery":
|
||||
cls.manifest["recovery_len"] = len(dat)
|
||||
|
||||
with tempfile.NamedTemporaryFile(delete=False, mode='w') as f:
|
||||
f.write(json.dumps(cls.manifest))
|
||||
cls.fake_manifest = f.name
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
os.unlink(cls.fake_manifest)
|
||||
os.unlink(os.path.basename(cls.manifest['ota_url']))
|
||||
os.unlink(os.path.basename(cls.manifest['recovery_url']))
|
||||
|
||||
def setUp(self):
|
||||
# server for update files
|
||||
self.server = multiprocessing.Process(target=server_thread, args=(PORT, ))
|
||||
self.server.start()
|
||||
|
||||
# clean up
|
||||
if os.path.exists(NEOSUPDATE_DIR):
|
||||
shutil.rmtree(NEOSUPDATE_DIR)
|
||||
|
||||
def tearDown(self):
|
||||
self.server.kill()
|
||||
self.server.join(1)
|
||||
|
||||
def _corrupt_recovery(self):
|
||||
with open(RECOVERY_DEV, "wb") as f:
|
||||
f.write(b'\x00'*100)
|
||||
|
||||
def test_manifest(self):
|
||||
with open(MANIFEST) as f:
|
||||
m = json.load(f)
|
||||
|
||||
assert m['ota_hash'] in m['ota_url']
|
||||
assert m['recovery_hash'] in m['recovery_url']
|
||||
assert m['recovery_len'] > 0
|
||||
|
||||
for url in (m['ota_url'], m['recovery_url']):
|
||||
r = requests.head(m['recovery_url'])
|
||||
r.raise_for_status()
|
||||
self.assertEqual(r.headers['Content-Type'], "application/octet-stream")
|
||||
if url == m['recovery_url']:
|
||||
self.assertEqual(int(r.headers['Content-Length']), m['recovery_len'])
|
||||
|
||||
def test_download_hash_check(self):
|
||||
os.makedirs(NEOSUPDATE_DIR, exist_ok=True)
|
||||
Path(get_fn(self.manifest['ota_url'])).touch()
|
||||
with self.assertRaisesRegex(Exception, "failed hash check"):
|
||||
download_file(self.manifest['ota_url'], get_fn(self.manifest['ota_url']),
|
||||
self.manifest['ota_hash']+'a', "system")
|
||||
|
||||
# should've unlinked after the failed hash check, should succeed now
|
||||
download_file(self.manifest['ota_url'], get_fn(self.manifest['ota_url']),
|
||||
self.manifest['ota_hash'], "system")
|
||||
|
||||
# TODO: needs an http server that supports Content-Range
|
||||
#def test_download_resume(self):
|
||||
# os.makedirs(NEOSUPDATE_DIR, exist_ok=True)
|
||||
# with open(os.path.basename(self.manifest['ota_url']), "rb") as src, \
|
||||
# open(get_fn(self.manifest['ota_url']), "wb") as dest:
|
||||
# l = dest.write(src.read(8192))
|
||||
# assert l == 8192
|
||||
# download_file(self.manifest['ota_url'], get_fn(self.manifest['ota_url']),
|
||||
# self.manifest['ota_hash'], "system")
|
||||
|
||||
def test_download_no_internet(self):
|
||||
self.server.kill()
|
||||
os.makedirs(NEOSUPDATE_DIR, exist_ok=True)
|
||||
# fail, no internet
|
||||
with self.assertRaises(requests.exceptions.ConnectionError):
|
||||
download_file(self.manifest['ota_url'], get_fn(self.manifest['ota_url']),
|
||||
self.manifest['ota_hash'], "system")
|
||||
|
||||
# already cached, ensure we don't hit the server
|
||||
shutil.copyfile(os.path.basename(self.manifest['ota_url']), get_fn(self.manifest['ota_url']))
|
||||
download_file(self.manifest['ota_url'], get_fn(self.manifest['ota_url']),
|
||||
self.manifest['ota_hash'], "system")
|
||||
|
||||
|
||||
def test_download_update(self):
|
||||
download_neos_update(self.fake_manifest)
|
||||
self.assertTrue(verify_update_ready(self.fake_manifest))
|
||||
|
||||
def test_verify_update(self):
|
||||
# good state
|
||||
download_neos_update(self.fake_manifest)
|
||||
self.assertTrue(verify_update_ready(self.fake_manifest))
|
||||
|
||||
# corrupt recovery
|
||||
self._corrupt_recovery()
|
||||
self.assertFalse(verify_update_ready(self.fake_manifest))
|
||||
|
||||
# back to good state
|
||||
download_neos_update(self.fake_manifest)
|
||||
self.assertTrue(verify_update_ready(self.fake_manifest))
|
||||
|
||||
# corrupt ota
|
||||
self._corrupt_recovery()
|
||||
with open(os.path.join(NEOSUPDATE_DIR, os.path.basename(self.manifest['ota_url'])), "ab") as f:
|
||||
f.write(b'\x00')
|
||||
self.assertFalse(verify_update_ready(self.fake_manifest))
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
Binary file not shown.
|
@ -58,17 +58,18 @@ qt_src = ["main.cc", "ui.cc", "paint.cc", "qt/sidebar.cc", "qt/onroad.cc",
|
|||
qt_env.Program("_ui", qt_src + [asset_obj], LIBS=qt_libs)
|
||||
|
||||
|
||||
# setup, factory resetter, and agnos updater
|
||||
# setup and factory resetter
|
||||
if arch != 'aarch64' and GetOption('setup'):
|
||||
|
||||
qt_env.Program("qt/setup/reset", ["qt/setup/reset.cc"], LIBS=qt_libs)
|
||||
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'])
|
||||
|
||||
|
||||
# build installers
|
||||
if GetOption('setup'):
|
||||
# buidl updater UI
|
||||
qt_env.Program("qt/setup/updater", ["qt/setup/updater.cc", asset_obj], LIBS=qt_libs)
|
||||
|
||||
# build installers
|
||||
senv = qt_env.Clone()
|
||||
senv['LINKFLAGS'].append('-Wl,-strip-debug')
|
||||
|
||||
|
|
|
@ -5,9 +5,11 @@
|
|||
#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"
|
||||
|
||||
#ifndef QCOM
|
||||
#include "selfdrive/ui/qt/offroad/networking.h"
|
||||
#endif
|
||||
|
||||
Updater::Updater(const QString &updater_path, const QString &manifest_path, QWidget *parent)
|
||||
: updater(updater_path), manifest(manifest_path), QStackedWidget(parent) {
|
||||
|
@ -41,7 +43,11 @@ Updater::Updater(const QString &updater_path, const QString &manifest_path, QWid
|
|||
QPushButton *connect = new QPushButton("Connect to WiFi");
|
||||
connect->setObjectName("navBtn");
|
||||
QObject::connect(connect, &QPushButton::clicked, [=]() {
|
||||
#ifndef QCOM
|
||||
setCurrentWidget(wifi);
|
||||
#else
|
||||
HardwareEon::launch_wifi();
|
||||
#endif
|
||||
});
|
||||
hlayout->addWidget(connect);
|
||||
|
||||
|
@ -58,9 +64,11 @@ Updater::Updater(const QString &updater_path, const QString &manifest_path, QWid
|
|||
QVBoxLayout *layout = new QVBoxLayout(wifi);
|
||||
layout->setContentsMargins(100, 100, 100, 100);
|
||||
|
||||
#ifndef QCOM
|
||||
Networking *networking = new Networking(this, false);
|
||||
networking->setStyleSheet("Networking { background-color: #292929; border-radius: 13px; }");
|
||||
layout->addWidget(networking, 1);
|
||||
#endif
|
||||
|
||||
QPushButton *back = new QPushButton("Back");
|
||||
back->setObjectName("navBtn");
|
||||
|
@ -78,7 +86,7 @@ Updater::Updater(const QString &updater_path, const QString &manifest_path, QWid
|
|||
layout->setContentsMargins(150, 330, 150, 150);
|
||||
layout->setSpacing(0);
|
||||
|
||||
text = new QLabel("Installing...");
|
||||
text = new QLabel("Loading...");
|
||||
text->setStyleSheet("font-size: 90px; font-weight: 600;");
|
||||
layout->addWidget(text, 0, Qt::AlignTop);
|
||||
|
||||
|
@ -111,6 +119,7 @@ Updater::Updater(const QString &updater_path, const QString &manifest_path, QWid
|
|||
setStyleSheet(R"(
|
||||
* {
|
||||
color: white;
|
||||
outline: none;
|
||||
font-family: Inter;
|
||||
}
|
||||
Updater {
|
||||
|
@ -166,10 +175,25 @@ void Updater::updateFinished(int exitCode, QProcess::ExitStatus exitStatus) {
|
|||
}
|
||||
}
|
||||
|
||||
bool Updater::eventFilter(QObject *obj, QEvent *event) {
|
||||
#ifdef QCOM
|
||||
// filter out touches while in android activity
|
||||
const static QSet<QEvent::Type> filter_events({QEvent::MouseButtonPress, QEvent::MouseMove, QEvent::TouchBegin, QEvent::TouchUpdate, QEvent::TouchEnd});
|
||||
if (HardwareEon::launched_activity && filter_events.contains(event->type())) {
|
||||
HardwareEon::check_activity();
|
||||
if (HardwareEon::launched_activity) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
initApp();
|
||||
QApplication a(argc, argv);
|
||||
Updater updater(argv[1], argv[2]);
|
||||
setMainWindow(&updater);
|
||||
a.installEventFilter(&updater);
|
||||
return a.exec();
|
||||
}
|
||||
|
|
|
@ -19,12 +19,13 @@ private slots:
|
|||
void updateFinished(int exitCode, QProcess::ExitStatus exitStatus);
|
||||
|
||||
private:
|
||||
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||
|
||||
QProcess proc;
|
||||
QString updater, manifest;
|
||||
|
||||
QLabel *text;
|
||||
QProgressBar *bar;
|
||||
QPushButton *reboot;
|
||||
QProcess proc;
|
||||
|
||||
QWidget *prompt, *wifi, *progress;
|
||||
};
|
||||
|
|
|
@ -247,6 +247,8 @@ def handle_agnos_update(wait_helper):
|
|||
|
||||
|
||||
def handle_neos_update(wait_helper: WaitTimeHelper) -> None:
|
||||
from selfdrive.hardware.eon.neos import download_neos_update
|
||||
|
||||
cur_neos = HARDWARE.get_os_version()
|
||||
updated_neos = run(["bash", "-c", r"unset REQUIRED_NEOS_VERSION && source launch_env.sh && \
|
||||
echo -n $REQUIRED_NEOS_VERSION"], OVERLAY_MERGED).strip()
|
||||
|
@ -258,8 +260,7 @@ def handle_neos_update(wait_helper: WaitTimeHelper) -> None:
|
|||
cloudlog.info(f"Beginning background download for NEOS {updated_neos}")
|
||||
set_offroad_alert("Offroad_NeosUpdate", True)
|
||||
|
||||
updater_path = os.path.join(OVERLAY_MERGED, "installer/updater/updater")
|
||||
update_manifest = f"file://{OVERLAY_MERGED}/installer/updater/update.json"
|
||||
update_manifest = os.path.join(OVERLAY_MERGED, "selfdrive/hardware/eon/neos.json")
|
||||
|
||||
neos_downloaded = False
|
||||
start_time = time.monotonic()
|
||||
|
@ -268,9 +269,9 @@ def handle_neos_update(wait_helper: WaitTimeHelper) -> None:
|
|||
(time.monotonic() - start_time < 60*60*24):
|
||||
wait_helper.ready_event.clear()
|
||||
try:
|
||||
run([updater_path, "bgcache", update_manifest], OVERLAY_MERGED, low_priority=True)
|
||||
download_neos_update(update_manifest, cloudlog)
|
||||
neos_downloaded = True
|
||||
except subprocess.CalledProcessError:
|
||||
except Exception:
|
||||
cloudlog.info("NEOS background download failed, retrying")
|
||||
wait_helper.sleep(120)
|
||||
|
||||
|
|
Loading…
Reference in New Issue