diff --git a/.github/workflows/build-all-tinygrad-models.yaml b/.github/workflows/build-all-tinygrad-models.yaml index e901baee0c..412676e5fd 100644 --- a/.github/workflows/build-all-tinygrad-models.yaml +++ b/.github/workflows/build-all-tinygrad-models.yaml @@ -34,10 +34,10 @@ jobs: echo "tinygrad_ref=$ref" >> $GITHUB_OUTPUT echo "tinygrad_ref is $ref" - - name: Checkout docs repo (sunnypilot-docs, gh-pages) + - name: Checkout docs repo (sunnypilot-models, gh-pages) uses: actions/checkout@v4 with: - repository: sunnypilot/sunnypilot-docs + repository: sunnypilot/sunnypilot-models ref: gh-pages path: docs ssh-key: ${{ secrets.CI_SUNNYPILOT_DOCS_PRIVATE_KEY }} @@ -202,7 +202,7 @@ jobs: - name: Checkout docs repo uses: actions/checkout@v4 with: - repository: sunnypilot/sunnypilot-docs + repository: sunnypilot/sunnypilot-models ref: gh-pages path: docs ssh-key: ${{ secrets.CI_SUNNYPILOT_DOCS_PRIVATE_KEY }} diff --git a/.github/workflows/build-single-tinygrad-model.yaml b/.github/workflows/build-single-tinygrad-model.yaml index fae9d6aa01..e7e3b67b51 100644 --- a/.github/workflows/build-single-tinygrad-model.yaml +++ b/.github/workflows/build-single-tinygrad-model.yaml @@ -119,7 +119,7 @@ jobs: - name: Checkout docs repo uses: actions/checkout@v4 with: - repository: sunnypilot/sunnypilot-docs + repository: sunnypilot/sunnypilot-models ref: gh-pages path: docs ssh-key: ${{ secrets.CI_SUNNYPILOT_DOCS_PRIVATE_KEY }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 908c570391..6ae5336557 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -22,7 +22,7 @@ jobs: running-workflow-name: 'build __nightly' repo-token: ${{ secrets.GITHUB_TOKEN }} check-regexp: ^((?!.*(build prebuilt|create badges).*).)*$ - - uses: actions/checkout@v6 + - uses: actions/checkout@v4 with: submodules: true fetch-depth: 0 diff --git a/.github/workflows/repo-maintenance.yaml b/.github/workflows/repo-maintenance.yaml index e7933dc470..c3646d326d 100644 --- a/.github/workflows/repo-maintenance.yaml +++ b/.github/workflows/repo-maintenance.yaml @@ -72,7 +72,6 @@ jobs: git add . - name: update car docs run: | - scons -j$(nproc) --minimal opendbc_repo python selfdrive/car/docs.py git add docs/CARS.md - name: Create Pull Request diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 760b16326a..1e10418771 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -181,7 +181,7 @@ jobs: echo "${{ github.sha }}" > ref_commit git add . git commit -m "process-replay refs for ${{ github.repository }}@${{ github.sha }}" || echo "No changes to commit" - git push origin process-replay + git push origin process-replay --force - name: Run regen if: false timeout-minutes: 4 diff --git a/.gitignore b/.gitignore index cd5e64e52b..738a150b7e 100644 --- a/.gitignore +++ b/.gitignore @@ -13,13 +13,13 @@ venv/ a.out .hypothesis .cache/ - -/docs_site/ +bin/ *.mp4 *.dylib *.DSYM *.d +*.pem *.pyc *.pyo .*.swp @@ -39,11 +39,13 @@ a.out *.mo *_pyx.cpp *.stats +*.pkl +*.pkl* config.json -clcache compile_commands.json compare_runtime*.html +# build artifacts selfdrive/pandad/pandad cereal/services.h cereal/gen @@ -56,51 +58,36 @@ system/camerad/test/ae_gray_test .coverage* coverage.xml htmlcov -pandaextra - -.mypy_cache/ -flycheck_* - -cppcheck_report.txt -comma*.sh - -selfdrive/modeld/models/*.pkl* -sunnypilot/modeld*/models/*.pkl # openpilot log files *.bz2 *.zst +*.rlog build/ !**/.gitkeep -poetry.toml -Pipfile ### VisualStudioCode ### +*.vsix +.history +.ionide .vscode/* +.history/ !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json !.vscode/*.code-snippets -# Local History for Visual Studio Code -.history/ - -# Built Visual Studio Code Extensions -*.vsix - -### VisualStudioCode Patch ### -# Ignore all local history of files -.history -.ionide - +# agents .claude/ .context/ PLAN.md TASK.md +CLAUDE.md +SKILL.md ### JetBrains ### !.idea/customTargets.xml diff --git a/.python-version b/.python-version new file mode 100644 index 0000000000..28d9a01b1f --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.12.13 diff --git a/Jenkinsfile b/Jenkinsfile index c5ebf6162b..39175d89e3 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -167,7 +167,7 @@ node { env.GIT_COMMIT = checkout(scm).GIT_COMMIT def excludeBranches = ['__nightly', 'devel', 'devel-staging', 'release3', 'release3-staging', - 'release-tici', 'release-tizi', 'release-tizi-staging', 'testing-closet*', 'hotfix-*'] + 'release-tici', 'release-tizi', 'release-tizi-staging', 'release-mici-staging', 'testing-closet*', 'hotfix-*'] def excludeRegex = excludeBranches.join('|').replaceAll('\\*', '.*') if (env.BRANCH_NAME != 'master' && !env.BRANCH_NAME.contains('__jenkins_loop_')) { @@ -179,7 +179,7 @@ node { try { if (env.BRANCH_NAME == 'devel-staging') { deviceStage("build release-tizi-staging", "tizi-needs-can", [], [ - step("build release-tizi-staging", "RELEASE_BRANCH=release-tizi-staging $SOURCE_DIR/release/build_release.sh"), + step("build release-tizi-staging", "RELEASE_BRANCH=release-tizi-staging $SOURCE_DIR/release/build_release.sh && git push -f origin release-tizi-staging:release-mici-staging"), ]) } @@ -218,14 +218,14 @@ node { 'camerad OX03C10': { deviceStage("OX03C10", "tizi-ox03c10", ["UNSAFE=1"], [ step("build", "cd system/manager && ./build.py"), - step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]), + step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py"), step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 90]), ]) }, 'camerad OS04C10': { deviceStage("OS04C10", "tici-os04c10", ["UNSAFE=1"], [ step("build", "cd system/manager && ./build.py"), - step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]), + step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py"), step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 90]), ]) }, diff --git a/RELEASES.md b/RELEASES.md index 895dcbba7a..a3a95ae6a8 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,8 +1,16 @@ -Version 0.10.4 (2026-02-17) +Version 0.11.1 (2026-04-08) ======================== +* New driver monitoring model +* Improved image processing pipeline for driver camera + +Version 0.11.0 (2026-03-17) +======================== +* New driving model #36798 + * Fully trained using a learned simulator + * Improved longitudinal performance in Experimental mode +* Reduce comma four standby power usage by 77% to 52 mW * Kia K7 2017 support thanks to royjr! * Lexus LS 2018 support thanks to Hacheoy! -* Reduce comma four standby power usage by 77% to 52 mW Version 0.10.3 (2025-12-17) ======================== diff --git a/SConstruct b/SConstruct index 7db9f7e3d2..5e84121fb5 100644 --- a/SConstruct +++ b/SConstruct @@ -4,9 +4,11 @@ import sys import sysconfig import platform import shlex +import importlib import numpy as np import SCons.Errors +from SCons.Defaults import _stripixes SCons.Warnings.warningAsException(True) @@ -14,9 +16,6 @@ Decider('MD5-timestamp') SetOption('num_jobs', max(1, int(os.cpu_count()/2))) -AddOption('--asan', action='store_true', help='turn on ASAN') -AddOption('--ubsan', action='store_true', help='turn on UBSan') -AddOption('--mutation', action='store_true', help='generate mutation-ready code') AddOption('--ccflags', action='store', type='string', default='', help='pass arbitrary flags over the command line') AddOption('--verbose', action='store_true', default=False, help='show full build commands') AddOption('--minimal', @@ -38,24 +37,46 @@ assert arch in [ "Darwin", # macOS arm64 (x86 not supported) ] -if arch != "larch64": - import bzip2 - import capnproto - import eigen - import ffmpeg as ffmpeg_pkg - import libjpeg - import libyuv - import ncurses - import openssl3 - import python3_dev - import zeromq - import zstd - pkgs = [bzip2, capnproto, eigen, ffmpeg_pkg, libjpeg, libyuv, ncurses, openssl3, zeromq, zstd] - py_include = python3_dev.INCLUDE_DIR -else: - # TODO: remove when AGNOS has our new vendor pkgs - pkgs = [] - py_include = sysconfig.get_paths()['include'] +pkg_names = ['bzip2', 'capnproto', 'eigen', 'ffmpeg', 'libjpeg', 'libyuv', 'ncurses', 'zeromq', 'zstd'] +pkgs = [importlib.import_module(name) for name in pkg_names] + + +# ***** enforce a whitelist of system libraries ***** +# this prevents silently relying on a 3rd party package, +# e.g. apt-installed libusb. all libraries should either +# be distributed with all Linux distros and macOS, or +# vendored in commaai/dependencies. +allowed_system_libs = { + "EGL", "GLESv2", "GL", "Qt5Charts", "Qt5Core", "Qt5Gui", "Qt5Widgets", + "dl", "drm", "gbm", "m", "pthread", +} + +def _resolve_lib(env, name): + for d in env.Flatten(env.get('LIBPATH', [])): + p = Dir(str(d)).abspath + for ext in ('.a', '.so', '.dylib'): + f = File(os.path.join(p, f'lib{name}{ext}')) + if f.exists() or f.has_builder(): + return name + if name in allowed_system_libs: + return name + raise SCons.Errors.UserError(f"Unexpected non-vendored library '{name}'") + +def _libflags(target, source, env, for_signature): + libs = [] + lp = env.subst('$LIBLITERALPREFIX') + for lib in env.Flatten(env.get('LIBS', [])): + if isinstance(lib, str): + if os.sep in lib or lib.startswith('#'): + libs.append(File(lib)) + elif lib.startswith('-') or (lp and lib.startswith(lp)): + libs.append(lib) + else: + libs.append(_resolve_lib(env, lib)) + else: + libs.append(lib) + return _stripixes(env['LIBLINKPREFIX'], libs, env['LIBLINKSUFFIX'], + env['LIBPREFIXES'], env['LIBSUFFIXES'], env, env['LIBLITERALPREFIX']) env = Environment( ENV={ @@ -108,14 +129,14 @@ env = Environment( tools=["default", "cython", "compilation_db", "rednose_filter"], toolpath=["#site_scons/site_tools", "#rednose_repo/site_scons/site_tools"], ) +if arch != "larch64": + env['_LIBFLAGS'] = _libflags # Arch-specific flags and paths if arch == "larch64": env["CC"] = "clang" env["CXX"] = "clang++" env.Append(LIBPATH=[ - "/usr/local/lib", - "/system/vendor/lib64", "/usr/lib/aarch64-linux-gnu", ]) arch_flags = ["-D__TICI__", "-mcpu=cortex-a57", "-DQCOM2"] @@ -127,19 +148,6 @@ elif arch == "Darwin": ]) env.Append(CCFLAGS=["-DGL_SILENCE_DEPRECATION"]) env.Append(CXXFLAGS=["-DGL_SILENCE_DEPRECATION"]) -else: - env.Append(LIBPATH=[ - "/usr/lib", - "/usr/local/lib", - ]) - -# Sanitizers and extra CCFLAGS from CLI -if GetOption('asan'): - env.Append(CCFLAGS=["-fsanitize=address", "-fno-omit-frame-pointer"]) - env.Append(LINKFLAGS=["-fsanitize=address"]) -elif GetOption('ubsan'): - env.Append(CCFLAGS=["-fsanitize=undefined"]) - env.Append(LINKFLAGS=["-fsanitize=undefined"]) _extra_cc = shlex.split(GetOption('ccflags') or '') if _extra_cc: @@ -177,7 +185,7 @@ if os.environ.get('SCONS_PROGRESS'): # ********** Cython build environment ********** envCython = env.Clone() -envCython["CPPPATH"] += [py_include, np.get_include()] +envCython["CPPPATH"] += [sysconfig.get_paths()['include'], np.get_include()] envCython["CCFLAGS"] += ["-Wno-#warnings", "-Wno-cpp", "-Wno-shadow", "-Wno-deprecated-declarations"] envCython["CCFLAGS"].remove("-Werror") @@ -211,7 +219,6 @@ Export('common') env_swaglog = env.Clone() env_swaglog['CXXFLAGS'].append('-DSWAGLOG="\\"common/swaglog.h\\""') SConscript(['msgq_repo/SConscript'], exports={'env': env_swaglog}) -SConscript(['opendbc_repo/SConscript'], exports={'env': env_swaglog}) SConscript(['cereal/SConscript']) @@ -237,7 +244,15 @@ if arch == "larch64": # Build openpilot SConscript(['third_party/SConscript']) -SConscript(['selfdrive/SConscript']) +# Build selfdrive +SConscript([ + 'selfdrive/pandad/SConscript', + 'selfdrive/controls/lib/lateral_mpc_lib/SConscript', + 'selfdrive/controls/lib/longitudinal_mpc_lib/SConscript', + 'selfdrive/locationd/SConscript', + 'selfdrive/modeld/SConscript', + 'selfdrive/ui/SConscript', +]) SConscript(['sunnypilot/SConscript']) diff --git a/common/.gitignore b/common/.gitignore deleted file mode 100644 index ce1da4c53c..0000000000 --- a/common/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.cpp diff --git a/common/SConscript b/common/SConscript index 15a0e5eff1..c9bd1c72d1 100644 --- a/common/SConscript +++ b/common/SConscript @@ -1,4 +1,4 @@ -Import('env', 'envCython', 'arch') +Import('env', 'envCython') common_libs = [ 'params.cc', diff --git a/common/filter_simple.py b/common/filter_simple.py index 212e1a8f40..b28c3d68f5 100644 --- a/common/filter_simple.py +++ b/common/filter_simple.py @@ -28,7 +28,7 @@ class BounceFilter(FirstOrderFilter): scale = self.dt / (1.0 / 60.0) # tuned at 60 fps self.velocity.x += (x - self.x) * self.bounce * scale * self.dt self.velocity.update(0.0) - if abs(self.velocity.x) < 1e-5: + if abs(self.velocity.x) < 1e-3: self.velocity.x = 0.0 self.x += self.velocity.x return self.x diff --git a/common/params_keys.h b/common/params_keys.h index f48b33621d..6d25a35303 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -172,6 +172,7 @@ inline static std::unordered_map keys = { {"OnroadScreenOffBrightness", {PERSISTENT | BACKUP, INT, "0"}}, {"OnroadScreenOffBrightnessMigrated", {PERSISTENT | BACKUP, STRING, "0.0"}}, {"OnroadScreenOffTimer", {PERSISTENT | BACKUP, INT, "15"}}, + {"OnroadScreenOffTimerMigrated", {PERSISTENT | BACKUP, STRING, "0.0"}}, {"OnroadUploads", {PERSISTENT | BACKUP, BOOL, "1"}}, {"QuickBootToggle", {PERSISTENT | BACKUP, BOOL, "0"}}, {"QuietMode", {PERSISTENT | BACKUP, BOOL, "0"}}, @@ -271,7 +272,7 @@ inline static std::unordered_map keys = { {"EnforceTorqueControl", {PERSISTENT | BACKUP, BOOL}}, {"LiveTorqueParamsToggle", {PERSISTENT | BACKUP , BOOL}}, {"LiveTorqueParamsRelaxedToggle", {PERSISTENT | BACKUP , BOOL}}, - {"TorqueControlTune", {PERSISTENT | BACKUP, FLOAT}}, + {"TorqueControlTune", {PERSISTENT | BACKUP, FLOAT, "0.0"}}, {"TorqueParamsOverrideEnabled", {PERSISTENT | BACKUP, BOOL, "0"}}, {"TorqueParamsOverrideFriction", {PERSISTENT | BACKUP, FLOAT, "0.1"}}, {"TorqueParamsOverrideLatAccelFactor", {PERSISTENT | BACKUP, FLOAT, "2.5"}}, diff --git a/common/time_helpers.py b/common/time_helpers.py index 8564e270c2..c709182d45 100644 --- a/common/time_helpers.py +++ b/common/time_helpers.py @@ -2,6 +2,7 @@ import datetime from pathlib import Path MIN_DATE = datetime.datetime(year=2025, month=2, day=21) +MAX_DATE = datetime.datetime(year=2035, month=1, day=1) def min_date(): # on systemd systems, the default time is the systemd build time @@ -12,4 +13,4 @@ def min_date(): return MIN_DATE def system_time_valid(): - return datetime.datetime.now() > min_date() + return min_date() < datetime.datetime.now() < MAX_DATE diff --git a/common/transformations/.gitignore b/common/transformations/.gitignore deleted file mode 100644 index a67290f09a..0000000000 --- a/common/transformations/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -transformations -transformations.cpp diff --git a/common/version.h b/common/version.h index 7e78d64b22..41440556c5 100644 --- a/common/version.h +++ b/common/version.h @@ -1 +1 @@ -#define COMMA_VERSION "0.10.4" +#define COMMA_VERSION "0.11.1" diff --git a/conftest.py b/conftest.py index 0bd638b87d..2f2db08d5d 100644 --- a/conftest.py +++ b/conftest.py @@ -10,7 +10,6 @@ from openpilot.system.hardware import TICI, HARDWARE # TODO: pytest-cpp doesn't support FAIL, and we need to create test translations in sessionstart # pending https://github.com/pytest-dev/pytest-cpp/pull/147 collect_ignore = [ - "selfdrive/ui/tests/test_translations", "selfdrive/test/process_replay/test_processes.py", "selfdrive/test/process_replay/test_regen.py", ] diff --git a/launch_env.sh b/launch_env.sh index 314366f429..e409a80dd4 100755 --- a/launch_env.sh +++ b/launch_env.sh @@ -16,7 +16,7 @@ export VECLIB_MAXIMUM_THREADS=1 export QCOM_PRIORITY=12 if [ -z "$AGNOS_VERSION" ]; then - export AGNOS_VERSION="16" + export AGNOS_VERSION="17.2" fi export STAGING_ROOT="/data/safe_staging" diff --git a/opendbc_repo b/opendbc_repo index 9918ec656f..b178bc5d4e 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 9918ec656ffa0d1a576f8ae159390408adcaf4cd +Subproject commit b178bc5d4e7cd15c50eb3e148cc2648b9379ca86 diff --git a/panda b/panda index f5f296c65c..6ddc631bdd 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit f5f296c65c756a9b81af845cd874ee054d9c598c +Subproject commit 6ddc631bdd57583a75d5668cad53c42be21a5c4b diff --git a/pyproject.toml b/pyproject.toml index 56664b16b7..6c84f63863 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,18 +26,18 @@ dependencies = [ "numpy >=2.0", # vendored native dependencies - "bzip2 @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=bzip2", - "capnproto @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=capnproto", - "eigen @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=eigen", - "ffmpeg @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=ffmpeg", - "libjpeg @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=libjpeg", - "libyuv @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=libyuv", - "openssl3 @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=openssl3", - "python3-dev @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=python3-dev", - "zstd @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=zstd", - "ncurses @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=ncurses", - "zeromq @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=zeromq", - "git-lfs @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=git-lfs", + "bzip2 @ git+https://github.com/commaai/dependencies.git@release-bzip2#subdirectory=bzip2", + "capnproto @ git+https://github.com/commaai/dependencies.git@release-capnproto#subdirectory=capnproto", + "eigen @ git+https://github.com/commaai/dependencies.git@release-eigen#subdirectory=eigen", + "ffmpeg @ git+https://github.com/commaai/dependencies.git@release-ffmpeg#subdirectory=ffmpeg", + "libjpeg @ git+https://github.com/commaai/dependencies.git@release-libjpeg#subdirectory=libjpeg", + "libyuv @ git+https://github.com/commaai/dependencies.git@release-libyuv#subdirectory=libyuv", + "zstd @ git+https://github.com/commaai/dependencies.git@release-zstd#subdirectory=zstd", + "ncurses @ git+https://github.com/commaai/dependencies.git@release-ncurses#subdirectory=ncurses", + "zeromq @ git+https://github.com/commaai/dependencies.git@release-zeromq#subdirectory=zeromq", + "libusb @ git+https://github.com/commaai/dependencies.git@release-libusb#subdirectory=libusb", + "git-lfs @ git+https://github.com/commaai/dependencies.git@release-git-lfs#subdirectory=git-lfs", + "gcc-arm-none-eabi @ git+https://github.com/commaai/dependencies.git@release-gcc-arm-none-eabi#subdirectory=gcc-arm-none-eabi", # body / webrtcd "av", @@ -76,6 +76,7 @@ dependencies = [ "raylib > 5.5.0.3", "qrcode", "jeepney", + "pillow", ] [project.optional-dependencies] @@ -103,12 +104,10 @@ testing = [ dev = [ "matplotlib", "opencv-python-headless", - "gcc-arm-none-eabi @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=gcc-arm-none-eabi", ] tools = [ "metadrive-simulator @ git+https://github.com/commaai/metadrive.git@minimal ; (platform_machine != 'aarch64')", - "dearpygui>=2.1.0; (sys_platform != 'linux' or platform_machine != 'aarch64')", # not vended for linux aarch64 ] [project.urls] @@ -207,6 +206,7 @@ lint.flake8-implicit-str-concat.allow-multiline = false "pyray.is_mouse_button_pressed".msg = "This can miss events. Use Widget._handle_mouse_press" "pyray.is_mouse_button_released".msg = "This can miss events. Use Widget._handle_mouse_release" "pyray.draw_text".msg = "Use a function (such as rl.draw_font_ex) that takes font as an argument" +"pyray.draw_texture".msg = "Use rl.draw_texture_ex for float position support" [tool.ruff.format] quote-style = "preserve" @@ -250,3 +250,6 @@ unsupported-operator = "ignore" # Ignore not-subscriptable - false positives from dynamic types not-subscriptable = "ignore" # not-iterable errors are now fixed + +[tool.uv] +python-preference = "only-managed" diff --git a/release/pack.py b/release/pack.py index 92ff68fe76..8831a0b34d 100755 --- a/release/pack.py +++ b/release/pack.py @@ -12,12 +12,13 @@ from openpilot.common.basedir import BASEDIR DIRS = ['cereal', 'openpilot'] -EXTS = ['.png', '.py', '.ttf', '.capnp', '.json', '.fnt', '.mo'] +EXTS = ['.png', '.py', '.ttf', '.capnp', '.json', '.fnt', '.mo', '.po'] +EXCLUDE = ['selfdrive/assets/training', 'third_party/raylib/raylib_repo/examples'] INTERPRETER = '/usr/bin/env python3' def copy(src, dest): - if any(src.endswith(ext) for ext in EXTS): + if any(src.endswith(ext) for ext in EXTS) and not any(exc in src for exc in EXCLUDE): shutil.copy2(src, dest, follow_symlinks=True) @@ -28,6 +29,8 @@ if __name__ == '__main__': parser.add_argument('module', help="the module to target, e.g. 'openpilot.system.ui.spinner'") args = parser.parse_args() + print('WARNING: copying all files! make sure to run scons and git tree is clean') + if not args.output: args.output = args.module diff --git a/selfdrive/SConscript b/selfdrive/SConscript deleted file mode 100644 index 55f347c44e..0000000000 --- a/selfdrive/SConscript +++ /dev/null @@ -1,6 +0,0 @@ -SConscript(['pandad/SConscript']) -SConscript(['controls/lib/lateral_mpc_lib/SConscript']) -SConscript(['controls/lib/longitudinal_mpc_lib/SConscript']) -SConscript(['locationd/SConscript']) -SConscript(['modeld/SConscript']) -SConscript(['ui/SConscript']) diff --git a/selfdrive/assets/.gitignore b/selfdrive/assets/.gitignore index fffd4b4ed9..2d97f8b111 100644 --- a/selfdrive/assets/.gitignore +++ b/selfdrive/assets/.gitignore @@ -1,4 +1,2 @@ -*.cc fonts/*.fnt fonts/*.png -translations_assets.qrc diff --git a/selfdrive/assets/icons_mici/onroad/bookmark_fill.png b/selfdrive/assets/icons_mici/onroad/bookmark_fill.png deleted file mode 100644 index 531d5db1cf..0000000000 --- a/selfdrive/assets/icons_mici/onroad/bookmark_fill.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f3f57346a1cf9a66f9fd746f87bcebb23b7a403e9d6e4fd7701b126abcdd47ea -size 18476 diff --git a/selfdrive/assets/icons_mici/settings/device/language.png b/selfdrive/assets/icons_mici/settings/device/language.png deleted file mode 100644 index d2ef27de36..0000000000 --- a/selfdrive/assets/icons_mici/settings/device/language.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f646263b26de46f79cac836ef6865b0f25ddc91e386b99311723b68bd06693c9 -size 3304 diff --git a/selfdrive/assets/icons_mici/setup/back_new.png b/selfdrive/assets/icons_mici/setup/back_new.png deleted file mode 100644 index 20e7fe3b88..0000000000 --- a/selfdrive/assets/icons_mici/setup/back_new.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d29a9c295b33b3164c37a68ad77795595e6ac877a5b308d28112b0315ecd498f -size 1687 diff --git a/selfdrive/assets/icons_mici/setup/cancel.png b/selfdrive/assets/icons_mici/setup/cancel.png new file mode 100644 index 0000000000..f50cc9ef3f --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/cancel.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6892bd4d9b14b587fa491a6d608562e38819b4c618b1d7a3e8c384f05d52a2b +size 1245 diff --git a/selfdrive/assets/icons_mici/setup/continue.png b/selfdrive/assets/icons_mici/setup/continue.png new file mode 100644 index 0000000000..7a67bb0c96 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/continue.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3428d8fcf2ecf9542c524706124f82b7fc809453c63418c9234ac9df5d85bd24 +size 10074 diff --git a/selfdrive/assets/icons_mici/setup/continue_disabled.png b/selfdrive/assets/icons_mici/setup/continue_disabled.png new file mode 100644 index 0000000000..8a2bcc2ffe --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/continue_disabled.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b2810add4943dd4f20a984ed6011b520925919a58d5c0dd0d846fc4d7f8a1d02 +size 7109 diff --git a/selfdrive/assets/icons_mici/setup/continue_pressed.png b/selfdrive/assets/icons_mici/setup/continue_pressed.png new file mode 100644 index 0000000000..3eaee7bf1c --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/continue_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3a3a87454a3d2f1ebb327211062c52480de945673dcfd137c5da3df8fa98d731 +size 22400 diff --git a/selfdrive/assets/icons_mici/setup/factory_reset.png b/selfdrive/assets/icons_mici/setup/factory_reset.png new file mode 100644 index 0000000000..bcb3ea92cb --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/factory_reset.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:122a614d1aa26187507951f932160eebfddfebcb4293e78f8d23e350fc97bc0f +size 11489 diff --git a/selfdrive/assets/icons_mici/setup/medium_button_bg.png b/selfdrive/assets/icons_mici/setup/medium_button_bg.png deleted file mode 100644 index e79dc2eb58..0000000000 --- a/selfdrive/assets/icons_mici/setup/medium_button_bg.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9e363a79dc35ca4c4e9efaa6a843d37ad219efa5299d3e538d8249affa230096 -size 7935 diff --git a/selfdrive/assets/icons_mici/setup/medium_button_pressed_bg.png b/selfdrive/assets/icons_mici/setup/medium_button_pressed_bg.png deleted file mode 100644 index e52fb0c17d..0000000000 --- a/selfdrive/assets/icons_mici/setup/medium_button_pressed_bg.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cc6fb48520143b6fa1f060d8212e6d929917ab616ce943b5fab5a60665f00da5 -size 18225 diff --git a/selfdrive/assets/icons_mici/setup/reset/small_button.png b/selfdrive/assets/icons_mici/setup/reset/small_button.png deleted file mode 100644 index e3f58b1078..0000000000 --- a/selfdrive/assets/icons_mici/setup/reset/small_button.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7a198f13f30b3dbc09f30d7fd8033a0bc07a0da9b010b7ca6ed2678430c9e5b4 -size 6949 diff --git a/selfdrive/assets/icons_mici/setup/reset/small_button_pressed.png b/selfdrive/assets/icons_mici/setup/reset/small_button_pressed.png deleted file mode 100644 index 5b502e00aa..0000000000 --- a/selfdrive/assets/icons_mici/setup/reset/small_button_pressed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:75289d004709def2a2d6101a0330ec867895068ec3807aefc2a26d423d907a13 -size 13437 diff --git a/selfdrive/assets/icons_mici/setup/reset/wide_button.png b/selfdrive/assets/icons_mici/setup/reset/wide_button.png deleted file mode 100644 index 3892f6eb8c..0000000000 --- a/selfdrive/assets/icons_mici/setup/reset/wide_button.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2452aaf59da18be1b74b475851d66e5c73c50aa49820419a288b1fdb7b42dee1 -size 9071 diff --git a/selfdrive/assets/icons_mici/setup/reset/wide_button_pressed.png b/selfdrive/assets/icons_mici/setup/reset/wide_button_pressed.png deleted file mode 100644 index 3a34af8846..0000000000 --- a/selfdrive/assets/icons_mici/setup/reset/wide_button_pressed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6478f7c1c5ef2013e94fc4218ab370889883c5c12231ba3e0975874cb0b6fec9 -size 21893 diff --git a/selfdrive/assets/icons_mici/setup/reset_failed.png b/selfdrive/assets/icons_mici/setup/reset_failed.png new file mode 100644 index 0000000000..680df97cbc --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/reset_failed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8d5b8f76e5f47e77e5af3016ebdbe548ad3bc9af83a1111b3214bf4017c95a28 +size 11792 diff --git a/selfdrive/assets/icons_mici/setup/restore.png b/selfdrive/assets/icons_mici/setup/restore.png index 5eff924040..5c62086f64 100644 --- a/selfdrive/assets/icons_mici/setup/restore.png +++ b/selfdrive/assets/icons_mici/setup/restore.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f5ee67cd334d259ac33f932281db36533877009b5769c92d9cff3054fd5627c -size 2942 +oid sha256:63c1499106621a4d927c21b2b04c87235a927216d9f513a0205f0fe03b8c799b +size 12320 diff --git a/selfdrive/assets/icons_mici/setup/scroll_down_indicator.png b/selfdrive/assets/icons_mici/setup/scroll_down_indicator.png deleted file mode 100644 index 3cd26e5181..0000000000 --- a/selfdrive/assets/icons_mici/setup/scroll_down_indicator.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a733c425113a7f6ff5ec3dc50ef94b5481c0f2d306e33d1485be8ee6b2798532 -size 1136 diff --git a/selfdrive/assets/icons_mici/setup/small_red_pill.png b/selfdrive/assets/icons_mici/setup/small_red_pill.png deleted file mode 100644 index 4a7db930a0..0000000000 --- a/selfdrive/assets/icons_mici/setup/small_red_pill.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b3a336afddad80dc91caca91d54bd29897ce491f180374edf9a5ba517cbc00e9 -size 8765 diff --git a/selfdrive/assets/icons_mici/setup/small_red_pill_pressed.png b/selfdrive/assets/icons_mici/setup/small_red_pill_pressed.png deleted file mode 100644 index a8d51960c4..0000000000 --- a/selfdrive/assets/icons_mici/setup/small_red_pill_pressed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8eee9f10ca80a4e6100c00c02bb46aa5f253b14b086ab9982cfa85ee94eec162 -size 22512 diff --git a/selfdrive/assets/icons_mici/setup/small_slider/slider_bg.png b/selfdrive/assets/icons_mici/setup/small_slider/slider_bg.png deleted file mode 100644 index 43c10a54ad..0000000000 --- a/selfdrive/assets/icons_mici/setup/small_slider/slider_bg.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:94a86fac6ffe8a8179812cf55350ab9ca6935f36244c6f679c1cf521a842316b -size 5723 diff --git a/selfdrive/assets/icons_mici/setup/small_slider/slider_red_circle.png b/selfdrive/assets/icons_mici/setup/small_slider/slider_red_circle.png deleted file mode 100644 index 541433be76..0000000000 --- a/selfdrive/assets/icons_mici/setup/small_slider/slider_red_circle.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6ccb5f2298389ae36df87de84d85440ee5a82c50e803c9bd362c9b89ea45aa69 -size 6611 diff --git a/selfdrive/assets/icons_mici/setup/small_slider/slider_red_circle_pressed.png b/selfdrive/assets/icons_mici/setup/small_slider/slider_red_circle_pressed.png deleted file mode 100644 index eea6eded86..0000000000 --- a/selfdrive/assets/icons_mici/setup/small_slider/slider_red_circle_pressed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a804da77b268f0a625f93949642ae74cdfe5b5caa5baea1c52c4605ae25c80e4 -size 12916 diff --git a/selfdrive/assets/icons_mici/setup/smaller_button.png b/selfdrive/assets/icons_mici/setup/smaller_button.png deleted file mode 100644 index 9b4851c568..0000000000 --- a/selfdrive/assets/icons_mici/setup/smaller_button.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:89ca7e6bb01dfa78300126ce828cb2a64e7a2e68e1e9152de242f57a36d0e57a -size 8604 diff --git a/selfdrive/assets/icons_mici/setup/smaller_button_disabled.png b/selfdrive/assets/icons_mici/setup/smaller_button_disabled.png deleted file mode 100644 index 6514791de7..0000000000 --- a/selfdrive/assets/icons_mici/setup/smaller_button_disabled.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b3242a411b559f1d0308f189fe0d25b81d6c7d964ca418a0c599a1bab4bffcbb -size 5341 diff --git a/selfdrive/assets/icons_mici/setup/smaller_button_pressed.png b/selfdrive/assets/icons_mici/setup/smaller_button_pressed.png deleted file mode 100644 index 64235b3a2f..0000000000 --- a/selfdrive/assets/icons_mici/setup/smaller_button_pressed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d354651c0c8107dcc5f599777d260f53ef1901123315785ed8190466166cdce8 -size 17554 diff --git a/selfdrive/assets/icons_mici/setup/widish_button.png b/selfdrive/assets/icons_mici/setup/widish_button.png deleted file mode 100644 index 529b7c80cc..0000000000 --- a/selfdrive/assets/icons_mici/setup/widish_button.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:74fc21132b1e761ea54ce64617730c6ee79d01668244ab555b3b89870cfea181 -size 7112 diff --git a/selfdrive/assets/icons_mici/setup/widish_button_disabled.png b/selfdrive/assets/icons_mici/setup/widish_button_disabled.png deleted file mode 100644 index 5028a8cd21..0000000000 --- a/selfdrive/assets/icons_mici/setup/widish_button_disabled.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9728423bd5e3197ef02d62e4bae415e6694aab875ca8630ffc9f188c38e18e5f -size 4141 diff --git a/selfdrive/assets/icons_mici/setup/widish_button_pressed.png b/selfdrive/assets/icons_mici/setup/widish_button_pressed.png deleted file mode 100644 index 1095d4fc23..0000000000 --- a/selfdrive/assets/icons_mici/setup/widish_button_pressed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0ff179f93f421edcb503ca5c22a12b37e3a2aaabc414bf90f57e20ff5255dd75 -size 15572 diff --git a/selfdrive/car/tests/.gitignore b/selfdrive/car/tests/.gitignore deleted file mode 100644 index 192fb0945e..0000000000 --- a/selfdrive/car/tests/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.bz2 diff --git a/selfdrive/controls/.gitignore b/selfdrive/controls/.gitignore deleted file mode 100644 index 22a371d8ff..0000000000 --- a/selfdrive/controls/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -calibration_param -traces diff --git a/selfdrive/locationd/.gitignore b/selfdrive/locationd/.gitignore deleted file mode 100644 index 1a8c72388a..0000000000 --- a/selfdrive/locationd/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -params_learner -paramsd diff --git a/selfdrive/locationd/lagd.py b/selfdrive/locationd/lagd.py index f432eb88bb..d037af613a 100755 --- a/selfdrive/locationd/lagd.py +++ b/selfdrive/locationd/lagd.py @@ -29,11 +29,26 @@ MIN_LAG = 0.15 MAX_LAG_STD = 0.1 MAX_LAT_ACCEL = 2.0 MAX_LAT_ACCEL_DIFF = 0.6 +MIN_LAT_ACCEL_RANGE = 0.5 MIN_CONFIDENCE = 0.7 CORR_BORDER_OFFSET = 5 LAG_CANDIDATE_CORR_THRESHOLD = 0.9 +SMOOTH_K = 5 +SMOOTH_SIGMA = 1.0 +def masked_symmetric_moving_average(x: np.ndarray, mask: np.ndarray, k: int, sigma: float) -> np.ndarray: + assert k >= 1 and k % 2 == 1, "k must be positive and odd" + pad = k // 2 + i = np.arange(k) - pad + w = np.exp(-0.5 * (i / sigma) ** 2) + w /= w.sum() + xp = np.pad(x * mask, pad, mode="edge") + mp = np.pad(mask, pad, mode="edge") + num = np.convolve(xp, w, mode="valid") + den = np.convolve(mp, w, mode="valid") + return np.divide(num, den, out=np.full_like(num, np.nan, dtype=np.float64), where=den != 0) + def masked_normalized_cross_correlation(expected_sig: np.ndarray, actual_sig: np.ndarray, mask: np.ndarray, n: int): """ References: @@ -295,11 +310,14 @@ class LateralLagEstimator: times, desired, actual, okay = self.points.get() # check if there are any new valid data points since the last update - is_valid = self.points_valid() + is_valid = self.points_valid() and (actual.max() - actual.min() >= MIN_LAT_ACCEL_RANGE) if self.last_estimate_t != 0 and times[0] <= self.last_estimate_t: new_values_start_idx = next(-i for i, t in enumerate(reversed(times)) if t <= self.last_estimate_t) is_valid = is_valid and not (new_values_start_idx == 0 or not np.any(okay[new_values_start_idx:])) + desired = masked_symmetric_moving_average(desired, okay, SMOOTH_K, SMOOTH_SIGMA) + actual = masked_symmetric_moving_average(actual, okay, SMOOTH_K, SMOOTH_SIGMA) + delay, corr, confidence = self.actuator_delay(desired, actual, okay, self.dt, MIN_LAG, MAX_LAG) if corr < self.min_ncc or confidence < self.min_confidence or not is_valid: return @@ -311,16 +329,16 @@ class LateralLagEstimator: def actuator_delay(expected_sig: np.ndarray, actual_sig: np.ndarray, mask: np.ndarray, dt: float, min_lag: float, max_lag: float) -> tuple[float, float, float]: assert len(expected_sig) == len(actual_sig) - min_lag_samples, max_lag_samples = int(round(min_lag / dt)), int(round(max_lag / dt)) - padded_size = fft_next_good_size(len(expected_sig) + max_lag_samples) + min_lag_samples, max_lag_samples, one_sec_samples = int(round(min_lag / dt)), int(round(max_lag / dt)), int(round(1.0 / dt)) + padded_size = fft_next_good_size(len(expected_sig) + max(max_lag_samples, one_sec_samples)) ncc = masked_normalized_cross_correlation(expected_sig, actual_sig, mask, padded_size) - # only consider lags from min_lag to max_lag - roi = np.s_[len(expected_sig) - 1 + min_lag_samples: len(expected_sig) - 1 + max_lag_samples] - extended_roi = np.s_[roi.start - CORR_BORDER_OFFSET: roi.stop + CORR_BORDER_OFFSET] - roi_ncc = ncc[roi] - extended_roi_ncc = ncc[extended_roi] + # only consider lags from ranges: + roi = np.s_[len(expected_sig) - 1 + min_lag_samples: len(expected_sig) - 1 + max_lag_samples] # min_lag - max_lag range + threshold_roi = np.s_[len(expected_sig) - 1: len(expected_sig) - 1 + one_sec_samples] # 0 - 1 second range + confidence_roi = np.s_[threshold_roi.start - CORR_BORDER_OFFSET: threshold_roi.stop + CORR_BORDER_OFFSET] # threshold range +/- border + roi_ncc, confidence_roi_ncc, threshold_roi_ncc = ncc[roi], ncc[confidence_roi], ncc[threshold_roi] max_corr_index = np.argmax(roi_ncc) corr = roi_ncc[max_corr_index] @@ -328,8 +346,8 @@ class LateralLagEstimator: # to estimate lag confidence, gather all high-correlation candidates and see how spread they are # if e.g. 0.8 and 0.4 are both viable, this is an ambiguous case - ncc_thresh = (roi_ncc.max() - roi_ncc.min()) * LAG_CANDIDATE_CORR_THRESHOLD + roi_ncc.min() - good_lag_candidate_mask = extended_roi_ncc >= ncc_thresh + ncc_thresh = (threshold_roi_ncc.max() - threshold_roi_ncc.min()) * LAG_CANDIDATE_CORR_THRESHOLD + threshold_roi_ncc.min() + good_lag_candidate_mask = confidence_roi_ncc >= ncc_thresh good_lag_candidate_edges = np.diff(good_lag_candidate_mask.astype(int), prepend=0, append=0) starts, ends = np.where(good_lag_candidate_edges == 1)[0], np.where(good_lag_candidate_edges == -1)[0] - 1 run_idx = np.searchsorted(starts, max_corr_index + CORR_BORDER_OFFSET, side='right') - 1 diff --git a/selfdrive/locationd/test/.gitignore b/selfdrive/locationd/test/.gitignore deleted file mode 100644 index 89f9ac04aa..0000000000 --- a/selfdrive/locationd/test/.gitignore +++ /dev/null @@ -1 +0,0 @@ -out/ diff --git a/selfdrive/locationd/test/test_lagd.py b/selfdrive/locationd/test/test_lagd.py index 4728413d9d..6249e6b04b 100644 --- a/selfdrive/locationd/test/test_lagd.py +++ b/selfdrive/locationd/test/test_lagd.py @@ -19,8 +19,8 @@ DT = 0.05 def process_messages(estimator, lag_frames, n_frames, vego=20.0, rejection_threshold=0.0): for i in range(n_frames): t = i * estimator.dt - desired_la = np.cos(10 * t) * 0.1 - actual_la = np.cos(10 * (t - lag_frames * estimator.dt)) * 0.1 + desired_la = np.cos(10 * t) * 0.3 + actual_la = np.cos(10 * (t - lag_frames * estimator.dt)) * 0.3 # if sample is masked out, set it to desired value (no lag) rejected = random.uniform(0, 1) < rejection_threshold diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index ff63b644d5..552a02025e 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -45,13 +45,17 @@ def tg_compile(flags, model_name): pkl = fn + "_tinygrad.pkl" onnx_path = fn + ".onnx" chunk_targets = get_chunk_paths(pkl, estimate_pickle_max_size(os.path.getsize(onnx_path))) + compile_node = lenv.Command( + pkl, + [onnx_path] + tinygrad_files + [chunker_file], + f'{pythonpath_string} {flags} {image_flag} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {pkl}', + ) def do_chunk(target, source, env): chunk_file(pkl, chunk_targets) return lenv.Command( chunk_targets, - [onnx_path] + tinygrad_files + [chunker_file], - [f'{pythonpath_string} {flags} {image_flag} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {pkl}', - do_chunk] + compile_node, + do_chunk, ) # Compile small models diff --git a/selfdrive/pandad/pandad.py b/selfdrive/pandad/pandad.py index a0bca3f239..f65c64259f 100755 --- a/selfdrive/pandad/pandad.py +++ b/selfdrive/pandad/pandad.py @@ -32,8 +32,7 @@ def flash_panda(panda_serial: str) -> Panda: raise # skip flashing if the detected panda is not supported - supported_panda = check_panda_support(panda) - if not supported_panda: + if panda.get_type() not in Panda.SUPPORTED_DEVICES: cloudlog.warning(f"Panda {panda_serial} is not supported (hw_type: {panda.get_type()}), skipping flash...") return panda @@ -69,12 +68,20 @@ def flash_panda(panda_serial: str) -> Panda: return panda -def check_panda_support(panda) -> bool: - hw_type = panda.get_type() - if hw_type in Panda.SUPPORTED_DEVICES: - return True +def check_panda_support(panda_serials: list[str]) -> list[str]: + spi_serials = set(Panda.spi_list()) + for serial in panda_serials: + if serial in spi_serials: + return [serial] - return False + for serial in panda_serials: + panda = Panda(serial) + is_internal = panda.is_internal() + panda.close() + if is_internal: + return [serial] + + return [] def main() -> None: @@ -126,13 +133,18 @@ def main() -> None: cloudlog.info(f"{len(panda_serials)} panda(s) found, connecting - {panda_serials}") + # custom flasher for xnor's Rivian Longitudinal Upgrade Kit + flash_rivian_long(panda_serials) + + # find the internal supported panda (e.g. skip external Black Panda) + panda_serials = check_panda_support(panda_serials) + if len(panda_serials) == 0: + continue + # Flash the first panda panda_serial = panda_serials[0] panda = flash_panda(panda_serial) - # flash Rivian longitudinal upgrade panda - flash_rivian_long(panda) - # Ensure internal panda is present if expected if HARDWARE.has_internal_panda() and not panda.is_internal(): cloudlog.error("Internal panda is missing, trying again") @@ -143,12 +155,6 @@ def main() -> None: # log panda fw version params.put("PandaSignatures", panda.get_signature()) - # skip health check if the detected panda is not supported - supported_panda = check_panda_support(panda) - if not supported_panda: - cloudlog.warning(f"Panda {panda.get_usb_serial()} is not supported (hw_type: {panda.get_type()}), skipping health check...") - continue - # check health for lost heartbeat health = panda.health() if health["heartbeat_lost"]: diff --git a/selfdrive/test/.gitignore b/selfdrive/test/.gitignore index 5801faadf4..b8c6bebd95 100644 --- a/selfdrive/test/.gitignore +++ b/selfdrive/test/.gitignore @@ -3,7 +3,7 @@ docker_out/ process_replay/diff.txt process_replay/model_diff.txt +process_replay/fakedata/ valgrind_logs.txt -*.bz2 *.hevc diff --git a/selfdrive/test/process_replay/.gitignore b/selfdrive/test/process_replay/.gitignore deleted file mode 100644 index a35cd58d41..0000000000 --- a/selfdrive/test/process_replay/.gitignore +++ /dev/null @@ -1 +0,0 @@ -fakedata/ diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index 008b8ebe7f..1129a1a2ff 100644 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -342,10 +342,15 @@ class TestOnroad: start, end = min(first_fid), min(last_fid) for i in range(end-start): - ts = {c: round(self.ts[c]['timestampSof'][i]/1e6, 1) for c in cams} + # road and wide cameras (first two) should be synced within 2ms + ts = {c: round(self.ts[c]['timestampSof'][i]/1e6, 1) for c in cams[:2]} diff = (max(ts.values()) - min(ts.values())) assert diff < 2, f"Cameras not synced properly: frame_id={start+i}, {diff=:.1f}ms, {ts=}" + # driver camera should be staggered ~25ms from road camera + offset_ms = abs(self.ts[cams[2]]['timestampSof'][i] - self.ts[cams[0]]['timestampSof'][i]) / 1e6 + assert 20 < offset_ms < 30, f"driver camera stagger out of range at frame {start+i}: {offset_ms:.1f}ms" + def test_camera_encoder_matches(self, subtests): # sanity check that the frame metadata is consistent with the encoded frames pairs = [('roadCameraState', 'roadEncodeIdx'), diff --git a/selfdrive/ui/.gitignore b/selfdrive/ui/.gitignore index 945928f617..30ae77d885 100644 --- a/selfdrive/ui/.gitignore +++ b/selfdrive/ui/.gitignore @@ -1 +1,4 @@ installer/installers/* + +tests/diff/report +.coverage diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 4d7448c62f..1a662e6b24 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -1,4 +1,3 @@ -import re from pathlib import Path Import('env', 'arch', 'common') @@ -19,39 +18,38 @@ env.Command( if GetOption('extras') and arch == "larch64": # build installers - if arch != "Darwin": - raylib_env = env.Clone() - raylib_env['LIBPATH'] += [f'#third_party/raylib/{arch}/'] - raylib_env['LINKFLAGS'].append('-Wl,-strip-debug') + raylib_env = env.Clone() + raylib_env['LIBPATH'] += [f'#third_party/raylib/{arch}/'] + raylib_env['LINKFLAGS'].append('-Wl,-strip-debug') - raylib_libs = common + ["raylib"] - if arch == "larch64": - raylib_libs += ["GLESv2", "EGL", "gbm", "drm"] - else: - raylib_libs += ["GL"] + raylib_libs = common + ["raylib"] + if arch == "larch64": + raylib_libs += ["GLESv2", "EGL", "gbm", "drm"] + else: + raylib_libs += ["GL"] - release = "release3" - installers = [ - ("openpilot", release), - ("openpilot_test", f"{release}-staging"), - ("openpilot_nightly", "nightly"), - ("openpilot_internal", "nightly-dev"), - ] + release = "release3" + installers = [ + ("openpilot", release), + ("openpilot_test", f"{release}-staging"), + ("openpilot_nightly", "nightly"), + ("openpilot_internal", "nightly-dev"), + ] - cont = raylib_env.Command("installer/continue_openpilot.o", "installer/continue_openpilot.sh", + cont = raylib_env.Command("installer/continue_openpilot.o", "installer/continue_openpilot.sh", + "ld -r -b binary -o $TARGET $SOURCE") + inter = raylib_env.Command("installer/inter_ttf.o", "installer/inter-ascii.ttf", + "ld -r -b binary -o $TARGET $SOURCE") + inter_bold = raylib_env.Command("installer/inter_bold.o", "../assets/fonts/Inter-Bold.ttf", "ld -r -b binary -o $TARGET $SOURCE") - inter = raylib_env.Command("installer/inter_ttf.o", "installer/inter-ascii.ttf", - "ld -r -b binary -o $TARGET $SOURCE") - inter_bold = raylib_env.Command("installer/inter_bold.o", "../assets/fonts/Inter-Bold.ttf", - "ld -r -b binary -o $TARGET $SOURCE") - inter_light = raylib_env.Command("installer/inter_light.o", "../assets/fonts/Inter-Light.ttf", - "ld -r -b binary -o $TARGET $SOURCE") - for name, branch in installers: - d = {'BRANCH': f"'\"{branch}\"'"} - if "internal" in name: - d['INTERNAL'] = "1" + inter_light = raylib_env.Command("installer/inter_light.o", "../assets/fonts/Inter-Light.ttf", + "ld -r -b binary -o $TARGET $SOURCE") + for name, branch in installers: + d = {'BRANCH': f"'\"{branch}\"'"} + if "internal" in name: + d['INTERNAL'] = "1" - obj = raylib_env.Object(f"installer/installers/installer_{name}.o", ["installer/installer.cc"], CPPDEFINES=d) - f = raylib_env.Program(f"installer/installers/installer_{name}", [obj, cont, inter, inter_bold, inter_light], LIBS=raylib_libs) - # keep installers small - assert f[0].get_size() < 2500*1e3, f[0].get_size() + obj = raylib_env.Object(f"installer/installers/installer_{name}.o", ["installer/installer.cc"], CPPDEFINES=d) + f = raylib_env.Program(f"installer/installers/installer_{name}", [obj, cont, inter, inter_bold, inter_light], LIBS=raylib_libs) + # keep installers small + assert f[0].get_size() < 2500*1e3, f[0].get_size() diff --git a/selfdrive/ui/layouts/home.py b/selfdrive/ui/layouts/home.py index bb4b868f2d..be231dcd4b 100644 --- a/selfdrive/ui/layouts/home.py +++ b/selfdrive/ui/layouts/home.py @@ -62,6 +62,7 @@ class HomeLayout(Widget): self._setup_callbacks() def show_event(self): + super().show_event() self._exp_mode_button.show_event() self.last_refresh = time.monotonic() self._refresh() diff --git a/selfdrive/ui/layouts/onboarding.py b/selfdrive/ui/layouts/onboarding.py index c53db2231a..452ed53c08 100644 --- a/selfdrive/ui/layouts/onboarding.py +++ b/selfdrive/ui/layouts/onboarding.py @@ -94,7 +94,7 @@ class TrainingGuide(Widget): def _render(self, _): # Safeguard against fast tapping step = min(self._step, len(self._textures) - 1) - rl.draw_texture(self._textures[step], 0, 0, rl.WHITE) + rl.draw_texture_ex(self._textures[step], rl.Vector2(0, 0), 0.0, 1.0, rl.WHITE) # progress bar if 0 < step < len(STEP_RECTS) - 1: diff --git a/selfdrive/ui/layouts/settings/developer.py b/selfdrive/ui/layouts/settings/developer.py index 56c2951d0d..acabb5e743 100644 --- a/selfdrive/ui/layouts/settings/developer.py +++ b/selfdrive/ui/layouts/settings/developer.py @@ -104,6 +104,7 @@ class DeveloperLayout(Widget): self._scroller.render(rect) def show_event(self): + super().show_event() self._scroller.show_event() self._update_toggles() diff --git a/selfdrive/ui/layouts/settings/device.py b/selfdrive/ui/layouts/settings/device.py index 45589af1f0..126ad22a3a 100644 --- a/selfdrive/ui/layouts/settings/device.py +++ b/selfdrive/ui/layouts/settings/device.py @@ -75,6 +75,7 @@ class DeviceLayout(Widget): self._power_off_btn.action_item.right_button.set_visible(ui_state.is_offroad()) def show_event(self): + super().show_event() self._scroller.show_event() def _render(self, rect): diff --git a/selfdrive/ui/layouts/settings/software.py b/selfdrive/ui/layouts/settings/software.py index f7424b974d..f42682e2f7 100644 --- a/selfdrive/ui/layouts/settings/software.py +++ b/selfdrive/ui/layouts/settings/software.py @@ -69,7 +69,6 @@ class SoftwareLayout(Widget): # Branch switcher self._branch_btn = button_item(lambda: tr("Target Branch"), lambda: tr("SELECT"), callback=self._on_select_branch) - self._branch_btn.set_visible(not ui_state.params.get_bool("IsTestedBranch")) self._branch_btn.action_item.set_value(ui_state.params.get("UpdaterTargetBranch") or "") self._branch_dialog: MultiOptionDialog | None = None @@ -83,6 +82,7 @@ class SoftwareLayout(Widget): ], line_separator=True, spacing=0) def show_event(self): + super().show_event() self._scroller.show_event() def _render(self, rect): diff --git a/selfdrive/ui/layouts/settings/toggles.py b/selfdrive/ui/layouts/settings/toggles.py index 9f704b1fb4..9923f3a356 100644 --- a/selfdrive/ui/layouts/settings/toggles.py +++ b/selfdrive/ui/layouts/settings/toggles.py @@ -152,6 +152,7 @@ class TogglesLayout(Widget): ui_state.personality = personality def show_event(self): + super().show_event() self._scroller.show_event() self._update_toggles() diff --git a/selfdrive/ui/layouts/sidebar.py b/selfdrive/ui/layouts/sidebar.py index bfa60c88ed..1dad597ca3 100644 --- a/selfdrive/ui/layouts/sidebar.py +++ b/selfdrive/ui/layouts/sidebar.py @@ -165,14 +165,14 @@ class Sidebar(Widget, SidebarSP): # Settings button settings_down = mouse_down and rl.check_collision_point_rec(mouse_pos, SETTINGS_BTN) tint = Colors.BUTTON_PRESSED if settings_down else Colors.BUTTON_NORMAL - rl.draw_texture(self._settings_img, int(SETTINGS_BTN.x), int(SETTINGS_BTN.y), tint) + rl.draw_texture_ex(self._settings_img, rl.Vector2(SETTINGS_BTN.x, SETTINGS_BTN.y), 0.0, 1.0, tint) # Home/Flag button flag_pressed = mouse_down and rl.check_collision_point_rec(mouse_pos, HOME_BTN) button_img = self._flag_img if ui_state.started else self._home_img tint = Colors.BUTTON_PRESSED if (ui_state.started and flag_pressed) else Colors.BUTTON_NORMAL - rl.draw_texture(button_img, int(HOME_BTN.x), int(HOME_BTN.y), tint) + rl.draw_texture_ex(button_img, rl.Vector2(HOME_BTN.x, HOME_BTN.y), 0.0, 1.0, tint) # Microphone button if self._recording_audio: @@ -182,8 +182,8 @@ class Sidebar(Widget, SidebarSP): bg_color = rl.Color(Colors.DANGER.r, Colors.DANGER.g, Colors.DANGER.b, int(255 * 0.65)) if mic_pressed else Colors.DANGER rl.draw_rectangle_rounded(self._mic_indicator_rect, 1, 10, bg_color) - rl.draw_texture(self._mic_img, int(self._mic_indicator_rect.x + (self._mic_indicator_rect.width - self._mic_img.width) / 2), - int(self._mic_indicator_rect.y + (self._mic_indicator_rect.height - self._mic_img.height) / 2), Colors.WHITE) + rl.draw_texture_ex(self._mic_img, rl.Vector2(self._mic_indicator_rect.x + (self._mic_indicator_rect.width - self._mic_img.width) / 2, + self._mic_indicator_rect.y + (self._mic_indicator_rect.height - self._mic_img.height) / 2), 0.0, 1.0, Colors.WHITE) def _draw_network_indicator(self, rect: rl.Rectangle): # Signal strength dots diff --git a/selfdrive/ui/mici/layouts/home.py b/selfdrive/ui/mici/layouts/home.py index 1d6d8dad2a..8200089c28 100644 --- a/selfdrive/ui/mici/layouts/home.py +++ b/selfdrive/ui/mici/layouts/home.py @@ -7,7 +7,7 @@ from collections.abc import Callable from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.layouts import HBoxLayout from openpilot.system.ui.widgets.icon_widget import IconWidget -from openpilot.system.ui.widgets.label import MiciLabel, UnifiedLabel +from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.version import RELEASE_BRANCHES @@ -77,7 +77,7 @@ class NetworkIcon(Widget): # Offset by difference in height between slashless and slash icons to make center align match draw_y -= (self._wifi_slash_txt.height - self._wifi_none_txt.height) / 2 - rl.draw_texture(draw_net_txt, int(draw_x), int(draw_y), rl.Color(255, 255, 255, int(255 * 0.9))) + rl.draw_texture_ex(draw_net_txt, rl.Vector2(draw_x, draw_y), 0.0, 1.0, rl.Color(255, 255, 255, int(255 * 0.9))) class MiciHomeLayout(Widget): @@ -103,14 +103,15 @@ class MiciHomeLayout(Widget): self._mic_icon, ], spacing=18) - self._openpilot_label = MiciLabel("sunnypilot", font_size=90, color=rl.Color(255, 255, 255, int(255 * 0.9)), font_weight=FontWeight.AUDIOWIDE) - self._version_label = MiciLabel("", font_size=36, font_weight=FontWeight.ROMAN) - self._large_version_label = MiciLabel("", font_size=64, color=rl.GRAY, font_weight=FontWeight.ROMAN) - self._date_label = MiciLabel("", font_size=36, color=rl.GRAY, font_weight=FontWeight.ROMAN) + self._openpilot_label = UnifiedLabel("sunnypilot", font_size=96, font_weight=FontWeight.DISPLAY, max_width=480, wrap_text=False) + self._version_label = UnifiedLabel("", font_size=36, font_weight=FontWeight.ROMAN, max_width=480, wrap_text=False) + self._large_version_label = UnifiedLabel("", font_size=64, text_color=rl.GRAY, font_weight=FontWeight.ROMAN, max_width=480, wrap_text=False) + self._date_label = UnifiedLabel("", font_size=36, text_color=rl.GRAY, font_weight=FontWeight.ROMAN, max_width=480, wrap_text=False) self._branch_label = UnifiedLabel("", font_size=36, text_color=rl.GRAY, font_weight=FontWeight.ROMAN, scroll=True) - self._version_commit_label = MiciLabel("", font_size=36, color=rl.GRAY, font_weight=FontWeight.ROMAN) + self._version_commit_label = UnifiedLabel("", font_size=36, text_color=rl.GRAY, font_weight=FontWeight.ROMAN, max_width=480, wrap_text=False) def show_event(self): + super().show_event() self._version_text = self._get_version_text() self._update_params() @@ -182,12 +183,12 @@ class MiciHomeLayout(Widget): self._version_label.render() self._date_label.set_text(" " + self._version_text[3]) - self._date_label.set_position(version_pos.x + self._version_label.rect.width + 10, version_pos.y) + self._date_label.set_position(version_pos.x + self._version_label.text_width + 10, version_pos.y) self._date_label.render() - self._branch_label.set_max_width(gui_app.width - self._version_label.rect.width - self._date_label.rect.width - 32) + self._branch_label.set_max_width(gui_app.width - self._version_label.text_width - self._date_label.text_width - 32) self._branch_label.set_text(" " + ("release" if release_branch else self._version_text[1])) - self._branch_label.set_position(version_pos.x + self._version_label.rect.width + self._date_label.rect.width + 20, version_pos.y) + self._branch_label.set_position(version_pos.x + self._version_label.text_width + self._date_label.text_width + 20, version_pos.y) self._branch_label.render() if not release_branch: diff --git a/selfdrive/ui/mici/layouts/main.py b/selfdrive/ui/mici/layouts/main.py index 860030a24e..2f41e1f172 100644 --- a/selfdrive/ui/mici/layouts/main.py +++ b/selfdrive/ui/mici/layouts/main.py @@ -56,7 +56,7 @@ class MiciMainLayout(Scroller): gui_app.push_widget(self) # Start onboarding if terms or training not completed, make sure to push after self - self._onboarding_window = OnboardingWindow() + self._onboarding_window = OnboardingWindow(lambda: gui_app.pop_widgets_to(self)) if not self._onboarding_window.completed: gui_app.push_widget(self._onboarding_window) @@ -82,7 +82,7 @@ class MiciMainLayout(Scroller): def _handle_transitions(self): # Don't pop if onboarding - if gui_app.get_active_widget() == self._onboarding_window: + if gui_app.widget_in_stack(self._onboarding_window): return if ui_state.started != self._prev_onroad: @@ -108,7 +108,7 @@ class MiciMainLayout(Scroller): def _on_interactive_timeout(self): # Don't pop if onboarding - if gui_app.get_active_widget() == self._onboarding_window: + if gui_app.widget_in_stack(self._onboarding_window): return if ui_state.started: diff --git a/selfdrive/ui/mici/layouts/offroad_alerts.py b/selfdrive/ui/mici/layouts/offroad_alerts.py index 3aec5bbfed..57ddfdbc48 100644 --- a/selfdrive/ui/mici/layouts/offroad_alerts.py +++ b/selfdrive/ui/mici/layouts/offroad_alerts.py @@ -144,7 +144,7 @@ class AlertItem(Widget): bg_texture = self._bg_small_pressed if self.is_pressed else self._bg_small # Draw background - rl.draw_texture(bg_texture, int(self._rect.x), int(self._rect.y), rl.WHITE) + rl.draw_texture_ex(bg_texture, rl.Vector2(self._rect.x, self._rect.y), 0.0, 1.0, rl.WHITE) # Calculate text area (left side, avoiding icon on right) title_width = self.ALERT_WIDTH - (self.ALERT_PADDING * 2) - self.ICON_SIZE - self.ICON_MARGIN @@ -183,7 +183,7 @@ class AlertItem(Widget): icon_texture = self._icon_orange icon_x = self._rect.x + self.ALERT_WIDTH - self.ALERT_PADDING - self.ICON_SIZE icon_y = self._rect.y + self.ALERT_PADDING - rl.draw_texture(icon_texture, int(icon_x), int(icon_y), rl.WHITE) + rl.draw_texture_ex(icon_texture, rl.Vector2(icon_x, icon_y), 0.0, 1.0, rl.WHITE) class MiciOffroadAlerts(Scroller): diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index 7340360575..d6d3f70330 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -1,32 +1,24 @@ -from enum import IntEnum - -import weakref import math import numpy as np +import qrcode import pyray as rl +from collections.abc import Callable from openpilot.common.filter_simple import FirstOrderFilter -from openpilot.system.hardware import HARDWARE from openpilot.system.ui.lib.application import FontWeight, gui_app from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.widgets.button import SmallButton, SmallCircleIconButton -from openpilot.system.ui.widgets.label import UnifiedLabel -from openpilot.system.ui.widgets.slider import SmallSlider -from openpilot.system.ui.mici_setup import TermsHeader, TermsPage as SetupTermsPage -from openpilot.selfdrive.ui.ui_state import ui_state, device -from openpilot.selfdrive.ui.mici.onroad.driver_state import DriverStateRenderer -from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import BaseDriverCameraDialog +from openpilot.system.ui.widgets.button import SmallCircleIconButton +from openpilot.system.ui.widgets.scroller import NavScroller, Scroller +from openpilot.system.ui.widgets.nav_widget import NavWidget +from openpilot.system.ui.mici_setup import GreyBigButton, BigPillButton from openpilot.system.ui.widgets.label import gui_label from openpilot.system.ui.lib.multilang import tr from openpilot.system.version import terms_version, training_version, terms_version_sp - -from openpilot.selfdrive.ui.sunnypilot.mici.layouts.onboarding import SunnylinkOnboarding - - -class OnboardingState(IntEnum): - TERMS = 0 - ONBOARDING = 1 - DECLINE = 2 - SUNNYLINK_CONSENT = 3 +from openpilot.system.version import sunnylink_consent_version, sunnylink_consent_declined +from openpilot.selfdrive.ui.ui_state import ui_state, device +from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationCircleButton +from openpilot.selfdrive.ui.mici.onroad.driver_state import DriverStateRenderer +from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import BaseDriverCameraDialog +from openpilot.selfdrive.ui.sunnypilot.mici.layouts.onboarding import SunnylinkConsentPage class DriverCameraSetupDialog(BaseDriverCameraDialog): @@ -60,91 +52,62 @@ class DriverCameraSetupDialog(BaseDriverCameraDialog): rl.end_scissor_mode() -class TrainingGuidePreDMTutorial(SetupTermsPage): - def __init__(self, continue_callback): - super().__init__(continue_callback, continue_text="continue") - self._title_header = TermsHeader("driver monitoring setup", gui_app.texture("icons_mici/setup/green_dm.png", 60, 60)) +class TrainingGuidePreDMTutorial(NavScroller): + def __init__(self, continue_callback: Callable[[], None]): + super().__init__() - self._dm_label = UnifiedLabel("Next, we'll ensure comma four is mounted properly.\n\nIf it does not have a clear view of the driver, " + - "unplug and remount before continuing.", 42, - FontWeight.ROMAN) + continue_button = BigPillButton("next") + continue_button.set_click_callback(continue_callback) + + self._scroller.add_widgets([ + GreyBigButton("driver monitoring\ncheck", "scroll to continue", + gui_app.texture("icons_mici/setup/green_dm.png", 64, 64)), + GreyBigButton("", "Next, we'll check if comma four can detect the driver properly."), + GreyBigButton("", "sunnypilot uses the cabin camera to check if the driver is distracted."), + GreyBigButton("", "If it does not have a clear view of the driver, unplug and remount before continuing."), + continue_button, + ]) def show_event(self): super().show_event() # Get driver monitoring model ready for next step - ui_state.params.put_bool("IsDriverViewEnabled", True) - - @property - def _content_height(self): - return self._dm_label.rect.y + self._dm_label.rect.height - self._scroll_panel.get_offset() - - def _render_content(self, scroll_offset): - self._title_header.render(rl.Rectangle( - self._rect.x + 16, - self._rect.y + 16 + scroll_offset, - self._title_header.rect.width, - self._title_header.rect.height, - )) - - self._dm_label.render(rl.Rectangle( - self._rect.x + 16, - self._title_header.rect.y + self._title_header.rect.height + 16, - self._rect.width - 32, - self._dm_label.get_content_height(int(self._rect.width - 32)), - )) + ui_state.params.put_bool_nonblocking("IsDriverViewEnabled", True) -class DMBadFaceDetected(SetupTermsPage): - def __init__(self, continue_callback, back_callback): - super().__init__(continue_callback, back_callback, continue_text="power off") - self._title_header = TermsHeader("make sure comma four can see your face", gui_app.texture("icons_mici/setup/orange_dm.png", 60, 60)) - self._dm_label = UnifiedLabel("Re-mount if your face is occluded or driver monitoring has difficulty tracking your face.", 42, FontWeight.ROMAN) +class DMBadFaceDetected(NavScroller): + def __init__(self): + super().__init__() - @property - def _content_height(self): - return self._dm_label.rect.y + self._dm_label.rect.height - self._scroll_panel.get_offset() + back_button = BigPillButton("back") + back_button.set_click_callback(self.dismiss) - def _render_content(self, scroll_offset): - self._title_header.render(rl.Rectangle( - self._rect.x + 16, - self._rect.y + 16 + scroll_offset, - self._title_header.rect.width, - self._title_header.rect.height, - )) - - self._dm_label.render(rl.Rectangle( - self._rect.x + 16, - self._title_header.rect.y + self._title_header.rect.height + 16, - self._rect.width - 32, - self._dm_label.get_content_height(int(self._rect.width - 32)), - )) + self._scroller.add_widgets([ + GreyBigButton("looking for driver", "make sure comma\nfour can see your face", + gui_app.texture("icons_mici/setup/orange_dm.png", 64, 64)), + GreyBigButton("", "Remount if your face is blocked, or driver monitoring has difficulty tracking your face."), + back_button, + ]) -class TrainingGuideDMTutorial(Widget): +class TrainingGuideDMTutorial(NavWidget): PROGRESS_DURATION = 4 LOOKING_THRESHOLD_DEG = 30.0 - def __init__(self, continue_callback): + def __init__(self, continue_callback: Callable[[], None]): super().__init__() - self_ref = weakref.ref(self) - self._back_button = SmallCircleIconButton(gui_app.texture("icons_mici/setup/driver_monitoring/dm_question.png", 28, 48)) - self._back_button.set_click_callback(lambda: self_ref() and self_ref()._show_bad_face_page()) + self._back_button.set_click_callback(lambda: gui_app.push_widget(self._bad_face_page)) + self._back_button.set_touch_valid_callback(lambda: self.enabled and not self.is_dismissing) # for nav stack self._good_button = SmallCircleIconButton(gui_app.texture("icons_mici/setup/driver_monitoring/dm_check.png", 42, 42)) + self._good_button.set_touch_valid_callback(lambda: self.enabled and not self.is_dismissing) # for nav stack - # Wrap the continue callback to restore settings - def wrapped_continue_callback(): - device.set_offroad_brightness(None) - continue_callback() - - self._good_button.set_click_callback(wrapped_continue_callback) + self._good_button.set_click_callback(continue_callback) self._good_button.set_enabled(False) self._progress = FirstOrderFilter(0.0, 0.5, 1 / gui_app.target_fps) self._dialog = DriverCameraSetupDialog() - self._bad_face_page = DMBadFaceDetected(HARDWARE.shutdown, lambda: self_ref() and self_ref()._hide_bad_face_page()) - self._should_show_bad_face_page = False + self._bad_face_page = DMBadFaceDetected() # Disable driver monitoring model when device times out for inactivity def inactivity_callback(): @@ -152,23 +115,11 @@ class TrainingGuideDMTutorial(Widget): device.add_interactive_timeout_callback(inactivity_callback) - def _show_bad_face_page(self): - self._bad_face_page.show_event() - self.hide_event() - self._should_show_bad_face_page = True - - def _hide_bad_face_page(self): - self._bad_face_page.hide_event() - self.show_event() - self._should_show_bad_face_page = False - def show_event(self): super().show_event() self._dialog.show_event() self._progress.x = 0.0 - device.set_offroad_brightness(100) - def _update_state(self): super()._update_state() if device.awake and not ui_state.params.get_bool("IsDriverViewEnabled"): @@ -188,7 +139,8 @@ class TrainingGuideDMTutorial(Widget): looking_center = False # stay at 100% once reached - if (dm_state.faceDetected and looking_center) or self._progress.x > 0.99: + in_bad_face = gui_app.get_active_widget() == self._bad_face_page + if ((dm_state.faceDetected and looking_center) or self._progress.x > 0.99) and not in_bad_face: slow = self._progress.x < 0.25 duration = self.PROGRESS_DURATION * 2 if slow else self.PROGRESS_DURATION self._progress.x += 1.0 / (duration * gui_app.target_fps) @@ -199,13 +151,12 @@ class TrainingGuideDMTutorial(Widget): self._good_button.set_enabled(self._progress.x >= 0.999) def _render(self, _): - if self._should_show_bad_face_page: - return self._bad_face_page.render(self._rect) - self._dialog.render(self._rect) - rl.draw_rectangle_gradient_v(int(self._rect.x), int(self._rect.y + self._rect.height - 80), - int(self._rect.width), 80, rl.BLANK, rl.BLACK) + gradient_y = int(self._rect.y + self._rect.height - 80) + gradient_h = int(self._rect.y) + int(self._rect.height) - gradient_y + rl.draw_rectangle_gradient_v(int(self._rect.x), gradient_y, + int(self._rect.width), gradient_h, rl.BLANK, rl.BLACK) # draw white ring around dm icon to indicate progress ring_thickness = 8 @@ -258,266 +209,229 @@ class TrainingGuideDMTutorial(Widget): )) # rounded border + rl.begin_scissor_mode(int(self._rect.x), int(self._rect.y), int(self._rect.width), int(self._rect.height)) rl.draw_rectangle_rounded_lines_ex(self._rect, 0.2 * 1.02, 10, 50, rl.BLACK) + rl.end_scissor_mode() -class TrainingGuideRecordFront(SetupTermsPage): - def __init__(self, continue_callback): - def on_back(): - ui_state.params.put_bool("RecordFront", False) - continue_callback() - - def on_continue(): - ui_state.params.put_bool("RecordFront", True) - continue_callback() - - super().__init__(on_continue, back_callback=on_back, back_text="no", continue_text="yes") - self._title_header = TermsHeader("improve driver monitoring", gui_app.texture("icons_mici/setup/green_dm.png", 60, 60)) - - self._dm_label = UnifiedLabel("Do you want to upload driver camera data?", 42, - FontWeight.ROMAN) - - def show_event(self): - super().show_event() - # Disable driver monitoring model after last step - ui_state.params.put_bool("IsDriverViewEnabled", False) - - @property - def _content_height(self): - return self._dm_label.rect.y + self._dm_label.rect.height - self._scroll_panel.get_offset() - - def _render_content(self, scroll_offset): - self._title_header.render(rl.Rectangle( - self._rect.x + 16, - self._rect.y + 16 + scroll_offset, - self._title_header.rect.width, - self._title_header.rect.height, - )) - - self._dm_label.render(rl.Rectangle( - self._rect.x + 16, - self._title_header.rect.y + self._title_header.rect.height + 16, - self._rect.width - 32, - self._dm_label.get_content_height(int(self._rect.width - 32)), - )) - - -class TrainingGuideAttentionNotice(SetupTermsPage): - def __init__(self, continue_callback): - super().__init__(continue_callback, continue_text="continue") - self._title_header = TermsHeader("driver assistance", gui_app.texture("icons_mici/setup/warning.png", 60, 60)) - self._warning_label = UnifiedLabel("1. sunnypilot is a driver assistance system.\n\n" + - "2. You must pay attention at all times.\n\n" + - "3. You must be ready to take over at any time.\n\n" + - "4. You are fully responsible for driving the car.", 42, - FontWeight.ROMAN) - - @property - def _content_height(self): - return self._warning_label.rect.y + self._warning_label.rect.height - self._scroll_panel.get_offset() - - def _render_content(self, scroll_offset): - self._title_header.render(rl.Rectangle( - self._rect.x + 16, - self._rect.y + 16 + scroll_offset, - self._title_header.rect.width, - self._title_header.rect.height, - )) - - self._warning_label.render(rl.Rectangle( - self._rect.x + 16, - self._title_header.rect.y + self._title_header.rect.height + 16, - self._rect.width - 32, - self._warning_label.get_content_height(int(self._rect.width - 32)), - )) - - -class TrainingGuide(Widget): - def __init__(self, completed_callback=None): +class TrainingGuideRecordFront(NavScroller): + def __init__(self, continue_callback: Callable[[], None]): super().__init__() - self._completed_callback = completed_callback - self._step = 0 - self_ref = weakref.ref(self) + def on_accept(): + ui_state.params.put_bool_nonblocking("RecordFront", True) + continue_callback() - def on_continue(): - if obj := self_ref(): - obj._advance_step() + def on_decline(): + ui_state.params.put_bool_nonblocking("RecordFront", False) + continue_callback() + + self._accept_button = BigConfirmationCircleButton("allow data uploading", gui_app.texture("icons_mici/setup/driver_monitoring/dm_check.png", 64, 64), + on_accept, exit_on_confirm=False) + + self._decline_button = BigConfirmationCircleButton("no, don't upload", gui_app.texture("icons_mici/setup/cancel.png", 64, 64), on_decline, + exit_on_confirm=False) + + self._scroller.add_widgets([ + GreyBigButton("driver camera data", "do you want to share video data for training?", + gui_app.texture("icons_mici/setup/green_dm.png", 64, 64)), + GreyBigButton("", "Sharing your data with comma helps improve openpilot and sunnypilot for everyone."), + self._accept_button, + self._decline_button, + ]) + + +class TrainingGuideAttentionNotice(Scroller): + def __init__(self, continue_callback: Callable[[], None]): + super().__init__() + + continue_button = BigPillButton("next") + continue_button.set_click_callback(continue_callback) + + self._scroller.add_widgets([ + GreyBigButton("what is sunnypilot?", "scroll to continue", + gui_app.texture("icons_mici/setup/green_info.png", 64, 64)), + GreyBigButton("", "1. sunnypilot is a driver assistance system."), + GreyBigButton("", "2. You must pay attention at all times."), + GreyBigButton("", "3. You must be ready to take over at any time."), + GreyBigButton("", "4. You are fully responsible for driving the car."), + continue_button, + ]) + + +class TrainingGuide(NavWidget): + def __init__(self, completed_callback: Callable[[], None]): + super().__init__() self._steps = [ - TrainingGuideAttentionNotice(continue_callback=on_continue), - TrainingGuidePreDMTutorial(continue_callback=on_continue), - TrainingGuideDMTutorial(continue_callback=on_continue), - TrainingGuideRecordFront(continue_callback=on_continue), + TrainingGuideAttentionNotice(continue_callback=lambda: gui_app.push_widget(self._steps[1])), + TrainingGuidePreDMTutorial(continue_callback=lambda: gui_app.push_widget(self._steps[2])), + TrainingGuideDMTutorial(continue_callback=lambda: gui_app.push_widget(self._steps[3])), + TrainingGuideRecordFront(continue_callback=completed_callback), ] - def show_event(self): - super().show_event() - device.set_override_interactive_timeout(300) + self._child(self._steps[0]) + self._steps[0].set_enabled(lambda: self.enabled and not self.is_dismissing) # for nav stack - def hide_event(self): - super().hide_event() - device.set_override_interactive_timeout(None) + def _render(self, _): + self._steps[0].render(self._rect) - def _advance_step(self): - if self._step < len(self._steps) - 1: - self._step += 1 - self._steps[self._step].show_event() - else: - self._step = 0 - if self._completed_callback: - self._completed_callback() + +class QRCodeWidget(Widget): + def __init__(self, url: str, size: int = 170): + super().__init__() + self.set_rect(rl.Rectangle(0, 0, size, size)) + self._size = size + self._qr_texture: rl.Texture | None = None + self._generate_qr(url) + + def _generate_qr(self, url: str): + qr = qrcode.QRCode(version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=10, border=0) + qr.add_data(url) + qr.make(fit=True) + + pil_img = qr.make_image(fill_color="white", back_color="black").convert('RGBA') + img_array = np.array(pil_img, dtype=np.uint8) + + rl_image = rl.Image() + rl_image.data = rl.ffi.cast("void *", img_array.ctypes.data) + rl_image.width = pil_img.width + rl_image.height = pil_img.height + rl_image.mipmaps = 1 + rl_image.format = rl.PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 + + self._qr_texture = rl.load_texture_from_image(rl_image) + + def _render(self, _): + if self._qr_texture: + scale = self._size / self._qr_texture.height + rl.draw_texture_ex(self._qr_texture, rl.Vector2(round(self._rect.x), round(self._rect.y)), 0.0, scale, rl.WHITE) + + def __del__(self): + if self._qr_texture and self._qr_texture.id != 0: + rl.unload_texture(self._qr_texture) + + +class TermsPage(Scroller): + def __init__(self, on_accept, on_decline): + super().__init__() + + self._accept_button = BigConfirmationCircleButton("accept\nterms", gui_app.texture("icons_mici/setup/driver_monitoring/dm_check.png", 64, 64), on_accept) + self._decline_button = BigConfirmationCircleButton("decline &\nuninstall", gui_app.texture("icons_mici/setup/cancel.png", 64, 64), on_decline, + red=True, exit_on_confirm=False) + + self._terms_header = GreyBigButton("terms of\nservice", "scroll to continue", + gui_app.texture("icons_mici/setup/green_info.png", 64, 64)) + self._must_accept_card = GreyBigButton("", "You must accept the Terms of Service to use sunnypilot.") + + self._scroller.add_widgets([ + self._terms_header, + GreyBigButton("swipe for QR code", "or go to https://sunnypilot.ai/terms", + gui_app.texture("icons_mici/setup/small_slider/slider_arrow.png", 64, 56, flip_x=True)), + QRCodeWidget("https://sunnypilot.ai/terms"), + self._must_accept_card, + self._accept_button, + self._decline_button, + ]) def _render(self, _): rl.draw_rectangle_rec(self._rect, rl.BLACK) - if self._step < len(self._steps): - self._steps[self._step].render(self._rect) - - -class DeclinePage(Widget): - def __init__(self, back_callback=None): - super().__init__() - self._uninstall_slider = SmallSlider("uninstall sunnypilot", self._on_uninstall) - - self._back_button = SmallButton("back") - self._back_button.set_click_callback(back_callback) - - self._warning_header = TermsHeader("you must accept the\nterms to use sunnypilot", - gui_app.texture("icons_mici/setup/red_warning.png", 66, 60)) - - def _on_uninstall(self): - ui_state.params.put_bool("DoUninstall", True) - gui_app.request_close() - - def _render(self, _): - self._warning_header.render(rl.Rectangle( - self._rect.x + 16, - self._rect.y + 16, - self._warning_header.rect.width, - self._warning_header.rect.height, - )) - - self._back_button.set_opacity(1 - self._uninstall_slider.slider_percentage) - self._back_button.render(rl.Rectangle( - self._rect.x + 8, - self._rect.y + self._rect.height - self._back_button.rect.height, - self._back_button.rect.width, - self._back_button.rect.height, - )) - - self._uninstall_slider.render(rl.Rectangle( - self._rect.x + self._rect.width - self._uninstall_slider.rect.width, - self._rect.y + self._rect.height - self._uninstall_slider.rect.height, - self._uninstall_slider.rect.width, - self._uninstall_slider.rect.height, - )) - - -class TermsPage(SetupTermsPage): - def __init__(self, on_accept=None, on_decline=None): - super().__init__(on_accept, on_decline, "decline") - - info_txt = gui_app.texture("icons_mici/setup/green_info.png", 60, 60) - self._title_header = TermsHeader("terms of service", info_txt) - - self._terms_label = UnifiedLabel("You must accept the Terms of Service to use sunnypilot. " + - "Read the latest terms at https://sunnypilot.ai/terms before continuing.", 36, - FontWeight.ROMAN) - - @property - def _content_height(self): - return self._terms_label.rect.y + self._terms_label.rect.height - self._scroll_panel.get_offset() - - def _render_content(self, scroll_offset): - self._title_header.set_position(self._rect.x + 16, self._rect.y + 12 + scroll_offset) - self._title_header.render() - - self._terms_label.render(rl.Rectangle( - self._rect.x + 16, - self._title_header.rect.y + self._title_header.rect.height + self.ITEM_SPACING, - self._rect.width - 100, - self._terms_label.get_content_height(int(self._rect.width - 100)), - )) + super()._render(_) class OnboardingWindow(Widget): - def __init__(self): + def __init__(self, completed_callback: Callable[[], None]): super().__init__() - self._accepted_terms: bool = ui_state.params.get("HasAcceptedTerms") == terms_version + self._completed_callback = completed_callback + self._accepted_terms: bool = (ui_state.params.get("HasAcceptedTerms") == terms_version and + ui_state.params.get("HasAcceptedTermsSP") == terms_version_sp) self._training_done: bool = ui_state.params.get("CompletedTrainingVersion") == training_version + self._sunnylink_consent_done: bool = ui_state.params.get("CompletedSunnylinkConsentVersion") in { + sunnylink_consent_version, sunnylink_consent_declined + } - self._state = OnboardingState.TERMS if not self._accepted_terms else OnboardingState.ONBOARDING + self.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) - self.set_rect(rl.Rectangle(0, 0, 458, gui_app.height)) + # Windows — all pushed onto nav stack, _terms is always rendered as base layer + self._terms = TermsPage(on_accept=self._on_terms_accepted, on_decline=self._on_uninstall) + self._terms.set_enabled(lambda: self.enabled) # for nav stack + + self._sunnylink_consent = SunnylinkConsentPage( + on_accept=self._on_sunnylink_accepted, + on_decline=self._on_sunnylink_declined, + ) - # Windows - self._terms = TermsPage(on_accept=self._on_terms_accepted, on_decline=self._on_terms_declined) self._training_guide = TrainingGuide(completed_callback=self._on_completed_training) - self._decline_page = DeclinePage(back_callback=self._on_decline_back) + self._training_guide.set_enabled(lambda: self.enabled) # for nav stack - # sunnylink consent pages - self._accepted_terms = self._accepted_terms and ui_state.params.get("HasAcceptedTermsSP") == terms_version_sp - self._sunnylink = SunnylinkOnboarding() - if not self._accepted_terms: - self._state = OnboardingState.TERMS - elif not self._sunnylink.completed: - self._state = OnboardingState.SUNNYLINK_CONSENT - elif not self._training_done: - self._state = OnboardingState.ONBOARDING - else: - self._state = OnboardingState.ONBOARDING + self._needs_initial_push = False + + def _on_uninstall(self): + ui_state.params.put_bool("DoUninstall", True) def show_event(self): super().show_event() device.set_override_interactive_timeout(300) + device.set_offroad_brightness(100) + self._needs_initial_push = True def hide_event(self): super().hide_event() + # FIXME: when nav stack sends hide event to widget 2 below on push, this needs to be moved device.set_override_interactive_timeout(None) + device.set_offroad_brightness(None) @property def completed(self) -> bool: - return self._accepted_terms and self._sunnylink.completed and self._training_done - - def _on_terms_declined(self): - self._state = OnboardingState.DECLINE - - def _on_decline_back(self): - self._state = OnboardingState.TERMS + return self._accepted_terms and self._sunnylink_consent_done and self._training_done def close(self): - ui_state.params.put_bool("IsDriverViewEnabled", False) - gui_app.pop_widget() + ui_state.params.put_bool_nonblocking("IsDriverViewEnabled", False) + self._completed_callback() def _on_terms_accepted(self): ui_state.params.put("HasAcceptedTerms", terms_version) ui_state.params.put("HasAcceptedTermsSP", terms_version_sp) - if not self._sunnylink.completed: - self._state = OnboardingState.SUNNYLINK_CONSENT + self._accepted_terms = True + if not self._sunnylink_consent_done: + gui_app.push_widget(self._sunnylink_consent) elif not self._training_done: - self._state = OnboardingState.ONBOARDING + gui_app.push_widget(self._training_guide) + else: + self.close() + + def _on_sunnylink_accepted(self): + ui_state.params.put("CompletedSunnylinkConsentVersion", sunnylink_consent_version) + ui_state.params.put_bool("SunnylinkEnabled", True) + self._sunnylink_consent_done = True + if not self._training_done: + gui_app.push_widget(self._training_guide) + else: + self.close() + + def _on_sunnylink_declined(self): + ui_state.params.put("CompletedSunnylinkConsentVersion", sunnylink_consent_declined) + ui_state.params.put_bool("SunnylinkEnabled", False) + self._sunnylink_consent_done = True + if not self._training_done: + gui_app.push_widget(self._training_guide) else: self.close() def _on_completed_training(self): ui_state.params.put("CompletedTrainingVersion", training_version) + self._training_done = True self.close() def _render(self, _): rl.draw_rectangle_rec(self._rect, rl.BLACK) - if self._state == OnboardingState.TERMS: - self._terms.render(self._rect) - elif self._state == OnboardingState.SUNNYLINK_CONSENT: - self._sunnylink.render(self._rect) - if self._sunnylink.completed: - if not self._training_done: - self._state = OnboardingState.ONBOARDING - else: - self.close() - elif self._state == OnboardingState.ONBOARDING: - if not self._training_done: - self._training_guide.render(self._rect) - else: - self.close() - elif self._state == OnboardingState.DECLINE: - self._decline_page.render(self._rect) + + # Deferred from show_event to avoid nested push_widget re-enable bug + if self._needs_initial_push: + self._needs_initial_push = False + if self._accepted_terms and not self._sunnylink_consent_done: + gui_app.push_widget(self._sunnylink_consent) + elif self._accepted_terms and self._sunnylink_consent_done and not self._training_done: + gui_app.push_widget(self._training_guide) + + self._terms.render(self._rect) diff --git a/selfdrive/ui/mici/layouts/settings/developer.py b/selfdrive/ui/mici/layouts/settings/developer.py index 4e7796814e..386b468928 100644 --- a/selfdrive/ui/mici/layouts/settings/developer.py +++ b/selfdrive/ui/mici/layouts/settings/developer.py @@ -5,32 +5,37 @@ from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigInputDialog from openpilot.system.ui.lib.application import gui_app from openpilot.selfdrive.ui.layouts.settings.common import restart_needed_callback from openpilot.selfdrive.ui.ui_state import ui_state -from openpilot.selfdrive.ui.widgets.ssh_key import SshKeyAction +from openpilot.selfdrive.ui.widgets.ssh_key import SshKeyFetcher class DeveloperLayoutMici(NavScroller): def __init__(self): super().__init__() + self._ssh_fetcher = SshKeyFetcher(ui_state.params) def github_username_callback(username: str): if username: - ssh_keys = SshKeyAction() - ssh_keys._fetch_ssh_key(username) - if not ssh_keys._error_message: - self._ssh_keys_btn.set_value(username) - else: - dlg = BigDialog("", ssh_keys._error_message) - gui_app.push_widget(dlg) + self._ssh_keys_btn.set_value("Loading...") + self._ssh_keys_btn.set_enabled(False) + + def on_response(error): + self._ssh_keys_btn.set_enabled(True) + if error is None: + self._ssh_keys_btn.set_value(username) + else: + self._ssh_keys_btn.set_value("Not set") + gui_app.push_widget(BigDialog("", error)) + + self._ssh_fetcher.fetch(username, on_response) else: - ui_state.params.remove("GithubUsername") - ui_state.params.remove("GithubSshKeys") + self._ssh_fetcher.clear() self._ssh_keys_btn.set_value("Not set") def ssh_keys_callback(): github_username = ui_state.params.get("GithubUsername") or "" dlg = BigInputDialog("enter GitHub username...", github_username, minimum_length=0, confirm_callback=github_username_callback) if not system_time_valid(): - dlg = BigDialog("Please connect to Wi-Fi to fetch your key", "") + dlg = BigDialog("", "Please connect to Wi-Fi to fetch your key.") gui_app.push_widget(dlg) return gui_app.push_widget(dlg) @@ -42,8 +47,8 @@ class DeveloperLayoutMici(NavScroller): # adb, ssh, ssh keys, debug mode, joystick debug mode, longitudinal maneuver mode, ip address # ******** Main Scroller ******** - self._adb_toggle = BigCircleParamControl("icons_mici/adb_short.png", "AdbEnabled", icon_size=(82, 82), icon_offset=(0, 12)) - self._ssh_toggle = BigCircleParamControl("icons_mici/ssh_short.png", "SshEnabled", icon_size=(82, 82), icon_offset=(0, 12)) + self._adb_toggle = BigCircleParamControl(gui_app.texture("icons_mici/adb_short.png", 82, 82), "AdbEnabled", icon_offset=(0, 12)) + self._ssh_toggle = BigCircleParamControl(gui_app.texture("icons_mici/ssh_short.png", 82, 82), "SshEnabled", icon_offset=(0, 12)) self._joystick_toggle = BigToggle("joystick debug mode", initial_state=ui_state.params.get_bool("JoystickDebugMode"), toggle_callback=self._on_joystick_debug_mode) @@ -99,6 +104,10 @@ class DeveloperLayoutMici(NavScroller): ui_state.add_offroad_transition_callback(self._update_toggles) + def _update_state(self): + super()._update_state() + self._ssh_fetcher.update() + def show_event(self): super().show_event() self._update_toggles() diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index 0d253cb26f..e0d89a5419 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -9,19 +9,40 @@ from openpilot.common.params import Params from openpilot.common.time_helpers import system_time_valid from openpilot.system.ui.widgets.scroller import NavRawScrollPanel, NavScroller from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigCircleButton -from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialogV2 +from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialog from openpilot.selfdrive.ui.mici.widgets.pairing_dialog import PairingDialog from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCameraDialog from openpilot.selfdrive.ui.mici.layouts.onboarding import TrainingGuide, TermsPage from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import Widget -from openpilot.selfdrive.ui.ui_state import ui_state -from openpilot.system.ui.widgets.label import MiciLabel +from openpilot.selfdrive.ui.ui_state import device, ui_state +from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.widgets.html_render import HtmlModal, HtmlRenderer from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID +class ReviewTermsPage(TermsPage, NavScroller): + """TermsPage with NavWidget swipe-to-dismiss for reviewing in device settings.""" + def __init__(self): + super().__init__(on_accept=self.dismiss, on_decline=self.dismiss) + self._terms_header.set_visible(False) + self._must_accept_card.set_visible(False) + self._accept_button.set_visible(False) + self._decline_button.set_visible(False) + + +class ReviewTrainingGuide(TrainingGuide): + def show_event(self): + super().show_event() + device.set_override_interactive_timeout(300) + + def hide_event(self): + super().hide_event() + device.set_override_interactive_timeout(None) + ui_state.params.put_bool_nonblocking("IsDriverViewEnabled", False) + + class MiciFccModal(NavRawScrollPanel): def __init__(self, file_path: str | None = None, text: str | None = None): super().__init__() @@ -43,34 +64,31 @@ class MiciFccModal(NavRawScrollPanel): rl.draw_texture_ex(self._fcc_logo, fcc_pos, 0.0, 1.0, rl.WHITE) -def _engaged_confirmation_callback(callback: Callable, action_text: str): +def _engaged_confirmation_click(callback: Callable, action_text: str, icon: rl.Texture, exit_on_confirm: bool = True, red: bool = False): if not ui_state.engaged: def confirm_callback(): # Check engaged again in case it changed while the dialog was open + # TODO: if true, we stay on the dialog if not exit_on_confirm until normal onroad timeout if not ui_state.engaged: callback() - red = False - if action_text == "power off": - icon = "icons_mici/settings/device/power.png" - red = True - elif action_text == "reboot": - icon = "icons_mici/settings/device/reboot.png" - elif action_text == "reset": - icon = "icons_mici/settings/device/lkas.png" - elif action_text == "uninstall": - icon = "icons_mici/settings/device/uninstall.png" - else: - # TODO: check - icon = "icons_mici/settings/comma_icon.png" - - dlg: BigConfirmationDialogV2 | BigDialog = BigConfirmationDialogV2(f"slide to\n{action_text.lower()}", icon, red=red, - exit_on_confirm=action_text == "reset", - confirm_callback=confirm_callback) - gui_app.push_widget(dlg) + gui_app.push_widget(BigConfirmationDialog(f"slide to\n{action_text.lower()}", icon, confirm_callback, exit_on_confirm=exit_on_confirm, red=red)) else: - dlg = BigDialog(f"Disengage to {action_text}", "") - gui_app.push_widget(dlg) + gui_app.push_widget(BigDialog("", f"Disengage to {action_text}")) + + +class EngagedConfirmationCircleButton(BigCircleButton): + def __init__(self, title: str, icon: rl.Texture, callback: Callable[[], None], exit_on_confirm: bool = True, + red: bool = False, icon_offset: tuple[int, int] = (0, 0)): + super().__init__(icon, red, icon_offset) + self.set_click_callback(lambda: _engaged_confirmation_click(callback, title, icon, exit_on_confirm=exit_on_confirm, red=red)) + + +class EngagedConfirmationButton(BigButton): + def __init__(self, text: str, action_text: str, icon: rl.Texture, callback: Callable[[], None], + exit_on_confirm: bool = True, red: bool = False): + super().__init__(text, "", icon) + self.set_click_callback(lambda: _engaged_confirmation_click(callback, action_text, icon, exit_on_confirm=exit_on_confirm, red=red)) class DeviceInfoLayoutMici(Widget): @@ -80,14 +98,15 @@ class DeviceInfoLayoutMici(Widget): self.set_rect(rl.Rectangle(0, 0, 360, 180)) params = Params() - header_color = rl.Color(255, 255, 255, int(255 * 0.9)) subheader_color = rl.Color(255, 255, 255, int(255 * 0.9 * 0.65)) max_width = int(self._rect.width - 20) - self._dongle_id_label = MiciLabel("device ID", 48, width=max_width, color=header_color, font_weight=FontWeight.DISPLAY) - self._dongle_id_text_label = MiciLabel(params.get("DongleId") or 'N/A', 32, width=max_width, color=subheader_color, font_weight=FontWeight.ROMAN) + self._dongle_id_label = UnifiedLabel("device ID", 48, max_width=max_width, font_weight=FontWeight.DISPLAY, wrap_text=False) + self._dongle_id_text_label = UnifiedLabel(params.get("DongleId") or 'N/A', 32, max_width=max_width, text_color=subheader_color, + font_weight=FontWeight.ROMAN, wrap_text=False) - self._serial_number_label = MiciLabel("serial", 48, color=header_color, font_weight=FontWeight.DISPLAY) - self._serial_number_text_label = MiciLabel(params.get("HardwareSerial") or 'N/A', 32, width=max_width, color=subheader_color, font_weight=FontWeight.ROMAN) + self._serial_number_label = UnifiedLabel("serial", 48, max_width=max_width, font_weight=FontWeight.DISPLAY, wrap_text=False) + self._serial_number_text_label = UnifiedLabel(params.get("HardwareSerial") or 'N/A', 32, max_width=max_width, text_color=subheader_color, + font_weight=FontWeight.ROMAN, wrap_text=False) def _render(self, _): self._dongle_id_label.set_position(self._rect.x + 20, self._rect.y - 10) @@ -111,7 +130,7 @@ class UpdaterState(IntEnum): class PairBigButton(BigButton): def __init__(self): - super().__init__("pair", "connect.comma.ai", "icons_mici/settings/comma_icon.png", icon_size=(33, 60)) + super().__init__("pair", "connect.comma.ai", gui_app.texture("icons_mici/settings/comma_icon.png", 33, 60)) def _get_label_font_size(self): return 64 @@ -137,9 +156,9 @@ class PairBigButton(BigButton): return dlg: BigDialog | PairingDialog if not system_time_valid(): - dlg = BigDialog(tr("Please connect to Wi-Fi to complete initial pairing"), "") + dlg = BigDialog("", tr("Please connect to Wi-Fi to complete initial pairing.")) elif UNREGISTERED_DONGLE_ID == (ui_state.params.get("DongleId") or UNREGISTERED_DONGLE_ID): - dlg = BigDialog(tr("Device must be registered with the comma.ai backend to pair"), "") + dlg = BigDialog("", tr("Device must be registered with the comma.ai backend to pair.")) else: dlg = PairingDialog() gui_app.push_widget(dlg) @@ -169,7 +188,7 @@ class UpdateOpenpilotBigButton(BigButton): super()._handle_mouse_release(mouse_pos) if not system_time_valid(): - dlg = BigDialog(tr("Please connect to Wi-Fi to update"), "") + dlg = BigDialog("", tr("Please connect to Wi-Fi to update.")) gui_app.push_widget(dlg) return @@ -290,33 +309,33 @@ class DeviceLayoutMici(NavScroller): def uninstall_openpilot_callback(): ui_state.params.put_bool("DoUninstall", True) - reset_calibration_btn = BigButton("reset calibration", "", "icons_mici/settings/device/lkas.png", icon_size=(114, 60)) - reset_calibration_btn.set_click_callback(lambda: _engaged_confirmation_callback(reset_calibration_callback, "reset")) + reset_calibration_btn = EngagedConfirmationButton("reset calibration", "reset", gui_app.texture("icons_mici/settings/device/lkas.png", 122, 64), + reset_calibration_callback) - uninstall_openpilot_btn = BigButton("uninstall sunnypilot", "", "icons_mici/settings/device/uninstall.png") - uninstall_openpilot_btn.set_click_callback(lambda: _engaged_confirmation_callback(uninstall_openpilot_callback, "uninstall")) + uninstall_openpilot_btn = EngagedConfirmationButton("uninstall sunnypilot", "uninstall", + gui_app.texture("icons_mici/settings/device/uninstall.png", 64, 64), + uninstall_openpilot_callback, exit_on_confirm=False) - reboot_btn = BigCircleButton("icons_mici/settings/device/reboot.png", red=False, icon_size=(64, 70)) - reboot_btn.set_click_callback(lambda: _engaged_confirmation_callback(reboot_callback, "reboot")) + reboot_btn = EngagedConfirmationCircleButton("reboot", gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70), + reboot_callback, exit_on_confirm=False) - self._power_off_btn = BigCircleButton("icons_mici/settings/device/power.png", red=True, icon_size=(64, 66)) - self._power_off_btn.set_click_callback(lambda: _engaged_confirmation_callback(power_off_callback, "power off")) + self._power_off_btn = EngagedConfirmationCircleButton("power off", gui_app.texture("icons_mici/settings/device/power.png", 64, 66), + power_off_callback, exit_on_confirm=False, red=True) self._power_off_btn.set_visible(lambda: not ui_state.ignition) - regulatory_btn = BigButton("regulatory info", "", "icons_mici/settings/device/info.png") + regulatory_btn = BigButton("regulatory info", "", gui_app.texture("icons_mici/settings/device/info.png", 64, 64)) regulatory_btn.set_click_callback(self._on_regulatory) - driver_cam_btn = BigButton("driver\ncamera preview", "", "icons_mici/settings/device/cameras.png") + driver_cam_btn = BigButton("driver\ncamera preview", "", gui_app.texture("icons_mici/settings/device/cameras.png", 64, 64)) driver_cam_btn.set_click_callback(lambda: gui_app.push_widget(DriverCameraDialog())) driver_cam_btn.set_enabled(lambda: ui_state.is_offroad()) - review_training_guide_btn = BigButton("review\ntraining guide", "", "icons_mici/settings/device/info.png") - review_training_guide_btn.set_click_callback(lambda: gui_app.push_widget(TrainingGuide(completed_callback=gui_app.pop_widget))) + review_training_guide_btn = BigButton("review\ntraining guide", "", gui_app.texture("icons_mici/settings/device/info.png", 64, 64)) + review_training_guide_btn.set_click_callback(lambda: gui_app.push_widget(ReviewTrainingGuide(completed_callback=lambda: gui_app.pop_widgets_to(self)))) review_training_guide_btn.set_enabled(lambda: ui_state.is_offroad()) - terms_btn = BigButton("terms &\nconditions", "", "icons_mici/settings/device/info.png") - terms_btn.set_click_callback(lambda: gui_app.push_widget(TermsPage(on_accept=gui_app.pop_widget))) - terms_btn.set_enabled(lambda: ui_state.is_offroad()) + terms_btn = BigButton("terms &\nconditions", "", gui_app.texture("icons_mici/settings/device/info.png", 64, 64)) + terms_btn.set_click_callback(lambda: gui_app.push_widget(ReviewTermsPage())) self._scroller.add_widgets([ DeviceInfoLayoutMici(), diff --git a/selfdrive/ui/mici/layouts/settings/firehose.py b/selfdrive/ui/mici/layouts/settings/firehose.py index 741ea9655a..5bf7426c77 100644 --- a/selfdrive/ui/mici/layouts/settings/firehose.py +++ b/selfdrive/ui/mici/layouts/settings/firehose.py @@ -81,12 +81,12 @@ class FirehoseLayoutBase(Widget): def _render(self, rect: rl.Rectangle): # compute total content height for scrolling content_height = self._measure_content_height(rect) - scroll_offset = round(self._scroll_panel.update(rect, content_height)) + scroll_offset = self._scroll_panel.update(rect, content_height) # start drawing with offset - x = int(rect.x + 40) - y = int(rect.y + 40 + scroll_offset) - w = int(rect.width - 80) + x = rect.x + 40 + y = rect.y + 40 + scroll_offset + w = rect.width - 80 # Title title_text = tr(TITLE) @@ -100,7 +100,7 @@ class FirehoseLayoutBase(Widget): y += 20 # Separator - rl.draw_rectangle(x, y, w, 2, self.GRAY) + rl.draw_rectangle_rec(rl.Rectangle(x, y, w, 2), self.GRAY) y += 20 # Status @@ -116,7 +116,7 @@ class FirehoseLayoutBase(Widget): y += 20 # Separator - rl.draw_rectangle(x, y, w, 2, self.GRAY) + rl.draw_rectangle_rec(rl.Rectangle(x, y, w, 2), self.GRAY) y += 20 # Instructions intro diff --git a/selfdrive/ui/mici/layouts/settings/network/__init__.py b/selfdrive/ui/mici/layouts/settings/network/__init__.py index 553a74fc60..ddbab4b478 100644 --- a/selfdrive/ui/mici/layouts/settings/network/__init__.py +++ b/selfdrive/ui/mici/layouts/settings/network/__init__.py @@ -1,13 +1,9 @@ import pyray as rl -from openpilot.system.ui.widgets.scroller import NavScroller -from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici, WifiIcon -from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigMultiToggle, BigParamControl, BigToggle -from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog -from openpilot.selfdrive.ui.ui_state import ui_state -from openpilot.selfdrive.ui.lib.prime_state import PrimeType +from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiIcon +from openpilot.selfdrive.ui.mici.widgets.button import BigButton from openpilot.system.ui.lib.application import gui_app -from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, MeteredType, ConnectStatus, SecurityType, normalize_ssid +from openpilot.system.ui.lib.wifi_manager import WifiManager, ConnectStatus, SecurityType, normalize_ssid class WifiNetworkButton(BigButton): @@ -62,148 +58,3 @@ class WifiNetworkButton(BigButton): lock_x = icon_x + self._txt_icon.width - self._lock_txt.width + 7 lock_y = icon_y + self._txt_icon.height - self._lock_txt.height + 8 rl.draw_texture_ex(self._lock_txt, (lock_x, lock_y), 0.0, 1.0, rl.WHITE) - - -class NetworkLayoutMici(NavScroller): - def __init__(self): - super().__init__() - - self._wifi_manager = WifiManager() - self._wifi_manager.set_active(False) - self._wifi_ui = WifiUIMici(self._wifi_manager) - - self._wifi_manager.add_callbacks( - networks_updated=self._on_network_updated, - ) - - # ******** Tethering ******** - def tethering_toggle_callback(checked: bool): - self._tethering_toggle_btn.set_enabled(False) - self._tethering_password_btn.set_enabled(False) - self._network_metered_btn.set_enabled(False) - self._wifi_manager.set_tethering_active(checked) - - self._tethering_toggle_btn = BigToggle("enable tethering", "", toggle_callback=tethering_toggle_callback) - - def tethering_password_callback(password: str): - if password: - self._tethering_toggle_btn.set_enabled(False) - self._tethering_password_btn.set_enabled(False) - self._wifi_manager.set_tethering_password(password) - - def tethering_password_clicked(): - tethering_password = self._wifi_manager.tethering_password - dlg = BigInputDialog("enter password...", tethering_password, minimum_length=8, - confirm_callback=tethering_password_callback) - gui_app.push_widget(dlg) - - txt_tethering = gui_app.texture("icons_mici/settings/network/tethering.png", 64, 54) - self._tethering_password_btn = BigButton("tethering password", "", txt_tethering) - self._tethering_password_btn.set_click_callback(tethering_password_clicked) - - # ******** Network Metered ******** - def network_metered_callback(value: str): - self._network_metered_btn.set_enabled(False) - metered = { - 'default': MeteredType.UNKNOWN, - 'metered': MeteredType.YES, - 'unmetered': MeteredType.NO - }.get(value, MeteredType.UNKNOWN) - self._wifi_manager.set_current_network_metered(metered) - - # TODO: signal for current network metered type when changing networks, this is wrong until you press it once - # TODO: disable when not connected - self._network_metered_btn = BigMultiToggle("network usage", ["default", "metered", "unmetered"], select_callback=network_metered_callback) - self._network_metered_btn.set_enabled(False) - - self._wifi_button = WifiNetworkButton(self._wifi_manager) - self._wifi_button.set_click_callback(lambda: gui_app.push_widget(self._wifi_ui)) - - # ******** Advanced settings ******** - # ******** Roaming toggle ******** - self._roaming_btn = BigParamControl("enable roaming", "GsmRoaming", toggle_callback=self._toggle_roaming) - - # ******** APN settings ******** - self._apn_btn = BigButton("apn settings", "edit") - self._apn_btn.set_click_callback(self._edit_apn) - - # ******** Cellular metered toggle ******** - self._cellular_metered_btn = BigParamControl("cellular metered", "GsmMetered", toggle_callback=self._toggle_cellular_metered) - - # Main scroller ---------------------------------- - self._scroller.add_widgets([ - self._wifi_button, - self._network_metered_btn, - self._tethering_toggle_btn, - self._tethering_password_btn, - # /* Advanced settings - self._roaming_btn, - self._apn_btn, - self._cellular_metered_btn, - # */ - ]) - - # Set initial config - roaming_enabled = ui_state.params.get_bool("GsmRoaming") - metered = ui_state.params.get_bool("GsmMetered") - self._wifi_manager.update_gsm_settings(roaming_enabled, ui_state.params.get("GsmApn") or "", metered) - - def _update_state(self): - super()._update_state() - - # If not using prime SIM, show GSM settings and enable IPv4 forwarding - show_cell_settings = ui_state.prime_state.get_type() in (PrimeType.NONE, PrimeType.LITE) - self._wifi_manager.set_ipv4_forward(show_cell_settings) - self._roaming_btn.set_visible(show_cell_settings) - self._apn_btn.set_visible(show_cell_settings) - self._cellular_metered_btn.set_visible(show_cell_settings) - - def show_event(self): - super().show_event() - self._wifi_manager.set_active(True) - - # Process wifi callbacks while at any point in the nav stack - gui_app.add_nav_stack_tick(self._wifi_manager.process_callbacks) - - def hide_event(self): - super().hide_event() - self._wifi_manager.set_active(False) - - gui_app.remove_nav_stack_tick(self._wifi_manager.process_callbacks) - - def _toggle_roaming(self, checked: bool): - self._wifi_manager.update_gsm_settings(checked, ui_state.params.get("GsmApn") or "", ui_state.params.get_bool("GsmMetered")) - - def _edit_apn(self): - def update_apn(apn: str): - apn = apn.strip() - if apn == "": - ui_state.params.remove("GsmApn") - else: - ui_state.params.put("GsmApn", apn) - - self._wifi_manager.update_gsm_settings(ui_state.params.get_bool("GsmRoaming"), apn, ui_state.params.get_bool("GsmMetered")) - - current_apn = ui_state.params.get("GsmApn") or "" - dlg = BigInputDialog("enter APN...", current_apn, minimum_length=0, confirm_callback=update_apn) - gui_app.push_widget(dlg) - - def _toggle_cellular_metered(self, checked: bool): - self._wifi_manager.update_gsm_settings(ui_state.params.get_bool("GsmRoaming"), ui_state.params.get("GsmApn") or "", checked) - - def _on_network_updated(self, networks: list[Network]): - # Update tethering state - tethering_active = self._wifi_manager.is_tethering_active() - # TODO: use real signals (like activated/settings changed, etc.) to speed up re-enabling buttons - self._tethering_toggle_btn.set_enabled(True) - self._tethering_password_btn.set_enabled(True) - self._network_metered_btn.set_enabled(lambda: not tethering_active and bool(self._wifi_manager.ipv4_address)) - self._tethering_toggle_btn.set_checked(tethering_active) - - # Update network metered - self._network_metered_btn.set_value( - { - MeteredType.UNKNOWN: 'default', - MeteredType.YES: 'metered', - MeteredType.NO: 'unmetered' - }.get(self._wifi_manager.current_network_metered, 'default')) diff --git a/selfdrive/ui/mici/layouts/settings/network/network_layout.py b/selfdrive/ui/mici/layouts/settings/network/network_layout.py new file mode 100644 index 0000000000..9f6fae4b5f --- /dev/null +++ b/selfdrive/ui/mici/layouts/settings/network/network_layout.py @@ -0,0 +1,154 @@ +from openpilot.system.ui.widgets.scroller import NavScroller +from openpilot.selfdrive.ui.mici.layouts.settings.network import WifiNetworkButton +from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici +from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigMultiToggle, BigParamControl, BigToggle +from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.selfdrive.ui.lib.prime_state import PrimeType +from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, MeteredType + + +class NetworkLayoutMici(NavScroller): + def __init__(self): + super().__init__() + + self._wifi_manager = WifiManager() + self._wifi_manager.set_active(False) + self._wifi_ui = WifiUIMici(self._wifi_manager) + + self._wifi_manager.add_callbacks( + networks_updated=self._on_network_updated, + ) + + # ******** Tethering ******** + def tethering_toggle_callback(checked: bool): + self._tethering_toggle_btn.set_enabled(False) + self._tethering_password_btn.set_enabled(False) + self._network_metered_btn.set_enabled(False) + self._wifi_manager.set_tethering_active(checked) + + self._tethering_toggle_btn = BigToggle("enable tethering", "", toggle_callback=tethering_toggle_callback) + + def tethering_password_callback(password: str): + if password: + self._tethering_toggle_btn.set_enabled(False) + self._tethering_password_btn.set_enabled(False) + self._wifi_manager.set_tethering_password(password) + + def tethering_password_clicked(): + tethering_password = self._wifi_manager.tethering_password + dlg = BigInputDialog("enter password...", tethering_password, minimum_length=8, + confirm_callback=tethering_password_callback) + gui_app.push_widget(dlg) + + txt_tethering = gui_app.texture("icons_mici/settings/network/tethering.png", 64, 54) + self._tethering_password_btn = BigButton("tethering password", "", txt_tethering) + self._tethering_password_btn.set_click_callback(tethering_password_clicked) + + # ******** Network Metered ******** + def network_metered_callback(value: str): + self._network_metered_btn.set_enabled(False) + metered = { + 'default': MeteredType.UNKNOWN, + 'metered': MeteredType.YES, + 'unmetered': MeteredType.NO + }.get(value, MeteredType.UNKNOWN) + self._wifi_manager.set_current_network_metered(metered) + + # TODO: signal for current network metered type when changing networks, this is wrong until you press it once + # TODO: disable when not connected + self._network_metered_btn = BigMultiToggle("network usage", ["default", "metered", "unmetered"], select_callback=network_metered_callback) + self._network_metered_btn.set_enabled(False) + + self._wifi_button = WifiNetworkButton(self._wifi_manager) + self._wifi_button.set_click_callback(lambda: gui_app.push_widget(self._wifi_ui)) + + # ******** Advanced settings ******** + # ******** Roaming toggle ******** + self._roaming_btn = BigParamControl("enable roaming", "GsmRoaming", toggle_callback=self._toggle_roaming) + + # ******** APN settings ******** + self._apn_btn = BigButton("apn settings", "edit") + self._apn_btn.set_click_callback(self._edit_apn) + + # ******** Cellular metered toggle ******** + self._cellular_metered_btn = BigParamControl("cellular metered", "GsmMetered", toggle_callback=self._toggle_cellular_metered) + + # Main scroller ---------------------------------- + self._scroller.add_widgets([ + self._wifi_button, + self._network_metered_btn, + self._tethering_toggle_btn, + self._tethering_password_btn, + # /* Advanced settings + self._roaming_btn, + self._apn_btn, + self._cellular_metered_btn, + # */ + ]) + + # Set initial config + roaming_enabled = ui_state.params.get_bool("GsmRoaming") + metered = ui_state.params.get_bool("GsmMetered") + self._wifi_manager.update_gsm_settings(roaming_enabled, ui_state.params.get("GsmApn") or "", metered) + + def _update_state(self): + super()._update_state() + + # If not using prime SIM, show GSM settings and enable IPv4 forwarding + show_cell_settings = ui_state.prime_state.get_type() in (PrimeType.NONE, PrimeType.LITE) + self._wifi_manager.set_ipv4_forward(show_cell_settings) + self._roaming_btn.set_visible(show_cell_settings) + self._apn_btn.set_visible(show_cell_settings) + self._cellular_metered_btn.set_visible(show_cell_settings) + + def show_event(self): + super().show_event() + self._wifi_manager.set_active(True) + + # Process wifi callbacks while at any point in the nav stack + gui_app.add_nav_stack_tick(self._wifi_manager.process_callbacks) + + def hide_event(self): + super().hide_event() + self._wifi_manager.set_active(False) + + gui_app.remove_nav_stack_tick(self._wifi_manager.process_callbacks) + + def _toggle_roaming(self, checked: bool): + self._wifi_manager.update_gsm_settings(checked, ui_state.params.get("GsmApn") or "", ui_state.params.get_bool("GsmMetered")) + + def _edit_apn(self): + def update_apn(apn: str): + apn = apn.strip() + if apn == "": + ui_state.params.remove("GsmApn") + else: + ui_state.params.put("GsmApn", apn) + + self._wifi_manager.update_gsm_settings(ui_state.params.get_bool("GsmRoaming"), apn, ui_state.params.get_bool("GsmMetered")) + + current_apn = ui_state.params.get("GsmApn") or "" + dlg = BigInputDialog("enter APN...", current_apn, minimum_length=0, confirm_callback=update_apn) + gui_app.push_widget(dlg) + + def _toggle_cellular_metered(self, checked: bool): + self._wifi_manager.update_gsm_settings(ui_state.params.get_bool("GsmRoaming"), ui_state.params.get("GsmApn") or "", checked) + + def _on_network_updated(self, networks: list[Network]): + # Update tethering state + tethering_active = self._wifi_manager.is_tethering_active() + # TODO: use real signals (like activated/settings changed, etc.) to speed up re-enabling buttons + self._tethering_toggle_btn.set_enabled(True) + self._tethering_password_btn.set_enabled(True) + self._network_metered_btn.set_enabled(lambda: not tethering_active and bool(self._wifi_manager.ipv4_address)) + self._tethering_toggle_btn.set_checked(tethering_active) + + # Update network metered + self._network_metered_btn.set_value( + { + MeteredType.UNKNOWN: 'default', + MeteredType.YES: 'metered', + MeteredType.NO: 'unmetered' + }.get(self._wifi_manager.current_network_metered, 'default')) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 22d3d1d0da..006027e258 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -3,9 +3,8 @@ import numpy as np import pyray as rl from collections.abc import Callable -from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.swaglog import cloudlog -from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog, BigConfirmationDialogV2 +from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog, BigConfirmationDialog from openpilot.selfdrive.ui.mici.widgets.button import BigButton, LABEL_COLOR from openpilot.system.ui.lib.application import gui_app, MousePos, FontWeight from openpilot.system.ui.widgets import Widget @@ -14,39 +13,26 @@ from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, SecurityT class LoadingAnimation(Widget): - HIDE_TIME = 4 + RADIUS = 8 + SPACING = 24 # center-to-center: diameter (16) + gap (8) + Y_MAG = 11.2 def __init__(self): super().__init__() - self._opacity_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps) - self._opacity_target = 1.0 - self._hide_time = 0.0 - - def show_event(self): - self._opacity_target = 1.0 - self._hide_time = rl.get_time() + w = self.SPACING * 2 + self.RADIUS * 2 + h = self.RADIUS * 2 + int(self.Y_MAG) + self.set_rect(rl.Rectangle(0, 0, w, h)) def _render(self, _): - if rl.get_time() - self._hide_time > self.HIDE_TIME: - self._opacity_target = 0.0 - - self._opacity_filter.update(self._opacity_target) - - if self._opacity_filter.x < 0.01: - return - - cx = int(self._rect.x + self._rect.width / 2) - cy = int(self._rect.y + self._rect.height / 2) - - y_mag = 7 - anim_scale = 4 - spacing = 14 + # Balls rest at bottom center; bounce upward + base_x = int(self._rect.x + self._rect.width / 2) + base_y = int(self._rect.y + self._rect.height - self.RADIUS) for i in range(3): - x = cx - spacing + i * spacing - y = int(cy + min(math.sin((rl.get_time() - i * 0.2) * anim_scale) * y_mag, 0)) - alpha = int(np.interp(cy - y, [0, y_mag], [255 * 0.45, 255 * 0.9]) * self._opacity_filter.x) - rl.draw_circle(x, y, 5, rl.Color(255, 255, 255, alpha)) + x = base_x + (i - 1) * self.SPACING + y = int(base_y + min(math.sin((rl.get_time() - i * 0.2) * 4) * self.Y_MAG, 0)) + alpha = int(np.interp(base_y - y, [0, self.Y_MAG], [255 * 0.45, 255 * 0.9])) + rl.draw_circle(x, y, self.RADIUS, rl.Color(255, 255, 255, alpha)) class WifiIcon(Widget): @@ -124,6 +110,10 @@ class WifiButton(BigButton): if self._is_connected or self._is_connecting: self._wrong_password = False + @property + def network_forgetting(self) -> bool: + return self._network_forgetting + def _forget_network(self): if self._network_forgetting: return @@ -175,7 +165,7 @@ class WifiButton(BigButton): if self._is_connected and not self._network_forgetting: check_y = int(label_y - sub_label_height + (sub_label_height - self._check_txt.height) / 2) - rl.draw_texture(self._check_txt, int(sub_label_x), check_y, rl.Color(255, 255, 255, int(255 * 0.9 * 0.65))) + rl.draw_texture_ex(self._check_txt, rl.Vector2(sub_label_x, check_y), 0.0, 1.0, rl.Color(255, 255, 255, int(255 * 0.9 * 0.65))) sub_label_x += self._check_txt.width + 14 sub_label_rect = rl.Rectangle(sub_label_x, label_y - sub_label_height, sub_label_w, sub_label_height) @@ -256,8 +246,7 @@ class ForgetButton(Widget): def _handle_mouse_release(self, mouse_pos: MousePos): super()._handle_mouse_release(mouse_pos) - dlg = BigConfirmationDialogV2("slide to forget", "icons_mici/settings/network/new/trash.png", red=True, - confirm_callback=self._forget_network) + dlg = BigConfirmationDialog("slide to forget", gui_app.texture("icons_mici/settings/network/new/trash.png", 54, 64), self._forget_network, red=True) gui_app.push_widget(dlg) def _render(self, _): @@ -270,11 +259,26 @@ class ForgetButton(Widget): rl.draw_texture_ex(self._trash_txt, (trash_x, trash_y), 0, 1.0, rl.WHITE) +class ScanningButton(BigButton): + def __init__(self): + super().__init__("", "searching for networks") + self.set_enabled(False) + self._loading_animation = LoadingAnimation() + + def _draw_content(self, btn_y: float): + super()._draw_content(btn_y) + anim = self._loading_animation + x = self._rect.x + self._rect.width - anim.rect.width - 40 + y = btn_y + self._rect.height - anim.rect.height - 30 + anim.set_position(x, y) + anim.render() + + class WifiUIMici(NavScroller): def __init__(self, wifi_manager: WifiManager): super().__init__() - self._loading_animation = LoadingAnimation() + self._scanning_btn = ScanningButton() self._wifi_manager = wifi_manager self._networks: dict[str, Network] = {} @@ -285,20 +289,23 @@ class WifiUIMici(NavScroller): networks_updated=self._on_network_updated, ) + @property + def any_network_forgetting(self) -> bool: + # TODO: deactivate before forget and add DISCONNECTING state + return any(btn.network_forgetting for btn in self._scroller.items if isinstance(btn, WifiButton)) + def show_event(self): - # Clear scroller items and update from latest scan results + # Re-sort scroller items and update from latest scan results super().show_event() - self._loading_animation.show_event() self._wifi_manager.set_active(True) - self._scroller.items.clear() - # trigger button update on latest sorted networks - self._on_network_updated(self._wifi_manager.networks) + self._networks = {n.ssid: n for n in self._wifi_manager.networks} + self._update_buttons(re_sort=True) def _on_network_updated(self, networks: list[Network]): self._networks = {network.ssid: network for network in networks} self._update_buttons() - def _update_buttons(self): + def _update_buttons(self, re_sort: bool = False): # Update existing buttons, add new ones to the end existing = {btn.network.ssid: btn for btn in self._scroller.items if isinstance(btn, WifiButton)} @@ -310,10 +317,22 @@ class WifiUIMici(NavScroller): btn.set_click_callback(lambda ssid=network.ssid: self._connect_to_network(ssid)) self._scroller.add_widget(btn) - # Mark networks no longer in scan results (display handled by _update_state) - for btn in self._scroller.items: - if isinstance(btn, WifiButton) and btn.network.ssid not in self._networks: - btn.set_network_missing(True) + if re_sort: + # Remove stale buttons and sort to match scan order, preserving eager state + btn_map = {btn.network.ssid: btn for btn in self._scroller.items if isinstance(btn, WifiButton)} + self._scroller.items[:] = [btn_map[ssid] for ssid in self._networks if ssid in btn_map] + else: + # Mark networks no longer in scan results (display handled by _update_state) + for btn in self._scroller.items: + if isinstance(btn, WifiButton) and btn.network.ssid not in self._networks: + btn.set_network_missing(True) + + # Keep scanning button at the end + items = self._scroller.items + if self._scanning_btn in items: + items.append(items.pop(items.index(self._scanning_btn))) + else: + self._scroller.add_widget(self._scanning_btn) def _connect_with_password(self, ssid: str, password: str): self._wifi_manager.connect_to_network(ssid, password) @@ -370,17 +389,3 @@ class WifiUIMici(NavScroller): super()._update_state() self._move_network_to_front(self._wifi_manager.wifi_state.ssid) - - # Show loading animation near end - max_scroll = max(self._scroller.content_size - self._scroller.rect.width, 1) - progress = -self._scroller.scroll_panel.get_offset() / max_scroll - if progress > 0.8 or len(self._scroller.items) <= 1: - self._loading_animation.show_event() - - def _render(self, _): - super()._render(self._rect) - - anim_w = 90 - anim_x = self._rect.x + self._rect.width - anim_w - anim_y = self._rect.y + self._rect.height - 25 + 2 - self._loading_animation.render(rl.Rectangle(anim_x, anim_y, anim_w, 20)) diff --git a/selfdrive/ui/mici/layouts/settings/settings.py b/selfdrive/ui/mici/layouts/settings/settings.py index c7fb3201f5..4ccc5ba139 100644 --- a/selfdrive/ui/mici/layouts/settings/settings.py +++ b/selfdrive/ui/mici/layouts/settings/settings.py @@ -2,7 +2,7 @@ from openpilot.common.params import Params from openpilot.system.ui.widgets.scroller import NavScroller from openpilot.selfdrive.ui.mici.widgets.button import BigButton from openpilot.selfdrive.ui.mici.layouts.settings.toggles import TogglesLayoutMici -from openpilot.selfdrive.ui.mici.layouts.settings.network import NetworkLayoutMici +from openpilot.selfdrive.ui.mici.layouts.settings.network.network_layout import NetworkLayoutMici from openpilot.selfdrive.ui.mici.layouts.settings.device import DeviceLayoutMici, PairBigButton from openpilot.selfdrive.ui.mici.layouts.settings.developer import DeveloperLayoutMici from openpilot.selfdrive.ui.mici.layouts.settings.firehose import FirehoseLayout @@ -20,23 +20,23 @@ class SettingsLayout(NavScroller): self._params = Params() toggles_panel = TogglesLayoutMici() - toggles_btn = SettingsBigButton("toggles", "", "icons_mici/settings.png") + toggles_btn = SettingsBigButton("toggles", "", gui_app.texture("icons_mici/settings.png", 64, 64)) toggles_btn.set_click_callback(lambda: gui_app.push_widget(toggles_panel)) network_panel = NetworkLayoutMici() - network_btn = SettingsBigButton("network", "", "icons_mici/settings/network/wifi_strength_full.png", icon_size=(76, 56)) + network_btn = SettingsBigButton("network", "", gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 76, 56)) network_btn.set_click_callback(lambda: gui_app.push_widget(network_panel)) device_panel = DeviceLayoutMici() - device_btn = SettingsBigButton("device", "", "icons_mici/settings/device_icon.png", icon_size=(74, 60)) + device_btn = SettingsBigButton("device", "", gui_app.texture("icons_mici/settings/device_icon.png", 72, 58)) device_btn.set_click_callback(lambda: gui_app.push_widget(device_panel)) developer_panel = DeveloperLayoutMici() - developer_btn = SettingsBigButton("developer", "", "icons_mici/settings/developer_icon.png", icon_size=(64, 60)) + developer_btn = SettingsBigButton("developer", "", gui_app.texture("icons_mici/settings/developer_icon.png", 64, 60)) developer_btn.set_click_callback(lambda: gui_app.push_widget(developer_panel)) firehose_panel = FirehoseLayout() - firehose_btn = SettingsBigButton("firehose", "", "icons_mici/settings/firehose.png", icon_size=(52, 62)) + firehose_btn = SettingsBigButton("firehose", "", gui_app.texture("icons_mici/settings/firehose.png", 52, 62)) firehose_btn.set_click_callback(lambda: gui_app.push_widget(firehose_panel)) self._scroller.add_widgets([ diff --git a/selfdrive/ui/mici/onroad/alert_renderer.py b/selfdrive/ui/mici/onroad/alert_renderer.py index 68b2aef7a5..5b550030de 100644 --- a/selfdrive/ui/mici/onroad/alert_renderer.py +++ b/selfdrive/ui/mici/onroad/alert_renderer.py @@ -231,7 +231,7 @@ class AlertRenderer(Widget, SpeedLimitAlertRenderer): self._alpha_filter.update(0 if alert is None else 1) if gui_app.sunnypilot_ui(): - ui_state.onroad_brightness_handle_alerts(ui_state.started, alert) + ui_state.onroad_brightness_handle_alerts(ui_state, alert) if alert is None: # If still animating out, keep the previous alert @@ -272,8 +272,8 @@ class AlertRenderer(Widget, SpeedLimitAlertRenderer): else: icon_alpha = int(min(self._turn_signal_alpha_filter.x, 255)) - rl.draw_texture(alert_layout.icon.texture, pos_x, int(self._rect.y + alert_layout.icon.margin_y), - rl.Color(255, 255, 255, int(icon_alpha * self._alpha_filter.x))) + rl.draw_texture_ex(alert_layout.icon.texture, rl.Vector2(pos_x, self._rect.y + alert_layout.icon.margin_y), 0.0, 1.0, + rl.Color(255, 255, 255, int(icon_alpha * self._alpha_filter.x))) def _draw_background(self, alert: Alert) -> None: # draw top gradient for alert text at top diff --git a/selfdrive/ui/mici/onroad/augmented_road_view.py b/selfdrive/ui/mici/onroad/augmented_road_view.py index 70cc249d2a..72be9cbe4d 100644 --- a/selfdrive/ui/mici/onroad/augmented_road_view.py +++ b/selfdrive/ui/mici/onroad/augmented_road_view.py @@ -130,7 +130,7 @@ class BookmarkIcon(Widget): if self._offset_filter.x > 0: icon_x = self.rect.x + self.rect.width - round(self._offset_filter.x) icon_y = self.rect.y + (self.rect.height - self._icon.height) / 2 # Vertically centered - rl.draw_texture(self._icon, int(icon_x), int(icon_y), rl.WHITE) + rl.draw_texture_ex(self._icon, rl.Vector2(icon_x, icon_y), 0.0, 1.0, rl.WHITE) class AugmentedRoadView(CameraView): @@ -251,7 +251,7 @@ class AugmentedRoadView(CameraView): # Draw darkened background and text if not onroad if not ui_state.started: rl.draw_rectangle(int(self.rect.x), int(self.rect.y), int(self.rect.width), int(self.rect.height), rl.Color(0, 0, 0, 175)) - self._offroad_label.render(self._content_rect) + self._offroad_label.render(self._rect) # publish uiDebug msg = messaging.new_message('uiDebug') diff --git a/selfdrive/ui/mici/onroad/cameraview.py b/selfdrive/ui/mici/onroad/cameraview.py index 89a4926ce9..62fcfd0654 100644 --- a/selfdrive/ui/mici/onroad/cameraview.py +++ b/selfdrive/ui/mici/onroad/cameraview.py @@ -155,11 +155,11 @@ class CameraView(Widget): # Prevent old frames from showing when going onroad. Qt has a separate thread # which drains the VisionIpcClient SubSocket for us. Re-connecting is not enough # and only clears internal buffers, not the message queue. - self.frame = None self.available_streams.clear() if self.client: del self.client self.client = VisionIpcClient(self._name, self._stream_type, conflate=True) + self.frame = None def _set_placeholder_color(self, color: rl.Color): """Set a placeholder color to be drawn when no frame is available.""" diff --git a/selfdrive/ui/mici/onroad/driver_state.py b/selfdrive/ui/mici/onroad/driver_state.py index 356d7ac832..92ff07c1e9 100644 --- a/selfdrive/ui/mici/onroad/driver_state.py +++ b/selfdrive/ui/mici/onroad/driver_state.py @@ -61,7 +61,7 @@ class DriverStateRenderer(Widget): self._dm_cone = gui_app.texture("icons_mici/onroad/driver_monitoring/dm_cone.png", cone_and_person_size, cone_and_person_size) center_size = round(36 / self.BASE_SIZE * self._rect.width) self._dm_center = gui_app.texture("icons_mici/onroad/driver_monitoring/dm_center.png", center_size, center_size) - self._dm_background = gui_app.texture("icons_mici/onroad/driver_monitoring/dm_background.png", self._rect.width, self._rect.height) + self._dm_background = gui_app.texture("icons_mici/onroad/driver_monitoring/dm_background.png", int(self._rect.width), int(self._rect.height)) def set_should_draw(self, should_draw: bool): self._should_draw = should_draw @@ -88,15 +88,14 @@ class DriverStateRenderer(Widget): if DEBUG: rl.draw_rectangle_lines_ex(self._rect, 1, rl.RED) - rl.draw_texture(self._dm_background, - int(self._rect.x), - int(self._rect.y), - rl.Color(255, 255, 255, int(255 * self._fade_filter.x))) + rl.draw_texture_ex(self._dm_background, + rl.Vector2(self._rect.x, self._rect.y), 0.0, 1.0, + rl.Color(255, 255, 255, int(255 * self._fade_filter.x))) - rl.draw_texture(self._dm_person, - int(self._rect.x + (self._rect.width - self._dm_person.width) / 2), - int(self._rect.y + (self._rect.height - self._dm_person.height) / 2), - rl.Color(255, 255, 255, int(255 * 0.9 * self._fade_filter.x))) + rl.draw_texture_ex(self._dm_person, + rl.Vector2(self._rect.x + (self._rect.width - self._dm_person.width) / 2, + self._rect.y + (self._rect.height - self._dm_person.height) / 2), 0.0, 1.0, + rl.Color(255, 255, 255, int(255 * 0.9 * self._fade_filter.x))) if self.effective_active: source_rect = rl.Rectangle(0, 0, self._dm_cone.width, self._dm_cone.height) diff --git a/selfdrive/ui/mici/onroad/hud_renderer.py b/selfdrive/ui/mici/onroad/hud_renderer.py index ccf32023ed..551798863a 100644 --- a/selfdrive/ui/mici/onroad/hud_renderer.py +++ b/selfdrive/ui/mici/onroad/hud_renderer.py @@ -172,8 +172,7 @@ class HudRenderer(Widget): def _render(self, rect: rl.Rectangle) -> None: """Render HUD elements to the screen.""" - if ui_state.sm['controlsState'].lateralControlState.which() != 'angleState': - self._torque_bar.render(rect) + self._torque_bar.render(rect) if self.is_cruise_set: self._draw_set_speed(rect) @@ -222,7 +221,7 @@ class HudRenderer(Widget): EXCLAMATION_POINT_SPACING = 10 exclamation_pos_x = pos_x - self._txt_exclamation_point.width / 2 + wheel_txt.width / 2 + EXCLAMATION_POINT_SPACING exclamation_pos_y = pos_y - self._txt_exclamation_point.height / 2 - rl.draw_texture(self._txt_exclamation_point, int(exclamation_pos_x), int(exclamation_pos_y), rl.WHITE) + rl.draw_texture_ex(self._txt_exclamation_point, rl.Vector2(exclamation_pos_x, exclamation_pos_y), 0.0, 1.0, rl.WHITE) def _draw_set_speed(self, rect: rl.Rectangle) -> None: """Draw the MAX speed indicator box.""" diff --git a/selfdrive/ui/mici/onroad/torque_bar.py b/selfdrive/ui/mici/onroad/torque_bar.py index c1de694633..f0690c0abf 100644 --- a/selfdrive/ui/mici/onroad/torque_bar.py +++ b/selfdrive/ui/mici/onroad/torque_bar.py @@ -145,6 +145,9 @@ def arc_bar_pts(cx: float, cy: float, return pts +DEFAULT_MAX_LAT_ACCEL = 3.0 # m/s^2 + + class TorqueBar(Widget): def __init__(self, demo: bool = False, scale: float = 1.0, always: bool = False): super().__init__() @@ -167,16 +170,23 @@ class TorqueBar(Widget): controls_state = ui_state.sm['controlsState'] car_state = ui_state.sm['carState'] live_parameters = ui_state.sm['liveParameters'] - lateral_acceleration = controls_state.curvature * car_state.vEgo ** 2 - live_parameters.roll * ACCELERATION_DUE_TO_GRAVITY - # TODO: pull from carparams - max_lateral_acceleration = 3 + car_control = ui_state.sm['carControl'] - # from selfdrived + # Include lateral accel error in estimated torque utilization actual_lateral_accel = controls_state.curvature * car_state.vEgo ** 2 desired_lateral_accel = controls_state.desiredCurvature * car_state.vEgo ** 2 accel_diff = (desired_lateral_accel - actual_lateral_accel) - self._torque_filter.update(min(max(lateral_acceleration / max_lateral_acceleration + accel_diff, -1), 1)) + # Include road roll in estimated torque utilization + # Roll is less accurate near standstill, so reduce its effect at low speed + roll_compensation = live_parameters.roll * ACCELERATION_DUE_TO_GRAVITY * np.interp(car_state.vEgo, [5, 15], [0.0, 1.0]) + lateral_acceleration = actual_lateral_accel - roll_compensation + max_lateral_acceleration = ui_state.CP.maxLateralAccel if ui_state.CP else DEFAULT_MAX_LAT_ACCEL + + if not car_control.latActive: + self._torque_filter.update(0.0) + else: + self._torque_filter.update(np.clip((lateral_acceleration + accel_diff) / max_lateral_acceleration, -1, 1)) else: self._torque_filter.update(-ui_state.sm['carOutput'].actuatorsOutput.torque) diff --git a/selfdrive/ui/mici/tests/test_widget_leaks.py b/selfdrive/ui/mici/tests/test_widget_leaks.py index be12839cd7..e35cb44776 100755 --- a/selfdrive/ui/mici/tests/test_widget_leaks.py +++ b/selfdrive/ui/mici/tests/test_widget_leaks.py @@ -10,7 +10,7 @@ from openpilot.system.ui.widgets import Widget from openpilot.selfdrive.ui.mici.layouts.onboarding import TrainingGuide as MiciTrainingGuide, OnboardingWindow as MiciOnboardingWindow from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCameraDialog as MiciDriverCameraDialog from openpilot.selfdrive.ui.mici.widgets.pairing_dialog import PairingDialog as MiciPairingDialog -from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialogV2, BigInputDialog +from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialog, BigInputDialog from openpilot.selfdrive.ui.mici.layouts.settings.device import MiciFccModal # tici dialogs @@ -44,7 +44,7 @@ KNOWN_LEAKS = { "openpilot.system.ui.widgets.scroller_tici.Scroller", "openpilot.system.ui.widgets.label.UnifiedLabel", "openpilot.system.ui.widgets.mici_keyboard.MiciKeyboard", - "openpilot.selfdrive.ui.mici.widgets.dialog.BigConfirmationDialogV2", + "openpilot.selfdrive.ui.mici.widgets.dialog.BigConfirmationDialog", "openpilot.system.ui.widgets.keyboard.Keyboard", "openpilot.system.ui.widgets.slider.BigSlider", "openpilot.selfdrive.ui.mici.widgets.dialog.BigInputDialog", @@ -68,9 +68,11 @@ def test_dialogs_do_not_leak(): for ctor in ( # mici - MiciDriverCameraDialog, MiciTrainingGuide, MiciOnboardingWindow, MiciPairingDialog, + MiciDriverCameraDialog, MiciPairingDialog, + lambda: MiciTrainingGuide(lambda: None), + lambda: MiciOnboardingWindow(lambda: None), lambda: BigDialog("test", "test"), - lambda: BigConfirmationDialogV2("test", "icons_mici/settings/network/new/trash.png"), + lambda: BigConfirmationDialog("test", gui_app.texture("icons_mici/settings/network/new/trash.png", 54, 64), lambda: None), lambda: BigInputDialog("test"), lambda: MiciFccModal(text="test"), # tici diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index b5bd65e2de..058c351fb6 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -28,7 +28,7 @@ class ScrollState(Enum): class BigCircleButton(Widget): - def __init__(self, icon: str, red: bool = False, icon_size: tuple[int, int] = (64, 53), icon_offset: tuple[int, int] = (0, 0)): + def __init__(self, icon: rl.Texture, red: bool = False, icon_offset: tuple[int, int] = (0, 0)): super().__init__() self._red = red self._icon_offset = icon_offset @@ -39,7 +39,7 @@ class BigCircleButton(Widget): self._click_delay = 0.075 # Icons - self._txt_icon = gui_app.texture(icon, *icon_size) + self._txt_icon = icon self._txt_btn_disabled_bg = gui_app.texture("icons_mici/buttons/button_circle_disabled.png", 180, 180) self._txt_btn_bg = gui_app.texture("icons_mici/buttons/button_circle.png", 180, 180) @@ -71,8 +71,8 @@ class BigCircleButton(Widget): class BigCircleToggle(BigCircleButton): - def __init__(self, icon: str, toggle_callback: Callable | None = None, icon_size: tuple[int, int] = (64, 53), icon_offset: tuple[int, int] = (0, 0)): - super().__init__(icon, False, icon_size=icon_size, icon_offset=icon_offset) + def __init__(self, icon: rl.Texture, toggle_callback: Callable | None = None, icon_offset: tuple[int, int] = (0, 0)): + super().__init__(icon, False, icon_offset=icon_offset) self._toggle_callback = toggle_callback # State @@ -107,19 +107,18 @@ class BigButton(Widget): """A lightweight stand-in for the Qt BigButton, drawn & updated each frame.""" - def __init__(self, text: str, value: str = "", icon: Union[str, rl.Texture] = "", icon_size: tuple[int, int] = (64, 64), - scroll: bool = False): + def __init__(self, text: str, value: str = "", icon: Union[rl.Texture, None] = None, scroll: bool = False): super().__init__() self.set_rect(rl.Rectangle(0, 0, 402, 180)) self.text = text self.value = value - self._icon_size = icon_size + self._txt_icon = icon self._scroll = scroll - self.set_icon(icon) self._scale_filter = BounceFilter(1.0, 0.1, 1 / gui_app.target_fps) self._click_delay = 0.075 self._shake_start: float | None = None + self._grow_animation_until: float | None = None self._rotate_icon_t: float | None = None @@ -132,8 +131,8 @@ class BigButton(Widget): self._load_images() - def set_icon(self, icon: Union[str, rl.Texture]): - self._txt_icon = gui_app.texture(icon, *self._icon_size) if isinstance(icon, str) and len(icon) else icon + def set_icon(self, icon: Union[rl.Texture, None]): + self._txt_icon = icon def set_rotate_icon(self, rotate: bool): if rotate and self._rotate_icon_t is not None: @@ -145,9 +144,12 @@ class BigButton(Widget): self._txt_pressed_bg = gui_app.texture("icons_mici/buttons/button_rectangle_pressed.png", 402, 180) self._txt_disabled_bg = gui_app.texture("icons_mici/buttons/button_rectangle_disabled.png", 402, 180) + def set_touch_valid_callback(self, touch_callback: Callable[[], bool]) -> None: + super().set_touch_valid_callback(lambda: touch_callback() and self._grow_animation_until is None) + def _width_hint(self) -> int: # Single line if scrolling, so hide behind icon if exists - icon_size = self._icon_size[0] if self._txt_icon and self._scroll and self.value else 0 + icon_size = self._txt_icon.width if self._txt_icon and self._scroll and self.value else 0 return int(self._rect.width - self.LABEL_HORIZONTAL_PADDING * 2 - icon_size) def _get_label_font_size(self): @@ -182,12 +184,17 @@ class BigButton(Widget): def trigger_shake(self): self._shake_start = rl.get_time() + def trigger_grow_animation(self, duration: float = 0.65): + self._grow_animation_until = rl.get_time() + duration + @property def _shake_offset(self) -> float: SHAKE_DURATION = 0.5 SHAKE_AMPLITUDE = 24.0 SHAKE_FREQUENCY = 32.0 - t = rl.get_time() - (self._shake_start or 0.0) + if self._shake_start is None: + return 0.0 + t = rl.get_time() - self._shake_start if t > SHAKE_DURATION: return 0.0 decay = 1.0 - t / SHAKE_DURATION @@ -197,6 +204,10 @@ class BigButton(Widget): super().set_position(x + self._shake_offset, y) def _handle_background(self) -> tuple[rl.Texture, float, float, float]: + if self._grow_animation_until is not None: + if rl.get_time() >= self._grow_animation_until: + self._grow_animation_until = None + # draw _txt_default_bg txt_bg = self._txt_default_bg if not self.enabled: @@ -204,7 +215,7 @@ class BigButton(Widget): elif self.is_pressed: txt_bg = self._txt_pressed_bg - scale = self._scale_filter.update(PRESSED_SCALE if self.is_pressed else 1.0) + scale = self._scale_filter.update(PRESSED_SCALE if self.is_pressed or self._grow_animation_until is not None else 1.0) btn_x = self._rect.x + (self._rect.width * (1 - scale)) / 2 btn_y = self._rect.y + (self._rect.height * (1 - scale)) / 2 return txt_bg, btn_x, btn_y, scale @@ -324,6 +335,43 @@ class BigMultiToggle(BigToggle): y += 35 +class GreyBigButton(BigButton): + """Users should manage newlines with this class themselves""" + + LABEL_HORIZONTAL_PADDING = 30 + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.set_touch_valid_callback(lambda: False) + + self._rect.width = 476 + + self._label.set_font_size(36) + self._label.set_font_weight(FontWeight.BOLD) + self._label.set_line_height(1.0) + + self._sub_label.set_font_size(36) + self._sub_label.set_text_color(rl.Color(255, 255, 255, int(255 * 0.9))) + self._sub_label.set_font_weight(FontWeight.DISPLAY_REGULAR) + self._sub_label.set_alignment_vertical(rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE if not self._label.text else + rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM) + self._sub_label.set_line_height(0.95) + + @property + def LABEL_VERTICAL_PADDING(self): + return BigButton.LABEL_VERTICAL_PADDING if self._label.text else 18 + + def _width_hint(self) -> int: + return int(self._rect.width - self.LABEL_HORIZONTAL_PADDING * 2) + + def _get_label_font_size(self): + return 36 + + def _render(self, _): + rl.draw_rectangle_rounded(self._rect, 0.4, 10, rl.Color(255, 255, 255, int(255 * 0.15))) + self._draw_content(self._rect.y) + + class BigMultiParamToggle(BigMultiToggle): def __init__(self, text: str, param: str, options: list[str], toggle_callback: Callable | None = None, select_callback: Callable | None = None): @@ -359,9 +407,9 @@ class BigParamControl(BigToggle): # TODO: param control base class class BigCircleParamControl(BigCircleToggle): - def __init__(self, icon: str, param: str, toggle_callback: Callable | None = None, icon_size: tuple[int, int] = (64, 53), + def __init__(self, icon: rl.Texture, param: str, toggle_callback: Callable | None = None, icon_offset: tuple[int, int] = (0, 0)): - super().__init__(icon, toggle_callback, icon_size=icon_size, icon_offset=icon_offset) + super().__init__(icon, toggle_callback, icon_offset=icon_offset) self._param = param self.params = Params() self.set_checked(self.params.get_bool(self._param, False)) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 619c1ca28f..ed1466449b 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -4,14 +4,13 @@ import pyray as rl from typing import Union from collections.abc import Callable from openpilot.system.ui.widgets.nav_widget import NavWidget -from openpilot.system.ui.widgets.label import UnifiedLabel, gui_label +from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.widgets.mici_keyboard import MiciKeyboard from openpilot.system.ui.lib.text_measure import measure_text_cached -from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.system.ui.widgets.slider import RedBigSlider, BigSlider from openpilot.common.filter_simple import FirstOrderFilter -from openpilot.selfdrive.ui.mici.widgets.button import BigButton +from openpilot.selfdrive.ui.mici.widgets.button import BigCircleButton, BigButton, GreyBigButton DEBUG = False @@ -25,58 +24,31 @@ class BigDialogBase(NavWidget, abc.ABC): class BigDialog(BigDialogBase): - def __init__(self, - title: str, - description: str): + def __init__(self, title: str, description: str, icon: Union[rl.Texture, None] = None): super().__init__() - self._title = title - self._description = description + self._card = GreyBigButton(title, description, icon) def _render(self, _): - super()._render(_) - - # draw title - # TODO: we desperately need layouts - # TODO: coming up with these numbers manually is a pain and not scalable - # TODO: no clue what any of these numbers mean. VBox and HBox would remove all of this shite - max_width = self._rect.width - PADDING * 2 - - title_wrapped = '\n'.join(wrap_text(gui_app.font(FontWeight.BOLD), self._title, 50, int(max_width))) - title_size = measure_text_cached(gui_app.font(FontWeight.BOLD), title_wrapped, 50) - text_x_offset = 0 - title_rect = rl.Rectangle(int(self._rect.x + text_x_offset + PADDING), - int(self._rect.y + PADDING), - int(max_width), - int(title_size.y)) - gui_label(title_rect, title_wrapped, 50, font_weight=FontWeight.BOLD, - alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) - - # draw description - desc_wrapped = '\n'.join(wrap_text(gui_app.font(FontWeight.MEDIUM), self._description, 30, int(max_width))) - desc_size = measure_text_cached(gui_app.font(FontWeight.MEDIUM), desc_wrapped, 30) - desc_rect = rl.Rectangle(int(self._rect.x + text_x_offset + PADDING), - int(self._rect.y + self._rect.height / 3), - int(max_width), - int(desc_size.y)) - # TODO: text align doesn't seem to work properly with newlines - gui_label(desc_rect, desc_wrapped, 30, font_weight=FontWeight.MEDIUM, - alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) + self._card.render(rl.Rectangle( + self._rect.x + self._rect.width / 2 - self._card.rect.width / 2, + self._rect.y + self._rect.height / 2 - self._card.rect.height / 2, + self._card.rect.width, + self._card.rect.height, + )) -class BigConfirmationDialogV2(BigDialogBase): - def __init__(self, title: str, icon: str, red: bool = False, - exit_on_confirm: bool = True, - confirm_callback: Callable | None = None): +class BigConfirmationDialog(BigDialogBase): + def __init__(self, title: str, icon: rl.Texture, confirm_callback: Callable[[], None], + exit_on_confirm: bool = True, red: bool = False): super().__init__() self._confirm_callback = confirm_callback self._exit_on_confirm = exit_on_confirm - icon_txt = gui_app.texture(icon, 64, 53) self._slider: BigSlider | RedBigSlider if red: - self._slider = RedBigSlider(title, icon_txt, confirm_callback=self._on_confirm) + self._slider = self._child(RedBigSlider(title, icon, confirm_callback=self._on_confirm)) else: - self._slider = BigSlider(title, icon_txt, confirm_callback=self._on_confirm) + self._slider = self._child(BigSlider(title, icon, confirm_callback=self._on_confirm)) self._slider.set_enabled(lambda: self.enabled and not self.is_dismissing) # for nav stack + NavWidget def _on_confirm(self): @@ -103,11 +75,12 @@ class BigInputDialog(BigDialogBase): hint: str, default_text: str = "", minimum_length: int = 1, - confirm_callback: Callable[[str], None] | None = None): + confirm_callback: Callable[[str], None] | None = None, + auto_return_to_letters: str = ""): super().__init__() self._hint_label = UnifiedLabel(hint, font_size=35, text_color=rl.Color(255, 255, 255, int(255 * 0.35)), font_weight=FontWeight.MEDIUM) - self._keyboard = MiciKeyboard() + self._keyboard = MiciKeyboard(auto_return_to_letters=auto_return_to_letters) self._keyboard.set_text(default_text) self._keyboard.set_enabled(lambda: self.enabled and not self.is_dismissing) # for nav stack + NavWidget self._minimum_length = minimum_length @@ -157,9 +130,9 @@ class BigInputDialog(BigDialogBase): bg_block_margin = 5 text_x = PADDING / 2 + self._enter_img.width + PADDING - text_field_rect = rl.Rectangle(text_x, int(self._rect.y + PADDING) - bg_block_margin, - int(self._rect.width - text_x * 2), - int(text_size.y)) + text_field_rect = rl.Rectangle(text_x, self._rect.y + PADDING - bg_block_margin, + self._rect.width - text_x * 2, + text_size.y) # draw text input # push text left with a gradient on left side if too long @@ -180,8 +153,8 @@ class BigInputDialog(BigDialogBase): # draw gradient on left side to indicate more text if text_size.x > text_field_rect.width: - rl.draw_rectangle_gradient_h(int(text_field_rect.x), int(text_field_rect.y), 80, int(text_field_rect.height), - rl.BLACK, rl.BLANK) + rl.draw_rectangle_gradient_ex(rl.Rectangle(text_field_rect.x, text_field_rect.y, 80, text_field_rect.height), + rl.BLACK, rl.BLANK, rl.BLANK, rl.BLACK) # draw cursor blink_alpha = (math.sin(rl.get_time() * 6) + 1) / 2 @@ -189,14 +162,14 @@ class BigInputDialog(BigDialogBase): cursor_x = min(text_x + text_size.x + 3, text_field_rect.x + text_field_rect.width) else: cursor_x = text_field_rect.x - 6 - rl.draw_rectangle_rounded(rl.Rectangle(int(cursor_x), int(text_field_rect.y), 4, int(text_size.y)), + rl.draw_rectangle_rounded(rl.Rectangle(cursor_x, text_field_rect.y, 4, text_size.y), 1, 4, rl.Color(255, 255, 255, int(255 * blink_alpha))) # draw backspace icon with nice fade self._backspace_img_alpha.update(255 * bool(text)) if self._backspace_img_alpha.x > 1: color = rl.Color(255, 255, 255, int(self._backspace_img_alpha.x)) - rl.draw_texture(self._backspace_img, int(self._rect.width - self._backspace_img.width - 27), int(self._rect.y + 14), color) + rl.draw_texture_ex(self._backspace_img, rl.Vector2(self._rect.width - self._backspace_img.width - 27, self._rect.y + 14), 0.0, 1.0, color) if not text and self._hint_label.text and not candidate_char: # draw description if no text entered yet and not drawing candidate char @@ -214,9 +187,9 @@ class BigInputDialog(BigDialogBase): # draw enter button self._enter_img_alpha.update(255 if len(text) >= self._minimum_length else 0) color = rl.Color(255, 255, 255, int(self._enter_img_alpha.x)) - rl.draw_texture(self._enter_img, int(self._rect.x + PADDING / 2), int(self._rect.y), color) + rl.draw_texture_ex(self._enter_img, rl.Vector2(self._rect.x + PADDING / 2, self._rect.y), 0.0, 1.0, color) color = rl.Color(255, 255, 255, 255 - int(self._enter_img_alpha.x)) - rl.draw_texture(self._enter_disabled_img, int(self._rect.x + PADDING / 2), int(self._rect.y), color) + rl.draw_texture_ex(self._enter_disabled_img, rl.Vector2(self._rect.x + PADDING / 2, self._rect.y), 0.0, 1.0, color) # keyboard goes over everything self._keyboard.render(self._rect) @@ -253,3 +226,15 @@ class BigDialogButton(BigButton): dlg = BigDialog(self.text, self._description) gui_app.push_widget(dlg) + + +class BigConfirmationCircleButton(BigCircleButton): + def __init__(self, title: str, icon: rl.Texture, confirm_callback: Callable[[], None], exit_on_confirm: bool = True, + red: bool = False, icon_offset: tuple[int, int] = (0, 0)): + super().__init__(icon, red, icon_offset) + + def show_confirm_dialog(): + gui_app.push_widget(BigConfirmationDialog(title, icon, confirm_callback, + exit_on_confirm=exit_on_confirm, red=red)) + + self.set_click_callback(show_confirm_dialog) diff --git a/selfdrive/ui/mici/widgets/pairing_dialog.py b/selfdrive/ui/mici/widgets/pairing_dialog.py index 991cb05a8c..a18b26ec02 100644 --- a/selfdrive/ui/mici/widgets/pairing_dialog.py +++ b/selfdrive/ui/mici/widgets/pairing_dialog.py @@ -9,7 +9,7 @@ from openpilot.common.params import Params from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.system.ui.lib.application import FontWeight, gui_app -from openpilot.system.ui.widgets.label import MiciLabel +from openpilot.system.ui.widgets.label import UnifiedLabel class PairingDialog(NavWidget): @@ -24,8 +24,7 @@ class PairingDialog(NavWidget): self._last_qr_generation = float("-inf") self._txt_pair = gui_app.texture("icons_mici/settings/device/pair.png", 33, 60) - self._pair_label = MiciLabel("pair with comma connect", 48, font_weight=FontWeight.BOLD, - color=rl.Color(255, 255, 255, int(255 * 0.9)), line_height=40, wrap_text=True) + self._pair_label = UnifiedLabel("pair with comma connect", font_size=48, font_weight=FontWeight.BOLD, line_height=0.8) def _get_pairing_url(self) -> str: try: @@ -77,7 +76,7 @@ class PairingDialog(NavWidget): self._render_qr_code() label_x = self._rect.x + 8 + self._rect.height + 24 - self._pair_label.set_width(int(self._rect.width - label_x)) + self._pair_label.set_max_width(int(self._rect.width - label_x)) self._pair_label.set_position(label_x, self._rect.y + 16) self._pair_label.render() @@ -93,7 +92,7 @@ class PairingDialog(NavWidget): return scale = self._rect.height / self._qr_texture.height - pos = rl.Vector2(self._rect.x + 8, self._rect.y) + pos = rl.Vector2(round(self._rect.x + 8), round(self._rect.y)) rl.draw_texture_ex(self._qr_texture, pos, 0.0, scale, rl.WHITE) def __del__(self): diff --git a/selfdrive/ui/onroad/alert_renderer.py b/selfdrive/ui/onroad/alert_renderer.py index 2c21b4006e..6e79d23253 100644 --- a/selfdrive/ui/onroad/alert_renderer.py +++ b/selfdrive/ui/onroad/alert_renderer.py @@ -118,7 +118,7 @@ class AlertRenderer(Widget): alert = self.get_alert(ui_state.sm) if gui_app.sunnypilot_ui(): - ui_state.onroad_brightness_handle_alerts(ui_state.started, alert) + ui_state.onroad_brightness_handle_alerts(ui_state, alert) if not alert: return diff --git a/selfdrive/ui/onroad/exp_button.py b/selfdrive/ui/onroad/exp_button.py index e5d8171413..9a92ebc3c3 100644 --- a/selfdrive/ui/onroad/exp_button.py +++ b/selfdrive/ui/onroad/exp_button.py @@ -50,7 +50,7 @@ class ExpButton(Widget): texture = self._txt_exp if self._held_or_actual_mode() else self._txt_wheel rl.draw_circle(center_x, center_y, self._rect.width / 2, self._black_bg) - rl.draw_texture(texture, center_x - texture.width // 2, center_y - texture.height // 2, self._white_color) + rl.draw_texture_ex(texture, rl.Vector2(center_x - texture.width / 2, center_y - texture.height / 2), 0.0, 1.0, self._white_color) def _held_or_actual_mode(self): now = time.monotonic() diff --git a/selfdrive/ui/sunnypilot/layouts/onboarding.py b/selfdrive/ui/sunnypilot/layouts/onboarding.py index eed3cfd6b3..7e532678b0 100644 --- a/selfdrive/ui/sunnypilot/layouts/onboarding.py +++ b/selfdrive/ui/sunnypilot/layouts/onboarding.py @@ -20,7 +20,7 @@ class SunnylinkConsentPage(Widget): self._done_callback = done_callback self._step = 0 - self._title = Label(tr("sunnylink"), font_size=90, font_weight=FontWeight.BOLD, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT) + self._title = self._child(Label(tr("sunnylink"), font_size=90, font_weight=FontWeight.BOLD, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT)) self._content = [ { @@ -40,9 +40,10 @@ class SunnylinkConsentPage(Widget): } ] - self._primary_btn = Button("", button_style=ButtonStyle.PRIMARY, click_callback=lambda: self._handle_choice("enable")) - self._secondary_btn = Button("", button_style=ButtonStyle.NORMAL, click_callback=lambda: self._handle_choice("secondary")) - self._danger_btn = Button("", button_style=ButtonStyle.DANGER, click_callback=lambda: self._handle_choice("disable")) + self._primary_btn = self._child(Button("", button_style=ButtonStyle.PRIMARY, click_callback=lambda: self._handle_choice("enable"))) + self._secondary_btn = self._child(Button("", button_style=ButtonStyle.NORMAL, click_callback=lambda: self._handle_choice("secondary"))) + self._danger_btn = self._child(Button("", button_style=ButtonStyle.DANGER, click_callback=lambda: self._handle_choice("disable"))) + self._desc = self._child(Label("", font_size=90, font_weight=FontWeight.MEDIUM, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT)) def _handle_choice(self, choice): if choice == "enable": @@ -73,8 +74,8 @@ class SunnylinkConsentPage(Widget): desc_y = welcome_y + 120 desc_rect = rl.Rectangle(desc_x, desc_y, self._rect.width - desc_x, self._rect.height - desc_y - 250) - desc_label = Label(step_data["text"], font_size=90, font_weight=FontWeight.MEDIUM, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT) - desc_label.render(desc_rect) + self._desc.set_text(step_data["text"]) + self._desc.render(desc_rect) btn_y = self._rect.y + self._rect.height - 160 - 45 diff --git a/selfdrive/ui/sunnypilot/layouts/settings/display.py b/selfdrive/ui/sunnypilot/layouts/settings/display.py index d44118d8f4..acd7b52dcc 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/display.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/display.py @@ -12,8 +12,7 @@ from openpilot.system.ui.widgets import Widget from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets.scroller_tici import Scroller from openpilot.system.ui.sunnypilot.widgets.list_view import option_item_sp, ToggleActionSP - -ONROAD_BRIGHTNESS_TIMER_VALUES = {0: 15, 1: 30, **{i: (i - 1) * 60 for i in range(2, 12)}} +from openpilot.sunnypilot.system.params_migration import ONROAD_BRIGHTNESS_TIMER_VALUES class OnroadBrightness(IntEnum): @@ -46,7 +45,7 @@ class DisplayLayout(Widget): title=lambda: tr("Onroad Brightness Delay"), description="", min_value=0, - max_value=11, + max_value=15, value_change_step=1, value_map=ONROAD_BRIGHTNESS_TIMER_VALUES, label_callback=lambda value: f"{value} s" if value < 60 else f"{int(value/60)} m", @@ -92,7 +91,11 @@ class DisplayLayout(Widget): if isinstance(_item.action_item, ToggleActionSP) and _item.action_item.toggle.param_key is not None: _item.action_item.set_state(self._params.get_bool(_item.action_item.toggle.param_key)) elif isinstance(_item.action_item, OptionControlSP) and _item.action_item.param_key is not None: - _item.action_item.set_value(self._params.get(_item.action_item.param_key, return_default=True)) + raw_value = self._params.get(_item.action_item.param_key, return_default=True) + if _item.action_item.value_map: + reverse_map = {v: k for k, v in _item.action_item.value_map.items()} + raw_value = reverse_map.get(raw_value, _item.action_item.current_value) + _item.action_item.set_value(raw_value) brightness_val = self._params.get("OnroadScreenOffBrightness", return_default=True) self._onroad_brightness_timer.action_item.set_enabled(brightness_val not in (OnroadBrightness.AUTO, OnroadBrightness.AUTO_DARK)) diff --git a/selfdrive/ui/sunnypilot/layouts/settings/settings.py b/selfdrive/ui/sunnypilot/layouts/settings/settings.py index e379a77104..4917c9a157 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/settings.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/settings.py @@ -82,8 +82,7 @@ class NavButton(Widget): if self.panel_info.icon: icon_texture = gui_app.texture(self.panel_info.icon, ICON_SIZE, ICON_SIZE, keep_aspect_ratio=True) - rl.draw_texture(icon_texture, int(content_x), int(rect.y + (OP.NAV_BTN_HEIGHT - icon_texture.height) / 2), - rl.WHITE) + rl.draw_texture_ex(icon_texture, rl.Vector2(content_x, rect.y + (OP.NAV_BTN_HEIGHT - icon_texture.height) / 2), 0.0, 1.0, rl.WHITE) content_x += ICON_SIZE + 20 # Draw button text (right-aligned) diff --git a/selfdrive/ui/sunnypilot/layouts/settings/sunnylink.py b/selfdrive/ui/sunnypilot/layouts/settings/sunnylink.py index 5edd2e5b89..1d9b99d5fd 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/sunnylink.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/sunnylink.py @@ -41,7 +41,7 @@ class SunnylinkHeader(Widget): self._description = UnifiedLabel( text=tr("For secure backup, restore, and remote configuration"), font_size=40, - font_weight=FontWeight.LIGHT, + font_weight=FontWeight.NORMAL, text_color=rl.Color(0, 255, 0, 255), # Green alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP, @@ -53,7 +53,7 @@ class SunnylinkHeader(Widget): text=tr("Sponsorship isn't required for basic backup/restore") + "\n" + tr("Click the Sponsor button for more details"), font_size=35, - font_weight=FontWeight.LIGHT, + font_weight=FontWeight.NORMAL, text_color=rl.Color(255, 165, 0, 255), # Orange alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP, @@ -107,7 +107,7 @@ class SunnylinkDescriptionItem(Widget): self._description = UnifiedLabel( text="", font_size=40, - font_weight=FontWeight.LIGHT, + font_weight=FontWeight.NORMAL, text_color=rl.WHITE, alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP, diff --git a/selfdrive/ui/sunnypilot/layouts/settings/trips.py b/selfdrive/ui/sunnypilot/layouts/settings/trips.py index da9eb42d29..066f52507f 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/trips.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/trips.py @@ -93,7 +93,7 @@ class TripsLayout(Widget): # Values number_font = gui_app.font(FontWeight.BOLD) - unit_font = gui_app.font(FontWeight.LIGHT) + unit_font = gui_app.font(FontWeight.NORMAL) number_base_size = 92 unit_base_size = 55 number_size = number_base_size * FONT_SCALE @@ -112,9 +112,9 @@ class TripsLayout(Widget): center_x = col_x + (col_width / 2) # Icon - icon_x = int(center_x - (icon.width / 2)) - icon_y = int(content_y + 60) - rl.draw_texture(icon, icon_x, icon_y, rl.WHITE) + icon_x = center_x - (icon.width / 2) + icon_y = content_y + 60 + rl.draw_texture_ex(icon, rl.Vector2(icon_x, icon_y), 0.0, 1.0, rl.WHITE) # Value val_size = measure_text_cached(number_font, value, number_base_size) diff --git a/selfdrive/ui/sunnypilot/mici/layouts/models.py b/selfdrive/ui/sunnypilot/mici/layouts/models.py index 5d8de4381a..d8da750fe1 100644 --- a/selfdrive/ui/sunnypilot/mici/layouts/models.py +++ b/selfdrive/ui/sunnypilot/mici/layouts/models.py @@ -20,10 +20,10 @@ class ModelsLayoutMici(NavScroller): self.original_back_callback = back_callback self.focused_widget = None - self.current_model_btn = BigButton(tr("current model"), "", "") + self.current_model_btn = BigButton(tr("current model")) self.current_model_btn.set_click_callback(self._show_folders) - self.cancel_download_btn = BigButton(tr("cancel download"), "", "") + self.cancel_download_btn = BigButton(tr("cancel download")) self.cancel_download_btn.set_click_callback(lambda: ui_state.params.remove("ModelManager_DownloadIndex")) self.main_items = [self.current_model_btn, self.cancel_download_btn] @@ -52,13 +52,13 @@ class ModelsLayoutMici(NavScroller): self.focused_widget = self.current_model_btn folders = self._get_grouped_bundles() folder_buttons = [] - default_btn = BigButton(tr("default model"), "", "") + default_btn = BigButton(tr("default model")) default_btn.set_click_callback(self._select_default) folder_buttons.append(default_btn) for folder in sorted(folders.keys(), key=lambda f: max((bundle.index for bundle in folders[f]), default=-1), reverse=True): if folder.lower() in ["release models", "master models"]: - btn = BigButton(folder.lower(), "", "") + btn = BigButton(folder.lower()) btn.set_click_callback(lambda f=folder: self._select_folder(f)) folder_buttons.append(btn) self._show_selection_view(folder_buttons, self._reset_main_view) @@ -78,7 +78,7 @@ class ModelsLayoutMici(NavScroller): btns = [] for bundle in bundles: txt = bundle.displayName.lower() - btn = BigButton(txt, "", "") + btn = BigButton(txt) btn.set_click_callback(lambda b=bundle: self._select_model(b)) btns.append(btn) self._show_selection_view(btns, self._show_folders) diff --git a/selfdrive/ui/sunnypilot/mici/layouts/onboarding.py b/selfdrive/ui/sunnypilot/mici/layouts/onboarding.py index 03729f0f2f..a98f5a2e2e 100644 --- a/selfdrive/ui/sunnypilot/mici/layouts/onboarding.py +++ b/selfdrive/ui/sunnypilot/mici/layouts/onboarding.py @@ -4,90 +4,28 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors. This file is part of sunnypilot and is licensed under the MIT License. See the LICENSE.md file in the root directory for more details. """ -import pyray as rl -from openpilot.system.ui.lib.application import FontWeight, gui_app -from openpilot.system.ui.widgets.label import UnifiedLabel -from openpilot.system.ui.widgets.slider import SmallSlider -from openpilot.system.ui.mici_setup import TermsHeader, TermsPage as SetupTermsPage -from openpilot.system.version import sunnylink_consent_version, sunnylink_consent_declined -from openpilot.selfdrive.ui.ui_state import ui_state +from collections.abc import Callable + +from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationCircleButton +from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.mici_setup import GreyBigButton +from openpilot.system.ui.widgets.scroller import NavScroller -class SunnylinkConsentPage(SetupTermsPage): - def __init__(self, on_accept=None, on_decline=None, left_text: str = "disable", right_text: str = "enable"): - super().__init__(on_accept, on_decline, left_text, continue_text=right_text) +class SunnylinkConsentPage(NavScroller): + def __init__(self, on_accept: Callable | None = None, on_decline: Callable | None = None): + super().__init__() - self._title_header = TermsHeader("sunnylink", - gui_app.texture("../../sunnypilot/selfdrive/assets/logo.png", 66, 60)) + self._accept_button = BigConfirmationCircleButton("enable\nsunnylink", gui_app.texture("icons_mici/setup/driver_monitoring/dm_check.png", 64, 64), + on_accept, exit_on_confirm=False) - self._terms_label = UnifiedLabel("sunnylink enables secured remote access to your comma device from anywhere, " + - "including settings management, remote monitoring, real-time dashboard, etc.", - 36, FontWeight.ROMAN) + self._decline_button = BigConfirmationCircleButton("disable\nsunnylink", gui_app.texture("icons_mici/setup/cancel.png", 64, 64), + on_decline, red=True, exit_on_confirm=False) - @property - def _content_height(self): - return self._terms_label.rect.y + self._terms_label.rect.height - self._scroll_panel.get_offset() - - def _render_content(self, scroll_offset): - self._title_header.set_position(self._rect.x + 16, self._rect.y + 12 + scroll_offset) - self._title_header.render() - - self._terms_label.render(rl.Rectangle( - self._rect.x + 16, - self._title_header.rect.y + self._title_header.rect.height + self.ITEM_SPACING, - self._rect.width - 100, - self._terms_label.get_content_height(int(self._rect.width - 100)), - )) - - -class SunnylinkConsentDisableConfirmPage(SunnylinkConsentPage): - def __init__(self, on_accept=None, on_decline=None): - super().__init__(on_accept=on_decline, on_decline=on_accept, left_text="enable", right_text="disable") - - # we flip the continue & disable buttons to use slider for disable - self._continue_slider = True - self._continue_button = SmallSlider("disable", confirm_callback=on_decline) - self._scroll_panel.set_enabled(lambda: not self._continue_button.is_pressed) - - self._title_header = TermsHeader("disable sunnylink?", - gui_app.texture("icons_mici/setup/red_warning.png", 66, 60)) - - self._terms_label = UnifiedLabel("sunnylink is designed to be enabled as part of sunnypilot's core functionality. " + - "If sunnylink is disabled, features such as settings management, " + - "remote monitoring, real-time dashboards will be unavailable.", - 36, FontWeight.ROMAN) - - -class SunnylinkOnboarding: - def __init__(self): - self.consent_done: bool = ui_state.params.get("CompletedSunnylinkConsentVersion") in {sunnylink_consent_version, sunnylink_consent_declined} - self.disable_confirm = False - - self.consent_page = SunnylinkConsentPage(on_decline=self._on_decline, on_accept=self._on_accept) - self.confirm_page = SunnylinkConsentDisableConfirmPage(on_decline=self._on_confirm_decline, on_accept=self._on_accept) - - @property - def completed(self) -> bool: - return self.consent_done - - def _on_accept(self): - ui_state.params.put("CompletedSunnylinkConsentVersion", sunnylink_consent_version) - ui_state.params.put_bool("SunnylinkEnabled", True) - self.consent_done = True - - def _on_decline(self): - self.disable_confirm = True - - def _on_confirm_decline(self): - ui_state.params.put_bool("SunnylinkEnabled", False) - ui_state.params.put("CompletedSunnylinkConsentVersion", sunnylink_consent_declined) - self.consent_done = True - - def render(self, rect): - if self.consent_done: - return - - if self.disable_confirm: - self.confirm_page.render(rect) - else: - self.consent_page.render(rect) + self._scroller.add_widgets([ + GreyBigButton("sunnylink", "scroll to continue", + gui_app.texture("../../sunnypilot/selfdrive/assets/logo.png", 64, 64)), + GreyBigButton("", "sunnylink enables secured remote access to your comma device from anywhere."), + self._accept_button, + self._decline_button, + ]) diff --git a/selfdrive/ui/sunnypilot/mici/layouts/settings.py b/selfdrive/ui/sunnypilot/mici/layouts/settings.py index 43d0bd2cef..9e160521c7 100644 --- a/selfdrive/ui/sunnypilot/mici/layouts/settings.py +++ b/selfdrive/ui/sunnypilot/mici/layouts/settings.py @@ -18,11 +18,11 @@ class SettingsLayoutSP(OP.SettingsLayout): OP.SettingsLayout.__init__(self) sunnylink_panel = SunnylinkLayoutMici(back_callback=gui_app.pop_widget) - sunnylink_btn = BigButton("sunnylink", "", "icons_mici/settings/developer/ssh.png") + sunnylink_btn = BigButton("sunnylink", "", gui_app.texture("icons_mici/settings/developer/ssh.png", ICON_SIZE, ICON_SIZE)) sunnylink_btn.set_click_callback(lambda: gui_app.push_widget(sunnylink_panel)) models_panel = ModelsLayoutMici(back_callback=gui_app.pop_widget) - models_btn = BigButton("models", "", "../../sunnypilot/selfdrive/assets/offroad/icon_models.png") + models_btn = BigButton("models", "", gui_app.texture("../../sunnypilot/selfdrive/assets/offroad/icon_models.png", ICON_SIZE, ICON_SIZE)) models_btn.set_click_callback(lambda: gui_app.push_widget(models_panel)) items = self._scroller._items.copy() diff --git a/selfdrive/ui/sunnypilot/mici/layouts/sunnylink.py b/selfdrive/ui/sunnypilot/mici/layouts/sunnylink.py index c892db9960..43ea07643e 100644 --- a/selfdrive/ui/sunnypilot/mici/layouts/sunnylink.py +++ b/selfdrive/ui/sunnypilot/mici/layouts/sunnylink.py @@ -8,7 +8,7 @@ from collections.abc import Callable from cereal import custom from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigToggle -from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialogV2 +from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialog from openpilot.selfdrive.ui.sunnypilot.mici.layouts.onboarding import SunnylinkConsentPage from openpilot.selfdrive.ui.sunnypilot.mici.widgets.sunnylink_pairing_dialog import SunnylinkPairingDialog from openpilot.selfdrive.ui.ui_state import ui_state @@ -32,9 +32,9 @@ class SunnylinkLayoutMici(NavScroller): toggle_callback=self._sunnylink_toggle_callback) self._sunnylink_sponsor_button = SunnylinkPairBigButton(sponsor_pairing=False) self._sunnylink_pair_button = SunnylinkPairBigButton(sponsor_pairing=True) - self._backup_btn = BigButton(tr("backup settings"), "", "") + self._backup_btn = BigButton(tr("backup settings"), "") self._backup_btn.set_click_callback(lambda: self._handle_backup_restore_btn(restore=False)) - self._restore_btn = BigButton(tr("restore settings"), "", "") + self._restore_btn = BigButton(tr("restore settings"), "") self._restore_btn.set_click_callback(lambda: self._handle_backup_restore_btn(restore=True)) self._sunnylink_uploader_toggle = BigToggle(text=tr("sunnylink uploader"), initial_state=False, toggle_callback=self._sunnylink_uploader_callback) @@ -105,8 +105,8 @@ class SunnylinkLayoutMici(NavScroller): def _handle_backup_restore_btn(self, restore: bool = False): lbl = tr("slide to restore") if restore else tr("slide to backup") - icon = "icons_mici/settings/device/update.png" - dlg = BigConfirmationDialogV2(lbl, icon, confirm_callback=self._restore_handler if restore else self._backup_handler) + icon = gui_app.texture("icons_mici/settings/device/update.png", 64, 64) + dlg = BigConfirmationDialog(lbl, icon, confirm_callback=self._restore_handler if restore else self._backup_handler) gui_app.push_widget(dlg) def _backup_handler(self): @@ -169,8 +169,8 @@ class SunnylinkLayoutMici(NavScroller): elif (restore_status == custom.BackupManagerSP.Status.completed or (restore_status == custom.BackupManagerSP.Status.idle and restore_progress == 100.0)): self._restore_in_progress = False - gui_app.push_widget(BigConfirmationDialogV2( - title="slide to restart", icon="icons_mici/settings/device/reboot.png", + gui_app.push_widget(BigConfirmationDialog( + title="slide to restart", icon=gui_app.texture("icons_mici/settings/device/reboot.png", 64, 64), confirm_callback=lambda: gui_app.request_close())) else: @@ -186,7 +186,7 @@ class SunnylinkLayoutMici(NavScroller): class SunnylinkPairBigButton(BigButton): def __init__(self, sponsor_pairing: bool = False): self.sponsor_pairing = sponsor_pairing - super().__init__("", "", "") + super().__init__("") def _update_state(self): super()._update_state() diff --git a/selfdrive/ui/sunnypilot/mici/widgets/sunnylink_pairing_dialog.py b/selfdrive/ui/sunnypilot/mici/widgets/sunnylink_pairing_dialog.py index eb3c2bddfe..c727e4fc1c 100644 --- a/selfdrive/ui/sunnypilot/mici/widgets/sunnylink_pairing_dialog.py +++ b/selfdrive/ui/sunnypilot/mici/widgets/sunnylink_pairing_dialog.py @@ -13,7 +13,7 @@ from openpilot.sunnypilot.sunnylink.api import SunnylinkApi, UNREGISTERED_SUNNYL from openpilot.system.ui.lib.application import FontWeight, gui_app from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets.nav_widget import NavWidget -from openpilot.system.ui.widgets.label import MiciLabel +from openpilot.system.ui.widgets.label import UnifiedLabel class SunnylinkPairingDialog(PairingDialog): @@ -23,8 +23,8 @@ class SunnylinkPairingDialog(PairingDialog): PairingDialog.__init__(self) self._sponsor_pairing = sponsor_pairing label_text = tr("pair with sunnylink") if sponsor_pairing else tr("become a sunnypilot sponsor") - self._pair_label = MiciLabel(label_text, 48, font_weight=FontWeight.BOLD, - color=rl.Color(255, 255, 255, int(255 * 0.9)), line_height=40, wrap_text=True) + self._pair_label = UnifiedLabel(label_text, font_size=48, font_weight=FontWeight.BOLD, + text_color=rl.Color(255, 255, 255, int(255 * 0.9)), line_height=0.8) def _get_pairing_url(self) -> str: qr_string = "https://github.com/sponsors/sunnyhaibin" diff --git a/selfdrive/ui/sunnypilot/onroad/augmented_road_view.py b/selfdrive/ui/sunnypilot/onroad/augmented_road_view.py index 64d7a3d946..c7dedee540 100644 --- a/selfdrive/ui/sunnypilot/onroad/augmented_road_view.py +++ b/selfdrive/ui/sunnypilot/onroad/augmented_road_view.py @@ -23,7 +23,7 @@ class AugmentedRoadViewSP: def update_fade_out_bottom_overlay(self, _content_rect): # Fade out bottom of overlays for looks (only when engaged) fade_alpha = self._fade_alpha_filter.update(ui_state.status != UIStatus.DISENGAGED) - if ui_state.torque_bar and ui_state.sm['controlsState'].lateralControlState.which() != 'angleState' and fade_alpha > 1e-2: + if ui_state.torque_bar and fade_alpha > 1e-2: # Scale the fade texture to the content rect rl.draw_texture_pro(self._fade_texture, rl.Rectangle(0, 0, self._fade_texture.width, self._fade_texture.height), diff --git a/selfdrive/ui/sunnypilot/onroad/blind_spot_indicators.py b/selfdrive/ui/sunnypilot/onroad/blind_spot_indicators.py index 4e1d748117..2efda17a2b 100644 --- a/selfdrive/ui/sunnypilot/onroad/blind_spot_indicators.py +++ b/selfdrive/ui/sunnypilot/onroad/blind_spot_indicators.py @@ -42,11 +42,11 @@ class BlindSpotIndicators: pos_y = int(rect.y + BLIND_SPOT_Y_OFFSET) alpha = int(255 * self._blind_spot_left_alpha_filter.x) color = rl.Color(255, 255, 255, alpha) - rl.draw_texture(self._txt_blind_spot_left, pos_x, pos_y, color) + rl.draw_texture_ex(self._txt_blind_spot_left, rl.Vector2(pos_x, pos_y), 0.0, 1.0, color) if self._blind_spot_right_alpha_filter.x > 0.01: pos_x = int(rect.x + rect.width - BLIND_SPOT_MARGIN_X - self._txt_blind_spot_right.width) pos_y = int(rect.y + BLIND_SPOT_Y_OFFSET) alpha = int(255 * self._blind_spot_right_alpha_filter.x) color = rl.Color(255, 255, 255, alpha) - rl.draw_texture(self._txt_blind_spot_right, pos_x, pos_y, color) + rl.draw_texture_ex(self._txt_blind_spot_right, rl.Vector2(pos_x, pos_y), 0.0, 1.0, color) diff --git a/selfdrive/ui/sunnypilot/onroad/circular_alerts.py b/selfdrive/ui/sunnypilot/onroad/circular_alerts.py index 9c9ab7ac84..f90fc81914 100644 --- a/selfdrive/ui/sunnypilot/onroad/circular_alerts.py +++ b/selfdrive/ui/sunnypilot/onroad/circular_alerts.py @@ -101,9 +101,9 @@ class CircularAlertsRenderer: # Draw Image if self._alert_img and self._e2e_alert_display_timer > 0: - img_x = int(center.x - self._alert_img.width / 2) - img_y = int(center.y - self._alert_img.height / 2) - rl.draw_texture(self._alert_img, img_x, img_y, rl.WHITE) + img_x = center.x - self._alert_img.width / 2 + img_y = center.y - self._alert_img.height / 2 + rl.draw_texture_ex(self._alert_img, rl.Vector2(img_x, img_y), 0.0, 1.0, rl.WHITE) # Draw Text txt_color = rl.Color(255, 255, 255, 255) if is_pulsing else rl.Color(0, 255, 0, 190) diff --git a/selfdrive/ui/sunnypilot/onroad/hud_renderer.py b/selfdrive/ui/sunnypilot/onroad/hud_renderer.py index 2a66b3664b..f8e4257733 100644 --- a/selfdrive/ui/sunnypilot/onroad/hud_renderer.py +++ b/selfdrive/ui/sunnypilot/onroad/hud_renderer.py @@ -131,7 +131,7 @@ class HudRendererSP(HudRenderer): def _render(self, rect: rl.Rectangle) -> None: super()._render(rect) - if ui_state.torque_bar and ui_state.sm['controlsState'].lateralControlState.which() != 'angleState': + if ui_state.torque_bar: torque_rect = rect if ui_state.developer_ui in (DeveloperUiState.BOTTOM, DeveloperUiState.BOTH): torque_rect = rl.Rectangle(rect.x, rect.y, rect.width, rect.height - get_bottom_dev_ui_offset()) diff --git a/selfdrive/ui/sunnypilot/onroad/speed_limit.py b/selfdrive/ui/sunnypilot/onroad/speed_limit.py index 1b03e9a204..98f5b29087 100644 --- a/selfdrive/ui/sunnypilot/onroad/speed_limit.py +++ b/selfdrive/ui/sunnypilot/onroad/speed_limit.py @@ -227,7 +227,7 @@ class SpeedLimitRenderer(Widget, SpeedLimitAlertRenderer): arrow_x = sign_rect.x + sign_rect.width + arrow_spacing arrow_y = sign_rect.y + (sign_rect.height - txt_icon.height) / 2 color = rl.Color(255, 255, 255, int(icon_alpha)) - rl.draw_texture(txt_icon, int(arrow_x), int(arrow_y), color) + rl.draw_texture_ex(txt_icon, rl.Vector2(arrow_x, arrow_y), 0.0, 1.0, color) def _render_vienna(self, rect, val, sub, color, has_limit, alpha=1.0): center = rl.Vector2(rect.x + rect.width / 2, rect.y + rect.height / 2) diff --git a/selfdrive/ui/sunnypilot/onroad/turn_signal.py b/selfdrive/ui/sunnypilot/onroad/turn_signal.py index e285009364..fc6f7eb915 100644 --- a/selfdrive/ui/sunnypilot/onroad/turn_signal.py +++ b/selfdrive/ui/sunnypilot/onroad/turn_signal.py @@ -55,10 +55,10 @@ class TurnSignalWidget(Widget): self._texture = self._blind_spot_texture if self._type == 'blind_spot' else self._signal_texture if self._texture: - pos_x = int(self._rect.x + (self._rect.width - self._texture.width) / 2) - pos_y = int(self._rect.y + (self._rect.height - self._texture.height) / 2) + pos_x = self._rect.x + (self._rect.width - self._texture.width) / 2 + pos_y = self._rect.y + (self._rect.height - self._texture.height) / 2 color = rl.Color(255, 255, 255, icon_alpha) - rl.draw_texture(self._texture, pos_x, pos_y, color) + rl.draw_texture_ex(self._texture, rl.Vector2(pos_x, pos_y), 0.0, 1.0, color) def activate(self, _type: str = 'signal'): if not self._active or self._type != _type: diff --git a/selfdrive/ui/sunnypilot/ui_state.py b/selfdrive/ui/sunnypilot/ui_state.py index 2998f0e20d..5c69ba6a02 100644 --- a/selfdrive/ui/sunnypilot/ui_state.py +++ b/selfdrive/ui/sunnypilot/ui_state.py @@ -49,8 +49,11 @@ class UIStateSP: else: self.sunnylink_state.stop() - def onroad_brightness_handle_alerts(self, started: bool, alert): - has_alert = started and self.onroad_brightness != OnroadBrightness.AUTO and alert is not None + def onroad_brightness_handle_alerts(self, _ui_state, alert): + if _ui_state.sm.recv_frame["carState"] < _ui_state.started_frame: + return + + has_alert = _ui_state.started and self.onroad_brightness != OnroadBrightness.AUTO and alert is not None self.update_onroad_brightness(has_alert) if has_alert: diff --git a/selfdrive/ui/tests/.gitignore b/selfdrive/ui/tests/.gitignore deleted file mode 100644 index 74ab2675db..0000000000 --- a/selfdrive/ui/tests/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -test -test_translations diff --git a/selfdrive/ui/tests/diff/.gitignore b/selfdrive/ui/tests/diff/.gitignore deleted file mode 100644 index e21a8d896e..0000000000 --- a/selfdrive/ui/tests/diff/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -report -.coverage diff --git a/selfdrive/ui/tests/diff/replay.py b/selfdrive/ui/tests/diff/replay.py index 08c45b7b7f..fd82e325a3 100755 --- a/selfdrive/ui/tests/diff/replay.py +++ b/selfdrive/ui/tests/diff/replay.py @@ -4,12 +4,16 @@ import argparse import coverage import pyray as rl +from tqdm import tqdm from typing import Literal from collections.abc import Callable from cereal.messaging import PubMaster +from openpilot.common.api import Api +from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params from openpilot.common.prefix import OpenpilotPrefix from openpilot.selfdrive.ui.tests.diff.diff import DIFF_OUT_DIR +from openpilot.system.updated.updated import parse_release_notes from openpilot.system.version import terms_version, training_version, terms_version_sp, sunnylink_consent_version LayoutVariant = Literal["mici", "tizi"] @@ -25,6 +29,7 @@ def setup_state(): params.put("DongleId", "test123456789") # Combined description for layouts that still use it (BIG home, settings/software) params.put("UpdaterCurrentDescription", "0.10.1 / test-branch / abc1234 / Nov 30") + params.put("UpdaterCurrentReleaseNotes", parse_release_notes(BASEDIR)) params.put("HasAcceptedTermsSP", terms_version_sp) params.put("CompletedSunnylinkConsentVersion", sunnylink_consent_version) @@ -34,6 +39,9 @@ def setup_state(): params.put("GitCommit", "abc12340ff9131237ba23a1d0fbd8edf9c80e87") params.put("GitCommitDate", "'1732924800 2024-11-30 00:00:00 +0000'") + # Patch Api.get_token to return a static token so the pairing QR code is deterministic across runs + Api.get_token = lambda self, payload_extra=None, expiry_hours=0: "test_token" + def run_replay(variant: LayoutVariant) -> None: if HEADLESS: @@ -43,7 +51,7 @@ def run_replay(variant: LayoutVariant) -> None: setup_state() os.makedirs(DIFF_OUT_DIR, exist_ok=True) - from openpilot.selfdrive.ui.ui_state import ui_state # Import within OpenpilotPrefix context so param values are setup correctly + from openpilot.selfdrive.ui.ui_state import ui_state, device # Import within OpenpilotPrefix context so param values are setup correctly from openpilot.system.ui.lib.application import gui_app # Import here for accurate coverage from openpilot.selfdrive.ui.tests.diff.replay_script import build_script @@ -56,6 +64,10 @@ def run_replay(variant: LayoutVariant) -> None: from openpilot.selfdrive.ui.layouts.main import MainLayout main_layout = MainLayout() + # Disable interactive timeout — replay clicks use left_down=False so they never reset the timer, + # and after 30s of real wall-clock time the settings panel would close automatically. + device.set_override_interactive_timeout(99999) + pm = PubMaster(["deviceState", "pandaStates", "driverStateV2", "selfdriveState"]) script = build_script(pm, main_layout, variant) script_index = 0 @@ -67,33 +79,35 @@ def run_replay(variant: LayoutVariant) -> None: rl.get_time = lambda: frame / FPS # Main loop to replay events and render frames - for _ in gui_app.render(): - # Handle all events for the current frame - while script_index < len(script) and script[script_index][0] == frame: - _, event = script[script_index] - # Call setup function, if any - if event.setup: - event.setup() - # Send mouse events to the application - if event.mouse_events: - with gui_app._mouse._lock: - gui_app._mouse._events.extend(event.mouse_events) - # Update persistent send function - if event.send_fn is not None: - send_fn = event.send_fn - # Move to next script event - script_index += 1 + with tqdm(total=script[-1][0] + 1, desc="Replaying", unit="frame", disable=bool(os.getenv("CI"))) as pbar: + for _ in gui_app.render(): + # Handle all events for the current frame + while script_index < len(script) and script[script_index][0] == frame: + _, event = script[script_index] + # Call setup function, if any + if event.setup: + event.setup() + # Send mouse events to the application + if event.mouse_events: + with gui_app._mouse._lock: + gui_app._mouse._events.extend(event.mouse_events) + # Update persistent send function + if event.send_fn is not None: + send_fn = event.send_fn + # Move to next script event + script_index += 1 - # Keep sending cereal messages for persistent states (onroad, alerts) - if send_fn: - send_fn() + # Keep sending cereal messages for persistent states (onroad, alerts) + if send_fn: + send_fn() - ui_state.update() + ui_state.update() - frame += 1 + frame += 1 + pbar.update(1) - if script_index >= len(script): - break + if script_index >= len(script): + break gui_app.close() diff --git a/selfdrive/ui/tests/diff/replay_script.py b/selfdrive/ui/tests/diff/replay_script.py index 9f2104ec49..c53d2f116b 100644 --- a/selfdrive/ui/tests/diff/replay_script.py +++ b/selfdrive/ui/tests/diff/replay_script.py @@ -3,15 +3,27 @@ from typing import TYPE_CHECKING from collections.abc import Callable from dataclasses import dataclass +import math + from cereal import car, log, messaging from cereal.messaging import PubMaster from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert +from openpilot.selfdrive.ui.lib.prime_state import PrimeType from openpilot.selfdrive.ui.tests.diff.replay import FPS, LayoutVariant from openpilot.system.updated.updated import parse_release_notes -WAIT = int(FPS * 0.5) # Default frames to wait after events +# Default frames to wait after events +WAIT_LONG = FPS +WAIT_SHORT = FPS // 2 +FAST_CLICK = FPS // 6 + +# Direction vectors for drag gestures +DIR_LEFT = (-1, 0) +DIR_RIGHT = (1, 0) +DIR_UP = (0, -1) +DIR_DOWN = (0, 1) AlertSize = log.SelfdriveState.AlertSize AlertStatus = log.SelfdriveState.AlertStatus @@ -56,49 +68,92 @@ class Script: """Add a delay for the given number of frames followed by an empty event.""" self.add(ScriptEvent(), before=frames) - def setup(self, fn: Callable, wait_after: int = WAIT) -> None: + def setup(self, fn: Callable, wait_after: int = WAIT_SHORT) -> None: """Add a setup function to be called immediately followed by a delay of the given number of frames.""" self.add(ScriptEvent(setup=fn), after=wait_after) - def set_send(self, fn: Callable, wait_after: int = WAIT) -> None: + def set_send(self, fn: Callable, wait_after: int = WAIT_SHORT) -> None: """Set a new persistent send function to be called every frame.""" self.add(ScriptEvent(send_fn=fn), after=wait_after) - # TODO: Also add more complex gestures, like swipe or drag - def click(self, x: int, y: int, wait_after: int = WAIT, wait_between: int = 2) -> None: + def click(self, x: int, y: int, wait_after: int = WAIT_SHORT, wait_between: int = 2) -> None: """Add a click event to the script for the given position and specify frames to wait between mouse events or after the click.""" # NOTE: By default we wait a couple frames between mouse events so pressed states will be rendered from openpilot.system.ui.lib.application import MouseEvent, MousePos - # TODO: Add support for long press (left_down=True) mouse_down = MouseEvent(pos=MousePos(x, y), slot=0, left_pressed=True, left_released=False, left_down=False, t=self.get_frame_time()) self.add(ScriptEvent(mouse_events=[mouse_down]), after=wait_between) mouse_up = MouseEvent(pos=MousePos(x, y), slot=0, left_pressed=False, left_released=True, left_down=False, t=self.get_frame_time()) self.add(ScriptEvent(mouse_events=[mouse_up]), after=wait_after) + def drag(self, start_x: int, start_y: int, direction: tuple[int, int], distance: int, duration_frames: int, wait_after: int = WAIT_LONG) -> None: + """Add a drag gesture to the script from start position in the specified direction by the given distance over the given number of frames.""" + from openpilot.system.ui.lib.application import MouseEvent, MousePos + + # Calculate delta and end position based on direction and distance + delta_x, delta_y = direction[0] * distance, direction[1] * distance + end_x, end_y = start_x + delta_x, start_y + delta_y + + # Mouse down at start + mouse_down = MouseEvent(pos=MousePos(start_x, start_y), slot=0, left_pressed=True, left_released=False, left_down=True, t=self.get_frame_time()) + self.add(ScriptEvent(mouse_events=[mouse_down]), after=1) + + # Interpolate positions over duration_frames + for i in range(1, duration_frames): + t = i / duration_frames + x, y = int(start_x + delta_x * t), int(start_y + delta_y * t) + mouse_move = MouseEvent(pos=MousePos(x, y), slot=0, left_pressed=False, left_released=False, left_down=True, t=self.get_frame_time()) + self.add(ScriptEvent(mouse_events=[mouse_move]), after=1) + + # Mouse up at end + mouse_up = MouseEvent(pos=MousePos(end_x, end_y), slot=0, left_pressed=False, left_released=True, left_down=False, t=self.get_frame_time()) + self.add(ScriptEvent(mouse_events=[mouse_up]), after=wait_after) + # --- Setup functions --- -def put_update_params(params: Params | None = None) -> None: - if params is None: - params = Params() - params.put("UpdaterCurrentReleaseNotes", parse_release_notes(BASEDIR)) - params.put("UpdaterNewReleaseNotes", parse_release_notes(BASEDIR)) - params.put("UpdaterTargetBranch", BRANCH_NAME) + +def set_prime_state(prime_type: PrimeType) -> None: + from openpilot.selfdrive.ui.ui_state import ui_state + ui_state.prime_state.set_type(prime_type) def setup_offroad_alerts() -> None: - put_update_params(Params()) set_offroad_alert("Offroad_TemperatureTooHigh", True, extra_text='99C') set_offroad_alert("Offroad_ExcessiveActuation", True, extra_text='longitudinal') set_offroad_alert("Offroad_IsTakingSnapshot", True) -def setup_update_available() -> None: +def setup_update_available(available: bool = True) -> None: params = Params() - params.put_bool("UpdateAvailable", True) - params.put("UpdaterNewDescription", f"0.10.2 / {BRANCH_NAME} / 0a1b2c3 / Jan 01") - put_update_params(params) + params.put_bool("UpdateAvailable", available) + params.put("UpdaterAvailableBranches", ",".join(["test-branch", "test-branch-2", BRANCH_NAME])) + if available: + params.put("UpdaterNewDescription", f"0.10.2 / {BRANCH_NAME} / 0a1b2c3 / Jan 01") + params.put("UpdaterNewReleaseNotes", parse_release_notes(BASEDIR)) + params.put("UpdaterTargetBranch", BRANCH_NAME) + else: + params.remove("UpdaterNewDescription") + params.remove("UpdaterNewReleaseNotes") + params.remove("UpdaterTargetBranch") + + +def setup_calibration_params() -> None: + params = Params() + # live calibration + calib = messaging.new_message('liveCalibration') + calib.liveCalibration.calStatus = log.LiveCalibrationData.Status.calibrated + calib.liveCalibration.rpyCalib = [0.0, math.radians(2.5), math.radians(-1.2)] + params.put("CalibrationParams", calib.to_bytes()) + # live delay + delay = messaging.new_message('liveDelay') + delay.liveDelay.calPerc = 75 + params.put("LiveDelay", delay.to_bytes()) + # live torque parameters + torque = messaging.new_message('liveTorqueParameters') + torque.liveTorqueParameters.useParams = True + torque.liveTorqueParameters.calPerc = 60 + params.put("LiveTorqueParameters", torque.to_bytes()) def setup_developer_params() -> None: @@ -132,7 +187,6 @@ def make_network_state_setup(pm: PubMaster, network_type) -> Callable: def make_alert_setup(pm: PubMaster, size, text1, text2, status) -> Callable: def _send() -> None: - send_onroad(pm) alert = messaging.new_message('selfdriveState') ss = alert.selfdriveState ss.alertSize = size @@ -143,18 +197,181 @@ def make_alert_setup(pm: PubMaster, size, text1, text2, status) -> Callable: return _send +def test_onroad_alerts(script: Script, pm: PubMaster) -> None: + """Go through various alert types and sizes and add them to the script to test alert rendering. + Each alert is sent as a separate event with a delay in between.""" + # Small alert (normal) + script.set_send(make_alert_setup(pm, AlertSize.small, "Small Alert", "This is a small alert", AlertStatus.normal)) + # Medium alert (userPrompt) + script.set_send(make_alert_setup(pm, AlertSize.mid, "Medium Alert", "This is a medium alert", AlertStatus.userPrompt)) + # Full alert (critical) + script.set_send(make_alert_setup(pm, AlertSize.full, "DISENGAGE IMMEDIATELY", "Driver Distracted", AlertStatus.critical)) + # Full alert multiline + script.set_send(make_alert_setup(pm, AlertSize.full, "Reverse\nGear", "", AlertStatus.normal)) + # Full alert long text + script.set_send(make_alert_setup(pm, AlertSize.full, "TAKE CONTROL IMMEDIATELY", "Calibration Invalid: Remount Device & Recalibrate", AlertStatus.userPrompt)) + + # --- Script builders --- def build_mici_script(pm: PubMaster, main_layout, script: Script) -> None: """Build the replay script for the mici layout.""" from openpilot.system.ui.lib.application import gui_app - center = (gui_app.width // 2, gui_app.height // 2) + width, height = gui_app.width, gui_app.height + center = (width // 2, height // 2) + right = (width * 4 // 5, height // 2) + left = (width // 5, height // 2) + top = (width // 2, height // 10) + bottom = (width // 2, height * 9 // 10) + + DURATION = 5 + SWIPE_WAIT = FPS * 3 // 4 + + def click(times: int = 1, wait_after: int = WAIT_SHORT) -> None: + """Click at the center of the screen the given number of times with optional delay after.""" + for _ in range(times): + script.click(*center, wait_after=wait_after) + + def press(x: int, y: int, duration_frames: int = DURATION, wait_after: int = WAIT_SHORT) -> None: + """Perform a drag with no movement to simulate a left_down mouse event at the given position for the specified duration and delay after.""" + script.drag(x, y, (0, 0), 0, duration_frames, wait_after=wait_after) + + def swipe_left(distance: int = right[0] - left[0], duration_frames: int = DURATION, wait_after: int = SWIPE_WAIT) -> None: + """Drag from right edge to left (scroll right / slide confirmation).""" + script.drag(*right, DIR_LEFT, distance, duration_frames, wait_after) + + def swipe_right(distance: int = right[0] - left[0], duration_frames: int = DURATION, wait_after: int = SWIPE_WAIT) -> None: + """Drag from left edge to right (scroll left).""" + script.drag(*left, DIR_RIGHT, distance, duration_frames, wait_after) + + def swipe_down(distance: int = bottom[1] - top[1], duration_frames: int = DURATION, wait_after: int = SWIPE_WAIT) -> None: + """Drag from top edge to bottom (scroll up / go back).""" + script.drag(*top, DIR_DOWN, distance, duration_frames, wait_after) + + def swipe_up(distance: int = bottom[1] - top[1], duration_frames: int = DURATION, wait_after: int = SWIPE_WAIT) -> None: + """Drag from bottom edge to top (scroll down).""" + script.drag(*bottom, DIR_UP, distance, duration_frames, wait_after) + + ActionFn = Callable[[], None] | None + Cases = list[ActionFn] + + def run_actions(*actions: ActionFn, after_each: ActionFn = None) -> None: + """Helper function to run a sequence of actions in order for interaction tests, calling after_each callback after each action if provided.""" + for action in actions: + if action is not None: + action() + if after_each is not None: + after_each() + + def explore_setting(*actions: ActionFn) -> None: + """Helper function to open a settings item, run the given actions, and go back.""" + run_actions(click, *actions, swipe_down) # open, interact, go back + + def scroll_through_cases(cases: Cases) -> None: + """Helper function to explore a panel by calling the interaction callbacks for each item/page before swiping to the next one.""" + run_actions(*cases, after_each=lambda: swipe_left(210, 10)) # swipe to roughly the center of the next toggle after each case + + def interact_keyboard() -> None: + """Interact with the keyboard in various ways to test different actions and states. + Assumes it's a password keyboard with 8 characters required. Closes by pressing confirm at the end.""" + KEY = (250, 160) # key in the middle of the keyboard ('G') + SHIFT = (50, 210) + NUMBERS = (480, 210) + SPACE = (500, 160) + BACKSPACE = (490, 30) + CONFIRM = (50, 30) + # Begin interactions + press(*CONFIRM, wait_after=FAST_CLICK) # confirm while disabled should do nothing + swipe_left(duration_frames=FPS // 2) # swipe to type + swipe_up(duration_frames=FPS // 2) # swipe out of keyboard (nothing typed) + # press various keys to test different states: + for key in [ + SHIFT, KEY, KEY, SHIFT, SHIFT, KEY, KEY, # test casing (upper, lower, caps lock) + SPACE, SPACE, BACKSPACE, BACKSPACE, # test multiple space and backspace + NUMBERS, KEY, center, SHIFT, KEY # test numbers and symbols + ]: + press(*key, wait_after=FAST_CLICK) + # press confirm to close + script.wait(WAIT_SHORT) # wait for confirm to enable + press(*CONFIRM) + + toggle_cases: Cases = [ + lambda: click(times=3, wait_after=FAST_CLICK), # first toggle is personality, which has 3 states + None, None, None, None, None, None, # skip other toggles to save time + lambda: click(times=2, wait_after=FAST_CLICK), # test final toggle (enable openpilot) + ] + + network_cases: Cases = [ + explore_setting, # select wifi (just open and close) + None, None, + lambda: run_actions(click, interact_keyboard), # tether password keyboard + ] + + device_cases: Cases = [ + None, + click, # update + explore_setting, # pairing (just open and close) + lambda: explore_setting( + # training guide + lambda: swipe_left(width * 2), click, # first page, click next + lambda: swipe_left(width * 2), swipe_down # second page, go back (TODO: make driver cam preview work) + ), + None, # TODO: preview driver camera; enabling this causes MultiplePublishersError later in onroad alert tests + lambda: explore_setting(swipe_left), # terms & conditions (swipe to view QR code) + lambda: explore_setting(lambda: swipe_up(height * 3), lambda: swipe_down(height * 3)), # regulatory info + lambda: run_actions(click, lambda: swipe_left(width)), # reset calibration confirm (goes back automatically) + lambda: explore_setting(lambda: swipe_left(width)), # uninstall + lambda: run_actions( + lambda: explore_setting(lambda: swipe_left(width)), # reboot + lambda: script.click(430, 120), lambda: swipe_left(width), swipe_down, # shutdown + ), + ] + + developer_cases: Cases = [ + lambda: click(times=2, wait_after=FAST_CLICK), # toggle ssh mode + explore_setting, # SSH keys keyboard (just open and close) + None, # joystick mode + lambda: click(wait_after=FAST_CLICK), # longitudinal maneuver mode (disabled; should do nothing) + lambda: click(times=2, wait_after=FAST_CLICK), # toggle UI debug mode + ] + + settings_cases: Cases = [ + lambda: scroll_through_cases(toggle_cases), + lambda: scroll_through_cases(network_cases), + lambda: scroll_through_cases(device_cases), + lambda: script.wait(WAIT_SHORT), # pairing + lambda: run_actions(lambda: swipe_up(height * 3), lambda: swipe_down(height * 3)), # firehose (scroll down and back up) + lambda: scroll_through_cases(developer_cases), + ] + + # === Homescreen === # + script.wait(WAIT_SHORT) + swipe_left(width, wait_after=WAIT_SHORT) # onroad screen + swipe_right(width, wait_after=WAIT_SHORT) # back to home + + # === Offroad Alerts === + def setup_offroad_alerts_and_refresh() -> None: + """Setup function to trigger offroad alerts and force a refresh on the alerts layout.""" + setup_offroad_alerts() + main_layout._alerts_layout.refresh() + + swipe_right(width, wait_after=WAIT_SHORT) # open alerts + script.setup(setup_offroad_alerts_and_refresh) # show alerts + swipe_up(height) # scroll alerts + swipe_left(width, wait_after=WAIT_SHORT) # close alerts + + # === Settings === # + click() # open settings + scroll_through_cases([lambda case=case: explore_setting(case) for case in settings_cases]) # explore settings + swipe_down() # back to home + + # === Onroad === + script.set_send(lambda: send_onroad(pm)) + swipe_left(width, wait_after=WAIT_SHORT) # onroad screen + test_onroad_alerts(script, pm) + swipe_right(width) # back to home - # TODO: Explore more - script.wait(FPS) - script.click(*center, FPS) # Open settings - script.click(*center, FPS) # Open toggles script.end() @@ -171,34 +388,104 @@ def build_tizi_script(pm: PubMaster, main_layout, script: Script) -> None: return setup + def add_prime_state_setup(prime_type: PrimeType) -> None: + script.set_send(lambda: set_prime_state(prime_type)) + + def do_onboarding() -> None: + """Click through the training guide and close.""" + from openpilot.selfdrive.ui.layouts.onboarding import STEP_RECTS + step = 0 + for step_rect in STEP_RECTS: + if step < len(STEP_RECTS) - 1: + script.click(int(step_rect.x), int(step_rect.y), wait_after=FAST_CLICK) + else: + script.click(950, 900) # On the last step, click Finish instead of restart + step += 1 + + def type_keyboard() -> None: + """Types 8 characters using the big keyboard to test different layouts and interactions.""" + KEY = (150, 430) # e.g. 'Q' key + SHIFT = (150, 750) # also symbols key in number mode + NUMBERS = (150, 950) + SPACE = (1060, 950) + BACKSPACE = (2000, 780) + for key in [ + SHIFT, KEY, KEY, SHIFT, SHIFT, KEY, KEY, # test casing (upper, lower, caps lock) + SPACE, SPACE, BACKSPACE, BACKSPACE, # test multiple space and backspace + NUMBERS, KEY, KEY, SHIFT, KEY, KEY # test numbers and symbols + ]: + script.click(*key, wait_after=FAST_CLICK) + # TODO: Better way of organizing the events # === Homescreen === script.set_send(make_network_state_setup(pm, log.DeviceState.NetworkType.wifi)) - - # === Offroad Alerts (auto-transitions via HomeLayout refresh) === - script.setup(make_home_refresh_setup(setup_offroad_alerts)) + # Go through different prime state layouts + add_prime_state_setup(PrimeType.LITE) + add_prime_state_setup(PrimeType.NONE) + add_prime_state_setup(PrimeType.UNPAIRED) # === Update Available (auto-transitions via HomeLayout refresh) === script.setup(make_home_refresh_setup(setup_update_available)) - # === Settings - Device (click sidebar settings button) === + # === Offroad Alerts (auto-transitions via HomeLayout refresh, overrides update) === + script.setup(make_home_refresh_setup(setup_offroad_alerts)) + script.click(620, 950) # close alerts + + # === Settings (click sidebar settings button) === script.click(150, 90) - script.click(1985, 790) # reset calibration confirmation - script.click(650, 750) # cancel + + # === Settings - Device === + # pair device + script.click(2000, 450) # pair device + script.click(110, 110) # close pairing dialog + add_prime_state_setup(PrimeType.NONE) # changed from unpaired to hide pair device button + # calibration + script.setup(setup_calibration_params, wait_after=0) + script.click(1000, 620) # expand calibration description + script.click(2000, 620) # reset calibration confirmation + script.click(1500, 750) # confirm reset + script.click(1000, 620) # collapse calibration description + # training guide + script.click(2000, 800) # open training guide + do_onboarding() + # regulatory info + script.click(2000, 970) # regulatory button + script.click(2000, 970) # OK # === Settings - Network === script.click(278, 450) + # TODO: mock networks script.click(1880, 100) # advanced network settings - script.click(630, 80) # back + + # Keyboard (tethering password) + script.click(2000, 420, wait_after=FAST_CLICK) # open tether password keyboard + script.click(2000, 950, wait_after=FAST_CLICK) # click confirm (disabled, should not close) + script.click(2000, 115) # cancel (close without typing) + script.click(2000, 420, wait_after=FAST_CLICK) # open keyboard again + type_keyboard() # test various keyboard layouts and interactions + script.click(2050, 250, wait_after=FAST_CLICK) # toggle show/hide password + script.click(2000, 950) # confirm (close keyboard) + + script.click(630, 80) # back from advanced network # === Settings - Toggles === script.click(278, 600) - script.click(1200, 280) # experimental mode description + script.click(1200, 280) # expand experimental mode description # === Settings - Software === - script.setup(put_update_params, wait_after=0) - script.click(278, 720) + script.setup(lambda: setup_update_available(False), wait_after=0) # start with no update available + script.click(278, 720) # software + for _ in range(2): + script.click(720, 120) # toggle current release notes + script.setup(setup_update_available) # set update available + for _ in range(2): + script.click(720, 450) # toggle new release notes + script.click(2000, 630) # open select branch dialog + script.click(1000, 300) # select 1st option + script.click(1600, 900) # confirm selection + script.click(2000, 800) # uninstall + script.click(650, 750) # cancel uninstall # === Settings - Firehose === script.click(278, 845) @@ -206,31 +493,18 @@ def build_tizi_script(pm: PubMaster, main_layout, script: Script) -> None: # === Settings - Developer (set CarParamsPersistent first) === script.setup(setup_developer_params, wait_after=0) script.click(278, 950) + script.click(1930, 470) # SSH keys (keyboard) + script.click(1930, 115) # click cancel on keyboard script.click(2000, 960) # toggle alpha long script.click(1500, 875) # confirm - # === Keyboard modal (SSH keys button in developer panel) === - script.click(1930, 470) # click SSH keys - script.click(1930, 115) # click cancel on keyboard - # === Close settings === script.click(250, 160) # === Onroad === script.set_send(lambda: send_onroad(pm)) script.click(1000, 500) # click onroad to toggle sidebar - - # === Onroad alerts === - # Small alert (normal) - script.set_send(make_alert_setup(pm, AlertSize.small, "Small Alert", "This is a small alert", AlertStatus.normal)) - # Medium alert (userPrompt) - script.set_send(make_alert_setup(pm, AlertSize.mid, "Medium Alert", "This is a medium alert", AlertStatus.userPrompt)) - # Full alert (critical) - script.set_send(make_alert_setup(pm, AlertSize.full, "DISENGAGE IMMEDIATELY", "Driver Distracted", AlertStatus.critical)) - # Full alert multiline - script.set_send(make_alert_setup(pm, AlertSize.full, "Reverse\nGear", "", AlertStatus.normal)) - # Full alert long text - script.set_send(make_alert_setup(pm, AlertSize.full, "TAKE CONTROL IMMEDIATELY", "Calibration Invalid: Remount Device & Recalibrate", AlertStatus.userPrompt)) + test_onroad_alerts(script, pm) # End script.end() diff --git a/selfdrive/ui/translations/app.pot b/selfdrive/ui/translations/app.pot index abb6940a54..468ff35fd9 100644 --- a/selfdrive/ui/translations/app.pot +++ b/selfdrive/ui/translations/app.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-23 00:51-0700\n" +"POT-Creation-Date: 2026-03-09 14:21+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,1113 +18,1018 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 -#, python-format -msgid "OK" -msgstr "" - -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 -#, python-format -msgid "Cancel" -msgstr "" - -#: system/ui/widgets/option_dialog.py:36 -#, python-format -msgid "Select" -msgstr "" - -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "" -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:201 +#, python-format +msgid "Enter APN" +msgstr "" + +#: system/ui/widgets/network.py:201 +#, python-format +msgid "leave blank for automatic configuration" +msgstr "" + +#: system/ui/widgets/network.py:243 +#, python-format +msgid "Enter SSID" +msgstr "" + +#: system/ui/widgets/network.py:257 +#, python-format +msgid "Enter new tethering password" +msgstr "" + +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:136 +#, python-format +msgid "APN Setting" +msgstr "" + +#: system/ui/widgets/network.py:141 +#, python-format +msgid "Wi-Fi Network Metered" +msgstr "" + +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 +#, python-format +msgid "Enter password" +msgstr "" + +#: system/ui/widgets/network.py:316 +#, python-format +msgid "Scanning Wi-Fi networks..." +msgstr "" + +#: system/ui/widgets/network.py:376 +#, python-format +msgid "CONNECTING..." +msgstr "" + +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 +#, python-format +msgid "Forget" +msgstr "" + +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "" #: system/ui/widgets/network.py:139 #, python-format -msgid "APN Setting" -msgstr "" - -#: system/ui/widgets/network.py:142 -#, python-format msgid "default" msgstr "" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "" -#: system/ui/widgets/network.py:144 -#, python-format -msgid "Wi-Fi Network Metered" -msgstr "" - -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "" -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "" -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "" -#: system/ui/widgets/network.py:204 -#, python-format -msgid "Enter APN" -msgstr "" - -#: system/ui/widgets/network.py:204 -#, python-format -msgid "leave blank for automatic configuration" -msgstr "" - -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 -#, python-format -msgid "Enter password" -msgstr "" - -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 -#, python-format -msgid "for \"{}\"" -msgstr "" - -#: system/ui/widgets/network.py:241 -#, python-format -msgid "Enter SSID" -msgstr "" - -#: system/ui/widgets/network.py:254 -#, python-format -msgid "Enter new tethering password" -msgstr "" - -#: system/ui/widgets/network.py:310 -#, python-format -msgid "Scanning Wi-Fi networks..." -msgstr "" - -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "" -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format -msgid "Forget" +msgid "Cancel" msgstr "" -#: system/ui/widgets/network.py:319 -#, python-format -msgid "Forget Wi-Fi Network \"{}\"?" -msgstr "" - -#: system/ui/widgets/network.py:369 -#, python-format -msgid "CONNECTING..." -msgstr "" - -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 +#, python-format +msgid "for \"{}\"" +msgstr "" + +#: system/ui/widgets/network.py:327 +#, python-format +msgid "Forget Wi-Fi Network \"{}\"?" +msgstr "" + +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#, python-format +msgid "OK" +msgstr "" + +#: system/ui/widgets/option_dialog.py:37 +#, python-format +msgid "Select" +msgstr "" + +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "" -#: selfdrive/ui/widgets/pairing_dialog.py:103 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "" -#: selfdrive/ui/widgets/pairing_dialog.py:128 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "" -#: selfdrive/ui/widgets/pairing_dialog.py:129 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "" -#: selfdrive/ui/widgets/pairing_dialog.py:130 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "" -#: selfdrive/ui/widgets/pairing_dialog.py:161 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "" -#: selfdrive/ui/widgets/ssh_key.py:29 -msgid "LOADING" -msgstr "" - -#: selfdrive/ui/widgets/ssh_key.py:30 -msgid "ADD" -msgstr "" - -#: selfdrive/ui/widgets/ssh_key.py:31 -msgid "REMOVE" -msgstr "" - -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "Enter your GitHub username" +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." msgstr "" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "No SSH keys found" +msgid "Maximize your training data uploads to improve openpilot's driving models." msgstr "" -#: selfdrive/ui/widgets/ssh_key.py:123 -#, python-format -msgid "Request timed out" -msgstr "" - -#: selfdrive/ui/widgets/ssh_key.py:126 -#, python-format -msgid "No SSH keys found for user '{}'" -msgstr "" - -#: selfdrive/ui/widgets/prime.py:33 -#, python-format -msgid "Upgrade Now" -msgstr "" - -#: selfdrive/ui/widgets/prime.py:38 -#, python-format -msgid "Become a comma prime member at connect.comma.ai" -msgstr "" - -#: selfdrive/ui/widgets/prime.py:44 -#, python-format -msgid "PRIME FEATURES:" -msgstr "" - -#: selfdrive/ui/widgets/prime.py:47 -#, python-format -msgid "Remote access" -msgstr "" - -#: selfdrive/ui/widgets/prime.py:47 -#, python-format -msgid "24/7 LTE connectivity" -msgstr "" - -#: selfdrive/ui/widgets/prime.py:47 -#, python-format -msgid "1 year of drive storage" -msgstr "" - -#: selfdrive/ui/widgets/prime.py:47 -#, python-format -msgid "Remote snapshots" -msgstr "" - -#: selfdrive/ui/widgets/prime.py:62 -#, python-format -msgid "✓ SUBSCRIBED" -msgstr "" - -#: selfdrive/ui/widgets/prime.py:63 -#, python-format -msgid "comma prime" -msgstr "" - -#: selfdrive/ui/widgets/exp_mode_button.py:50 -#, python-format -msgid "EXPERIMENTAL MODE ON" -msgstr "" - -#: selfdrive/ui/widgets/exp_mode_button.py:50 -#, python-format -msgid "CHILL MODE ON" -msgstr "" - -#: selfdrive/ui/widgets/offroad_alerts.py:104 -#, python-format -msgid "Close" -msgstr "" - -#: selfdrive/ui/widgets/offroad_alerts.py:106 -#, python-format -msgid "Snooze Update" -msgstr "" - -#: selfdrive/ui/widgets/offroad_alerts.py:109 -#, python-format -msgid "Acknowledge Excessive Actuation" -msgstr "" - -#: selfdrive/ui/widgets/offroad_alerts.py:112 -#, python-format -msgid "Reboot and Update" -msgstr "" - -#: selfdrive/ui/widgets/offroad_alerts.py:320 -#, python-format -msgid "No release notes available." -msgstr "" - -#: selfdrive/ui/widgets/setup.py:19 -#, python-format -msgid "Pair device" -msgstr "" - -#: selfdrive/ui/widgets/setup.py:20 -#, python-format -msgid "Open" -msgstr "" - -#: selfdrive/ui/widgets/setup.py:22 -#, python-format -msgid "🔥 Firehose Mode 🔥" -msgstr "" - -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "" -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." +msgid "Pair device" msgstr "" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." +msgid "Open" msgstr "" -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:21 +#, python-format +msgid "🔥 Firehose Mode 🔥" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 +msgid "LOADING" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 +msgid "ADD" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 +msgid "REMOVE" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 +#, python-format +msgid "Enter your GitHub username" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 +#, python-format +msgid "Request timed out" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 +#, python-format +msgid "No SSH keys found" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 +#, python-format +msgid "No SSH keys found for user '{}'" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:33 +#, python-format +msgid "Upgrade Now" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:44 +#, python-format +msgid "PRIME FEATURES:" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "Remote access" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "24/7 LTE connectivity" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "1 year of drive storage" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "Remote snapshots" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:62 +#, python-format +msgid "✓ SUBSCRIBED" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:63 +#, python-format +msgid "comma prime" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:38 +#, python-format +msgid "Become a comma prime member at connect.comma.ai" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 +#, python-format +msgid "EXPERIMENTAL MODE ON" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 +#, python-format +msgid "CHILL MODE ON" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 +#, python-format +msgid "Close" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 +#, python-format +msgid "Snooze Update" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 +#, python-format +msgid "Acknowledge Excessive Actuation" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 +#, python-format +msgid "Reboot and Update" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 +#, python-format +msgid "No release notes available." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 +msgid "--" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 +msgid "Wi-Fi" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 +msgid "ETH" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 +msgid "2G" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 +msgid "3G" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 +msgid "LTE" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 +msgid "5G" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 +msgid "TEMP" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +msgid "GOOD" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +msgid "VEHICLE" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +msgid "ONLINE" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +msgid "OFFLINE" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 +msgid "Unknown" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 +msgid "NO" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 +msgid "PANDA" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 +msgid "HIGH" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 +msgid "ERROR" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "" msgstr[1] "" -#: selfdrive/ui/layouts/sidebar.py:43 -msgid "--" -msgstr "" - -#: selfdrive/ui/layouts/sidebar.py:44 -msgid "Wi-Fi" -msgstr "" - -#: selfdrive/ui/layouts/sidebar.py:45 -msgid "ETH" -msgstr "" - -#: selfdrive/ui/layouts/sidebar.py:46 -msgid "2G" -msgstr "" - -#: selfdrive/ui/layouts/sidebar.py:47 -msgid "3G" -msgstr "" - -#: selfdrive/ui/layouts/sidebar.py:48 -msgid "LTE" -msgstr "" - -#: selfdrive/ui/layouts/sidebar.py:49 -msgid "5G" -msgstr "" - -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 -msgid "TEMP" -msgstr "" - -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -msgid "GOOD" -msgstr "" - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 -msgid "VEHICLE" -msgstr "" - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 -msgid "ONLINE" -msgstr "" - -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 -msgid "OFFLINE" -msgstr "" - -#: selfdrive/ui/layouts/sidebar.py:117 -msgid "Unknown" -msgstr "" - -#: selfdrive/ui/layouts/sidebar.py:129 -msgid "HIGH" -msgstr "" - -#: selfdrive/ui/layouts/sidebar.py:138 -msgid "ERROR" -msgstr "" - -#: selfdrive/ui/layouts/sidebar.py:142 -msgid "NO" -msgstr "" - -#: selfdrive/ui/layouts/sidebar.py:142 -msgid "PANDA" -msgstr "" - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "" -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." msgstr "" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "" -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +msgid "When enabled, pressing the accelerator pedal will disengage openpilot." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +msgid "Enable driver monitoring even when openpilot is not engaged." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +msgid "Display speed in km/h instead of mph." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 +#, python-format +msgid "Driving Personality" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 +#, python-format +msgid "Changing this setting will restart openpilot if the car is powered on." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 +#, python-format +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 +#, python-format +msgid "Enable" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 +#, python-format +msgid "Enable openpilot" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 +#, python-format +msgid "Experimental Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 +#, python-format +msgid "Disengage on Accelerator Pedal" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 +#, python-format +msgid "Enable Lane Departure Warnings" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 +#, python-format +msgid "Always-On Driver Monitoring" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 +#, python-format +msgid "Record and Upload Driver Camera" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 +#, python-format +msgid "Record and Upload Microphone Audio" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 +#, python-format +msgid "Use Metric System" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 +#, python-format +msgid "openpilot longitudinal control may come in a future update." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Aggressive" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Standard" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Relaxed" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 +#, python-format +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 +msgid "Device" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 +msgid "Network" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 +msgid "Toggles" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 +msgid "Software" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 +msgid "Firehose" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 +msgid "Developer" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 +msgid "Review the rules, features, and limitations of openpilot" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 +#, python-format +msgid "Select a language" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 +#, python-format +msgid "Are you sure you want to reset calibration?" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 +#, python-format +msgid "Reset" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 +#, python-format +msgid "

Steering lag calibration is complete." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#, python-format +msgid "Are you sure you want to reboot?" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 +#, python-format +msgid "Reboot" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#, python-format +msgid "Are you sure you want to power off?" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 +#, python-format +msgid "Power Off" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 +#, python-format +msgid "Pair Device" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 +#, python-format +msgid "PAIR" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 +#, python-format +msgid "Reset Calibration" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 +#, python-format +msgid "RESET" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#, python-format +msgid "Dongle ID" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 +#, python-format +msgid "Serial" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 +#, python-format +msgid "Driver Camera" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 +#, python-format +msgid "PREVIEW" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 +#, python-format +msgid "Review Training Guide" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 +#, python-format +msgid "REVIEW" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 +#, python-format +msgid "Regulatory" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 +#, python-format +msgid "VIEW" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 +#, python-format +msgid "Change Language" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 +#, python-format +msgid "CHANGE" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 +#, python-format +msgid "Disengage to Reset Calibration" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 +#, python-format +msgid "

Steering lag calibration is {}% complete." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 +#, python-format +msgid "Disengage to Reboot" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 +#, python-format +msgid "Disengage to Power Off" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 +#, python-format +msgid "N/A" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 +#, python-format +msgid " Steering torque response calibration is complete." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 +#, python-format +msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 +#, python-format +msgid "down" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 +#, python-format +msgid "up" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 +#, python-format +msgid "left" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 +#, python-format +msgid "right" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 +#, python-format +msgid " Steering torque response calibration is {}% complete." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "" -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" - -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" - -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "" msgstr[1] "" -#: selfdrive/ui/layouts/settings/firehose.py:138 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 #, python-format -msgid "ACTIVE" +msgid "checking..." msgstr "" -#: selfdrive/ui/layouts/settings/firehose.py:140 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 #, python-format -msgid "INACTIVE: connect to an unmetered network" +msgid "downloading..." msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" - -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" - -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" - -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 #, python-format -msgid "Enable ADB" +msgid "finalizing update..." msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:48 -#, python-format -msgid "Enable SSH" -msgstr "" - -#: selfdrive/ui/layouts/settings/developer.py:53 -#, python-format -msgid "SSH Keys" -msgstr "" - -#: selfdrive/ui/layouts/settings/developer.py:56 -#, python-format -msgid "Joystick Debug Mode" -msgstr "" - -#: selfdrive/ui/layouts/settings/developer.py:64 -#, python-format -msgid "Longitudinal Maneuver Mode" -msgstr "" - -#: selfdrive/ui/layouts/settings/developer.py:71 -#, python-format -msgid "openpilot Longitudinal Control (Alpha)" -msgstr "" - -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 -#, python-format -msgid "Enable" -msgstr "" - -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 +#, python-format +msgid "CHECK" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#, python-format +msgid "Are you sure you want to uninstall?" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 +#, python-format +msgid "Uninstall" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 +#, python-format +msgid "Select a branch" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "" msgstr[1] "" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "" msgstr[1] "" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "" msgstr[1] "" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 -#, python-format -msgid "CHECK" -msgstr "" - -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 -#, python-format -msgid "Uninstall" -msgstr "" - -#: selfdrive/ui/layouts/settings/software.py:72 -#, python-format -msgid "UNINSTALL" -msgstr "" - -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:109 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 +#, python-format +msgid "UNINSTALL" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:110 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:115 -#, python-format -msgid "up to date, last checked {}" -msgstr "" - -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format -msgid "Are you sure you want to uninstall?" +msgid "up to date, last checked {}" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:183 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format -msgid "Select a branch" +msgid "Enable ADB" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:27 -msgid "Review the rules, features, and limitations of openpilot" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format -msgid "Pair Device" +msgid "Enable SSH" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format -msgid "PAIR" +msgid "SSH Keys" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format -msgid "Reset Calibration" +msgid "Joystick Debug Mode" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format -msgid "RESET" +msgid "Longitudinal Maneuver Mode" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format -msgid "Reboot" +msgid "openpilot Longitudinal Control (Alpha)" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 #, python-format -msgid "Power Off" +msgid "UI Debug Mode" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:59 -#, python-format -msgid "Dongle ID" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 -#, python-format -msgid "N/A" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:60 -#, python-format -msgid "Serial" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:62 -#, python-format -msgid "Driver Camera" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:62 -#, python-format -msgid "PREVIEW" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:65 -#, python-format -msgid "Review Training Guide" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:65 -#, python-format -msgid "REVIEW" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:67 -#, python-format -msgid "Regulatory" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:67 -#, python-format -msgid "VIEW" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:68 -#, python-format -msgid "Change Language" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:68 -#, python-format -msgid "CHANGE" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:91 -#, python-format -msgid "Select a language" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:103 -#, python-format -msgid "Disengage to Reset Calibration" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:119 -#, python-format -msgid "Are you sure you want to reset calibration?" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:119 -#, python-format -msgid "Reset" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:133 -#, python-format -msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:133 -#, python-format -msgid "down" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:133 -#, python-format -msgid "up" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:134 -#, python-format -msgid "left" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:134 -#, python-format -msgid "right" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:146 -#, python-format -msgid "

Steering lag calibration is {}% complete." -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:148 -#, python-format -msgid "

Steering lag calibration is complete." -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:158 -#, python-format -msgid " Steering torque response calibration is {}% complete." -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:160 -#, python-format -msgid " Steering torque response calibration is complete." -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:172 -#, python-format -msgid "Disengage to Reboot" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:175 -#, python-format -msgid "Are you sure you want to reboot?" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:184 -#, python-format -msgid "Disengage to Power Off" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:187 -#, python-format -msgid "Are you sure you want to power off?" -msgstr "" - -#: selfdrive/ui/layouts/settings/settings.py:62 -msgid "Device" -msgstr "" - -#: selfdrive/ui/layouts/settings/settings.py:63 -msgid "Network" -msgstr "" - -#: selfdrive/ui/layouts/settings/settings.py:64 -msgid "Toggles" -msgstr "" - -#: selfdrive/ui/layouts/settings/settings.py:65 -msgid "Software" -msgstr "" - -#: selfdrive/ui/layouts/settings/settings.py:66 -msgid "Firehose" -msgstr "" - -#: selfdrive/ui/layouts/settings/settings.py:67 -msgid "Developer" -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:20 -msgid "When enabled, pressing the accelerator pedal will disengage openpilot." -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:30 -msgid "Enable driver monitoring even when openpilot is not engaged." -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:32 -msgid "Display speed in km/h instead of mph." -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:46 -#, python-format -msgid "Enable openpilot" -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:52 -#, python-format -msgid "Experimental Mode" -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:58 -#, python-format -msgid "Disengage on Accelerator Pedal" -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:64 -#, python-format -msgid "Enable Lane Departure Warnings" -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:70 -#, python-format -msgid "Always-On Driver Monitoring" -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:76 -#, python-format -msgid "Record and Upload Driver Camera" -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:82 -#, python-format -msgid "Record and Upload Microphone Audio" -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:88 -#, python-format -msgid "Use Metric System" -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:96 -#, python-format -msgid "Driving Personality" -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format -msgid "Aggressive" -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format -msgid "Standard" -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format -msgid "Relaxed" -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:125 -#, python-format -msgid "Changing this setting will restart openpilot if the car is powered on." -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:181 -#, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:183 -#, python-format -msgid "openpilot longitudinal control may come in a future update." -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:189 -#, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." -msgstr "" - -#: selfdrive/ui/onroad/hud_renderer.py:148 -#, python-format -msgid "MAX" -msgstr "" - -#: selfdrive/ui/onroad/hud_renderer.py:177 -#, python-format -msgid "km/h" -msgstr "" - -#: selfdrive/ui/onroad/hud_renderer.py:177 -#, python-format -msgid "mph" -msgstr "" - -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 -#, python-format -msgid "camera starting" -msgstr "" - -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "" -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "" + +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 +#, python-format +msgid "camera starting" +msgstr "" + +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 +#, python-format +msgid "MAX" +msgstr "" + +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#, python-format +msgid "km/h" +msgstr "" + +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#, python-format +msgid "mph" +msgstr "" + diff --git a/selfdrive/ui/translations/app_de.po b/selfdrive/ui/translations/app_de.po index f32c27a9ef..9888bb718e 100644 --- a/selfdrive/ui/translations/app_de.po +++ b/selfdrive/ui/translations/app_de.po @@ -17,1205 +17,1018 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: selfdrive/ui/layouts/settings/device.py:160 +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 #, python-format msgid " Steering torque response calibration is complete." msgstr " Die Lenkmoment-Reaktionskalibrierung ist abgeschlossen." -#: selfdrive/ui/layouts/settings/device.py:158 +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " Die Lenkmoment-Reaktionskalibrierung ist zu {}% abgeschlossen." -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Ihr Gerät ist um {:.1f}° {} und {:.1f}° {} ausgerichtet." -#: selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "1 Jahr Fahrtdatenspeicherung" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "24/7 LTE‑Verbindung" -#: selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" -"WARNUNG: Die Längsregelung von openpilot befindet sich für dieses " -"Fahrzeug in der Alpha-Phase und deaktiviert das automatische Notbremssystem " -"(AEB).

Auf diesem Fahrzeug verwendet openpilot standardmäßig den " -"integrierten ACC statt der openpilot-Längsregelung. Aktivieren Sie dies, um " -"auf die openpilot-Längsregelung umzuschalten. Das Aktivieren des " -"Experimentalmodus wird empfohlen, wenn Sie die openpilot-Längsregelung " -"(Alpha) aktivieren." - -#: selfdrive/ui/layouts/settings/device.py:148 +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 #, python-format msgid "

Steering lag calibration is complete." msgstr "

Kalibrierung der Lenkverzögerung abgeschlossen." -#: selfdrive/ui/layouts/settings/device.py:146 +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "

Kalibrierung der Lenkverzögerung zu {}% abgeschlossen." -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "AKTIV" - -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" -"ADB (Android Debug Bridge) ermöglicht die Verbindung mit Ihrem Gerät über " -"USB oder über das Netzwerk. Siehe https://docs.comma.ai/how-to/connect-to-" -"comma für weitere Informationen." - -#: selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "HINZUFÜGEN" -#: system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:136 #, python-format msgid "APN Setting" msgstr "APN‑Einstellung" -#: selfdrive/ui/widgets/offroad_alerts.py:109 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "Übermäßige Betätigung bestätigen" -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "Erweitert" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "Aggressiv" -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "Zustimmen" -#: selfdrive/ui/layouts/settings/toggles.py:70 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "Immer aktive Fahrerüberwachung" -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "" -"Eine Alpha-Version der openpilot-Längsregelung kann zusammen mit dem " -"Experimentalmodus auf Nicht-Release-Zweigen getestet werden." - -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 #, python-format msgid "Are you sure you want to power off?" msgstr "Sind Sie sicher, dass Sie ausschalten möchten?" -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 #, python-format msgid "Are you sure you want to reboot?" msgstr "Sind Sie sicher, dass Sie neu starten möchten?" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "Sind Sie sicher, dass Sie die Kalibrierung zurücksetzen möchten?" -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 #, python-format msgid "Are you sure you want to uninstall?" msgstr "Sind Sie sicher, dass Sie deinstallieren möchten?" -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "Zurück" -#: selfdrive/ui/widgets/prime.py:38 +#: openpilot/selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "Werden Sie comma prime Mitglied auf connect.comma.ai" -#: selfdrive/ui/widgets/pairing_dialog.py:130 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" -msgstr "" -"Fügen Sie connect.comma.ai Ihrem Startbildschirm hinzu, um es wie eine App " -"zu verwenden" +msgstr "Fügen Sie connect.comma.ai Ihrem Startbildschirm hinzu, um es wie eine App zu verwenden" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "CHANGE" msgstr "ÄNDERN" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 #, python-format msgid "CHECK" msgstr "PRÜFEN" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "CHILL MODE ON" msgstr "CHILL‑MODUS AKTIV" -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "VERBINDUNG" -#: system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:376 #, python-format msgid "CONNECTING..." msgstr "VERBINDUNG" -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format msgid "Cancel" msgstr "Abbrechen" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "Getaktete Mobilfunkverbindung" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "Change Language" msgstr "Sprache ändern" -#: selfdrive/ui/layouts/settings/toggles.py:125 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." -msgstr "" -" Durch Ändern dieser Einstellung wird openpilot neu gestartet, wenn das Auto " -"eingeschaltet ist." +msgstr " Durch Ändern dieser Einstellung wird openpilot neu gestartet, wenn das Auto eingeschaltet ist." -#: selfdrive/ui/widgets/pairing_dialog.py:129 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "Klicken Sie auf \"add new device\" und scannen Sie den QR‑Code rechts" -#: selfdrive/ui/widgets/offroad_alerts.py:104 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "Schließen" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "Aktuelle Version" -#: selfdrive/ui/layouts/settings/software.py:110 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "HERUNTERLADEN" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "Ablehnen" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "Ablehnen, openpilot deinstallieren" -#: selfdrive/ui/layouts/settings/settings.py:67 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 msgid "Developer" msgstr "Entwickler" -#: selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 msgid "Device" msgstr "Gerät" -#: selfdrive/ui/layouts/settings/toggles.py:58 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "Beim Gaspedal deaktivieren" -#: selfdrive/ui/layouts/settings/device.py:184 +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 #, python-format msgid "Disengage to Power Off" msgstr "Zum Ausschalten deaktivieren" -#: selfdrive/ui/layouts/settings/device.py:172 +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 #, python-format msgid "Disengage to Reboot" msgstr "Zum Neustart deaktivieren" -#: selfdrive/ui/layouts/settings/device.py:103 +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 #, python-format msgid "Disengage to Reset Calibration" msgstr "Zum Zurücksetzen der Kalibrierung deaktivieren" -#: selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "Geschwindigkeit in km/h statt mph anzeigen." -#: selfdrive/ui/layouts/settings/device.py:59 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 #, python-format msgid "Dongle ID" msgstr "Dongle-ID" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "Herunterladen" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Driver Camera" msgstr "Fahrerkamera" -#: selfdrive/ui/layouts/settings/toggles.py:96 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "Fahrstil" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "BEARBEITEN" -#: selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "FEHLER" -#: selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "EXPERIMENTALMODUS AKTIV" -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 #, python-format msgid "Enable" msgstr "Aktivieren" -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "ADB aktivieren" -#: selfdrive/ui/layouts/settings/toggles.py:64 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "Spurverlassenswarnungen aktivieren" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "openpilot aktivieren" -#: selfdrive/ui/layouts/settings/developer.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "SSH aktivieren" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "Spurverlassenswarnungen aktivieren" -#: selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "Fahrerüberwachung auch aktivieren, wenn openpilot nicht aktiv ist." -#: selfdrive/ui/layouts/settings/toggles.py:46 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "openpilot aktivieren" -#: selfdrive/ui/layouts/settings/toggles.py:189 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 #, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." -msgstr "" -"Den Schalter für die openpilot-Längsregelung (Alpha) aktivieren, um den " -"Experimentalmodus zu erlauben." +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." +msgstr "Den Schalter für die openpilot-Längsregelung (Alpha) aktivieren, um den Experimentalmodus zu erlauben." -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "Enter APN" msgstr "APN eingeben" -#: system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:243 #, python-format msgid "Enter SSID" msgstr "SSID eingeben" -#: system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:257 #, python-format msgid "Enter new tethering password" msgstr "Neues Tethering‑Passwort eingeben" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 #, python-format msgid "Enter password" msgstr "Passwort eingeben" -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "Geben Sie Ihren GitHub‑Benutzernamen ein" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "Fehler" -#: selfdrive/ui/layouts/settings/toggles.py:52 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "Experimentalmodus" -#: selfdrive/ui/layouts/settings/toggles.py:181 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 #, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." -msgstr "" -"Der Experimentalmodus ist derzeit auf diesem Fahrzeug nicht verfügbar, da " -"der serienmäßige ACC für die Längsregelung verwendet wird." +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." +msgstr "Der Experimentalmodus ist derzeit auf diesem Fahrzeug nicht verfügbar, da der serienmäßige ACC für die Längsregelung verwendet wird." -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "WIRD VERGESSEN..." -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "Einrichtung abschließen" -#: selfdrive/ui/layouts/settings/settings.py:66 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 msgid "Firehose" msgstr "Firehose" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "Firehose‑Modus" -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" -"Für maximale Wirksamkeit bringen Sie Ihr Gerät regelmäßig ins Haus und " -"verbinden es wöchentlich mit einem guten USB‑C‑Adapter und WLAN.\n" -"\n" -"Der Firehose‑Modus kann auch während der Fahrt funktionieren, wenn eine " -"Verbindung zu einem Hotspot oder einer unbegrenzten SIM besteht.\n" -"\n" -"\n" -"Häufig gestellte Fragen\n" -"\n" -"Spielt es eine Rolle, wie oder wo ich fahre? Nein, fahren Sie einfach wie " -"gewöhnlich.\n" -"\n" -"Werden alle meine Segmente im Firehose‑Modus abgeholt? Nein, wir ziehen " -"selektiv eine Teilmenge Ihrer Segmente.\n" -"\n" -"Was ist ein guter USB‑C‑Adapter? Jeder schnelle Telefon‑ oder Laptoplader " -"sollte ausreichen.\n" -"\n" -"Spielt es eine Rolle, welche Software ich verwende? Ja, nur " -"Upstream‑openpilot (und bestimmte Forks) können für das Training verwendet " -"werden." - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 #, python-format msgid "Forget" msgstr "Vergessen" -#: system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:327 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "WLAN‑Netz „{}“ vergessen?" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "GUT" -#: selfdrive/ui/widgets/pairing_dialog.py:128 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "Gehen Sie auf Ihrem Telefon zu https://connect.comma.ai" -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "HOCH" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "Netzwerk" -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "INAKTIV: Mit einem unlimitierten Netzwerk verbinden" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "INSTALLIEREN" -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "IP‑Adresse" -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "Update installieren" -#: selfdrive/ui/layouts/settings/developer.py:56 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "Joystick‑Debugmodus" -#: selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "LADEN" -#: selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: selfdrive/ui/layouts/settings/developer.py:64 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "Längsmanövermodus" -#: selfdrive/ui/onroad/hud_renderer.py:148 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "MAX" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." -msgstr "" -"Maximieren Sie Ihre Trainingsdaten‑Uploads, um die Fahrmodelle von openpilot " -"zu verbessern." +msgid "Maximize your training data uploads to improve openpilot's driving models." +msgstr "Maximieren Sie Ihre Trainingsdaten‑Uploads, um die Fahrmodelle von openpilot zu verbessern." -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "N/A" msgstr "k. A." -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "KEIN" -#: selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 msgid "Network" msgstr "Netzwerk" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 #, python-format msgid "No SSH keys found" msgstr "Keine SSH‑Schlüssel gefunden" -#: selfdrive/ui/widgets/ssh_key.py:126 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 #, python-format msgid "No SSH keys found for user '{}'" msgstr "Keine SSH‑Schlüssel für Benutzer '{username}' gefunden" -#: selfdrive/ui/widgets/offroad_alerts.py:320 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 #, python-format msgid "No release notes available." msgstr "Keine Versionshinweise verfügbar." -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "OFFLINE" -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "OK" -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 msgid "ONLINE" msgstr "ONLINE" -#: selfdrive/ui/widgets/setup.py:20 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Open" msgstr "Öffnen" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "PAIR" msgstr "KOPPELN" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "PREVIEW" msgstr "VORSCHAU" -#: selfdrive/ui/widgets/prime.py:44 +#: openpilot/selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "PRIME‑FUNKTIONEN:" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "Pair Device" msgstr "Gerät koppeln" -#: selfdrive/ui/widgets/setup.py:19 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format msgid "Pair device" msgstr "Gerät koppeln" -#: selfdrive/ui/widgets/pairing_dialog.py:103 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "Koppeln Sie Ihr Gerät mit Ihrem comma‑Konto" -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." -msgstr "" -"Koppeln Sie Ihr Gerät mit comma connect (connect.comma.ai) und lösen Sie Ihr " -"comma‑prime‑Angebot ein." +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." +msgstr "Koppeln Sie Ihr Gerät mit comma connect (connect.comma.ai) und lösen Sie Ihr comma‑prime‑Angebot ein." -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "Bitte mit WLAN verbinden, um das erste Koppeln abzuschließen" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Power Off" msgstr "Ausschalten" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" -msgstr "" -"Vorschau der Fahrer‑Kamera, um sicherzustellen, dass die Fahrerüberwachung " -"gute Sicht hat. (Fahrzeug muss ausgeschaltet sein)" +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "Vorschau der Fahrer‑Kamera, um sicherzustellen, dass die Fahrerüberwachung gute Sicht hat. (Fahrzeug muss ausgeschaltet sein)" -#: selfdrive/ui/widgets/pairing_dialog.py:161 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "QR‑Code‑Fehler" -#: selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "ENTFERNEN" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "RESET" msgstr "ZURÜCKSETZEN" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "REVIEW" msgstr "ANSEHEN" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Reboot" msgstr "Neustart" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "Gerät neu starten" -#: selfdrive/ui/widgets/offroad_alerts.py:112 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "Neustarten und aktualisieren" -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" -"Erhalten Sie Warnungen, um zurück in die Spur zu lenken, wenn Ihr Fahrzeug " -"ohne Blinker über eine erkannte Spurlinie driftet und über 31 mph (50 km/h) " -"fährt." - -#: selfdrive/ui/layouts/settings/toggles.py:76 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "Fahrerkamera aufzeichnen und hochladen" -#: selfdrive/ui/layouts/settings/toggles.py:82 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "Mikrofonton aufzeichnen und hochladen" -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." -msgstr "" -"Mikrofonton während der Fahrt aufzeichnen und speichern. Die Audiospur wird " -"im Dashcam‑Video in comma connect enthalten sein." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." +msgstr "Mikrofonton während der Fahrt aufzeichnen und speichern. Die Audiospur wird im Dashcam‑Video in comma connect enthalten sein." -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Regulatory" msgstr "Vorschriften" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "Entspannt" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "Fernzugriff" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "Remote‑Schnappschüsse" -#: selfdrive/ui/widgets/ssh_key.py:123 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 #, python-format msgid "Request timed out" msgstr "Zeitüberschreitung bei der Anfrage" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Reset" msgstr "Zurücksetzen" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "Reset Calibration" msgstr "Kalibrierung zurücksetzen" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "Review Training Guide" msgstr "Trainingsanleitung ansehen" -#: selfdrive/ui/layouts/settings/device.py:27 +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 msgid "Review the rules, features, and limitations of openpilot" -msgstr "" -"Überprüfen Sie die Regeln, Funktionen und Einschränkungen von openpilot" +msgstr "Überprüfen Sie die Regeln, Funktionen und Einschränkungen von openpilot" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:53 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "SSH‑Schlüssel" -#: system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:316 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "WLAN‑Netzwerke werden gesucht..." -#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:37 #, python-format msgid "Select" msgstr "Auswählen" -#: selfdrive/ui/layouts/settings/software.py:183 +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 #, python-format msgid "Select a branch" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:91 +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 #, python-format msgid "Select a language" msgstr "Sprache auswählen" -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "Serial" msgstr "Seriennummer" -#: selfdrive/ui/widgets/offroad_alerts.py:106 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "Update verschieben" -#: selfdrive/ui/layouts/settings/settings.py:65 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 msgid "Software" msgstr "Software" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "Standard" -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" -"Standard wird empfohlen. Im aggressiven Modus folgt openpilot " -"vorausfahrenden Fahrzeugen näher und ist beim Gasgeben und Bremsen " -"aggressiver. Im entspannten Modus bleibt openpilot weiter entfernt. Bei " -"unterstützten Fahrzeugen können Sie mit der Abstandstaste am Lenkrad " -"zwischen diesen Profilen wechseln." - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "System reagiert nicht" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "SOFORT DIE KONTROLLE ÜBERNEHMEN" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "TEMP" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "Tethering‑Passwort" -#: selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 msgid "Toggles" msgstr "Schalter" -#: selfdrive/ui/layouts/settings/software.py:72 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 +#, python-format +msgid "UI Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "UNINSTALL" msgstr "DEINSTALLIEREN" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "UPDATE" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "Uninstall" msgstr "Deinstallieren" -#: selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "Unbekannt" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "Updates werden nur heruntergeladen, wenn das Auto aus ist." -#: selfdrive/ui/widgets/prime.py:33 +#: openpilot/selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "Jetzt abonnieren" -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." -msgstr "" -"Daten von der Fahrer‑Kamera hochladen und den Fahrerüberwachungs‑Algorithmus " -"verbessern." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." +msgstr "Daten von der Fahrer‑Kamera hochladen und den Fahrerüberwachungs‑Algorithmus verbessern." -#: selfdrive/ui/layouts/settings/toggles.py:88 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "Metersystem verwenden" -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" -"Verwenden Sie openpilot für adaptive Geschwindigkeitsregelung und " -"Spurhalteassistenz. Ihre Aufmerksamkeit ist jederzeit erforderlich, um diese " -"Funktion zu nutzen." - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "FAHRZEUG" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "VIEW" msgstr "ANSEHEN" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "Warten auf Start" -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" -"Warnung: Dies gewährt SSH‑Zugriff auf alle öffentlichen Schlüssel in Ihren " -"GitHub‑Einstellungen. Geben Sie niemals einen anderen GitHub‑Benutzernamen " -"als Ihren eigenen ein. Ein comma‑Mitarbeiter wird Sie NIEMALS bitten, seinen " -"GitHub‑Benutzernamen hinzuzufügen." - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "Willkommen bei openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "Wenn aktiviert, deaktiviert das Drücken des Gaspedals openpilot." -#: selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "WLAN" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Wi-Fi Network Metered" msgstr "Getaktetes WLAN‑Netzwerk" -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "Falsches Passwort" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." -msgstr "" -"Sie müssen die Nutzungsbedingungen akzeptieren, um openpilot zu verwenden." +msgstr "Sie müssen die Nutzungsbedingungen akzeptieren, um openpilot zu verwenden." -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." -msgstr "" -"Sie müssen die Nutzungsbedingungen akzeptieren, um openpilot zu verwenden. " -"Lesen Sie die aktuellen Bedingungen unter https://comma.ai/terms, bevor Sie " -"fortfahren." +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." +msgstr "Sie müssen die Nutzungsbedingungen akzeptieren, um openpilot zu verwenden. Lesen Sie die aktuellen Bedingungen unter https://comma.ai/terms, bevor Sie fortfahren." -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 #, python-format msgid "camera starting" msgstr "Kamera startet" -#: selfdrive/ui/widgets/prime.py:63 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "checking..." +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "default" msgstr "Standard" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "down" msgstr "unten" -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "downloading..." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "Überprüfung auf Updates fehlgeschlagen" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 +#, python-format +msgid "finalizing update..." +msgstr "" + +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 #, python-format msgid "for \"{}\"" msgstr "für „{}“" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "leave blank for automatic configuration" msgstr "für automatische Konfiguration leer lassen" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "left" msgstr "links" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "getaktet" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "nie" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "jetzt" -#: selfdrive/ui/layouts/settings/developer.py:71 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "openpilot Längsregelung (Alpha)" -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot nicht verfügbar" -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." -msgstr "" -"openpilot fährt standardmäßig im Chill‑Modus. Der Experimentalmodus " -"aktiviert Funktionen im Alpha‑Status, die für den Chill‑Modus noch nicht " -"bereit sind. Die experimentellen Funktionen sind unten aufgeführt:" -"

End-to‑End‑Längsregelung


Das Fahrmodell steuert Gas und " -"Bremse. openpilot fährt so, wie es einen Menschen einschätzt, einschließlich " -"Anhalten an roten Ampeln und Stoppschildern. Da das Modell die " -"Geschwindigkeit bestimmt, dient die eingestellte Geschwindigkeit nur als " -"Obergrenze. Dies ist eine Alpha‑Funktion; Fehler sind zu erwarten." -"

Neue Fahrvisualisierung


Die Visualisierung wechselt bei " -"niedriger Geschwindigkeit auf die nach vorn gerichtete Weitwinkelkamera, um " -"manche Kurven besser zu zeigen. Das Experimentalmodus‑Logo wird außerdem " -"oben rechts angezeigt." - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" -" Durch Ändern dieser Einstellung wird openpilot neu gestartet, wenn das Auto " -"eingeschaltet ist." - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" -"openpilot lernt das Fahren, indem es Menschen wie Sie beobachtet.\n" -"\n" -"Der Firehose‑Modus ermöglicht es Ihnen, Ihre Trainingsdaten‑Uploads zu " -"maximieren, um die Fahrmodelle von openpilot zu verbessern. Mehr Daten " -"bedeuten größere Modelle – und damit einen besseren Experimentalmodus." - -#: selfdrive/ui/layouts/settings/toggles.py:183 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "Die openpilot‑Längsregelung könnte in einem zukünftigen Update kommen." -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." -msgstr "" -"openpilot erfordert, dass das Gerät innerhalb von 4° nach links oder rechts " -"und innerhalb von 5° nach oben oder 9° nach unten montiert ist." +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." +msgstr "openpilot erfordert, dass das Gerät innerhalb von 4° nach links oder rechts und innerhalb von 5° nach oben oder 9° nach unten montiert ist." -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "right" msgstr "rechts" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "unbegrenzt" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "up" msgstr "oben" -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "Aktuell, zuletzt geprüft: nie" -#: selfdrive/ui/layouts/settings/software.py:115 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format msgid "up to date, last checked {}" msgstr "Aktuell, zuletzt geprüft: {}" -#: selfdrive/ui/layouts/settings/software.py:109 +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "Update verfügbar" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} WARNUNG" msgstr[1] "{} WARNUNGEN" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "vor {} Tag" msgstr[1] "vor {} Tagen" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "vor {} Stunde" msgstr[1] "vor {} Stunden" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "vor {} Minute" msgstr[1] "vor {} Minuten" -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "{} Segment Ihrer Fahrten ist bisher im Trainingsdatensatz." msgstr[1] "{} Segmente Ihrer Fahrten sind bisher im Trainingsdatensatz." -#: selfdrive/ui/widgets/prime.py:62 +#: openpilot/selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ ABONNIERT" -#: selfdrive/ui/widgets/setup.py:22 +#: openpilot/selfdrive/ui/widgets/setup.py:21 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehose‑Modus 🔥" + diff --git a/selfdrive/ui/translations/app_en.po b/selfdrive/ui/translations/app_en.po index 6fbb537aff..3744096226 100644 --- a/selfdrive/ui/translations/app_en.po +++ b/selfdrive/ui/translations/app_en.po @@ -17,1191 +17,1018 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: selfdrive/ui/layouts/settings/device.py:160 +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 #, python-format msgid " Steering torque response calibration is complete." msgstr " Steering torque response calibration is complete." -#: selfdrive/ui/layouts/settings/device.py:158 +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " Steering torque response calibration is {}% complete." -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Your device is pointed {:.1f}° {} and {:.1f}° {}." -#: selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "1 year of drive storage" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "24/7 LTE connectivity" -#: selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." - -#: selfdrive/ui/layouts/settings/device.py:148 +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 #, python-format msgid "

Steering lag calibration is complete." msgstr "

Steering lag calibration is complete." -#: selfdrive/ui/layouts/settings/device.py:146 +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "

Steering lag calibration is {}% complete." -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "ACTIVE" - -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." - -#: selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "ADD" -#: system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:136 #, python-format msgid "APN Setting" msgstr "APN Setting" -#: selfdrive/ui/widgets/offroad_alerts.py:109 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "Acknowledge Excessive Actuation" -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "Advanced" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "Aggressive" -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "Agree" -#: selfdrive/ui/layouts/settings/toggles.py:70 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "Always-On Driver Monitoring" -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." - -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 #, python-format msgid "Are you sure you want to power off?" msgstr "Are you sure you want to power off?" -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 #, python-format msgid "Are you sure you want to reboot?" msgstr "Are you sure you want to reboot?" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "Are you sure you want to reset calibration?" -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 #, python-format msgid "Are you sure you want to uninstall?" msgstr "Are you sure you want to uninstall?" -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "Back" -#: selfdrive/ui/widgets/prime.py:38 +#: openpilot/selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "Become a comma prime member at connect.comma.ai" -#: selfdrive/ui/widgets/pairing_dialog.py:130 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "Bookmark connect.comma.ai to your home screen to use it like an app" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "CHANGE" msgstr "CHANGE" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 #, python-format msgid "CHECK" msgstr "CHECK" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "CHILL MODE ON" msgstr "CHILL MODE ON" -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "CONNECT" -#: system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:376 #, python-format msgid "CONNECTING..." msgstr "CONNECTING..." -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format msgid "Cancel" msgstr "Cancel" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "Cellular Metered" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "Change Language" msgstr "Change Language" -#: selfdrive/ui/layouts/settings/toggles.py:125 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "Changing this setting will restart openpilot if the car is powered on." -#: selfdrive/ui/widgets/pairing_dialog.py:129 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "Click \"add new device\" and scan the QR code on the right" -#: selfdrive/ui/widgets/offroad_alerts.py:104 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "Close" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "Current Version" -#: selfdrive/ui/layouts/settings/software.py:110 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "DOWNLOAD" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "Decline" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "Decline, uninstall openpilot" -#: selfdrive/ui/layouts/settings/settings.py:67 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 msgid "Developer" msgstr "Developer" -#: selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 msgid "Device" msgstr "Device" -#: selfdrive/ui/layouts/settings/toggles.py:58 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "Disengage on Accelerator Pedal" -#: selfdrive/ui/layouts/settings/device.py:184 +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 #, python-format msgid "Disengage to Power Off" msgstr "Disengage to Power Off" -#: selfdrive/ui/layouts/settings/device.py:172 +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 #, python-format msgid "Disengage to Reboot" msgstr "Disengage to Reboot" -#: selfdrive/ui/layouts/settings/device.py:103 +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 #, python-format msgid "Disengage to Reset Calibration" msgstr "Disengage to Reset Calibration" -#: selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "Display speed in km/h instead of mph." -#: selfdrive/ui/layouts/settings/device.py:59 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 #, python-format msgid "Dongle ID" msgstr "Dongle ID" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "Download" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Driver Camera" msgstr "Driver Camera" -#: selfdrive/ui/layouts/settings/toggles.py:96 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "Driving Personality" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "EDIT" -#: selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "ERROR" -#: selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "EXPERIMENTAL MODE ON" -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 #, python-format msgid "Enable" msgstr "Enable" -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "Enable ADB" -#: selfdrive/ui/layouts/settings/toggles.py:64 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "Enable Lane Departure Warnings" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "Enable Roaming" -#: selfdrive/ui/layouts/settings/developer.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "Enable SSH" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "Enable Tethering" -#: selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "Enable driver monitoring even when openpilot is not engaged." -#: selfdrive/ui/layouts/settings/toggles.py:46 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "Enable openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:189 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 #, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." -msgstr "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." +msgstr "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "Enter APN" msgstr "Enter APN" -#: system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:243 #, python-format msgid "Enter SSID" msgstr "Enter SSID" -#: system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:257 #, python-format msgid "Enter new tethering password" msgstr "Enter new tethering password" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 #, python-format msgid "Enter password" msgstr "Enter password" -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "Enter your GitHub username" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "Error" -#: selfdrive/ui/layouts/settings/toggles.py:52 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "Experimental Mode" -#: selfdrive/ui/layouts/settings/toggles.py:181 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 #, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." -msgstr "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." +msgstr "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "FORGETTING..." -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "Finish Setup" -#: selfdrive/ui/layouts/settings/settings.py:66 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 msgid "Firehose" msgstr "Firehose" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "Firehose Mode" -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 #, python-format msgid "Forget" msgstr "Forget" -#: system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:327 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "Forget Wi-Fi Network \"{}\"?" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "GOOD" -#: selfdrive/ui/widgets/pairing_dialog.py:128 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "Go to https://connect.comma.ai on your phone" -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "HIGH" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "Hidden Network" -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "INACTIVE: connect to an unmetered network" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "INSTALL" -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "IP Address" -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "Install Update" -#: selfdrive/ui/layouts/settings/developer.py:56 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "Joystick Debug Mode" -#: selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "LOADING" -#: selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: selfdrive/ui/layouts/settings/developer.py:64 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "Longitudinal Maneuver Mode" -#: selfdrive/ui/onroad/hud_renderer.py:148 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "MAX" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." -msgstr "" -"Maximize your training data uploads to improve openpilot's driving models." +msgid "Maximize your training data uploads to improve openpilot's driving models." +msgstr "Maximize your training data uploads to improve openpilot's driving models." -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "N/A" msgstr "N/A" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "NO" -#: selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 msgid "Network" msgstr "Network" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 #, python-format msgid "No SSH keys found" msgstr "No SSH keys found" -#: selfdrive/ui/widgets/ssh_key.py:126 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 #, python-format msgid "No SSH keys found for user '{}'" msgstr "No SSH keys found for user '{}'" -#: selfdrive/ui/widgets/offroad_alerts.py:320 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 #, python-format msgid "No release notes available." msgstr "No release notes available." -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "OFFLINE" -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "OK" -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 msgid "ONLINE" msgstr "ONLINE" -#: selfdrive/ui/widgets/setup.py:20 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Open" msgstr "Open" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "PAIR" msgstr "PAIR" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "PREVIEW" msgstr "PREVIEW" -#: selfdrive/ui/widgets/prime.py:44 +#: openpilot/selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "PRIME FEATURES:" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "Pair Device" msgstr "Pair Device" -#: selfdrive/ui/widgets/setup.py:19 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format msgid "Pair device" msgstr "Pair device" -#: selfdrive/ui/widgets/pairing_dialog.py:103 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "Pair your device to your comma account" -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." -msgstr "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." +msgstr "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "Please connect to Wi-Fi to complete initial pairing" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Power Off" msgstr "Power Off" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "Prevent large data uploads when on a metered Wi-Fi connection" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "Prevent large data uploads when on a metered cellular connection" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" -msgstr "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" -#: selfdrive/ui/widgets/pairing_dialog.py:161 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "QR Code Error" -#: selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "REMOVE" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "RESET" msgstr "RESET" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "REVIEW" msgstr "REVIEW" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Reboot" msgstr "Reboot" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "Reboot Device" -#: selfdrive/ui/widgets/offroad_alerts.py:112 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "Reboot and Update" -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." - -#: selfdrive/ui/layouts/settings/toggles.py:76 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "Record and Upload Driver Camera" -#: selfdrive/ui/layouts/settings/toggles.py:82 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "Record and Upload Microphone Audio" -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." -msgstr "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." +msgstr "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Regulatory" msgstr "Regulatory" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "Relaxed" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "Remote access" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "Remote snapshots" -#: selfdrive/ui/widgets/ssh_key.py:123 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 #, python-format msgid "Request timed out" msgstr "Request timed out" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Reset" msgstr "Reset" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "Reset Calibration" msgstr "Reset Calibration" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "Review Training Guide" msgstr "Review Training Guide" -#: selfdrive/ui/layouts/settings/device.py:27 +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 msgid "Review the rules, features, and limitations of openpilot" msgstr "Review the rules, features, and limitations of openpilot" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:53 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "SSH Keys" -#: system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:316 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "Scanning Wi-Fi networks..." -#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:37 #, python-format msgid "Select" msgstr "Select" -#: selfdrive/ui/layouts/settings/software.py:183 +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 #, python-format msgid "Select a branch" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:91 +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 #, python-format msgid "Select a language" msgstr "Select a language" -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "Serial" msgstr "Serial" -#: selfdrive/ui/widgets/offroad_alerts.py:106 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "Snooze Update" -#: selfdrive/ui/layouts/settings/settings.py:65 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 msgid "Software" msgstr "Software" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "Standard" -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "System Unresponsive" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "TAKE CONTROL IMMEDIATELY" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "TEMP" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "Tethering Password" -#: selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 msgid "Toggles" msgstr "Toggles" -#: selfdrive/ui/layouts/settings/software.py:72 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 +#, python-format +msgid "UI Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "UNINSTALL" msgstr "UNINSTALL" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "UPDATE" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "Uninstall" msgstr "Uninstall" -#: selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "Unknown" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "Updates are only downloaded while the car is off." -#: selfdrive/ui/widgets/prime.py:33 +#: openpilot/selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "Upgrade Now" -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." -msgstr "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." +msgstr "Upload data from the driver facing camera and help improve the driver monitoring algorithm." -#: selfdrive/ui/layouts/settings/toggles.py:88 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "Use Metric System" -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "VEHICLE" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "VIEW" msgstr "VIEW" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "Waiting to start" -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "Welcome to openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "When enabled, pressing the accelerator pedal will disengage openpilot." -#: selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi-Fi" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Wi-Fi Network Metered" msgstr "Wi-Fi Network Metered" -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "Wrong password" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "You must accept the Terms and Conditions in order to use openpilot." -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." -msgstr "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." +msgstr "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 #, python-format msgid "camera starting" msgstr "camera starting" -#: selfdrive/ui/widgets/prime.py:63 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "checking..." +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "default" msgstr "default" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "down" msgstr "down" -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "downloading..." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "failed to check for update" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 +#, python-format +msgid "finalizing update..." +msgstr "" + +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 #, python-format msgid "for \"{}\"" msgstr "for \"{}\"" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "leave blank for automatic configuration" msgstr "leave blank for automatic configuration" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "left" msgstr "left" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "metered" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "never" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "now" -#: selfdrive/ui/layouts/settings/developer.py:71 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "openpilot Longitudinal Control (Alpha)" -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot Unavailable" -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." -msgstr "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." - -#: selfdrive/ui/layouts/settings/toggles.py:183 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "openpilot longitudinal control may come in a future update." -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." -msgstr "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." +msgstr "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "right" msgstr "right" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "unmetered" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "up" msgstr "up" -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "up to date, last checked never" -#: selfdrive/ui/layouts/settings/software.py:115 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format msgid "up to date, last checked {}" msgstr "up to date, last checked {}" -#: selfdrive/ui/layouts/settings/software.py:109 +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "update available" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} ALERT" msgstr[1] "{} ALERTS" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{} day ago" msgstr[1] "{} days ago" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{} hour ago" msgstr[1] "{} hours ago" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{} minute ago" msgstr[1] "{} minutes ago" -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "{} segment of your driving is in the training dataset so far." msgstr[1] "{} segments of your driving is in the training dataset so far." -#: selfdrive/ui/widgets/prime.py:62 +#: openpilot/selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ SUBSCRIBED" -#: selfdrive/ui/widgets/setup.py:22 +#: openpilot/selfdrive/ui/widgets/setup.py:21 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehose Mode 🔥" + diff --git a/selfdrive/ui/translations/app_es.po b/selfdrive/ui/translations/app_es.po index 59b9e6dfdb..35188fe2fc 100644 --- a/selfdrive/ui/translations/app_es.po +++ b/selfdrive/ui/translations/app_es.po @@ -17,1209 +17,1018 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: selfdrive/ui/layouts/settings/device.py:160 +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 #, python-format msgid " Steering torque response calibration is complete." msgstr " La calibración de respuesta de par de dirección está completa." -#: selfdrive/ui/layouts/settings/device.py:158 +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " La calibración de respuesta de par de dirección está {}% completa." -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Tu dispositivo está orientado {:.1f}° {} y {:.1f}° {}." -#: selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "1 año de almacenamiento de conducción" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "Conectividad LTE 24/7" -#: selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" -"ADVERTENCIA: el control longitudinal de openpilot está en alpha para este " -"coche y deshabilitará el Frenado Automático de Emergencia (AEB).

En este coche, openpilot usa por defecto el ACC integrado del " -"coche en lugar del control longitudinal de openpilot. Activa esto para " -"cambiar al control longitudinal de openpilot. Se recomienda activar el modo " -"Experimental al habilitar el control longitudinal de openpilot (alpha)." - -#: selfdrive/ui/layouts/settings/device.py:148 +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 #, python-format msgid "

Steering lag calibration is complete." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:146 +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "" -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "ACTIVO" - -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" -"ADB (Android Debug Bridge) permite conectar tu dispositivo por USB o por la " -"red. Consulta https://docs.comma.ai/how-to/connect-to-comma para más " -"información." - -#: selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "AÑADIR" -#: system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:136 #, python-format msgid "APN Setting" msgstr "" -#: selfdrive/ui/widgets/offroad_alerts.py:109 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "Reconocer actuación excesiva" -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "Agresivo" -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "Aceptar" -#: selfdrive/ui/layouts/settings/toggles.py:70 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "Supervisión del conductor siempre activa" -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "" -"Se puede probar una versión alpha del control longitudinal de openpilot, " -"junto con el modo Experimental, en ramas que no son de lanzamiento." - -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 #, python-format msgid "Are you sure you want to power off?" msgstr "¿Seguro que quieres apagar?" -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 #, python-format msgid "Are you sure you want to reboot?" msgstr "¿Seguro que quieres reiniciar?" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "¿Seguro que quieres restablecer la calibración?" -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 #, python-format msgid "Are you sure you want to uninstall?" msgstr "¿Seguro que quieres desinstalar?" -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "Atrás" -#: selfdrive/ui/widgets/prime.py:38 +#: openpilot/selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "Hazte miembro de comma prime en connect.comma.ai" -#: selfdrive/ui/widgets/pairing_dialog.py:130 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" -msgstr "" -"Añade connect.comma.ai a tu pantalla de inicio para usarlo como una app" +msgstr "Añade connect.comma.ai a tu pantalla de inicio para usarlo como una app" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "CHANGE" msgstr "CAMBIAR" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 #, python-format msgid "CHECK" msgstr "COMPROBAR" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "CHILL MODE ON" msgstr "MODO CHILL ACTIVADO" -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "CONECTAR" -#: system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:376 #, python-format msgid "CONNECTING..." msgstr "CONECTAR" -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format msgid "Cancel" msgstr "" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "Change Language" msgstr "Cambiar idioma" -#: selfdrive/ui/layouts/settings/toggles.py:125 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." -msgstr "" -" Cambiar esta configuración reiniciará openpilot si el coche está encendido." +msgstr " Cambiar esta configuración reiniciará openpilot si el coche está encendido." -#: selfdrive/ui/widgets/pairing_dialog.py:129 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" -msgstr "" -"Haz clic en \"añadir nuevo dispositivo\" y escanea el código QR de la derecha" +msgstr "Haz clic en \"añadir nuevo dispositivo\" y escanea el código QR de la derecha" -#: selfdrive/ui/widgets/offroad_alerts.py:104 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "Cerrar" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "Versión actual" -#: selfdrive/ui/layouts/settings/software.py:110 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "DESCARGAR" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "Rechazar" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "Rechazar, desinstalar openpilot" -#: selfdrive/ui/layouts/settings/settings.py:67 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 msgid "Developer" msgstr "Desarrollador" -#: selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 msgid "Device" msgstr "Dispositivo" -#: selfdrive/ui/layouts/settings/toggles.py:58 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "Desactivar con el pedal del acelerador" -#: selfdrive/ui/layouts/settings/device.py:184 +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 #, python-format msgid "Disengage to Power Off" msgstr "Desactivar para apagar" -#: selfdrive/ui/layouts/settings/device.py:172 +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 #, python-format msgid "Disengage to Reboot" msgstr "Desactivar para reiniciar" -#: selfdrive/ui/layouts/settings/device.py:103 +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 #, python-format msgid "Disengage to Reset Calibration" msgstr "Desactivar para restablecer la calibración" -#: selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "Mostrar la velocidad en km/h en lugar de mph." -#: selfdrive/ui/layouts/settings/device.py:59 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 #, python-format msgid "Dongle ID" msgstr "ID del dongle" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "Descargar" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Driver Camera" msgstr "Cámara del conductor" -#: selfdrive/ui/layouts/settings/toggles.py:96 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "Estilo de conducción" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "ERROR" -#: selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "MODO EXPERIMENTAL ACTIVADO" -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 #, python-format msgid "Enable" msgstr "Activar" -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "Activar ADB" -#: selfdrive/ui/layouts/settings/toggles.py:64 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "Activar advertencias de salida de carril" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "Activar openpilot" -#: selfdrive/ui/layouts/settings/developer.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "Activar SSH" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "Activar advertencias de salida de carril" -#: selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." -msgstr "" -"Activar la supervisión del conductor incluso cuando openpilot no esté " -"activado." +msgstr "Activar la supervisión del conductor incluso cuando openpilot no esté activado." -#: selfdrive/ui/layouts/settings/toggles.py:46 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "Activar openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:189 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 #, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." -msgstr "" -"Activa el interruptor de control longitudinal de openpilot (alpha) para " -"permitir el modo Experimental." +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." +msgstr "Activa el interruptor de control longitudinal de openpilot (alpha) para permitir el modo Experimental." -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "Enter APN" msgstr "" -#: system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:243 #, python-format msgid "Enter SSID" msgstr "" -#: system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:257 #, python-format msgid "Enter new tethering password" msgstr "" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 #, python-format msgid "Enter password" msgstr "" -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "Introduce tu nombre de usuario de GitHub" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:52 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "Modo experimental" -#: selfdrive/ui/layouts/settings/toggles.py:181 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 #, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." -msgstr "" -"El modo experimental no está disponible actualmente en este coche, ya que se " -"usa el ACC de fábrica para el control longitudinal." +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." +msgstr "El modo experimental no está disponible actualmente en este coche, ya que se usa el ACC de fábrica para el control longitudinal." -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "" -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "Finalizar configuración" -#: selfdrive/ui/layouts/settings/settings.py:66 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 msgid "Firehose" msgstr "Firehose" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "Modo Firehose" -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" -"Para la máxima efectividad, lleva tu dispositivo al interior y conéctalo " -"semanalmente a un buen adaptador USB‑C y Wi‑Fi.\n" -"\n" -"El Modo Firehose también puede funcionar mientras conduces si está conectado " -"a un hotspot o a una SIM ilimitada.\n" -"\n" -"\n" -"Preguntas frecuentes\n" -"\n" -"¿Importa cómo o dónde conduzco? No, conduce como normalmente lo harías.\n" -"\n" -"¿Se suben todos mis segmentos en el Modo Firehose? No, seleccionamos un " -"subconjunto de tus segmentos.\n" -"\n" -"¿Qué es un buen adaptador USB‑C? Cualquier cargador rápido de teléfono o " -"laptop sirve.\n" -"\n" -"¿Importa qué software ejecuto? Sí, solo openpilot upstream (y forks " -"particulares) pueden usarse para entrenamiento." - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 #, python-format msgid "Forget" msgstr "" -#: system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:327 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "BUENO" -#: selfdrive/ui/widgets/pairing_dialog.py:128 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "Ve a https://connect.comma.ai en tu teléfono" -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "ALTO" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "Red" -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "INACTIVO: conéctate a una red sin límites" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "INSTALAR" -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "Instalar actualización" -#: selfdrive/ui/layouts/settings/developer.py:56 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "Modo de depuración de joystick" -#: selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "CARGANDO" -#: selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: selfdrive/ui/layouts/settings/developer.py:64 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "Modo de maniobra longitudinal" -#: selfdrive/ui/onroad/hud_renderer.py:148 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "MÁX" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." -msgstr "" -"Maximiza tus cargas de datos de entrenamiento para mejorar los modelos de " -"conducción de openpilot." +msgid "Maximize your training data uploads to improve openpilot's driving models." +msgstr "Maximiza tus cargas de datos de entrenamiento para mejorar los modelos de conducción de openpilot." -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "N/A" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "NO" -#: selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 msgid "Network" msgstr "Red" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 #, python-format msgid "No SSH keys found" msgstr "No se encontraron claves SSH" -#: selfdrive/ui/widgets/ssh_key.py:126 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 #, python-format msgid "No SSH keys found for user '{}'" msgstr "No se encontraron claves SSH para el usuario '{username}'" -#: selfdrive/ui/widgets/offroad_alerts.py:320 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 #, python-format msgid "No release notes available." msgstr "No hay notas de versión disponibles." -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "SIN CONEXIÓN" -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "OK" -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 msgid "ONLINE" msgstr "EN LÍNEA" -#: selfdrive/ui/widgets/setup.py:20 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Open" msgstr "Abrir" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "PAIR" msgstr "EMPAREJAR" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "PREVIEW" msgstr "VISTA PREVIA" -#: selfdrive/ui/widgets/prime.py:44 +#: openpilot/selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "FUNCIONES PRIME:" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "Pair Device" msgstr "Emparejar dispositivo" -#: selfdrive/ui/widgets/setup.py:19 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format msgid "Pair device" msgstr "Emparejar dispositivo" -#: selfdrive/ui/widgets/pairing_dialog.py:103 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "Empareja tu dispositivo con tu cuenta de comma" -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." -msgstr "" -"Empareja tu dispositivo con comma connect (connect.comma.ai) y reclama tu " -"oferta de comma prime." +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." +msgstr "Empareja tu dispositivo con comma connect (connect.comma.ai) y reclama tu oferta de comma prime." -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "Conéctate a Wi‑Fi para completar el emparejamiento inicial" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Power Off" msgstr "Apagar" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" -msgstr "" -"Previsualiza la cámara hacia el conductor para asegurarte de que la " -"supervisión del conductor tenga buena visibilidad. (el vehículo debe estar " -"apagado)" +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "Previsualiza la cámara hacia el conductor para asegurarte de que la supervisión del conductor tenga buena visibilidad. (el vehículo debe estar apagado)" -#: selfdrive/ui/widgets/pairing_dialog.py:161 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "Error de código QR" -#: selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "ELIMINAR" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "RESET" msgstr "RESTABLECER" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "REVIEW" msgstr "REVISAR" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Reboot" msgstr "Reiniciar" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "Reiniciar dispositivo" -#: selfdrive/ui/widgets/offroad_alerts.py:112 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "Reiniciar y actualizar" -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" -"Recibe alertas para volver al carril cuando tu vehículo se desvíe sobre una " -"línea de carril detectada sin la direccional activada mientras conduces a " -"más de 31 mph (50 km/h)." - -#: selfdrive/ui/layouts/settings/toggles.py:76 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "Grabar y subir cámara del conductor" -#: selfdrive/ui/layouts/settings/toggles.py:82 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "Grabar y subir audio del micrófono" -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." -msgstr "" -"Grabar y almacenar audio del micrófono mientras conduces. El audio se " -"incluirá en el video de la dashcam en comma connect." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." +msgstr "Grabar y almacenar audio del micrófono mientras conduces. El audio se incluirá en el video de la dashcam en comma connect." -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Regulatory" msgstr "Reglamentario" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "Relajado" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "Acceso remoto" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "Capturas remotas" -#: selfdrive/ui/widgets/ssh_key.py:123 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 #, python-format msgid "Request timed out" msgstr "Se agotó el tiempo de espera de la solicitud" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Reset" msgstr "Restablecer" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "Reset Calibration" msgstr "Restablecer calibración" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "Review Training Guide" msgstr "Revisar guía de entrenamiento" -#: selfdrive/ui/layouts/settings/device.py:27 +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 msgid "Review the rules, features, and limitations of openpilot" msgstr "Revisa las reglas, funciones y limitaciones de openpilot" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:53 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "" -#: system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:316 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "" -#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:37 #, python-format msgid "Select" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:183 +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 #, python-format msgid "Select a branch" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:91 +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 #, python-format msgid "Select a language" msgstr "Selecciona un idioma" -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "Serial" msgstr "Número de serie" -#: selfdrive/ui/widgets/offroad_alerts.py:106 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "Posponer actualización" -#: selfdrive/ui/layouts/settings/settings.py:65 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 msgid "Software" msgstr "Software" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "Estándar" -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" -"Se recomienda Estándar. En modo agresivo, openpilot seguirá más de cerca a " -"los coches delanteros y será más agresivo con el acelerador y el freno. En " -"modo relajado, openpilot se mantendrá más lejos de los coches delanteros. En " -"coches compatibles, puedes cambiar entre estas personalidades con el botón " -"de distancia del volante." - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "Sistema sin respuesta" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "TOME EL CONTROL INMEDIATAMENTE" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "TEMP" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "" -#: selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 msgid "Toggles" msgstr "Interruptores" -#: selfdrive/ui/layouts/settings/software.py:72 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 +#, python-format +msgid "UI Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "UNINSTALL" msgstr "DESINSTALAR" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "ACTUALIZAR" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "Uninstall" msgstr "Desinstalar" -#: selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "Desconocido" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "Las actualizaciones solo se descargan cuando el coche está apagado." -#: selfdrive/ui/widgets/prime.py:33 +#: openpilot/selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "Mejorar ahora" -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." -msgstr "" -"Sube datos de la cámara orientada al conductor y ayuda a mejorar el " -"algoritmo de supervisión del conductor." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." +msgstr "Sube datos de la cámara orientada al conductor y ayuda a mejorar el algoritmo de supervisión del conductor." -#: selfdrive/ui/layouts/settings/toggles.py:88 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "Usar sistema métrico" -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" -"Usa el sistema openpilot para control de crucero adaptativo y asistencia de " -"mantenimiento de carril. Tu atención se requiere en todo momento para usar " -"esta función." - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "VEHÍCULO" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "VIEW" msgstr "VER" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "Esperando para iniciar" -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" -"Advertencia: Esto otorga acceso SSH a todas las claves públicas en tu " -"configuración de GitHub. Nunca introduzcas un nombre de usuario de GitHub " -"que no sea el tuyo. Un empleado de comma NUNCA te pedirá que agregues su " -"nombre de usuario de GitHub." - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "Bienvenido a openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." -msgstr "" -"Cuando está activado, al presionar el pedal del acelerador se desactivará " -"openpilot." +msgstr "Cuando está activado, al presionar el pedal del acelerador se desactivará openpilot." -#: selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Wi-Fi Network Metered" msgstr "" -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "Debes aceptar los Términos y Condiciones para poder usar openpilot." -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." -msgstr "" -"Debes aceptar los Términos y Condiciones para usar openpilot. Lee los " -"términos más recientes en https://comma.ai/terms antes de continuar." +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." +msgstr "Debes aceptar los Términos y Condiciones para usar openpilot. Lee los términos más recientes en https://comma.ai/terms antes de continuar." -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 #, python-format msgid "camera starting" msgstr "iniciando cámara" -#: selfdrive/ui/widgets/prime.py:63 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "checking..." +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "default" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "down" msgstr "abajo" -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "downloading..." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "Error al buscar actualizaciones" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 +#, python-format +msgid "finalizing update..." +msgstr "" + +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 #, python-format msgid "for \"{}\"" msgstr "" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "leave blank for automatic configuration" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "left" msgstr "izquierda" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "nunca" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "ahora" -#: selfdrive/ui/layouts/settings/developer.py:71 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "Control longitudinal de openpilot (Alpha)" -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot no disponible" -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." -msgstr "" -"openpilot conduce por defecto en modo chill. El modo Experimental habilita " -"funciones de nivel alpha que no están listas para el modo chill. Las " -"funciones experimentales se enumeran a continuación:

Control " -"longitudinal de extremo a extremo


Deja que el modelo de conducción " -"controle el acelerador y los frenos. openpilot conducirá como piensa que lo " -"haría un humano, incluyendo detenerse en luces rojas y señales de alto. Dado " -"que el modelo decide la velocidad a la que conducir, la velocidad " -"establecida solo actuará como límite superior. Esta es una función de " -"calidad alpha; se deben esperar errores.

Nueva visualización de " -"conducción


La visualización de conducción hará la transición a la " -"cámara gran angular orientada a la carretera a bajas velocidades para " -"mostrar mejor algunos giros. El logotipo del modo Experimental también se " -"mostrará en la esquina superior derecha." - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" -" Cambiar esta configuración reiniciará openpilot si el coche está encendido." - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" -"openpilot aprende a conducir observando a humanos, como tú, conducir.\n" -"\n" -"El Modo Firehose te permite maximizar tus cargas de datos de entrenamiento " -"para mejorar los modelos de conducción de openpilot. Más datos significan " -"modelos más grandes, lo que significa un mejor Modo Experimental." - -#: selfdrive/ui/layouts/settings/toggles.py:183 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 #, python-format msgid "openpilot longitudinal control may come in a future update." -msgstr "" -"El control longitudinal de openpilot podría llegar en una actualización " -"futura." +msgstr "El control longitudinal de openpilot podría llegar en una actualización futura." -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." -msgstr "" -"openpilot requiere que el dispositivo esté montado dentro de 4° a izquierda " -"o derecha y dentro de 5° hacia arriba o 9° hacia abajo." +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." +msgstr "openpilot requiere que el dispositivo esté montado dentro de 4° a izquierda o derecha y dentro de 5° hacia arriba o 9° hacia abajo." -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "right" msgstr "derecha" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "up" msgstr "arriba" -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "actualizado, última comprobación: nunca" -#: selfdrive/ui/layouts/settings/software.py:115 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format msgid "up to date, last checked {}" msgstr "actualizado, última comprobación: {}" -#: selfdrive/ui/layouts/settings/software.py:109 +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "actualización disponible" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} ALERTA" msgstr[1] "{} ALERTAS" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "hace {} día" msgstr[1] "hace {} días" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "hace {} hora" msgstr[1] "hace {} horas" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "hace {} minuto" msgstr[1] "hace {} minutos" -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." -msgstr[0] "" -"{} segmento de tu conducción está en el conjunto de entrenamiento hasta " -"ahora." -msgstr[1] "" -"{} segmentos de tu conducción están en el conjunto de entrenamiento hasta " -"ahora." +msgstr[0] "{} segmento de tu conducción está en el conjunto de entrenamiento hasta ahora." +msgstr[1] "{} segmentos de tu conducción están en el conjunto de entrenamiento hasta ahora." -#: selfdrive/ui/widgets/prime.py:62 +#: openpilot/selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ SUSCRITO" -#: selfdrive/ui/widgets/setup.py:22 +#: openpilot/selfdrive/ui/widgets/setup.py:21 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Modo Firehose 🔥" + diff --git a/selfdrive/ui/translations/app_ja.po b/selfdrive/ui/translations/app_ja.po index ca8aac1515..41eb91dd58 100644 --- a/selfdrive/ui/translations/app_ja.po +++ b/selfdrive/ui/translations/app_ja.po @@ -17,1181 +17,1013 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: selfdrive/ui/layouts/settings/device.py:160 +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 #, python-format msgid " Steering torque response calibration is complete." msgstr " ステアリングトルク応答のキャリブレーションが完了しました。" -#: selfdrive/ui/layouts/settings/device.py:158 +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " ステアリングトルク応答のキャリブレーションは{}%完了しました。" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " デバイスは{:.1f}°{}、{:.1f}°{}の向きです。" -#: selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "走行データを1年間保存" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "24時間365日のLTE接続" -#: selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" -"警告: この車におけるopenpilotの縦制御はアルファ版であり、自動緊急ブレーキ" -"(AEB)を無効にします。

この車では、openpilotは縦制御として" -"openpilotではなく車両の内蔵ACCを既定で使用します。openpilotの縦制御に切り替え" -"るにはこの設定を有効にしてください。openpilot縦制御アルファを有効にする場合は" -"実験モードの有効化を推奨します。この設定を変更すると、車が起動中の場合は" -"openpilotが再起動します。" - -#: selfdrive/ui/layouts/settings/device.py:148 +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 #, python-format msgid "

Steering lag calibration is complete." msgstr "

ステアリング遅延のキャリブレーションが完了しました。" -#: selfdrive/ui/layouts/settings/device.py:146 +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "

ステアリング遅延のキャリブレーションは{}%完了しました。" -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "アクティブ" - -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" -"ADB(Android Debug Bridge)を使用すると、USBまたはネットワーク経由でデバイス" -"に接続できます。詳しくは https://docs.comma.ai/how-to/connect-to-comma を参照" -"してください。" - -#: selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "追加" -#: system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:136 #, python-format msgid "APN Setting" msgstr "APN設定" -#: selfdrive/ui/widgets/offroad_alerts.py:109 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "過度な作動を承認" -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "詳細設定" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "アグレッシブ" -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "同意する" -#: selfdrive/ui/layouts/settings/toggles.py:70 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "常時ドライバーモニタリング" -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "" -"openpilotの縦制御アルファ版は、実験モードと併せて非リリースブランチでテストで" -"きます。" - -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 #, python-format msgid "Are you sure you want to power off?" msgstr "本当に電源をオフにしますか?" -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 #, python-format msgid "Are you sure you want to reboot?" msgstr "本当に再起動しますか?" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "本当にキャリブレーションをリセットしますか?" -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 #, python-format msgid "Are you sure you want to uninstall?" msgstr "本当にアンインストールしますか?" -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "戻る" -#: selfdrive/ui/widgets/prime.py:38 +#: openpilot/selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "connect.comma.aiで comma prime に加入" -#: selfdrive/ui/widgets/pairing_dialog.py:130 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "connect.comma.aiをホーム画面に追加してアプリのように使いましょう" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "CHANGE" msgstr "変更" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 #, python-format msgid "CHECK" msgstr "確認" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "CHILL MODE ON" msgstr "チルモードON" -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "接続" -#: system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:376 #, python-format msgid "CONNECTING..." msgstr "接続中..." -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format msgid "Cancel" msgstr "キャンセル" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "従量課金の携帯回線" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "Change Language" msgstr "言語を変更" -#: selfdrive/ui/layouts/settings/toggles.py:125 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "車が起動中の場合、この設定を変更するとopenpilotが再起動します。" -#: selfdrive/ui/widgets/pairing_dialog.py:129 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "\"add new device\"を押して右側のQRコードをスキャン" -#: selfdrive/ui/widgets/offroad_alerts.py:104 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "閉じる" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "現在のバージョン" -#: selfdrive/ui/layouts/settings/software.py:110 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "ダウンロード" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "拒否する" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "拒否してopenpilotをアンインストール" -#: selfdrive/ui/layouts/settings/settings.py:67 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 msgid "Developer" msgstr "開発者" -#: selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 msgid "Device" msgstr "デバイス" -#: selfdrive/ui/layouts/settings/toggles.py:58 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "アクセルで解除" -#: selfdrive/ui/layouts/settings/device.py:184 +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 #, python-format msgid "Disengage to Power Off" msgstr "解除して電源オフ" -#: selfdrive/ui/layouts/settings/device.py:172 +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 #, python-format msgid "Disengage to Reboot" msgstr "解除して再起動" -#: selfdrive/ui/layouts/settings/device.py:103 +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 #, python-format msgid "Disengage to Reset Calibration" msgstr "解除してキャリブレーションをリセット" -#: selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "速度をmphではなくkm/hで表示します。" -#: selfdrive/ui/layouts/settings/device.py:59 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 #, python-format msgid "Dongle ID" msgstr "ドングルID" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "ダウンロード" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Driver Camera" msgstr "ドライバーカメラ" -#: selfdrive/ui/layouts/settings/toggles.py:96 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "走行性格" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "編集" -#: selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "エラー" -#: selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "実験モードON" -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 #, python-format msgid "Enable" msgstr "有効化" -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "ADBを有効化" -#: selfdrive/ui/layouts/settings/toggles.py:64 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "車線逸脱警報を有効化" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "ローミングを有効化" -#: selfdrive/ui/layouts/settings/developer.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "SSHを有効化" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "テザリングを有効化" -#: selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "openpilotが未作動でもドライバーモニタリングを有効にします。" -#: selfdrive/ui/layouts/settings/toggles.py:46 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "openpilotを有効化" -#: selfdrive/ui/layouts/settings/toggles.py:189 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 #, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." -msgstr "" -"openpilot縦制御(アルファ)のトグルを有効にすると実験モードが使用できます。" +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." +msgstr "openpilot縦制御(アルファ)のトグルを有効にすると実験モードが使用できます。" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "Enter APN" msgstr "APNを入力" -#: system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:243 #, python-format msgid "Enter SSID" msgstr "SSIDを入力" -#: system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:257 #, python-format msgid "Enter new tethering password" msgstr "新しいテザリングのパスワードを入力" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 #, python-format msgid "Enter password" msgstr "パスワードを入力" -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "GitHubユーザー名を入力" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "エラー" -#: selfdrive/ui/layouts/settings/toggles.py:52 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "実験モード" -#: selfdrive/ui/layouts/settings/toggles.py:181 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 #, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." -msgstr "" -"この車では縦制御に純正ACCを使用するため、現在実験モードは利用できません。" +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." +msgstr "この車では縦制御に純正ACCを使用するため、現在実験モードは利用できません。" -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "削除中..." -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "セットアップを完了" -#: selfdrive/ui/layouts/settings/settings.py:66 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 msgid "Firehose" msgstr "Firehose" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "Firehoseモード" -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" -"最大限の効果を得るため、デバイスを屋内に持ち込み、週に一度は品質の良いUSB-Cア" -"ダプターとWi‑Fiに接続してください。\n" -"\n" -"Firehoseモードは、ホットスポットや無制限SIMに接続していれば走行中でも動作しま" -"す。\n" -"\n" -"\n" -"よくある質問\n" -"\n" -"運転の仕方や場所は関係ありますか? いいえ。普段どおりに運転してください。\n" -"\n" -"Firehoseモードではすべてのセグメントが取得されますか? いいえ。セグメントの一" -"部を選択的に取得します。\n" -"\n" -"良いUSB‑Cアダプターとは? 高速なスマホまたはノートPC用充電器で問題ありませ" -"ん。\n" -"\n" -"どのソフトウェアを使うかは重要ですか? はい。学習に使えるのは上流のopenpilot" -"(および特定のフォーク)のみです。" - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 #, python-format msgid "Forget" msgstr "削除" -#: system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:327 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "Wi‑Fiネットワーク「{}」を削除しますか?" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "良好" -#: selfdrive/ui/widgets/pairing_dialog.py:128 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "スマートフォンで https://connect.comma.ai にアクセス" -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "高温" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "非公開ネットワーク" -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "非アクティブ:非従量のネットワークに接続してください" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "インストール" -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "IPアドレス" -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "アップデートをインストール" -#: selfdrive/ui/layouts/settings/developer.py:56 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "ジョイスティックデバッグモード" -#: selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "読み込み中" -#: selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: selfdrive/ui/layouts/settings/developer.py:64 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "縦制御マヌーバーモード" -#: selfdrive/ui/onroad/hud_renderer.py:148 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "最大" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." -msgstr "" -"学習データのアップロードを最大化してopenpilotの運転モデルを改善しましょう。" +msgid "Maximize your training data uploads to improve openpilot's driving models." +msgstr "学習データのアップロードを最大化してopenpilotの運転モデルを改善しましょう。" -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "N/A" msgstr "該当なし" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "いいえ" -#: selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 msgid "Network" msgstr "ネットワーク" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 #, python-format msgid "No SSH keys found" msgstr "SSH鍵が見つかりません" -#: selfdrive/ui/widgets/ssh_key.py:126 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 #, python-format msgid "No SSH keys found for user '{}'" msgstr "ユーザー'{}'のSSH鍵が見つかりません" -#: selfdrive/ui/widgets/offroad_alerts.py:320 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 #, python-format msgid "No release notes available." msgstr "リリースノートはありません。" -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "オフライン" -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "OK" -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 msgid "ONLINE" msgstr "オンライン" -#: selfdrive/ui/widgets/setup.py:20 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Open" msgstr "開く" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "PAIR" msgstr "ペアリング" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "PREVIEW" msgstr "プレビュー" -#: selfdrive/ui/widgets/prime.py:44 +#: openpilot/selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "prime の特典:" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "Pair Device" msgstr "デバイスをペアリング" -#: selfdrive/ui/widgets/setup.py:19 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format msgid "Pair device" msgstr "デバイスをペアリング" -#: selfdrive/ui/widgets/pairing_dialog.py:103 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "デバイスをあなたの comma アカウントにペアリング" -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." -msgstr "" -"デバイスを comma connect(connect.comma.ai)とペアリングして、comma prime 特" -"典を受け取りましょう。" +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." +msgstr "デバイスを comma connect(connect.comma.ai)とペアリングして、comma prime 特典を受け取りましょう。" -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "初回ペアリングを完了するにはWi‑Fiに接続してください" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Power Off" msgstr "電源オフ" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "従量課金のWi‑Fi接続時は大きなデータのアップロードを抑制" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "従量課金の携帯回線接続時は大きなデータのアップロードを抑制" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" -msgstr "" -"ドライバー向きカメラのプレビューでモニタリングの視界を確認します。(車両は停" -"止状態である必要があります)" +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "ドライバー向きカメラのプレビューでモニタリングの視界を確認します。(車両は停止状態である必要があります)" -#: selfdrive/ui/widgets/pairing_dialog.py:161 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "QRコードエラー" -#: selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "削除" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "RESET" msgstr "リセット" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "REVIEW" msgstr "確認" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Reboot" msgstr "再起動" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "デバイスを再起動" -#: selfdrive/ui/widgets/offroad_alerts.py:112 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "再起動して更新" -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" -"時速31mph(50km/h)を超えて走行中にウインカーを出さず検出された車線を外れた場" -"合、車線内に戻るよう警告を受け取ります。" - -#: selfdrive/ui/layouts/settings/toggles.py:76 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "ドライバーカメラを記録してアップロード" -#: selfdrive/ui/layouts/settings/toggles.py:82 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "マイク音声を記録してアップロード" -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." -msgstr "" -"走行中にマイク音声を記録・保存します。音声は comma connect のドライブレコー" -"ダー動画に含まれます。" +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." +msgstr "走行中にマイク音声を記録・保存します。音声は comma connect のドライブレコーダー動画に含まれます。" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Regulatory" msgstr "規制情報" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "リラックス" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "リモートアクセス" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "リモートスナップショット" -#: selfdrive/ui/widgets/ssh_key.py:123 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 #, python-format msgid "Request timed out" msgstr "リクエストがタイムアウトしました" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Reset" msgstr "リセット" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "Reset Calibration" msgstr "キャリブレーションをリセット" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "Review Training Guide" msgstr "トレーニングガイドを確認" -#: selfdrive/ui/layouts/settings/device.py:27 +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 msgid "Review the rules, features, and limitations of openpilot" msgstr "openpilotのルール、機能、制限を確認" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "選択" -#: selfdrive/ui/layouts/settings/developer.py:53 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "SSH鍵" -#: system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:316 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "Wi‑Fiネットワークを検索中..." -#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:37 #, python-format msgid "Select" msgstr "選択" -#: selfdrive/ui/layouts/settings/software.py:183 +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 #, python-format msgid "Select a branch" msgstr "ブランチを選択" -#: selfdrive/ui/layouts/settings/device.py:91 +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 #, python-format msgid "Select a language" msgstr "言語を選択" -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "Serial" msgstr "シリアル" -#: selfdrive/ui/widgets/offroad_alerts.py:106 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "更新を後で通知" -#: selfdrive/ui/layouts/settings/settings.py:65 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 msgid "Software" msgstr "ソフトウェア" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "スタンダード" -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" -"標準を推奨します。アグレッシブでは前走車に近づき、加減速も積極的になります。" -"リラックスでは前走車との距離を保ちます。対応車種ではステアリングの車間ボタン" -"でこれらの性格を切り替えられます。" - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "システムが応答しません" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "すぐに手動介入してください" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "温度" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "対象ブランチ" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "テザリングのパスワード" -#: selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 msgid "Toggles" msgstr "トグル" -#: selfdrive/ui/layouts/settings/software.py:72 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 +#, python-format +msgid "UI Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "UNINSTALL" msgstr "アンインストール" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "更新" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "Uninstall" msgstr "アンインストール" -#: selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "不明" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "アップデートは車両の電源が切れている間のみダウンロードされます。" -#: selfdrive/ui/widgets/prime.py:33 +#: openpilot/selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "今すぐアップグレード" -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." -msgstr "" -"ドライバー向きカメラのデータをアップロードしてモニタリングアルゴリズムの改善" -"に協力してください。" +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." +msgstr "ドライバー向きカメラのデータをアップロードしてモニタリングアルゴリズムの改善に協力してください。" -#: selfdrive/ui/layouts/settings/toggles.py:88 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "メートル法を使用" -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" -"ACCと車線維持支援にopenpilotを使用します。本機能の使用中は常に注意が必要で" -"す。" - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "車両" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "VIEW" msgstr "表示" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "開始待機中" -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" -"警告: これはGitHub設定内のすべての公開鍵にSSHアクセスを与えます。自分以外の" -"GitHubユーザー名を絶対に入力しないでください。comma の従業員が自分のGitHub" -"ユーザー名を追加するよう求めることは決してありません。" - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "openpilotへようこそ" -#: selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "有効にすると、アクセルを踏むとopenpilotが解除されます。" -#: selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Wi-Fi Network Metered" msgstr "Wi‑Fiネットワーク(従量課金)" -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "パスワードが違います" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "openpilotを使用するには、利用規約に同意する必要があります。" -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." -msgstr "" -"openpilotを使用するには利用規約に同意する必要があります。続行する前に " -"https://comma.ai/terms の最新の規約をお読みください。" +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." +msgstr "openpilotを使用するには利用規約に同意する必要があります。続行する前に https://comma.ai/terms の最新の規約をお読みください。" -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 #, python-format msgid "camera starting" msgstr "カメラを起動中" -#: selfdrive/ui/widgets/prime.py:63 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "checking..." +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "default" msgstr "既定" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "down" msgstr "下" -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "downloading..." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "アップデートの確認に失敗しました" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 +#, python-format +msgid "finalizing update..." +msgstr "" + +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 #, python-format msgid "for \"{}\"" msgstr "「{}」向け" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "leave blank for automatic configuration" msgstr "自動設定の場合は空欄のままにしてください" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "left" msgstr "左" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "従量" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "なし" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "今" -#: selfdrive/ui/layouts/settings/developer.py:71 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "openpilot 縦制御(アルファ)" -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilotは利用できません" -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." -msgstr "" -"openpilotは既定でチルモードで走行します。実験モードでは、チルモードにはまだ準" -"備ができていないアルファレベルの機能が有効になります。実験的な機能は以下のと" -"おりです:

エンドツーエンド縦制御


運転モデルがアクセルとブレー" -"キを制御します。openpilotは人間のように走行し、赤信号や一時停止でも停止しま" -"す。走行速度は運転モデルが決めるため、設定速度は上限としてのみ機能します。こ" -"れはアルファ品質の機能であり、誤動作が発生する可能性があります。

新し" -"い運転ビジュアライゼーション


低速時には道路向きの広角カメラに切り替わ" -"り、一部の曲がりをより良く表示します。画面右上には実験モードのロゴも表示され" -"ます。" - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" -"openpilotは継続的にキャリブレーションを行っており、リセットが必要になることは" -"稀です。車が起動中にキャリブレーションをリセットするとopenpilotが再起動しま" -"す。" - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" -"openpilotは、あなたのような人間の運転を見て運転を学習します。\n" -"\n" -"Firehoseモードを使うと、学習データのアップロードを最大化してopenpilotの運転モ" -"デルを改善できます。データが増えるほどモデルが大きくなり、実験モードがより良" -"くなります。" - -#: selfdrive/ui/layouts/settings/toggles.py:183 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "openpilotの縦制御は将来のアップデートで提供される可能性があります。" -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." -msgstr "" -"openpilotでは、デバイスの取り付け角度が左右±4°、上方向5°以内、下方向9°以内で" -"ある必要があります。" +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." +msgstr "openpilotでは、デバイスの取り付け角度が左右±4°、上方向5°以内、下方向9°以内である必要があります。" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "right" msgstr "右" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "非従量" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "up" msgstr "上" -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "最新です。最終確認: なし" -#: selfdrive/ui/layouts/settings/software.py:115 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format msgid "up to date, last checked {}" msgstr "最新です。最終確認: {}" -#: selfdrive/ui/layouts/settings/software.py:109 +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "更新があります" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{}件のアラート" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{}日前" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{}時間前" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{}分前" -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." -msgstr[0] "" -"これまでにあなたの走行の{}セグメントが学習データセットに含まれています。" +msgstr[0] "これまでにあなたの走行の{}セグメントが学習データセットに含まれています。" -#: selfdrive/ui/widgets/prime.py:62 +#: openpilot/selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ 登録済み" -#: selfdrive/ui/widgets/setup.py:22 +#: openpilot/selfdrive/ui/widgets/setup.py:21 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehoseモード 🔥" + diff --git a/selfdrive/ui/translations/app_ko.po b/selfdrive/ui/translations/app_ko.po index f12aebaeb3..9b73a22387 100644 --- a/selfdrive/ui/translations/app_ko.po +++ b/selfdrive/ui/translations/app_ko.po @@ -17,1174 +17,1013 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: selfdrive/ui/layouts/settings/device.py:160 +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 #, python-format msgid " Steering torque response calibration is complete." msgstr " 스티어링 토크 응답 보정이 완료되었습니다." -#: selfdrive/ui/layouts/settings/device.py:158 +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " 스티어링 토크 응답 보정이 {}% 완료되었습니다." -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " 장치는 {:.1f}° {} 및 {:.1f}° {} 방향을 가리키고 있습니다." -#: selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "주행 데이터 1년 보관" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "연중무휴 LTE 연결" -#: selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" -"경고: 이 차량에서 openpilot의 롱컨 제어는 알파 버전이며 자동 긴급 제동" -"(AEB)을 비활성화합니다.

이 차량에서는 openpilot 롱컨 제어 대신 " -"차량 내장 ACC가 기본으로 사용됩니다. openpilot 롱컨 제어로 전환하려면 이 설" -"정을 켜세요. 롱컨 제어 알파를 켤 때는 실험 모드 사용을 권장합니다. 차량 전" -"원이 켜져 있는 경우 이 설정을 변경하면 openpilot이 재시작됩니다." - -#: selfdrive/ui/layouts/settings/device.py:148 +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 #, python-format msgid "

Steering lag calibration is complete." msgstr "

스티어링 지연 보정이 완료되었습니다." -#: selfdrive/ui/layouts/settings/device.py:146 +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "

스티어링 지연 보정이 {}% 완료되었습니다." -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "활성" - -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" -"ADB(Android Debug Bridge)를 사용하면 USB 또는 네트워크로 장치에 연결할 수 있" -"습니다. 자세한 내용은 https://docs.comma.ai/how-to/connect-to-comma 를 참고하" -"세요." - -#: selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "추가" -#: system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:136 #, python-format msgid "APN Setting" msgstr "APN 설정" -#: selfdrive/ui/widgets/offroad_alerts.py:109 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "과도한 작동을 확인" -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "고급" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "공격적" -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "동의" -#: selfdrive/ui/layouts/settings/toggles.py:70 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "운전자 모니터링 항상 켜짐" -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "" -"openpilot 롱컨 제어 알파 버전은 실험 모드와 함께 비릴리스 브랜치에서 테스트" -"할 수 있습니다." - -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 #, python-format msgid "Are you sure you want to power off?" msgstr "정말 전원을 끄시겠습니까?" -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 #, python-format msgid "Are you sure you want to reboot?" msgstr "정말 재시작하시겠습니까?" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "정말 보정을 재설정하시겠습니까?" -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 #, python-format msgid "Are you sure you want to uninstall?" msgstr "정말 제거하시겠습니까?" -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "뒤로" -#: selfdrive/ui/widgets/prime.py:38 +#: openpilot/selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "connect.comma.ai에서 comma prime 회원이 되세요" -#: selfdrive/ui/widgets/pairing_dialog.py:130 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "connect.comma.ai를 홈 화면에 추가하여 앱처럼 사용하세요" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "CHANGE" msgstr "변경" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 #, python-format msgid "CHECK" msgstr "확인" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "CHILL MODE ON" msgstr "안정적 모드 켜짐" -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "연결" -#: system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:376 #, python-format msgid "CONNECTING..." msgstr "연결 중..." -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format msgid "Cancel" msgstr "취소" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "종량제 셀룰러" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "Change Language" msgstr "언어 변경" -#: selfdrive/ui/layouts/settings/toggles.py:125 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "차량 전원이 켜져 있으면 이 설정을 변경할 때 openpilot이 재시작됩니다." -#: selfdrive/ui/widgets/pairing_dialog.py:129 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "\"add new device\"를 눌러 오른쪽의 QR 코드를 스캔하세요" -#: selfdrive/ui/widgets/offroad_alerts.py:104 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "닫기" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "현재 버전" -#: selfdrive/ui/layouts/settings/software.py:110 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "다운로드" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "거부" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "거부하고 openpilot 제거" -#: selfdrive/ui/layouts/settings/settings.py:67 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 msgid "Developer" msgstr "개발자" -#: selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 msgid "Device" msgstr "장치" -#: selfdrive/ui/layouts/settings/toggles.py:58 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "가속 페달로 해제" -#: selfdrive/ui/layouts/settings/device.py:184 +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 #, python-format msgid "Disengage to Power Off" msgstr "해제 후 전원 끄기" -#: selfdrive/ui/layouts/settings/device.py:172 +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 #, python-format msgid "Disengage to Reboot" msgstr "해제 후 재시작" -#: selfdrive/ui/layouts/settings/device.py:103 +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 #, python-format msgid "Disengage to Reset Calibration" msgstr "해제 후 캘리브레이션 재설정" -#: selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "속도를 mph 대신 km/h로 표시합니다." -#: selfdrive/ui/layouts/settings/device.py:59 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 #, python-format msgid "Dongle ID" msgstr "동글 ID" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "다운로드" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Driver Camera" msgstr "운전자 카메라" -#: selfdrive/ui/layouts/settings/toggles.py:96 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "주행 성향" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "편집" -#: selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "오류" -#: selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "실험 모드 켜짐" -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 #, python-format msgid "Enable" msgstr "사용" -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "ADB 사용" -#: selfdrive/ui/layouts/settings/toggles.py:64 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "차선 이탈 경고 사용" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "로밍 사용" -#: selfdrive/ui/layouts/settings/developer.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "SSH 사용" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "테더링 사용" -#: selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "openpilot이 작동 중이 아닐 때도 운전자 모니터링을 사용합니다." -#: selfdrive/ui/layouts/settings/toggles.py:46 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "openpilot 사용" -#: selfdrive/ui/layouts/settings/toggles.py:189 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 #, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "실험 모드를 사용하려면 openpilot 롱컨 제어(알파) 토글을 켜세요." -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "Enter APN" msgstr "APN 입력" -#: system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:243 #, python-format msgid "Enter SSID" msgstr "SSID 입력" -#: system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:257 #, python-format msgid "Enter new tethering password" msgstr "새 테더링 비밀번호 입력" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 #, python-format msgid "Enter password" msgstr "비밀번호 입력" -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "GitHub 사용자 이름 입력" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "오류" -#: selfdrive/ui/layouts/settings/toggles.py:52 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "실험 모드" -#: selfdrive/ui/layouts/settings/toggles.py:181 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 #, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." -msgstr "" -"이 차량은 롱컨 제어에 순정 ACC를 사용하므로 현재 실험 모드를 사용할 수 없습" -"니다." +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." +msgstr "이 차량은 롱컨 제어에 순정 ACC를 사용하므로 현재 실험 모드를 사용할 수 없습니다." -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "삭제 중..." -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "설정 완료" -#: selfdrive/ui/layouts/settings/settings.py:66 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 msgid "Firehose" msgstr "파이어호스" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "파이어호스 모드" -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" -"최대의 효과를 위해 주 1회는 장치를 실내로 가져와 품질 좋은 USB‑C 어댑터와 " -"Wi‑Fi에 연결하세요.\n" -"\n" -"핫스팟이나 무제한 SIM에 연결되어 있다면 주행 중에도 파이어호스 모드가 동작합니" -"다.\n" -"\n" -"\n" -"자주 묻는 질문\n" -"\n" -"어떻게, 어디서 운전하는지가 중요한가요? 아니요. 평소처럼 운전하세요.\n" -"\n" -"파이어호스 모드에서 모든 구간을 가져가지나요? 아니요. 일부 구간만 선택" -"적으로 가져갑니다.\n" -"\n" -"좋은 USB‑C 어댑터는 무엇인가요? 빠른 휴대폰 또는 노트북 충전기면 충분합니" -"다.\n" -"\n" -"어떤 소프트웨어를 실행하는지가 중요한가요? 예. 학습에는 업스트림 " -"openpilot(및 일부 포크)만 사용할 수 있습니다." - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 #, python-format msgid "Forget" msgstr "삭제" -#: system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:327 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "Wi‑Fi 네트워크 \"{}\"를 삭제하시겠습니까?" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "양호" -#: selfdrive/ui/widgets/pairing_dialog.py:128 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "휴대폰에서 https://connect.comma.ai 에 접속하세요" -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "높음" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "숨겨진 네트워크" -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "비활성: 비종량제 네트워크에 연결하세요" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "설치" -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "IP 주소" -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "업데이트 설치" -#: selfdrive/ui/layouts/settings/developer.py:56 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "조이스틱 디버그 모드" -#: selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "로딩 중" -#: selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: selfdrive/ui/layouts/settings/developer.py:64 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "롱컨 기동 모드" -#: selfdrive/ui/onroad/hud_renderer.py:148 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "최대" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." +msgid "Maximize your training data uploads to improve openpilot's driving models." msgstr "학습 데이터 업로드를 최대화하여 openpilot의 주행 모델을 개선하세요." -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "N/A" msgstr "해당 없음" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "아니오" -#: selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 msgid "Network" msgstr "네트워크" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 #, python-format msgid "No SSH keys found" msgstr "SSH 키를 찾을 수 없습니다" -#: selfdrive/ui/widgets/ssh_key.py:126 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 #, python-format msgid "No SSH keys found for user '{}'" msgstr "사용자 '{}'의 SSH 키를 찾을 수 없습니다" -#: selfdrive/ui/widgets/offroad_alerts.py:320 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 #, python-format msgid "No release notes available." msgstr "릴리스 노트가 없습니다." -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "오프라인" -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "확인" -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 msgid "ONLINE" msgstr "온라인" -#: selfdrive/ui/widgets/setup.py:20 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Open" msgstr "열기" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "PAIR" msgstr "페어링" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "PREVIEW" msgstr "미리보기" -#: selfdrive/ui/widgets/prime.py:44 +#: openpilot/selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "프라임 기능:" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "Pair Device" msgstr "장치 페어링" -#: selfdrive/ui/widgets/setup.py:19 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format msgid "Pair device" msgstr "장치 페어링" -#: selfdrive/ui/widgets/pairing_dialog.py:103 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "장치를 귀하의 comma 계정에 페어링하세요" -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." -msgstr "" -"장치를 comma connect(connect.comma.ai)와 페어링하고 comma 프라임 혜택을 받으세" -"요." +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." +msgstr "장치를 comma connect(connect.comma.ai)와 페어링하고 comma 프라임 혜택을 받으세요." -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "초기 페어링을 완료하려면 Wi‑Fi에 연결하세요" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Power Off" msgstr "전원 끄기" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "종량제 Wi‑Fi 연결 시 대용량 업로드 방지" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "종량제 셀룰러 연결 시 대용량 업로드 방지" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" -msgstr "" -"운전자 모니터링의 가시성을 확인하기 위해 운전자 카메라를 미리 봅니다. (차량" -"은 꺼져 있어야 합니다)" +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "운전자 모니터링의 가시성을 확인하기 위해 운전자 카메라를 미리 봅니다. (차량은 꺼져 있어야 합니다)" -#: selfdrive/ui/widgets/pairing_dialog.py:161 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "QR 코드 오류" -#: selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "제거" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "RESET" msgstr "재설정" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "REVIEW" msgstr "검토" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Reboot" msgstr "재시작" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "장치 재시작" -#: selfdrive/ui/widgets/offroad_alerts.py:112 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "재시작 및 업데이트" -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" -"시속 31mph(50km/h) 이상에서 방향지시등 없이 감지된 차선 밖으로 벗어나면 차선" -"으로 복귀하라는 경고를 받습니다." - -#: selfdrive/ui/layouts/settings/toggles.py:76 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "운전자 카메라 기록 및 업로드" -#: selfdrive/ui/layouts/settings/toggles.py:82 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "마이크 오디오 기록 및 업로드" -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." -msgstr "" -"주행 중 마이크 오디오를 기록하고 저장합니다. 오디오는 comma connect의 대시캠 " -"영상에 포함됩니다." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." +msgstr "주행 중 마이크 오디오를 기록하고 저장합니다. 오디오는 comma connect의 대시캠 영상에 포함됩니다." -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Regulatory" msgstr "규제 정보" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "편안한" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "원격 액세스" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "원격 스냅샷" -#: selfdrive/ui/widgets/ssh_key.py:123 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 #, python-format msgid "Request timed out" msgstr "요청 시간이 초과되었습니다" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Reset" msgstr "재설정" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "Reset Calibration" msgstr "캘리브레이션 재설정" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "Review Training Guide" msgstr "학습 가이드 검토" -#: selfdrive/ui/layouts/settings/device.py:27 +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 msgid "Review the rules, features, and limitations of openpilot" msgstr "openpilot의 규칙, 기능 및 제한을 검토" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "선택" -#: selfdrive/ui/layouts/settings/developer.py:53 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "SSH 키" -#: system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:316 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "Wi‑Fi 네트워크 검색 중..." -#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:37 #, python-format msgid "Select" msgstr "선택" -#: selfdrive/ui/layouts/settings/software.py:183 +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 #, python-format msgid "Select a branch" msgstr "브랜치 선택" -#: selfdrive/ui/layouts/settings/device.py:91 +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 #, python-format msgid "Select a language" msgstr "언어 선택" -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "Serial" msgstr "시리얼" -#: selfdrive/ui/widgets/offroad_alerts.py:106 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "업데이트 나중에 알림" -#: selfdrive/ui/layouts/settings/settings.py:65 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 msgid "Software" msgstr "소프트웨어" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "표준" -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" -"표준을 권장합니다. 공격적 모드에서는 앞차를 더 가깝게 따라가고 가감속이 더 적" -"극적입니다. 편안한 모드에서는 앞차와 거리를 더 둡니다. 지원 차량에서는 스티어" -"링의 차간 버튼으로 이 성향들을 전환할 수 있습니다." - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "시스템 응답 없음" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "즉시 수동 조작하세요" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "온도" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "대상 브랜치" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "테더링 비밀번호" -#: selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 msgid "Toggles" msgstr "토글" -#: selfdrive/ui/layouts/settings/software.py:72 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 +#, python-format +msgid "UI Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "UNINSTALL" msgstr "제거" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "업데이트" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "Uninstall" msgstr "제거" -#: selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "알수없음" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "업데이트는 차량 전원이 꺼져 있을 때만 다운로드됩니다." -#: selfdrive/ui/widgets/prime.py:33 +#: openpilot/selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "지금 업그레이드" -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." -msgstr "" -"운전자 방향 카메라 데이터를 업로드하여 운전자 모니터링 알고리즘 개선에 도움" -"을 주세요." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." +msgstr "운전자 방향 카메라 데이터를 업로드하여 운전자 모니터링 알고리즘 개선에 도움을 주세요." -#: selfdrive/ui/layouts/settings/toggles.py:88 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "미터법 사용" -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" -"ACC 및 차선 유지 보조에 openpilot을 사용합니다. 이 기능을 사용할 때는 항상 주" -"의가 필요합니다." - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "차량" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "VIEW" msgstr "보기" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "시작 대기 중" -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" -"경고: 이는 GitHub 설정의 모든 공개 키에 SSH 액세스를 부여합니다. 자신의 것이 " -"아닌 GitHub 사용자 이름을 절대 입력하지 마세요. comma 직원이 본인의 GitHub 사" -"용자 이름 추가를 요구하는 일은 결코 없습니다." - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "openpilot에 오신 것을 환영합니다" -#: selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "이 옵션을 켜면 가속 페달을 밟을 때 openpilot이 해제됩니다." -#: selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Wi-Fi Network Metered" msgstr "Wi‑Fi 네트워크 종량제" -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "비밀번호가 올바르지 않습니다" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "openpilot을 사용하려면 약관에 동의해야 합니다." -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." -msgstr "" -"openpilot을 사용하려면 약관에 동의해야 합니다. 계속하기 전에 https://comma." -"ai/terms 에서 최신 약관을 읽어주세요." +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." +msgstr "openpilot을 사용하려면 약관에 동의해야 합니다. 계속하기 전에 https://comma.ai/terms 에서 최신 약관을 읽어주세요." -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 #, python-format msgid "camera starting" msgstr "카메라 시작 중" -#: selfdrive/ui/widgets/prime.py:63 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "checking..." +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma 프라임" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "default" msgstr "기본값" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "down" msgstr "아래" -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "downloading..." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "업데이트 확인 실패" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 +#, python-format +msgid "finalizing update..." +msgstr "" + +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 #, python-format msgid "for \"{}\"" msgstr "\"{}\"용" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "leave blank for automatic configuration" msgstr "자동 구성을 사용하려면 비워 두세요" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "left" msgstr "왼쪽" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "종량제" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "없음" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "지금" -#: selfdrive/ui/layouts/settings/developer.py:71 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "openpilot 롱컨 제어(알파)" -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot 사용 불가" -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." -msgstr "" -"openpilot은 기본적으로 안정적 모드로 주행합니다. 실험 모드를 사용하면 안정적 모드에 " -"아직 준비되지 않은 알파 수준의 기능이 활성화됩니다. 실험 기능은 아래와 같습니" -"다:

엔드투엔드 롱컨 제어


주행 모델이 가속과 제동을 제어합니" -"다. openpilot은 빨간 신호 및 정지 표지에서의 정지를 포함해 사람이 운전한다고 " -"판단하는 방식으로 주행합니다. 주행 속도는 모델이 결정하므로 설정 속도는 상한" -"으로만 동작합니다. 알파 품질 기능이므로 오작동이 발생할 수 있습니다.

" -"새로운 주행 시각화


저속에서는 도로 방향의 광각 카메라로 전환되어 일" -"부 회전을 더 잘 보여줍니다. 화면 오른쪽 위에는 실험 모드 로고도 표시됩니다." - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" -"openpilot은 지속적으로 보정을 진행하므로 재설정이 필요한 경우는 드뭅니다. 차" -"량 전원이 켜져 있을 때 보정을 재설정하면 openpilot이 재시작됩니다." - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" -"openpilot은 당신과 같은 사람의 운전을 보며 운전을 학습합니다.\n" -"\n" -"Firehose 모드는 학습 데이터 업로드를 최대화하여 openpilot의 주행 모델을 개선" -"할 수 있게 해줍니다. 데이터가 많을수록 모델은 커지고, 실험 모드는 더 좋아집니" -"다." - -#: selfdrive/ui/layouts/settings/toggles.py:183 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "openpilot 롱컨 제어는 향후 업데이트에서 제공될 수 있습니다." -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." msgstr "openpilot은 장치를 좌우 4°, 위쪽 5°, 아래쪽 9° 이내로 장착해야 합니다." -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "right" msgstr "오른쪽" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "비종량제" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "up" msgstr "위" -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "최신입니다. 마지막 확인: 없음" -#: selfdrive/ui/layouts/settings/software.py:115 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format msgid "up to date, last checked {}" msgstr "최신입니다. 마지막 확인: {}" -#: selfdrive/ui/layouts/settings/software.py:109 +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "업데이트 가능" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{}건의 알림" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{}일 전" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{}시간 전" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{}분 전" -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "현재까지 귀하의 주행 {}구간이 학습 데이터셋에 포함되었습니다." -#: selfdrive/ui/widgets/prime.py:62 +#: openpilot/selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ 구독됨" -#: selfdrive/ui/widgets/setup.py:22 +#: openpilot/selfdrive/ui/widgets/setup.py:21 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 파이어호스 모드 🔥" + diff --git a/selfdrive/ui/translations/app_pt-BR.po b/selfdrive/ui/translations/app_pt-BR.po index 84b53c6e8d..1adb797c88 100644 --- a/selfdrive/ui/translations/app_pt-BR.po +++ b/selfdrive/ui/translations/app_pt-BR.po @@ -19,1202 +19,1018 @@ msgstr "" "X-Language: pt_BR\n" "X-Source-Language: C\n" -#: selfdrive/ui/layouts/settings/device.py:160 +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 #, python-format msgid " Steering torque response calibration is complete." msgstr " A calibração da resposta de torque da direção foi concluída." -#: selfdrive/ui/layouts/settings/device.py:158 +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " A calibração da resposta de torque da direção está {}% concluída." -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Seu dispositivo está apontado {:.1f}° {} e {:.1f}° {}." -#: selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "1 ano de armazenamento de condução" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "Conectividade LTE 24/7" -#: selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" -"AVISO: o controle longitudinal do openpilot está em alpha para este carro " -"e desativará a Frenagem Automática de Emergência (AEB).

Neste " -"carro, o openpilot usa por padrão o ACC integrado do carro em vez do " -"controle longitudinal do openpilot. Ative isto para alternar para o controle " -"longitudinal do openpilot. Recomenda-se ativar o Modo Experimental ao ativar " -"o controle longitudinal do openpilot em alpha." - -#: selfdrive/ui/layouts/settings/device.py:148 +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 #, python-format msgid "

Steering lag calibration is complete." msgstr "

A calibração da latência da direção está concluída." -#: selfdrive/ui/layouts/settings/device.py:146 +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "

A calibração da latência da direção está {}% concluída." -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "ATIVO" - -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" -"ADB (Android Debug Bridge) permite conectar ao seu dispositivo via USB ou " -"pela rede. Veja https://docs.comma.ai/how-to/connect-to-comma para mais " -"informações." - -#: selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "ADICIONAR" -#: system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:136 #, python-format msgid "APN Setting" msgstr "Configuração de APN" -#: selfdrive/ui/widgets/offroad_alerts.py:109 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "Reconhecer Atuação Excessiva" -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "Avançado" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "Agressivo" -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "Concordo" -#: selfdrive/ui/layouts/settings/toggles.py:70 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "Monitoramento de Motorista Sempre Ativo" -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "" -"Uma versão alpha do controle longitudinal do openpilot pode ser testada, " -"junto com o Modo Experimental, em ramificações fora de release." - -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 #, python-format msgid "Are you sure you want to power off?" msgstr "Tem certeza de que deseja desligar?" -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 #, python-format msgid "Are you sure you want to reboot?" msgstr "Tem certeza de que deseja reiniciar?" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "Tem certeza de que deseja redefinir a calibração?" -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 #, python-format msgid "Are you sure you want to uninstall?" msgstr "Tem certeza de que deseja desinstalar?" -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "Voltar" -#: selfdrive/ui/widgets/prime.py:38 +#: openpilot/selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "Torne-se membro comma prime em connect.comma.ai" -#: selfdrive/ui/widgets/pairing_dialog.py:130 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "Adicione connect.comma.ai à tela inicial para usá-lo como um app" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "CHANGE" msgstr "ALTERAR" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 #, python-format msgid "CHECK" msgstr "VERIFICAR" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "CHILL MODE ON" msgstr "MODO CHILL ATIVO" -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "CONECTAR" -#: system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:376 #, python-format msgid "CONNECTING..." msgstr "CONECTANDO..." -#: system/ui/widgets/confirm_dialog.py:23 -#: system/ui/widgets/option_dialog.py:35 system/ui/widgets/keyboard.py:81 -#: system/ui/widgets/network.py:318 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format msgid "Cancel" msgstr "Cancelar" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "Dados móveis limitados" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "Change Language" msgstr "Alterar Idioma" -#: selfdrive/ui/layouts/settings/toggles.py:125 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." -msgstr "" -"Alterar esta configuração reiniciará o openpilot se o carro estiver ligado." +msgstr "Alterar esta configuração reiniciará o openpilot se o carro estiver ligado." -#: selfdrive/ui/widgets/pairing_dialog.py:129 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "Toque em \"adicionar novo dispositivo\" e escaneie o QR code à direita" -#: selfdrive/ui/widgets/offroad_alerts.py:104 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "Fechar" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "Versão Atual" -#: selfdrive/ui/layouts/settings/software.py:110 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "BAIXAR" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "Recusar" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "Recusar, desinstalar o openpilot" -#: selfdrive/ui/layouts/settings/settings.py:67 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 msgid "Developer" msgstr "Desenvolv" -#: selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 msgid "Device" msgstr "Dispositivo" -#: selfdrive/ui/layouts/settings/toggles.py:58 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "Desativar ao pressionar o acelerador" -#: selfdrive/ui/layouts/settings/device.py:184 +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 #, python-format msgid "Disengage to Power Off" msgstr "Desativar para Desligar" -#: selfdrive/ui/layouts/settings/device.py:172 +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 #, python-format msgid "Disengage to Reboot" msgstr "Desativar para Reiniciar" -#: selfdrive/ui/layouts/settings/device.py:103 +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 #, python-format msgid "Disengage to Reset Calibration" msgstr "Desativar para Redefinir Calibração" -#: selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "Exibir velocidade em km/h em vez de mph." -#: selfdrive/ui/layouts/settings/device.py:59 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 #, python-format msgid "Dongle ID" msgstr "ID do Dongle" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "Baixar" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Driver Camera" msgstr "Câmera do Motorista" -#: selfdrive/ui/layouts/settings/toggles.py:96 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "Personalidade" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "EDITAR" -#: selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "ERRO" -#: selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "MODO EXPERIMENTAL ATIVO" -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 #, python-format msgid "Enable" msgstr "Ativar" -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "Ativar ADB" -#: selfdrive/ui/layouts/settings/toggles.py:64 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "Ativar alertas de saída de faixa" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "Ativar openpilot" -#: selfdrive/ui/layouts/settings/developer.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "Ativar SSH" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "Ativar alertas de saída de faixa" -#: selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." -msgstr "" -"Ativar monitoramento do motorista mesmo quando o openpilot não está engajado." +msgstr "Ativar monitoramento do motorista mesmo quando o openpilot não está engajado." -#: selfdrive/ui/layouts/settings/toggles.py:46 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "Ativar openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:189 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 #, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." -msgstr "" -"Ative a opção de controle longitudinal do openpilot (alpha) para permitir o " -"Modo Experimental." +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." +msgstr "Ative a opção de controle longitudinal do openpilot (alpha) para permitir o Modo Experimental." -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "Enter APN" msgstr "Digite APN" -#: system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:243 #, python-format msgid "Enter SSID" msgstr "Digite SSID" -#: system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:257 #, python-format msgid "Enter new tethering password" msgstr "Digite nova senha tethering" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 #, python-format msgid "Enter password" msgstr "Digite a senha" -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "Digite seu nome de usuário do GitHub" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "Erro" -#: selfdrive/ui/layouts/settings/toggles.py:52 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "Modo Experimental" -#: selfdrive/ui/layouts/settings/toggles.py:181 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 #, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." -msgstr "" -"O Modo Experimental está indisponível neste carro pois o ACC original do " -"carro é usado para controle longitudinal." +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." +msgstr "O Modo Experimental está indisponível neste carro pois o ACC original do carro é usado para controle longitudinal." -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "ESQUECENDO..." -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "Configure" -#: selfdrive/ui/layouts/settings/settings.py:66 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 msgid "Firehose" msgstr "Firehose" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "Modo Firehose" -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" -"Para máxima efetividade, leve seu dispositivo para dentro e conecte a um bom " -"adaptador USB-C e Wi‑Fi semanalmente.\n" -"\n" -"O Modo Firehose também pode funcionar enquanto você dirige se estiver " -"conectado a um hotspot ou a um SIM ilimitado.\n" -"\n" -"\n" -"Perguntas Frequentes\n" -"\n" -"Importa como ou onde eu dirijo? Não, apenas dirija como normalmente.\n" -"\n" -"Todos os meus segmentos são puxados no Modo Firehose? Não, puxamos " -"seletivamente um subconjunto dos seus segmentos.\n" -"\n" -"Qual é um bom adaptador USB‑C? Qualquer carregador rápido de telefone ou " -"laptop serve.\n" -"\n" -"Importa qual software eu executo? Sim, apenas o openpilot upstream (e forks " -"específicos) podem ser usados para treinamento." - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 #, python-format msgid "Forget" msgstr "Esquecer" -#: system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:327 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "Esquecer rede Wi-Fi \"{}\"?" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "BOM" -#: selfdrive/ui/widgets/pairing_dialog.py:128 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "Acesse https://connect.comma.ai no seu telefone" -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "ALTO" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "Rede" -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "INATIVO: conecte a uma rede sem franquia" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "INSTALAR" -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "Endereço IP" -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "Instalar Atualização" -#: selfdrive/ui/layouts/settings/developer.py:56 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "Modo de Depuração do Joystick" -#: selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "CARREGANDO" -#: selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: selfdrive/ui/layouts/settings/developer.py:64 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "Modo de Manobra Longitudinal" -#: selfdrive/ui/onroad/hud_renderer.py:148 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "MÁX" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." -msgstr "" -"Maximize seus envios de dados de treinamento para melhorar os modelos de " -"condução do openpilot." +msgid "Maximize your training data uploads to improve openpilot's driving models." +msgstr "Maximize seus envios de dados de treinamento para melhorar os modelos de condução do openpilot." -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "N/A" msgstr "N/A" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "NÃO" -#: selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 msgid "Network" msgstr "Rede" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 #, python-format msgid "No SSH keys found" msgstr "Nenhuma chave SSH encontrada" -#: selfdrive/ui/widgets/ssh_key.py:126 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 #, python-format msgid "No SSH keys found for user '{}'" msgstr "Nenhuma chave SSH encontrada para o usuário '{username}'" -#: selfdrive/ui/widgets/offroad_alerts.py:320 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 #, python-format msgid "No release notes available." msgstr "Sem notas de versão disponíveis." -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "OFFLINE" -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "OK" -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 msgid "ONLINE" msgstr "ONLINE" -#: selfdrive/ui/widgets/setup.py:20 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Open" msgstr "Abrir" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "PAIR" msgstr "EMPARELHAR" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "PREVIEW" msgstr "PRÉVIA" -#: selfdrive/ui/widgets/prime.py:44 +#: openpilot/selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "RECURSOS PRIME:" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "Pair Device" msgstr "Emparelhar Dispositivo" -#: selfdrive/ui/widgets/setup.py:19 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format msgid "Pair device" msgstr "Emparelhar dispositivo" -#: selfdrive/ui/widgets/pairing_dialog.py:103 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "Emparelhe seu dispositivo à sua conta comma" -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." -msgstr "" -"Emparelhe seu dispositivo com o comma connect (connect.comma.ai) e resgate " -"sua oferta comma prime." +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." +msgstr "Emparelhe seu dispositivo com o comma connect (connect.comma.ai) e resgate sua oferta comma prime." -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "Conecte-se ao Wi‑Fi para concluir o emparelhamento inicial" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Power Off" msgstr "Desligar" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "Evitar uploads grandes de dados em conexões Wi-Fi limitadas" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "Evitar uploads grandes de dados em conexões móveis limitadas" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" -msgstr "" -"Pré-visualize a câmera voltada para o motorista para garantir que o " -"monitoramento do motorista tenha boa visibilidade. (veículo deve estar " -"desligado)" +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "Pré-visualize a câmera voltada para o motorista para garantir que o monitoramento do motorista tenha boa visibilidade. (veículo deve estar desligado)" -#: selfdrive/ui/widgets/pairing_dialog.py:161 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "Erro no QR Code" -#: selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "REMOVER" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "RESET" msgstr "REDEFINIR" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "REVIEW" msgstr "REVISAR" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Reboot" msgstr "Reiniciar" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "Reiniciar Dispositivo" -#: selfdrive/ui/widgets/offroad_alerts.py:112 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "Reiniciar e Atualizar" -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" -"Receba alertas para voltar à faixa quando seu veículo cruzar uma linha de " -"faixa detectada sem seta ativada ao dirigir acima de 31 mph (50 km/h)." - -#: selfdrive/ui/layouts/settings/toggles.py:76 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "Gravar e Enviar Câmera do Motorista" -#: selfdrive/ui/layouts/settings/toggles.py:82 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "Gravar e Enviar Áudio do Microfone" -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." -msgstr "" -"Grave e armazene o áudio do microfone enquanto dirige. O áudio será incluído " -"no vídeo da dashcam no comma connect." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." +msgstr "Grave e armazene o áudio do microfone enquanto dirige. O áudio será incluído no vídeo da dashcam no comma connect." -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Regulatory" msgstr "Regulatório" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "Relaxado" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "Acesso remoto" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "Capturas remotas" -#: selfdrive/ui/widgets/ssh_key.py:123 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 #, python-format msgid "Request timed out" msgstr "Tempo da solicitação esgotado" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Reset" msgstr "Redefinir" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "Reset Calibration" msgstr "Redefinir Calibração" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "Review Training Guide" msgstr "Revisar Guia de Treinamento" -#: selfdrive/ui/layouts/settings/device.py:27 +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 msgid "Review the rules, features, and limitations of openpilot" msgstr "Revise as regras, recursos e limitações do openpilot" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "SELECIONAR" -#: selfdrive/ui/layouts/settings/developer.py:53 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "Chaves SSH" -#: system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:316 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "Procurando redes Wi-Fi..." -#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:37 #, python-format msgid "Select" msgstr "Selecione" -#: selfdrive/ui/layouts/settings/software.py:183 +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 #, python-format msgid "Select a branch" msgstr "Selecione uma branch" -#: selfdrive/ui/layouts/settings/device.py:91 +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 #, python-format msgid "Select a language" msgstr "Selecione um idioma" -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "Serial" msgstr "Serial" -#: selfdrive/ui/widgets/offroad_alerts.py:106 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "Adiar Atualização" -#: selfdrive/ui/layouts/settings/settings.py:65 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 msgid "Software" msgstr "Software" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "Padrão" -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" -"Padrão é recomendado. No modo agressivo, o openpilot seguirá veículos à " -"frente mais de perto e será mais agressivo com acelerador e freio. No modo " -"relaxado, o openpilot ficará mais longe dos veículos à frente. Em carros " -"compatíveis, você pode alternar essas personalidades com o botão de " -"distância do volante." - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "Sistema sem resposta" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "ASSUMA O CONTROLE IMEDIATAMENTE" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "TEMP" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "Branch Alvo" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "Senha Tethering" -#: selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 msgid "Toggles" msgstr "Toggles" -#: selfdrive/ui/layouts/settings/software.py:72 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 +#, python-format +msgid "UI Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "UNINSTALL" msgstr "DESINSTALAR" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "ATUALIZAR" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "Uninstall" msgstr "Desinstalar" -#: selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "Desconhecido" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "Atualizações são baixadas apenas com o carro desligado." -#: selfdrive/ui/widgets/prime.py:33 +#: openpilot/selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "Atualizar Agora" -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." -msgstr "" -"Envie dados da câmera voltada para o motorista e ajude a melhorar o " -"algoritmo de monitoramento do motorista." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." +msgstr "Envie dados da câmera voltada para o motorista e ajude a melhorar o algoritmo de monitoramento do motorista." -#: selfdrive/ui/layouts/settings/toggles.py:88 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "Usar Sistema Métrico" -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" -"Use o sistema openpilot para controle de cruzeiro adaptativo e assistência " -"de permanência em faixa. Sua atenção é necessária o tempo todo para usar " -"este recurso." - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "VEÍCULO" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "VIEW" msgstr "VER" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "Aguardando para iniciar" -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" -"Aviso: Isso concede acesso SSH a todas as chaves públicas nas suas " -"configurações do GitHub. Nunca informe um nome de usuário do GitHub que não " -"seja o seu. Um funcionário da comma NUNCA pedirá para você adicionar o nome " -"de usuário do GitHub dele." - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "Bem-vindo ao openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." -msgstr "" -"Quando ativado, pressionar o pedal do acelerador desengajará o openpilot." +msgstr "Quando ativado, pressionar o pedal do acelerador desengajará o openpilot." -#: selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Wi-Fi Network Metered" msgstr "Rede Wi-Fi limitada" -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "Senha errada" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "Você deve aceitar os Termos e Condições para usar o openpilot." -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." -msgstr "" -"Você deve aceitar os Termos e Condições para usar o openpilot. Leia os " -"termos mais recentes em https://comma.ai/terms antes de continuar." +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." +msgstr "Você deve aceitar os Termos e Condições para usar o openpilot. Leia os termos mais recentes em https://comma.ai/terms antes de continuar." -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 #, python-format msgid "camera starting" msgstr "câmera iniciando" -#: selfdrive/ui/widgets/prime.py:63 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "checking..." +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "default" msgstr "default" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "down" msgstr "para baixo" -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "downloading..." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "falha ao verificar atualização" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 +#, python-format +msgid "finalizing update..." +msgstr "" + +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 #, python-format msgid "for \"{}\"" msgstr "para \"{}\"" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "leave blank for automatic configuration" msgstr "deixe em branco para configuração automática" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "left" msgstr "à esquerda" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "limitados" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "nunca" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "agora" -#: selfdrive/ui/layouts/settings/developer.py:71 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "Controle Longitudinal do openpilot (Alpha)" -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot Indisponível" -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables " -"alpha-level features that aren't ready for chill mode. Experimental features " -"are listed below:

End-to-End Longitudinal Control


Let the " -"driving model control the gas and brakes. openpilot will drive as it thinks " -"a human would, including stopping for red lights and stop signs. Since the " -"driving model decides the speed to drive, the set speed will only act as an " -"upper bound. This is an alpha quality feature; mistakes should be " -"expected.

New Driving Visualization


The driving visualization " -"will transition to the road-facing wide-angle camera at low speeds to better " -"show some turns. The Experimental mode logo will also be shown in the top " -"right corner." -msgstr "" -"o openpilot dirige por padrão no modo chill. O Modo Experimental habilita " -"recursos em nível alpha que não estão prontos para o modo chill. Os recursos " -"experimentais são listados abaixo:

Controle Longitudinal " -"End-to-End


Permita que o modelo de condução controle o acelerador e " -"os freios. O openpilot dirigirá como acha que um humano faria, incluindo " -"parar em sinais e semáforos vermelhos. Como o modelo decide a velocidade, a " -"velocidade definida atuará apenas como limite superior. Este é um recurso de " -"qualidade alpha; erros devem ser esperados.

Nova Visualização de " -"Condução


A visualização de condução mudará para a câmera " -"grande-angular voltada para a estrada em baixas velocidades para mostrar " -"melhor algumas curvas. O logotipo do Modo Experimental também será exibido " -"no canto superior direito." - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" -"O openpilot está continuamente calibrando, resetar é raramente solicitado. " -"Alterar esta configuração reiniciará o openpilot se o carro estiver ligado." - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" -"o openpilot aprende a dirigir observando humanos, como você, dirigirem.\n" -"\n" -"O Modo Firehose permite maximizar seus envios de dados de treinamento para " -"melhorar os modelos de condução do openpilot. Mais dados significam modelos " -"maiores, o que significa um Modo Experimental melhor." - -#: selfdrive/ui/layouts/settings/toggles.py:183 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 #, python-format msgid "openpilot longitudinal control may come in a future update." -msgstr "" -"o controle longitudinal do openpilot pode vir em uma atualização futura." +msgstr "o controle longitudinal do openpilot pode vir em uma atualização futura." -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." -msgstr "" -"o openpilot requer que o dispositivo seja montado dentro de 4° para a " -"esquerda ou direita e dentro de 5° para cima ou 9° para baixo." +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." +msgstr "o openpilot requer que o dispositivo seja montado dentro de 4° para a esquerda ou direita e dentro de 5° para cima ou 9° para baixo." -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "right" msgstr "à direita" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "ilimitados" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "up" msgstr "para cima" -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "atualizado, última verificação: nunca" -#: selfdrive/ui/layouts/settings/software.py:115 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format msgid "up to date, last checked {}" msgstr "atualizado, última verificação: {}" -#: selfdrive/ui/layouts/settings/software.py:109 +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "atualização disponível" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} ALERTA" msgstr[1] "{} ALERTAS" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{} dia atrás" msgstr[1] "{} dias atrás" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{} hora atrás" msgstr[1] "{} horas atrás" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{} minuto atrás" msgstr[1] "{} minutos atrás" -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." -msgstr[0] "" -"{} segmento da sua condução está no conjunto de treinamento até agora." -msgstr[1] "" -"{} segmentos da sua condução estão no conjunto de treinamento até agora." +msgstr[0] "{} segmento da sua condução está no conjunto de treinamento até agora." +msgstr[1] "{} segmentos da sua condução estão no conjunto de treinamento até agora." -#: selfdrive/ui/widgets/prime.py:62 +#: openpilot/selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ ASSINADO" -#: selfdrive/ui/widgets/setup.py:22 +#: openpilot/selfdrive/ui/widgets/setup.py:21 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Modo Firehose 🔥" + diff --git a/selfdrive/ui/translations/app_th.po b/selfdrive/ui/translations/app_th.po index f2e56f2882..facf52d922 100644 --- a/selfdrive/ui/translations/app_th.po +++ b/selfdrive/ui/translations/app_th.po @@ -17,1113 +17,1018 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: selfdrive/ui/layouts/settings/device.py:160 +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 #, python-format msgid " Steering torque response calibration is complete." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:158 +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr "" -#: selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:148 +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 #, python-format msgid "

Steering lag calibration is complete." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:146 +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "" -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "" - -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" - -#: selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "" -#: system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:136 #, python-format msgid "APN Setting" msgstr "" -#: selfdrive/ui/widgets/offroad_alerts.py:109 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "" -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "" -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:70 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 #, python-format msgid "Are you sure you want to power off?" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 #, python-format msgid "Are you sure you want to reboot?" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 #, python-format msgid "Are you sure you want to uninstall?" msgstr "" -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "" -#: selfdrive/ui/widgets/prime.py:38 +#: openpilot/selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "" -#: selfdrive/ui/widgets/pairing_dialog.py:130 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "CHANGE" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 #, python-format msgid "CHECK" msgstr "" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "CHILL MODE ON" msgstr "" -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "" -#: system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:376 #, python-format msgid "CONNECTING..." msgstr "" -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format msgid "Cancel" msgstr "" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "Change Language" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:125 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "" -#: selfdrive/ui/widgets/pairing_dialog.py:129 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "" -#: selfdrive/ui/widgets/offroad_alerts.py:104 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:110 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "" -#: selfdrive/ui/layouts/settings/settings.py:67 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 msgid "Developer" msgstr "" -#: selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 msgid "Device" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:58 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:184 +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 #, python-format msgid "Disengage to Power Off" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:172 +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 #, python-format msgid "Disengage to Reboot" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:103 +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 #, python-format msgid "Disengage to Reset Calibration" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:59 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 #, python-format msgid "Dongle ID" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Driver Camera" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:96 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 #, python-format msgid "Enable" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:64 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:46 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:189 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 #, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "Enter APN" msgstr "" -#: system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:243 #, python-format msgid "Enter SSID" msgstr "" -#: system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:257 #, python-format msgid "Enter new tethering password" msgstr "" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 #, python-format msgid "Enter password" msgstr "" -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:52 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:181 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 #, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." msgstr "" -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "" -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "" -#: selfdrive/ui/layouts/settings/settings.py:66 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 msgid "Firehose" msgstr "" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "" -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 #, python-format msgid "Forget" msgstr "" -#: system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:327 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "" -#: selfdrive/ui/widgets/pairing_dialog.py:128 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "" -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "" -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:56 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "" -#: selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:64 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "" -#: selfdrive/ui/onroad/hud_renderer.py:148 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." +msgid "Maximize your training data uploads to improve openpilot's driving models." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "N/A" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "" -#: selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 msgid "Network" msgstr "" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 #, python-format msgid "No SSH keys found" msgstr "" -#: selfdrive/ui/widgets/ssh_key.py:126 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 #, python-format msgid "No SSH keys found for user '{}'" msgstr "" -#: selfdrive/ui/widgets/offroad_alerts.py:320 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 #, python-format msgid "No release notes available." msgstr "" -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "" -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 msgid "ONLINE" msgstr "" -#: selfdrive/ui/widgets/setup.py:20 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Open" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "PAIR" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "PREVIEW" msgstr "" -#: selfdrive/ui/widgets/prime.py:44 +#: openpilot/selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "Pair Device" msgstr "" -#: selfdrive/ui/widgets/setup.py:19 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format msgid "Pair device" msgstr "" -#: selfdrive/ui/widgets/pairing_dialog.py:103 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "" -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." msgstr "" -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Power Off" msgstr "" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" msgstr "" -#: selfdrive/ui/widgets/pairing_dialog.py:161 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "" -#: selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "RESET" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "REVIEW" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Reboot" msgstr "" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "" -#: selfdrive/ui/widgets/offroad_alerts.py:112 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:76 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:82 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Regulatory" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "" -#: selfdrive/ui/widgets/ssh_key.py:123 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 #, python-format msgid "Request timed out" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Reset" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "Reset Calibration" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "Review Training Guide" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:27 +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 msgid "Review the rules, features, and limitations of openpilot" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:53 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "" -#: system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:316 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "" -#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:37 #, python-format msgid "Select" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:183 +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 #, python-format msgid "Select a branch" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:91 +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 #, python-format msgid "Select a language" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "Serial" msgstr "" -#: selfdrive/ui/widgets/offroad_alerts.py:106 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "" -#: selfdrive/ui/layouts/settings/settings.py:65 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 msgid "Software" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "" -#: selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 msgid "Toggles" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:72 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 +#, python-format +msgid "UI Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "UNINSTALL" msgstr "" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "Uninstall" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "" -#: selfdrive/ui/widgets/prime.py:33 +#: openpilot/selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:88 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "VIEW" msgstr "" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "" -#: selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Wi-Fi Network Metered" msgstr "" -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "" -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." msgstr "" -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 #, python-format msgid "camera starting" msgstr "" -#: selfdrive/ui/widgets/prime.py:63 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "checking..." +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "default" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "down" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "downloading..." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 +#, python-format +msgid "finalizing update..." +msgstr "" + +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 #, python-format msgid "for \"{}\"" msgstr "" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "leave blank for automatic configuration" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "left" msgstr "" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:71 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "" -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:183 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "right" msgstr "" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "up" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:115 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format msgid "up to date, last checked {}" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:109 +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "" msgstr[1] "" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "" msgstr[1] "" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "" msgstr[1] "" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "" msgstr[1] "" -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "" msgstr[1] "" -#: selfdrive/ui/widgets/prime.py:62 +#: openpilot/selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "" -#: selfdrive/ui/widgets/setup.py:22 +#: openpilot/selfdrive/ui/widgets/setup.py:21 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "" + diff --git a/selfdrive/ui/translations/app_tr.po b/selfdrive/ui/translations/app_tr.po index 10191234a1..137d350c2a 100644 --- a/selfdrive/ui/translations/app_tr.po +++ b/selfdrive/ui/translations/app_tr.po @@ -17,1194 +17,1018 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: selfdrive/ui/layouts/settings/device.py:160 +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 #, python-format msgid " Steering torque response calibration is complete." msgstr " Direksiyon tork tepkisi kalibrasyonu tamamlandı." -#: selfdrive/ui/layouts/settings/device.py:158 +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " Direksiyon tork tepkisi kalibrasyonu {}% tamamlandı." -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Cihazınız {:.1f}° {} ve {:.1f}° {} yönünde konumlandırılmış." -#: selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "1 yıl sürüş depolaması" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "7/24 LTE bağlantısı" -#: selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" -"UYARI: Bu araç için openpilot boylamsal kontrolü alfa aşamasındadır ve " -"Otomatik Acil Frenlemeyi (AEB) devre dışı bırakacaktır.

Bu araçta " -"openpilot, openpilot'un boylamsal kontrolü yerine aracın yerleşik ACC'sini " -"varsayılan olarak kullanır. openpilot boylamsal kontrolüne geçmek için bunu " -"etkinleştirin. openpilot boylamsal kontrol alfayı etkinleştirirken Deneysel " -"modu etkinleştirmeniz önerilir." - -#: selfdrive/ui/layouts/settings/device.py:148 +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 #, python-format msgid "

Steering lag calibration is complete." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:146 +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "" -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "AKTİF" - -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" -"ADB (Android Debug Bridge), cihazınıza USB veya ağ üzerinden bağlanmayı " -"sağlar. Daha fazla bilgi için https://docs.comma.ai/how-to/connect-to-comma " -"adresine bakın." - -#: selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "EKLE" -#: system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:136 #, python-format msgid "APN Setting" msgstr "" -#: selfdrive/ui/widgets/offroad_alerts.py:109 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "Aşırı Müdahaleyi Onayla" -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "Agresif" -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "Kabul et" -#: selfdrive/ui/layouts/settings/toggles.py:70 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "Sürekli Sürücü İzleme" -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "" -"openpilot boylamsal kontrolünün alfa sürümü, Deneysel mod ile birlikte, " -"yayın dışı dallarda test edilebilir." - -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 #, python-format msgid "Are you sure you want to power off?" msgstr "Kapatmak istediğinizden emin misiniz?" -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 #, python-format msgid "Are you sure you want to reboot?" msgstr "Yeniden başlatmak istediğinizden emin misiniz?" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "Kalibrasyonu sıfırlamak istediğinizden emin misiniz?" -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 #, python-format msgid "Are you sure you want to uninstall?" msgstr "Kaldırmak istediğinizden emin misiniz?" -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "Geri" -#: selfdrive/ui/widgets/prime.py:38 +#: openpilot/selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "connect.comma.ai adresinde comma prime üyesi olun" -#: selfdrive/ui/widgets/pairing_dialog.py:130 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" -msgstr "" -"connect.comma.ai'yi ana ekranınıza ekleyerek bir uygulama gibi kullanın" +msgstr "connect.comma.ai'yi ana ekranınıza ekleyerek bir uygulama gibi kullanın" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "CHANGE" msgstr "DEĞİŞTİR" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 #, python-format msgid "CHECK" msgstr "KONTROL ET" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "CHILL MODE ON" msgstr "CHILL MODU AÇIK" -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "BAĞLAN" -#: system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:376 #, python-format msgid "CONNECTING..." msgstr "BAĞLAN" -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format msgid "Cancel" msgstr "" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "Change Language" msgstr "Dili Değiştir" -#: selfdrive/ui/layouts/settings/toggles.py:125 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." -msgstr "" -" Bu ayarı değiştirmek, araç çalışıyorsa openpilot'u yeniden başlatacaktır." +msgstr " Bu ayarı değiştirmek, araç çalışıyorsa openpilot'u yeniden başlatacaktır." -#: selfdrive/ui/widgets/pairing_dialog.py:129 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "\"yeni cihaz ekle\"ye tıklayın ve sağdaki QR kodunu tarayın" -#: selfdrive/ui/widgets/offroad_alerts.py:104 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "Kapat" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "Geçerli Sürüm" -#: selfdrive/ui/layouts/settings/software.py:110 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "İNDİR" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "Reddet" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "Reddet, openpilot'u kaldır" -#: selfdrive/ui/layouts/settings/settings.py:67 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 msgid "Developer" msgstr "Geliştirici" -#: selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 msgid "Device" msgstr "Cihaz" -#: selfdrive/ui/layouts/settings/toggles.py:58 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "Gaz Pedalında Devreden Çık" -#: selfdrive/ui/layouts/settings/device.py:184 +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 #, python-format msgid "Disengage to Power Off" msgstr "Kapatmak için Devreden Çıkın" -#: selfdrive/ui/layouts/settings/device.py:172 +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 #, python-format msgid "Disengage to Reboot" msgstr "Yeniden Başlatmak için Devreden Çıkın" -#: selfdrive/ui/layouts/settings/device.py:103 +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 #, python-format msgid "Disengage to Reset Calibration" msgstr "Kalibrasyonu Sıfırlamak için Devreden Çıkın" -#: selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "Hızı mph yerine km/h olarak göster." -#: selfdrive/ui/layouts/settings/device.py:59 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 #, python-format msgid "Dongle ID" msgstr "Dongle ID" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "İndir" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Driver Camera" msgstr "Sürücü Kamerası" -#: selfdrive/ui/layouts/settings/toggles.py:96 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "Sürüş Kişiliği" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "HATA" -#: selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "DENEYSEL MOD AÇIK" -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 #, python-format msgid "Enable" msgstr "Etkinleştir" -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "ADB'yi Etkinleştir" -#: selfdrive/ui/layouts/settings/toggles.py:64 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "Şerit Terk Uyarılarını Etkinleştir" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "openpilot'u etkinleştir" -#: selfdrive/ui/layouts/settings/developer.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "SSH'yi Etkinleştir" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "Şerit Terk Uyarılarını Etkinleştir" -#: selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "openpilot devrede değilken bile sürücü izlemesini etkinleştir." -#: selfdrive/ui/layouts/settings/toggles.py:46 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "openpilot'u etkinleştir" -#: selfdrive/ui/layouts/settings/toggles.py:189 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 #, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." -msgstr "" -"Deneysel modu etkinleştirmek için openpilot boylamsal kontrolünü (alfa) açın." +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." +msgstr "Deneysel modu etkinleştirmek için openpilot boylamsal kontrolünü (alfa) açın." -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "Enter APN" msgstr "" -#: system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:243 #, python-format msgid "Enter SSID" msgstr "" -#: system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:257 #, python-format msgid "Enter new tethering password" msgstr "" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 #, python-format msgid "Enter password" msgstr "" -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "GitHub kullanıcı adınızı girin" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:52 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "Deneysel Mod" -#: selfdrive/ui/layouts/settings/toggles.py:181 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 #, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." -msgstr "" -"Bu araçta boylamsal kontrol için stok ACC kullanıldığından şu anda Deneysel " -"mod kullanılamıyor." +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." +msgstr "Bu araçta boylamsal kontrol için stok ACC kullanıldığından şu anda Deneysel mod kullanılamıyor." -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "" -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "Kurulumu Bitir" -#: selfdrive/ui/layouts/settings/settings.py:66 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 msgid "Firehose" msgstr "Firehose" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "Firehose Modu" -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" -"Maksimum verim için cihazınızı içeri alın ve haftalık olarak iyi bir USB-C " -"adaptörüne ve Wi‑Fi'a bağlayın.\n" -"\n" -"Firehose Modu, bir hotspot'a veya sınırsız SIM karta bağlıyken sürüş " -"sırasında da çalışabilir.\n" -"\n" -"\n" -"Sıkça Sorulan Sorular\n" -"\n" -"Nasıl veya nerede sürdüğüm önemli mi? Hayır, normalde nasıl sürüyorsanız " -"öyle sürün.\n" -"\n" -"Firehose Modu'nda tüm segmentlerim çekiliyor mu? Hayır, segmentlerinizin bir " -"alt kümesini seçerek çekiyoruz.\n" -"\n" -"İyi bir USB‑C adaptörü nedir? Hızlı bir telefon veya dizüstü şarj cihazı " -"uygundur.\n" -"\n" -"Hangi yazılımı çalıştırdığım önemli mi? Evet, yalnızca upstream openpilot " -"(ve bazı fork'lar) eğitim için kullanılabilir." - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 #, python-format msgid "Forget" msgstr "" -#: system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:327 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "İYİ" -#: selfdrive/ui/widgets/pairing_dialog.py:128 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "Telefonunuzda https://connect.comma.ai adresine gidin" -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "YÜKSEK" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "Ağ" -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "PASİF: sınırsız bir ağa bağlanın" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "YÜKLE" -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "Güncellemeyi Yükle" -#: selfdrive/ui/layouts/settings/developer.py:56 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "Joystick Hata Ayıklama Modu" -#: selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "YÜKLENİYOR" -#: selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: selfdrive/ui/layouts/settings/developer.py:64 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "Boylamsal Manevra Modu" -#: selfdrive/ui/onroad/hud_renderer.py:148 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "MAKS" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." -msgstr "" -"openpilot'un sürüş modellerini iyileştirmek için eğitim veri yüklemelerinizi " -"en üst düzeye çıkarın." +msgid "Maximize your training data uploads to improve openpilot's driving models." +msgstr "openpilot'un sürüş modellerini iyileştirmek için eğitim veri yüklemelerinizi en üst düzeye çıkarın." -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "N/A" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "HAYIR" -#: selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 msgid "Network" msgstr "Ağ" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 #, python-format msgid "No SSH keys found" msgstr "SSH anahtarı bulunamadı" -#: selfdrive/ui/widgets/ssh_key.py:126 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 #, python-format msgid "No SSH keys found for user '{}'" msgstr "'{username}' için SSH anahtarı bulunamadı" -#: selfdrive/ui/widgets/offroad_alerts.py:320 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 #, python-format msgid "No release notes available." msgstr "Sürüm notu mevcut değil." -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "ÇEVRİMDIŞI" -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "OK" -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 msgid "ONLINE" msgstr "ÇEVRİMİÇİ" -#: selfdrive/ui/widgets/setup.py:20 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Open" msgstr "Aç" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "PAIR" msgstr "EŞLE" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "PREVIEW" msgstr "ÖNİZLEME" -#: selfdrive/ui/widgets/prime.py:44 +#: openpilot/selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "PRIME ÖZELLİKLERİ:" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "Pair Device" msgstr "Cihazı Eşle" -#: selfdrive/ui/widgets/setup.py:19 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format msgid "Pair device" msgstr "Cihazı eşle" -#: selfdrive/ui/widgets/pairing_dialog.py:103 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "Cihazınızı comma hesabınızla eşleştirin" -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." -msgstr "" -"Cihazınızı comma connect (connect.comma.ai) ile eşleştirin ve comma prime " -"teklifinizi alın." +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." +msgstr "Cihazınızı comma connect (connect.comma.ai) ile eşleştirin ve comma prime teklifinizi alın." -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "İlk eşleştirmeyi tamamlamak için lütfen Wi‑Fi'a bağlanın" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Power Off" msgstr "Kapat" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" -msgstr "" -"Sürücü izleme görünürlüğünün iyi olduğundan emin olmak için sürücüye bakan " -"kamerayı önizleyin. (araç kapalı olmalıdır)" +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "Sürücü izleme görünürlüğünün iyi olduğundan emin olmak için sürücüye bakan kamerayı önizleyin. (araç kapalı olmalıdır)" -#: selfdrive/ui/widgets/pairing_dialog.py:161 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "QR Kod Hatası" -#: selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "KALDIR" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "RESET" msgstr "SIFIRLA" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "REVIEW" msgstr "GÖZDEN GEÇİR" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Reboot" msgstr "Yeniden Başlat" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "Cihazı Yeniden Başlat" -#: selfdrive/ui/widgets/offroad_alerts.py:112 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "Yeniden Başlat ve Güncelle" -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" -"Araç 31 mph (50 km/h) üzerindeyken sinyal verilmeden algılanan şerit " -"çizgisini aştığınızda şeride geri dönmeniz için uyarılar alın." - -#: selfdrive/ui/layouts/settings/toggles.py:76 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "Sürücü Kamerasını Kaydet ve Yükle" -#: selfdrive/ui/layouts/settings/toggles.py:82 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "Mikrofon Sesini Kaydet ve Yükle" -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." -msgstr "" -"Sürüş sırasında mikrofon sesini kaydedip saklayın. Ses, comma connect'teki " -"ön kamera videosuna dahil edilecektir." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." +msgstr "Sürüş sırasında mikrofon sesini kaydedip saklayın. Ses, comma connect'teki ön kamera videosuna dahil edilecektir." -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Regulatory" msgstr "Mevzuat" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "Rahat" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "Uzaktan erişim" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "Uzaktan anlık görüntüler" -#: selfdrive/ui/widgets/ssh_key.py:123 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 #, python-format msgid "Request timed out" msgstr "İstek zaman aşımına uğradı" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Reset" msgstr "Sıfırla" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "Reset Calibration" msgstr "Kalibrasyonu Sıfırla" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "Review Training Guide" msgstr "Eğitim Kılavuzunu İncele" -#: selfdrive/ui/layouts/settings/device.py:27 +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 msgid "Review the rules, features, and limitations of openpilot" -msgstr "" -"openpilot'un kurallarını, özelliklerini ve sınırlamalarını gözden geçirin" +msgstr "openpilot'un kurallarını, özelliklerini ve sınırlamalarını gözden geçirin" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:53 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "" -#: system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:316 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "" -#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:37 #, python-format msgid "Select" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:183 +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 #, python-format msgid "Select a branch" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:91 +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 #, python-format msgid "Select a language" msgstr "Bir dil seçin" -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "Serial" msgstr "Seri" -#: selfdrive/ui/widgets/offroad_alerts.py:106 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "Güncellemeyi Ertele" -#: selfdrive/ui/layouts/settings/settings.py:65 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 msgid "Software" msgstr "Yazılım" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "Standart" -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" -"Standart önerilir. Agresif modda openpilot öndeki aracı daha yakından takip " -"eder ve gaz/fren kullanımında daha ataktır. Rahat modda openpilot öndeki " -"araçlardan daha uzak durur. Desteklenen araçlarda bu kişilikler arasında " -"direksiyon mesafe düğmesiyle geçiş yapabilirsiniz." - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "Sistem Yanıt Vermiyor" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "HEMEN KONTROLÜ DEVRALIN" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "TEMP" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "" -#: selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 msgid "Toggles" msgstr "Seçenekler" -#: selfdrive/ui/layouts/settings/software.py:72 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 +#, python-format +msgid "UI Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "UNINSTALL" msgstr "KALDIR" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "GÜNCELLE" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "Uninstall" msgstr "Kaldır" -#: selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "Bilinmiyor" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "Güncellemeler yalnızca araç kapalıyken indirilir." -#: selfdrive/ui/widgets/prime.py:33 +#: openpilot/selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "Şimdi Yükselt" -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." -msgstr "" -"Sürücüye bakan kameradan veri yükleyin ve sürücü izleme algoritmasını " -"geliştirmeye yardımcı olun." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." +msgstr "Sürücüye bakan kameradan veri yükleyin ve sürücü izleme algoritmasını geliştirmeye yardımcı olun." -#: selfdrive/ui/layouts/settings/toggles.py:88 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "Metrik Sistemi Kullan" -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" -"Uyarlanabilir hız sabitleyici ve şerit koruma sürücü yardımında openpilot " -"sistemini kullanın. Bu özelliği kullanırken her zaman dikkatli olmanız " -"gerekir." - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "ARAÇ" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "VIEW" msgstr "GÖRÜNTÜLE" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "Başlatma bekleniyor" -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" -"Uyarı: Bu, GitHub ayarlarınızdaki tüm açık anahtarlara SSH erişimi verir. " -"Kendi adınız dışında asla bir GitHub kullanıcı adı girmeyin. Bir comma " -"çalışanı sizden asla GitHub kullanıcı adlarını eklemenizi İSTEMEZ." - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "openpilot'a hoş geldiniz" -#: selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." -msgstr "" -"Etkinleştirildiğinde, gaz pedalına basmak openpilot'u devreden çıkarır." +msgstr "Etkinleştirildiğinde, gaz pedalına basmak openpilot'u devreden çıkarır." -#: selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Wi-Fi Network Metered" msgstr "" -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "openpilot'u kullanmak için Şartlar ve Koşulları kabul etmelisiniz." -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." -msgstr "" -"openpilot'u kullanmak için Şartlar ve Koşulları kabul etmelisiniz. Devam " -"etmeden önce en güncel şartları https://comma.ai/terms adresinde okuyun." +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." +msgstr "openpilot'u kullanmak için Şartlar ve Koşulları kabul etmelisiniz. Devam etmeden önce en güncel şartları https://comma.ai/terms adresinde okuyun." -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 #, python-format msgid "camera starting" msgstr "kamera başlatılıyor" -#: selfdrive/ui/widgets/prime.py:63 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "checking..." +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "default" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "down" msgstr "aşağı" -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "downloading..." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "güncelleme kontrolü başarısız" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 +#, python-format +msgid "finalizing update..." +msgstr "" + +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 #, python-format msgid "for \"{}\"" msgstr "" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "leave blank for automatic configuration" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "left" msgstr "sol" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "asla" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "şimdi" -#: selfdrive/ui/layouts/settings/developer.py:71 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "openpilot Boylamsal Kontrol (Alfa)" -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot Kullanılamıyor" -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." -msgstr "" -"openpilot varsayılan olarak chill modunda sürer. Deneysel mod, chill moduna " -"hazır olmayan alfa seviyesindeki özellikleri etkinleştirir. Deneysel " -"özellikler aşağıda listelenmiştir:

Uçtan Uca Boylamsal Kontrol
Sürüş modelinin gaz ve frenleri kontrol etmesine izin verin. " -"openpilot, kırmızı ışıklarda ve dur işaretlerinde durmak dahil, bir insan " -"nasıl sürer diye düşündüğüne göre sürer. Hızı sürüş modeli belirlediğinden, " -"ayarlanan hız yalnızca üst sınır olarak işlev görür. Bu bir alfa kalitesinde " -"özelliktir; hatalar beklenmelidir.

Yeni Sürüş Görselleştirmesi
Sürüş görselleştirmesi, düşük hızlarda bazı dönüşleri daha iyi " -"göstermek için yola bakan geniş açılı kameraya geçer. Deneysel mod logosu " -"sağ üst köşede de gösterilecektir." - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" -" Bu ayarı değiştirmek, araç çalışıyorsa openpilot'u yeniden başlatacaktır." - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" -"openpilot, sizin gibi insanların nasıl sürdüğünü izleyerek sürmeyi öğrenir.\n" -"\n" -"Firehose Modu, openpilot'un sürüş modellerini geliştirmek için eğitim veri " -"yüklemelerinizi en üst düzeye çıkarmanıza olanak tanır. Daha fazla veri, " -"daha büyük modeller demektir; bu da daha iyi Deneysel Mod anlamına gelir." - -#: selfdrive/ui/layouts/settings/toggles.py:183 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "openpilot boylamsal kontrolü gelecekteki bir güncellemede gelebilir." -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." -msgstr "" -"openpilot, cihazın sağa/sola 4° ve yukarı 5° veya aşağı 9° içinde monte " -"edilmesini gerektirir." +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." +msgstr "openpilot, cihazın sağa/sola 4° ve yukarı 5° veya aşağı 9° içinde monte edilmesini gerektirir." -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "right" msgstr "sağ" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "up" msgstr "yukarı" -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "güncel, son kontrol asla" -#: selfdrive/ui/layouts/settings/software.py:115 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format msgid "up to date, last checked {}" msgstr "güncel, son kontrol {}" -#: selfdrive/ui/layouts/settings/software.py:109 +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "güncelleme mevcut" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} UYARI" msgstr[1] "{} UYARILAR" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{} gün önce" msgstr[1] "{} gün önce" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{} saat önce" msgstr[1] "{} saat önce" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{} dakika önce" msgstr[1] "{} dakika önce" -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "{} segment sürüşünüz eğitim veri setinde." msgstr[1] "{} segment sürüşünüz eğitim veri setinde." -#: selfdrive/ui/widgets/prime.py:62 +#: openpilot/selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ ABONE" -#: selfdrive/ui/widgets/setup.py:22 +#: openpilot/selfdrive/ui/widgets/setup.py:21 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehose Modu 🔥" + diff --git a/selfdrive/ui/translations/app_uk.po b/selfdrive/ui/translations/app_uk.po index cf78fb5a33..e36ceac2ce 100644 --- a/selfdrive/ui/translations/app_uk.po +++ b/selfdrive/ui/translations/app_uk.po @@ -15,1193 +15,980 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " -"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "X-Generator: Poedit 3.8\n" -#: selfdrive/ui/layouts/settings/device.py:160 +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 #, python-format msgid " Steering torque response calibration is complete." msgstr " Калібрування реакції крутного моменту керма завершено." -#: selfdrive/ui/layouts/settings/device.py:158 +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr "Калібрування реакції крутного моменту керма завершено на {}%." -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Ваш пристрій нахилено на {:.1f}° {} та {:.1f}° {}." -#: selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "1 рік зберігання поїздок" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "Підключення LTE 24/7" -#: selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" -"ПОПЕРЕДЖЕННЯ: поздовжнє керування openpilot для цього автомобіля знаходиться " -"в стадії альфа-тестування і вимкне автоматичне екстрене гальмування (AEB)." - -#: selfdrive/ui/layouts/settings/device.py:148 +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 #, python-format msgid "

Steering lag calibration is complete." msgstr "

Калібрування затримки кермування завершено." -#: selfdrive/ui/layouts/settings/device.py:146 +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "

Калібрування затримки кермування завершено на {}%." -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "АКТИВНИЙ" - -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" -"ADB (Android Debug Bridge) дозволяє підключатися до вашого пристрою через " -"USB або мережу. Дивіться https://docs.comma.ai/how-to/connect-to-comma для " -"отримання додаткової інформації." - -#: selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "ДОДАТИ" -#: system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:136 #, python-format msgid "APN Setting" msgstr "Налаштування APN" -#: selfdrive/ui/widgets/offroad_alerts.py:109 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "Визнайте надмірне спрацьовування" -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "Розширені" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "Агресивн." -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "Погодитися" -#: selfdrive/ui/layouts/settings/toggles.py:70 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "Постійний моніторинг водія" -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "" -"Альфа-версію поздовжнього керування openpilot можна протестувати разом з " -"експериментальним режимом на нерелізних гілках." - -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 #, python-format msgid "Are you sure you want to power off?" msgstr "Ви впевнені, що хочете вимкнути?" -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 #, python-format msgid "Are you sure you want to reboot?" msgstr "Ви впевнені, що хочете перезавантажити?" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "Ви впевнені, що хочете скинути калібрування?" -#: selfdrive/ui/layouts/settings/software.py:171 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 #, python-format msgid "Are you sure you want to uninstall?" msgstr "Ви впевнені, що хочете видалити?" -#: system/ui/widgets/network.py:99 -#: selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "Назад" -#: selfdrive/ui/widgets/prime.py:38 +#: openpilot/selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "Станьте членом comma prime на connect.comma.ai" -#: selfdrive/ui/widgets/pairing_dialog.py:119 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" -msgstr "" -"Додайте connect.comma.ai до головного екрану, щоб використовувати його як " -"додаток." +msgstr "Додайте connect.comma.ai до головного екрану, щоб використовувати його як додаток." -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "CHANGE" msgstr "ЗМІНИТИ" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:115 -#: selfdrive/ui/layouts/settings/software.py:126 -#: selfdrive/ui/layouts/settings/software.py:155 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 #, python-format msgid "CHECK" msgstr "ПЕРЕВІРИТИ" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "CHILL MODE ON" msgstr "СПОКІЙНИЙ РЕЖИМ" -#: system/ui/widgets/network.py:155 -#: selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 -#: selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "CONNECT" -#: system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:376 #, python-format msgid "CONNECTING..." msgstr "ПІДКЛЮЧА..." -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/network.py:318 system/ui/widgets/keyboard.py:81 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format msgid "Cancel" msgstr "Скасувати" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "Лімітне стільникове з'єднання" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "Change Language" msgstr "Змінити мову" -#: selfdrive/ui/layouts/settings/toggles.py:125 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." -msgstr "" -"Зміна цього параметра призведе до перезапуску openpilot, якщо автомобіль " -"увімкнено." +msgstr "Зміна цього параметра призведе до перезапуску openpilot, якщо автомобіль увімкнено." -#: selfdrive/ui/widgets/pairing_dialog.py:118 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "Натисніть «додати новий пристрій» і відскануйте QR-код праворуч." -#: selfdrive/ui/widgets/offroad_alerts.py:104 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "Закрити" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "Поточна версія" -#: selfdrive/ui/layouts/settings/software.py:118 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "ВАНТАЖ" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "Відхилити" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "Відхилити, видалити openpilot" -#: selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 msgid "Developer" msgstr "Розробник" -#: selfdrive/ui/layouts/settings/settings.py:59 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 msgid "Device" msgstr "Пристрій" -#: selfdrive/ui/layouts/settings/toggles.py:58 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "Вимкнення при натисканні на педаль газу" -#: selfdrive/ui/layouts/settings/device.py:184 +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 #, python-format msgid "Disengage to Power Off" msgstr "Вимкніть openpilot, щоб вимкнути пристрій" -#: selfdrive/ui/layouts/settings/device.py:172 +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 #, python-format msgid "Disengage to Reboot" msgstr "Вимкніть openpilot, щоб перезавантажити" -#: selfdrive/ui/layouts/settings/device.py:103 +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 #, python-format msgid "Disengage to Reset Calibration" msgstr "Деактивуйте для скидання калібрування" -#: selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "Відображати швидкість у км/год замість миль/год." -#: selfdrive/ui/layouts/settings/device.py:59 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 #, python-format msgid "Dongle ID" msgstr "ID ключа" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "Завантажити" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Driver Camera" msgstr "Камера водія" -#: selfdrive/ui/layouts/settings/toggles.py:96 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "Стиль водіння" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "РЕДАГ." -#: selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "ПОМИЛКА" -#: selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "ЕКСПЕРИМЕНТ. РЕЖИМ" -#: selfdrive/ui/layouts/settings/toggles.py:228 -#: selfdrive/ui/layouts/settings/developer.py:166 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 #, python-format msgid "Enable" msgstr "Увімкнути" -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "Увімкнути ADB" -#: selfdrive/ui/layouts/settings/toggles.py:64 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "Увімкнути попередження про виїзд зі смуги" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "Увімкнути роумінг" -#: selfdrive/ui/layouts/settings/developer.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "Увімкнути SSH" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "Увімкнути точку доступу" -#: selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "Увімкнути моніторинг водія, навіть коли openpilot не ввімкнено." -#: selfdrive/ui/layouts/settings/toggles.py:46 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "Увімкнути openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:189 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 #, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." -msgstr "" -"Увімкніть перемикач поздовжнього керування openpilot (альфа), щоб увімкнути " -"експериментальний режим." +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." +msgstr "Увімкніть перемикач поздовжнього керування openpilot (альфа), щоб увімкнути експериментальний режим." -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "Enter APN" msgstr "Введіть APN" -#: system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:243 #, python-format msgid "Enter SSID" msgstr "Введіть SSID" -#: system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:257 #, python-format msgid "Enter new tethering password" msgstr "Введіть новий пароль для модему" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 #, python-format msgid "Enter password" msgstr "Введіть пароль" -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "Введіть ваш логін GitHub" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "Помилка" -#: selfdrive/ui/layouts/settings/toggles.py:52 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "Експериментальний режим" -#: selfdrive/ui/layouts/settings/toggles.py:181 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 #, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." -msgstr "" -"Експериментальний режим наразі недоступний для цього автомобіля, оскільки " -"для поздовжнього керування використовується штатний адаптивний круїз-" -"контроль (ACC)." +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." +msgstr "Експериментальний режим наразі недоступний для цього автомобіля, оскільки для поздовжнього керування використовується штатний адаптивний круїз-контроль (ACC)." -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "ЗАБУВАЮ..." -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "Завершити налаштування" -#: selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 msgid "Firehose" msgstr "Злива" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "Режим зливи" -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" -"Для максимальної ефективності щотижня заносьте пристрій у приміщення та " -"підключайте його до якісного адаптера USB-C і Wi-Fi.\n" -"\n" -"Режим Зливи також може працювати під час руху, якщо пристрій підключено до " -"точки доступу або SIM-картки з необмеженим трафіком.\n" -"\n" -"\n" -"Поширені запитання\n" -"\n" -"Чи має значення, як і де я їду? Ні, просто їдьте, як зазвичай.\n" -"\n" -"Чи всі мої сегменти потрапляють у режим Зливи? Ні, ми вибірково вибираємо " -"підмножину ваших сегментів.\n" -"\n" -"Що таке хороший адаптер USB-C? Будь-який швидкий зарядний пристрій для " -"телефону або ноутбука підійде.\n" -"\n" -"Чи має значення, яке програмне забезпечення я використовую? Так, для " -"навчання можна використовувати тільки upstream openpilot (і певні його " -"форки)." - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 #, python-format msgid "Forget" msgstr "Заб-и" -#: system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:327 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "Забути мережу Wi-Fi \"{}\"?" -#: selfdrive/ui/layouts/sidebar.py:71 -#: selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "ДОБРА" -#: selfdrive/ui/widgets/pairing_dialog.py:117 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "Перейдіть на сайт https://connect.comma.ai на своєму телефоні." -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "ВИСОКА" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "Прихована мережа" -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "НЕАКТИВНО: підключення до мережі без ліміту трафіку" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:144 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "ВСТАНОВ." -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "IP-адреса" -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "Встановити оновлення" -#: selfdrive/ui/layouts/settings/developer.py:56 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "Режим зневадження джойстика" -#: selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "ЗАВАНТАЖЕННЯ" -#: selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: selfdrive/ui/layouts/settings/developer.py:64 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "Режим поздовжнього маневрування" -#: selfdrive/ui/onroad/hud_renderer.py:148 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "МАКС" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." -msgstr "" -"Максимізуйте завантаження навчальних даних, щоб поліпшити моделі openpilot." +msgid "Maximize your training data uploads to improve openpilot's driving models." +msgstr "Максимізуйте завантаження навчальних даних, щоб поліпшити моделі openpilot." -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "N/A" msgstr "Н/Д" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "НЕМАЄ" -#: selfdrive/ui/layouts/settings/settings.py:60 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 msgid "Network" msgstr "Мережа" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 #, python-format msgid "No SSH keys found" msgstr "Не знайдено ключів SSH" -#: selfdrive/ui/widgets/ssh_key.py:126 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 #, python-format msgid "No SSH keys found for user '{}'" msgstr "Користувач '{}' не має ключів на GitHub" -#: selfdrive/ui/widgets/offroad_alerts.py:320 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 #, python-format msgid "No release notes available." msgstr "Інформація про випуск відсутня." -#: selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "ОФЛАЙН" -#: system/ui/widgets/confirm_dialog.py:93 system/ui/widgets/html_render.py:263 -#: selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "OK" -#: selfdrive/ui/layouts/sidebar.py:72 -#: selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 msgid "ONLINE" msgstr "ОНЛАЙН" -#: selfdrive/ui/widgets/setup.py:20 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Open" msgstr "ВІДКРИТИ" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "PAIR" msgstr "ПІДКЛЮЧИТИ" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "PREVIEW" msgstr "ПОКАЖИ" -#: selfdrive/ui/widgets/prime.py:44 +#: openpilot/selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "XАРАКТЕРИСТИКИ PRIME:" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "Pair Device" msgstr "Підключити пристрій" -#: selfdrive/ui/widgets/setup.py:19 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format msgid "Pair device" msgstr "Підключити пристрій" -#: selfdrive/ui/widgets/pairing_dialog.py:92 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "Підключіть свій пристрій до обліковки comma connect" -#: selfdrive/ui/widgets/setup.py:48 -#: selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." -msgstr "" -"Підключіть свій пристрій до comma connect (connect.comma.ai) і отримайте " -"свою пропозицію comma prime." +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." +msgstr "Підключіть свій пристрій до comma connect (connect.comma.ai) і отримайте свою пропозицію comma prime." -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "Будь ласка, підключіться до Wi-Fi, щоб завершити початкове сполучення." -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Power Off" msgstr "Вимкнути" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" -msgstr "" -"Запобігайте завантаженню великих обсягів даних під час використання Wi-Fi-" -"з'єднання з обмеженим трафіком" +msgstr "Запобігайте завантаженню великих обсягів даних під час використання Wi-Fi-з'єднання з обмеженим трафіком" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" -msgstr "" -"Запобігати великим завантаженням даних під час лімітного стільникового " -"з'єднання" +msgstr "Запобігати великим завантаженням даних під час лімітного стільникового з'єднання" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" -msgstr "" -"Попередньо перегляньте камеру, спрямовану на водія, щоб переконатися, що " -"система моніторингу водія має добру видимість. (автомобіль повинен бути " -"вимкнений)" +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "Попередньо перегляньте камеру, спрямовану на водія, щоб переконатися, що система моніторингу водія має добру видимість. (автомобіль повинен бути вимкнений)" -#: selfdrive/ui/widgets/pairing_dialog.py:150 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "Помилка QR-коду" -#: selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "ВИДАЛИТИ" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "RESET" msgstr "Скинути" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "REVIEW" msgstr "ДИВИТИСЬ" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Reboot" msgstr "Перезавантажити" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "Перезавантажте пристрій" -#: selfdrive/ui/widgets/offroad_alerts.py:112 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "Перезавантажити та оновити" -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" -"Отримувати попередження про необхідність повернутися в смугу, коли ваш " -"автомобіль перетинає виявлену лінію розмітки без увімкненого сигналу " -"повороту під час руху зі швидкістю понад 31 миль/год (50 км/год)." - -#: selfdrive/ui/layouts/settings/toggles.py:76 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "Писати та вантажити відео з камери водія" -#: selfdrive/ui/layouts/settings/toggles.py:82 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "Запис та завантаження аудіо з мікрофона" -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." -msgstr "" -"Записуйте та зберігайте аудіо з мікрофона під час руху. Аудіо буде включено " -"до відео з відеореєстратора в comma connect." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." +msgstr "Записуйте та зберігайте аудіо з мікрофона під час руху. Аудіо буде включено до відео з відеореєстратора в comma connect." -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Regulatory" msgstr "Нормативні документи" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "Спокійний" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "Віддалений доступ" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "Віддалені знімки" -#: selfdrive/ui/widgets/ssh_key.py:123 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 #, python-format msgid "Request timed out" msgstr "Час запиту вичерпано" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Reset" msgstr "Скинути" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "Reset Calibration" msgstr "Скинути калібрування" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "Review Training Guide" msgstr "Переглянути посібник з навчання" -#: selfdrive/ui/layouts/settings/device.py:27 +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 msgid "Review the rules, features, and limitations of openpilot" msgstr "Перегляньте правила, функції та обмеження openpilot" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "ВИБРАТИ" -#: selfdrive/ui/layouts/settings/developer.py:53 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "SSH ключі" -#: system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:316 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "Пошук мереж..." -#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:37 #, python-format msgid "Select" msgstr "Вибрати" -#: selfdrive/ui/layouts/settings/software.py:191 +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 #, python-format msgid "Select a branch" msgstr "Виберіть гілку" -#: selfdrive/ui/layouts/settings/device.py:91 +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 #, python-format msgid "Select a language" msgstr "Виберіть мову" -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "Serial" msgstr "Серійний номер" -#: selfdrive/ui/widgets/offroad_alerts.py:106 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "Відкласти оновлення" -#: selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 msgid "Software" msgstr "Програма" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "Стандарт" -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" -"Рекомендується стандартний режим. В агресивному режимі openpilot буде " -"триматися ближче до автомобілів попереду і більш агресивно використовувати " -"газ і гальма. У спокійному режимі openpilot буде триматися на більшій " -"відстані від автомобілів попереду. На підтримуваних автомобілях ви можете " -"перемикатися між цими режимами за допомогою кнопки дистанції на кермі." - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "Система не реагує" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "КЕРМУЙТЕ НЕГАЙНО" -#: selfdrive/ui/layouts/sidebar.py:71 -#: selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "ТЕМП" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "Цільова гілка" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "Пароль для точки доступу" -#: selfdrive/ui/layouts/settings/settings.py:61 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 msgid "Toggles" msgstr "Перемикачі" -#: selfdrive/ui/layouts/settings/software.py:72 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 +#, python-format +msgid "UI Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "UNINSTALL" msgstr "ВИДАЛИТИ" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "ОНОВИТИ" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:171 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "Uninstall" msgstr "Видалити" -#: selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "Невідомо" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "Оновлення завантажуються лише тоді, коли автомобіль вимкнено." -#: selfdrive/ui/widgets/prime.py:33 +#: openpilot/selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "Оновити зараз" -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." -msgstr "" -"Завантажуйте дані з камери, спрямованої на водія, та допоможіть покращити " -"алгоритм моніторингу водія." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." +msgstr "Завантажуйте дані з камери, спрямованої на водія, та допоможіть покращити алгоритм моніторингу водія." -#: selfdrive/ui/layouts/settings/toggles.py:88 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "Використовувати метричну систему" -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" -"Використовуйте систему openpilot для адаптивного круїз-контролю та допомоги " -"в утриманні смуги руху. Ваша увага потрібна постійно при використанні цієї " -"функції. Зміна цього налаштування набуває чинності після вимкнення живлення " -"автомобіля." - -#: selfdrive/ui/layouts/sidebar.py:72 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "АВТО" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "VIEW" msgstr "ДИВИСЬ" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "Очікування початку" -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" -"Попередження: це надає доступ по SSH до всіх публічних ключів у ваших " -"налаштуваннях GitHub. Ніколи не вводьте ім'я користувача GitHub, окрім " -"вашого власного. Співробітник comma НІКОЛИ не попросить вас додати його ім'я " -"користувача GitHub." - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "Ласкаво просимо до openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "Якщо увімкнено, натискання на педаль акселератора вимкне openpilot." -#: selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi-Fi" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Wi-Fi Network Metered" msgstr "Трафік Wi-Fi" -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "Невірний пароль" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "Ви повинні прийняти Умови та положення, щоб користуватися openpilot." -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." -msgstr "" -"Ви повинні прийняти Умови використання, щоб користуватися openpilot. Перед " -"тим, як продовжити, ознайомтеся з останніми умовами на сайті https://" -"comma.ai/terms." +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." +msgstr "Ви повинні прийняти Умови використання, щоб користуватися openpilot. Перед тим, як продовжити, ознайомтеся з останніми умовами на сайті https://comma.ai/terms." -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 #, python-format msgid "camera starting" msgstr "запуск камери" -#: selfdrive/ui/layouts/settings/software.py:105 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 #, python-format msgid "checking..." msgstr "перевіряю..." -#: selfdrive/ui/widgets/prime.py:63 +#: openpilot/selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "default" msgstr "замовч." -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "down" msgstr "вниз" -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 #, python-format msgid "downloading..." msgstr "завантажую..." -#: selfdrive/ui/layouts/settings/software.py:114 +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "не вдалося перевірити оновлення" -#: selfdrive/ui/layouts/settings/software.py:107 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 #, python-format msgid "finalizing update..." msgstr "завершую..." -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 #, python-format msgid "for \"{}\"" msgstr "для \"{}\"" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "км/год" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "leave blank for automatic configuration" msgstr "залиште порожнім для автоматичного налаштування" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "left" msgstr "вліво" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "обмеж." -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "миль/год" -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "ніколи" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "зараз" -#: selfdrive/ui/layouts/settings/developer.py:71 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "Поздовжнє керування openpilot (Альфа)" -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot Недоступний" -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." -msgstr "" -"openpilot за замовчуванням працює в режимі спокій. Експериментальний режим " -"увімкне функції альфа-рівня, які ще не готові для режиму спокій. " -"Експериментальні функції перелічені нижче:

Кінцевий поздовжній " -"контроль


Дозвольте моделі водіння контролювати газ і гальма. " -"openpilot буде керувати автомобілем так, як це робив би людина, включаючи " -"зупинку на червоне світло і знаки зупинки. Оскільки модель водіння визначає " -"швидкість руху, задана швидкість буде діяти лише як верхня межа. Це функція " -"альфа-рівня; слід очікувати помилок.

Нова візуалізація водіння
Візуалізація водіння перейде на ширококутну камеру, спрямовану на " -"дорогу, при низьких швидкостях, щоб краще показувати деякі повороти. Логотип " -"експериментального режиму також буде показаний у верхньому правому куті." - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" -"openpilot постійно калібрується, скидання рідко потрібне. Скидання " -"калібрування призведе до перезапуску openpilot, якщо автомобіль увімкнено." - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" -"openpilot вчиться керувати автомобілем, спостерігаючи за тим, як це роблять " -"люди, такі як ви.\n" -"\n" -"Режим зливи дозволяє максимально збільшити обсяг завантажуваних навчальних " -"даних, щоб поліпшити моделі керування автомобілем openpilot. Більше даних " -"означає більші моделі, а це означає кращий експериментальний режим." - -#: selfdrive/ui/layouts/settings/toggles.py:183 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "Поздовжнє керування openpilot може з'явитися в майбутньому оновленні." -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." -msgstr "" -"Для роботи openpilot потрібно, щоб пристрій був встановлений з нахилом не " -"більше 4° вліво або вправо та не більше 5° вгору або 9° вниз. openpilot " -"постійно калібрується, тому скидання калібрування потрібне рідко." +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." +msgstr "Для роботи openpilot потрібно, щоб пристрій був встановлений з нахилом не більше 4° вліво або вправо та не більше 5° вгору або 9° вниз. openpilot постійно калібрується, тому скидання калібрування потрібне рідко." -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "right" msgstr "вправо" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "необмеж." -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "up" msgstr "вгору" -#: selfdrive/ui/layouts/settings/software.py:125 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "оновлено, ніколи не перевірялось" -#: selfdrive/ui/layouts/settings/software.py:123 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format msgid "up to date, last checked {}" msgstr "оновлено, перевірив {}" -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "доступне оновлення" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" @@ -1209,7 +996,7 @@ msgstr[0] "{} СПОВІЩЕННЯ" msgstr[1] "{} СПОВІЩЕННЯ" msgstr[2] "{} СПОВІЩЕНЬ" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" @@ -1217,7 +1004,7 @@ msgstr[0] "{} день тому" msgstr[1] "{} дні тому" msgstr[2] "{} днів тому" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" @@ -1225,7 +1012,7 @@ msgstr[0] "{} година тому" msgstr[1] "{} години тому" msgstr[2] "{} годин тому" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" @@ -1233,26 +1020,21 @@ msgstr[0] "{} хвилина тому" msgstr[1] "{} хвилини тому" msgstr[2] "{} хвилин тому" -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." -msgstr[0] "" -"{} сегмент вашого водіння на даний момент містяться в тренувальному наборі " -"даних." -msgstr[1] "" -"{} сегменти вашого водіння на даний момент містяться в тренувальному наборі " -"даних." -msgstr[2] "" -"{} сегментів вашого водіння на даний момент містяться в тренувальному наборі " -"даних." +msgstr[0] "{} сегмент вашого водіння на даний момент містяться в тренувальному наборі даних." +msgstr[1] "{} сегменти вашого водіння на даний момент містяться в тренувальному наборі даних." +msgstr[2] "{} сегментів вашого водіння на даний момент містяться в тренувальному наборі даних." -#: selfdrive/ui/widgets/prime.py:62 +#: openpilot/selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ ПІДПИСАНО" -#: selfdrive/ui/widgets/setup.py:22 +#: openpilot/selfdrive/ui/widgets/setup.py:21 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🌧️ Режим зливи 🌧️" + diff --git a/selfdrive/ui/translations/app_zh-CHS.po b/selfdrive/ui/translations/app_zh-CHS.po index 2400b6f44a..4d9a5b78e2 100644 --- a/selfdrive/ui/translations/app_zh-CHS.po +++ b/selfdrive/ui/translations/app_zh-CHS.po @@ -17,1158 +17,1018 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: selfdrive/ui/layouts/settings/device.py:160 +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 #, python-format msgid " Steering torque response calibration is complete." msgstr " 转向扭矩响应校准完成。" -#: selfdrive/ui/layouts/settings/device.py:158 +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " 转向扭矩响应校准已完成 {}%。" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " 您的设备朝向 {:.1f}° {} 与 {:.1f}° {}。" -#: selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "1 年行驶数据存储" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "全天候 LTE 连接" -#: selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" -"警告:此车型的 openpilot 纵向控制仍为 alpha,将会停用自动紧急制动 (AEB)。" -"

在此车型上,openpilot 默认使用车载 ACC,而非 openpilot 的纵向控" -"制。启用此选项可切换为 openpilot 纵向控制。建议同时启用实验模式。若车辆通电," -"更改此设置将会重启 openpilot。" - -#: selfdrive/ui/layouts/settings/device.py:148 +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 #, python-format msgid "

Steering lag calibration is complete." msgstr "

转向延迟校准完成。" -#: selfdrive/ui/layouts/settings/device.py:146 +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "

转向延迟校准已完成 {}%。" -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "已启用" - -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" -"ADB(Android 调试桥)可通过 USB 或网络连接到您的设备。详见 https://docs." -"comma.ai/how-to/connect-to-comma。" - -#: selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "添加" -#: system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:136 #, python-format msgid "APN Setting" msgstr "APN 设置" -#: selfdrive/ui/widgets/offroad_alerts.py:109 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "确认过度作动" -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "高级" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "激进" -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "同意" -#: selfdrive/ui/layouts/settings/toggles.py:70 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "始终启用驾驶员监控" -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "openpilot 纵向控制的 alpha 版本可在非发布分支搭配实验模式进行测试。" - -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 #, python-format msgid "Are you sure you want to power off?" msgstr "确定要关机吗?" -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 #, python-format msgid "Are you sure you want to reboot?" msgstr "确定要重启吗?" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "确定要重置校准吗?" -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 #, python-format msgid "Are you sure you want to uninstall?" msgstr "确定要卸载吗?" -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "返回" -#: selfdrive/ui/widgets/prime.py:38 +#: openpilot/selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "前往 connect.comma.ai 成为 comma prime 会员" -#: selfdrive/ui/widgets/pairing_dialog.py:130 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "将 connect.comma.ai 添加到主屏幕,像应用一样使用" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "CHANGE" msgstr "更改" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 #, python-format msgid "CHECK" msgstr "检查" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "CHILL MODE ON" msgstr "安稳模式已开启" -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "CONNECT" -#: system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:376 #, python-format msgid "CONNECTING..." msgstr "连接中..." -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format msgid "Cancel" msgstr "取消" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "蜂窝计量" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "Change Language" msgstr "更改语言" -#: selfdrive/ui/layouts/settings/toggles.py:125 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "若车辆通电,更改此设置将重启 openpilot。" -#: selfdrive/ui/widgets/pairing_dialog.py:129 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "点击“添加新设备”,扫描右侧二维码" -#: selfdrive/ui/widgets/offroad_alerts.py:104 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "关闭" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "当前版本" -#: selfdrive/ui/layouts/settings/software.py:110 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "下载" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "拒绝" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "拒绝并卸载 openpilot" -#: selfdrive/ui/layouts/settings/settings.py:67 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 msgid "Developer" msgstr "开发者" -#: selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 msgid "Device" msgstr "设备" -#: selfdrive/ui/layouts/settings/toggles.py:58 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "踩下加速踏板时脱离" -#: selfdrive/ui/layouts/settings/device.py:184 +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 #, python-format msgid "Disengage to Power Off" msgstr "脱离以关机" -#: selfdrive/ui/layouts/settings/device.py:172 +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 #, python-format msgid "Disengage to Reboot" msgstr "脱离以重启" -#: selfdrive/ui/layouts/settings/device.py:103 +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 #, python-format msgid "Disengage to Reset Calibration" msgstr "脱离以重置校准" -#: selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "以 km/h 显示速度(非 mph)。" -#: selfdrive/ui/layouts/settings/device.py:59 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 #, python-format msgid "Dongle ID" msgstr "Dongle ID" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "下载" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Driver Camera" msgstr "车内摄像头" -#: selfdrive/ui/layouts/settings/toggles.py:96 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "驾驶风格" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "编辑" -#: selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "错误" -#: selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "实验模式已开启" -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 #, python-format msgid "Enable" msgstr "启用" -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "启用 ADB" -#: selfdrive/ui/layouts/settings/toggles.py:64 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "启用车道偏离警示" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "启用漫游" -#: selfdrive/ui/layouts/settings/developer.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "启用 SSH" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "启用网络共享" -#: selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "即使未启用 openpilot 也启用驾驶员监控。" -#: selfdrive/ui/layouts/settings/toggles.py:46 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "启用 openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:189 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 #, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "启用 openpilot 纵向控制(alpha)开关,以使用实验模式。" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "Enter APN" msgstr "输入 APN" -#: system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:243 #, python-format msgid "Enter SSID" msgstr "输入 SSID" -#: system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:257 #, python-format msgid "Enter new tethering password" msgstr "输入新的网络共享密码" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 #, python-format msgid "Enter password" msgstr "输入密码" -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "输入您的 GitHub 用户名" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "错误" -#: selfdrive/ui/layouts/settings/toggles.py:52 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "实验模式" -#: selfdrive/ui/layouts/settings/toggles.py:181 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 #, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." msgstr "此车型当前无法使用实验模式,因为纵向控制使用的是原厂 ACC。" -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "正在遗忘..." -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "完成设置" -#: selfdrive/ui/layouts/settings/settings.py:66 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 msgid "Firehose" msgstr "Firehose" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "Firehose 模式" -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" -"为达到最佳效果,请将设备带到室内,并每周连接优质 USB‑C 充电器与 Wi‑Fi。\n" -"\n" -"若连接热点或不限流量卡,行车中也可使用 Firehose 模式。\n" -"\n" -"\n" -"常见问题\n" -"\n" -"我怎么开、在哪开有区别吗?没有,平常怎么开就怎么开。\n" -"\n" -"Firehose 模式会拉取我所有片段吗?不会,我们会选择性拉取部分片段。\n" -"\n" -"什么是好的 USB‑C 充电器?任何快速的手机或笔电充电器都可以。\n" -"\n" -"我跑什么软件有区别吗?有,只有上游 openpilot(及特定分支)可用于训练。" - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 #, python-format msgid "Forget" msgstr "忘记" -#: system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:327 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "要忘记 Wi‑Fi 网络“{}”吗?" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "良好" -#: selfdrive/ui/widgets/pairing_dialog.py:128 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "在手机上前往 https://connect.comma.ai" -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "高" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "隐藏网络" -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "未启用:请连接不限流量网络" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "安装" -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "IP 地址" -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "安装更新" -#: selfdrive/ui/layouts/settings/developer.py:56 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "摇杆调试模式" -#: selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "加载中" -#: selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: selfdrive/ui/layouts/settings/developer.py:64 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "纵向操作模式" -#: selfdrive/ui/onroad/hud_renderer.py:148 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "最大" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." +msgid "Maximize your training data uploads to improve openpilot's driving models." msgstr "最大化上传训练数据,以改进 openpilot 的驾驶模型。" -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "N/A" msgstr "无" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "否" -#: selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 msgid "Network" msgstr "网络" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 #, python-format msgid "No SSH keys found" msgstr "未找到 SSH 密钥" -#: selfdrive/ui/widgets/ssh_key.py:126 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 #, python-format msgid "No SSH keys found for user '{}'" msgstr "未找到用户“{}”的 SSH 密钥" -#: selfdrive/ui/widgets/offroad_alerts.py:320 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 #, python-format msgid "No release notes available." msgstr "暂无发行说明。" -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "离线" -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "确定" -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 msgid "ONLINE" msgstr "在线" -#: selfdrive/ui/widgets/setup.py:20 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Open" msgstr "打开" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "PAIR" msgstr "配对" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "PREVIEW" msgstr "预览" -#: selfdrive/ui/widgets/prime.py:44 +#: openpilot/selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "PRIME 功能:" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "Pair Device" msgstr "配对设备" -#: selfdrive/ui/widgets/setup.py:19 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format msgid "Pair device" msgstr "配对设备" -#: selfdrive/ui/widgets/pairing_dialog.py:103 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "将设备配对到您的 comma 账号" -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." -msgstr "" -"将设备与 comma connect(connect.comma.ai)配对,领取您的 comma prime 优惠。" +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." +msgstr "将设备与 comma connect(connect.comma.ai)配对,领取您的 comma prime 优惠。" -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "请连接 Wi‑Fi 以完成初始配对" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Power Off" msgstr "关机" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "在计量制 Wi‑Fi 连接时避免大量上传" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "在计量制蜂窝网络时避免大量上传" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" msgstr "预览车内摄像头以确保驾驶员监控视野良好。(车辆必须熄火)" -#: selfdrive/ui/widgets/pairing_dialog.py:161 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "二维码错误" -#: selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "移除" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "RESET" msgstr "重置" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "REVIEW" msgstr "查看" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Reboot" msgstr "重启" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "重启设备" -#: selfdrive/ui/widgets/offroad_alerts.py:112 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "重启并更新" -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" -"当车辆以超过 31 mph(50 km/h)行驶且未打转向灯越过检测到的车道线时,接收引导" -"回车道的警报。" - -#: selfdrive/ui/layouts/settings/toggles.py:76 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "录制并上传车内摄像头" -#: selfdrive/ui/layouts/settings/toggles.py:82 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "录制并上传麦克风音频" -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." -msgstr "" -"行驶时录制并保存麦克风音频。音频将包含在 comma connect 的行车记录视频中。" +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." +msgstr "行驶时录制并保存麦克风音频。音频将包含在 comma connect 的行车记录视频中。" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Regulatory" msgstr "法规" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "从容" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "远程访问" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "远程快照" -#: selfdrive/ui/widgets/ssh_key.py:123 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 #, python-format msgid "Request timed out" msgstr "请求超时" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Reset" msgstr "重置" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "Reset Calibration" msgstr "重置校准" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "Review Training Guide" msgstr "查看训练指南" -#: selfdrive/ui/layouts/settings/device.py:27 +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 msgid "Review the rules, features, and limitations of openpilot" msgstr "查看 openpilot 的规则、功能与限制" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "选择" -#: selfdrive/ui/layouts/settings/developer.py:53 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "SSH 密钥" -#: system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:316 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "正在扫描 Wi‑Fi 网络…" -#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:37 #, python-format msgid "Select" msgstr "选择" -#: selfdrive/ui/layouts/settings/software.py:183 +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 #, python-format msgid "Select a branch" msgstr "选择分支" -#: selfdrive/ui/layouts/settings/device.py:91 +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 #, python-format msgid "Select a language" msgstr "选择语言" -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "Serial" msgstr "序列号" -#: selfdrive/ui/widgets/offroad_alerts.py:106 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "延后更新" -#: selfdrive/ui/layouts/settings/settings.py:65 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 msgid "Software" msgstr "软件" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "标准" -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" -"建议使用标准模式。激进模式下,openpilot 会更贴近前车,油门与刹车更为激进;从" -"容模式下,会与前车保持更远距离。在支持的车型上,可用方向盘距离按钮切换这些风" -"格。" - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "系统无响应" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "请立即接管控制" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "温度" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "目标分支" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "网络共享密码" -#: selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 msgid "Toggles" msgstr "切换" -#: selfdrive/ui/layouts/settings/software.py:72 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 +#, python-format +msgid "UI Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "UNINSTALL" msgstr "卸载" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "更新" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "Uninstall" msgstr "卸载" -#: selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "未知" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "仅在车辆熄火时下载更新。" -#: selfdrive/ui/widgets/prime.py:33 +#: openpilot/selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "立即升级" -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." msgstr "上传车内摄像头数据,帮助改进驾驶员监控算法。" -#: selfdrive/ui/layouts/settings/toggles.py:88 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "使用公制" -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" -"使用 openpilot 进行自适应巡航与车道保持辅助。使用此功能时,您必须始终保持专" -"注。" - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "车辆" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "VIEW" msgstr "查看" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "等待开始" -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" -"警告:这将授予对您 GitHub 设置中所有公钥的 SSH 访问权限。请勿输入非您本人的 " -"GitHub 用户名。comma 员工绝不会要求您添加他们的用户名。" - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "欢迎使用 openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "启用后,踩下加速踏板将会脱离 openpilot。" -#: selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Wi-Fi Network Metered" msgstr "Wi‑Fi 计量网络" -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "密码错误" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "您必须接受条款与条件才能使用 openpilot。" -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." -msgstr "" -"您必须接受条款与条件才能使用 openpilot。继续前请阅读 https://comma.ai/terms " -"上的最新条款。" +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." +msgstr "您必须接受条款与条件才能使用 openpilot。继续前请阅读 https://comma.ai/terms 上的最新条款。" -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 #, python-format msgid "camera starting" msgstr "相机启动中" -#: selfdrive/ui/widgets/prime.py:63 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "checking..." +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "default" msgstr "默认" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "down" msgstr "下" -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "downloading..." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "检查更新失败" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 +#, python-format +msgid "finalizing update..." +msgstr "" + +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 #, python-format msgid "for \"{}\"" msgstr "用于“{}”" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "leave blank for automatic configuration" msgstr "留空以自动配置" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "left" msgstr "左" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "计量" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "从不" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "现在" -#: selfdrive/ui/layouts/settings/developer.py:71 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "openpilot 纵向控制(Alpha)" -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot 无法使用" -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." -msgstr "" -"openpilot 默认以安稳模式行驶。实验模式会启用尚未准备好用于安稳模式的 Alpha 级" -"功能。实验功能如下:

端到端纵向控制


让驾驶模型控制油门与刹车。" -"openpilot 会像人类一样驾驶,包括在红灯与停牌前停车。由于驾驶模型决定行驶速" -"度,设定速度仅作为上限。这是 Alpha 质量功能;预期会有错误。

全新驾驶可" -"视化


在低速时,驾驶可视化将切换至面向道路的广角摄像头以更好显示部分转" -"弯。右上角也会显示实验模式图标。" - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" -"openpilot 持续进行校准,通常无需重置。若车辆通电,重置校准将会重启 " -"openpilot。" - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" -"openpilot 通过观察人类(例如您)的驾驶来学习。\n" -"\n" -"Firehose 模式可让您最大化上传训练数据,以改进 openpilot 的驾驶模型。更多数据" -"意味着更大的模型,也意味着更好的实验模式。" - -#: selfdrive/ui/layouts/settings/toggles.py:183 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "openpilot 纵向控制可能会在未来更新中提供。" -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." msgstr "openpilot 要求设备安装在左右 4°、上 5° 或下 9° 以内。" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "right" msgstr "右" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "不限流量" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "up" msgstr "上" -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "已是最新,最后检查:从未" -#: selfdrive/ui/layouts/settings/software.py:115 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format msgid "up to date, last checked {}" msgstr "已是最新,最后检查:{}" -#: selfdrive/ui/layouts/settings/software.py:109 +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "有可用更新" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} 条警报" msgstr[1] "{} 条警报" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{} 天前" msgstr[1] "{} 天前" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{} 小时前" msgstr[1] "{} 小时前" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{} 分钟前" msgstr[1] "{} 分钟前" -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "目前已有 {} 个您的驾驶片段被纳入训练数据集。" msgstr[1] "目前已有 {} 个您的驾驶片段被纳入训练数据集。" -#: selfdrive/ui/widgets/prime.py:62 +#: openpilot/selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ 已订阅" -#: selfdrive/ui/widgets/setup.py:22 +#: openpilot/selfdrive/ui/widgets/setup.py:21 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehose 模式 🔥" + diff --git a/selfdrive/ui/translations/app_zh-CHT.po b/selfdrive/ui/translations/app_zh-CHT.po index f4d5e0a4ed..1c2fd06563 100644 --- a/selfdrive/ui/translations/app_zh-CHT.po +++ b/selfdrive/ui/translations/app_zh-CHT.po @@ -17,1157 +17,1018 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: selfdrive/ui/layouts/settings/device.py:160 +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 #, python-format msgid " Steering torque response calibration is complete." msgstr " 轉向扭矩回應校正完成。" -#: selfdrive/ui/layouts/settings/device.py:158 +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " 轉向扭矩回應校正已完成 {}%。" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " 您的裝置朝向 {:.1f}° {} 與 {:.1f}° {}。" -#: selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "1 年行駛資料儲存" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "全年無休 LTE 連線" -#: selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" -"警告:此車款的 openpilot 縱向控制仍為 alpha,將會停用自動緊急煞車 (AEB)。" -"

在此車款上,openpilot 預設使用車載 ACC,而非 openpilot 的縱向控" -"制。啟用此選項可切換為 openpilot 縱向控制。建議同時啟用實驗模式。若車輛通電," -"變更此設定將會重新啟動 openpilot。" - -#: selfdrive/ui/layouts/settings/device.py:148 +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 #, python-format msgid "

Steering lag calibration is complete." msgstr "

轉向延遲校正完成。" -#: selfdrive/ui/layouts/settings/device.py:146 +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "

轉向延遲校正已完成 {}%。" -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "啟用" - -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" -"ADB (Android Debug Bridge) 可透過 USB 或網路連線至您的裝置。詳見 https://" -"docs.comma.ai/how-to/connect-to-comma。" - -#: selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "新增" -#: system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:136 #, python-format msgid "APN Setting" msgstr "APN 設定" -#: selfdrive/ui/widgets/offroad_alerts.py:109 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "確認過度作動" -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "進階" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "積極" -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "同意" -#: selfdrive/ui/layouts/settings/toggles.py:70 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "持續啟用駕駛監控" -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "openpilot 縱向控制的 alpha 版本可於非發行分支搭配實驗模式進行測試。" - -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 #, python-format msgid "Are you sure you want to power off?" msgstr "確定要關機嗎?" -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 #, python-format msgid "Are you sure you want to reboot?" msgstr "確定要重新啟動嗎?" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "確定要重設校正嗎?" -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 #, python-format msgid "Are you sure you want to uninstall?" msgstr "確定要解除安裝嗎?" -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "返回" -#: selfdrive/ui/widgets/prime.py:38 +#: openpilot/selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "前往 connect.comma.ai 成為 comma prime 會員" -#: selfdrive/ui/widgets/pairing_dialog.py:130 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "將 connect.comma.ai 加到主畫面,像 App 一樣使用" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "CHANGE" msgstr "變更" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 #, python-format msgid "CHECK" msgstr "檢查" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "CHILL MODE ON" msgstr "安穩模式已開啟" -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "CONNECT" -#: system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:376 #, python-format msgid "CONNECTING..." msgstr "連線中..." -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format msgid "Cancel" msgstr "取消" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "行動網路計量" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "Change Language" msgstr "變更語言" -#: selfdrive/ui/layouts/settings/toggles.py:125 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "若車輛通電,變更此設定將重新啟動 openpilot。" -#: selfdrive/ui/widgets/pairing_dialog.py:129 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "點選「新增裝置」,掃描右側 QR 碼" -#: selfdrive/ui/widgets/offroad_alerts.py:104 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "關閉" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "目前版本" -#: selfdrive/ui/layouts/settings/software.py:110 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "下載" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "拒絕" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "拒絕並解除安裝 openpilot" -#: selfdrive/ui/layouts/settings/settings.py:67 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 msgid "Developer" msgstr "開發人員" -#: selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 msgid "Device" msgstr "裝置" -#: selfdrive/ui/layouts/settings/toggles.py:58 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "踩下加速踏板時脫離" -#: selfdrive/ui/layouts/settings/device.py:184 +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 #, python-format msgid "Disengage to Power Off" msgstr "脫離以關機" -#: selfdrive/ui/layouts/settings/device.py:172 +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 #, python-format msgid "Disengage to Reboot" msgstr "脫離以重新啟動" -#: selfdrive/ui/layouts/settings/device.py:103 +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 #, python-format msgid "Disengage to Reset Calibration" msgstr "脫離以重設校正" -#: selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "以 km/h 顯示速度(非 mph)。" -#: selfdrive/ui/layouts/settings/device.py:59 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 #, python-format msgid "Dongle ID" msgstr "Dongle ID" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "下載" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Driver Camera" msgstr "車內鏡頭" -#: selfdrive/ui/layouts/settings/toggles.py:96 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "駕駛風格" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "編輯" -#: selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "錯誤" -#: selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "實驗模式已開啟" -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 #, python-format msgid "Enable" msgstr "啟用" -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "啟用 ADB" -#: selfdrive/ui/layouts/settings/toggles.py:64 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "啟用偏離車道警示" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "啟用漫遊" -#: selfdrive/ui/layouts/settings/developer.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "啟用 SSH" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "啟用網路共享" -#: selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "即使未啟動 openpilot 亦啟用駕駛監控。" -#: selfdrive/ui/layouts/settings/toggles.py:46 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "啟用 openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:189 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 #, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "啟用 openpilot 縱向控制(alpha)切換,以使用實驗模式。" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "Enter APN" msgstr "輸入 APN" -#: system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:243 #, python-format msgid "Enter SSID" msgstr "輸入 SSID" -#: system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:257 #, python-format msgid "Enter new tethering password" msgstr "輸入新的網路共享密碼" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 #, python-format msgid "Enter password" msgstr "輸入密碼" -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "輸入您的 GitHub 使用者名稱" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "錯誤" -#: selfdrive/ui/layouts/settings/toggles.py:52 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "實驗模式" -#: selfdrive/ui/layouts/settings/toggles.py:181 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 #, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." msgstr "此車款目前無法使用實驗模式,因為縱向控制使用的是原廠 ACC。" -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "正在遺忘..." -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "完成設定" -#: selfdrive/ui/layouts/settings/settings.py:66 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 msgid "Firehose" msgstr "Firehose" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "Firehose 模式" -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" -"為達最佳效果,請將裝置帶到室內,並每週連接優質 USB‑C 充電器與 Wi‑Fi。\n" -"\n" -"若連上熱點或吃到飽門號,行車中也可使用 Firehose 模式。\n" -"\n" -"\n" -"常見問題\n" -"\n" -"我怎麼開、在哪裡開有差嗎?沒有,平常怎麼開就怎麼開。\n" -"\n" -"Firehose 模式會拉取我所有片段嗎?不會,我們會選擇性拉取部分片段。\n" -"\n" -"什麼是好的 USB‑C 充電器?任何快速的手機或筆電充電器都可以。\n" -"\n" -"我跑什麼軟體有差嗎?有,只有上游 openpilot(及特定分支)可用於訓練。" - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 #, python-format msgid "Forget" msgstr "忘記" -#: system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:327 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "要忘記 Wi‑Fi 網路「{}」嗎?" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "良好" -#: selfdrive/ui/widgets/pairing_dialog.py:128 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "在手機上前往 https://connect.comma.ai" -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "高" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "隱藏網路" -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "未啟用:請連接不限流量網路" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "安裝" -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "IP 位址" -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "安裝更新" -#: selfdrive/ui/layouts/settings/developer.py:56 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "搖桿除錯模式" -#: selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "載入中" -#: selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: selfdrive/ui/layouts/settings/developer.py:64 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "縱向操作模式" -#: selfdrive/ui/onroad/hud_renderer.py:148 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "最大" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." +msgid "Maximize your training data uploads to improve openpilot's driving models." msgstr "最大化上傳訓練資料,以改進 openpilot 的駕駛模型。" -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "N/A" msgstr "無" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "否" -#: selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 msgid "Network" msgstr "網路" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 #, python-format msgid "No SSH keys found" msgstr "找不到 SSH 金鑰" -#: selfdrive/ui/widgets/ssh_key.py:126 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 #, python-format msgid "No SSH keys found for user '{}'" msgstr "找不到使用者 '{}' 的 SSH 金鑰" -#: selfdrive/ui/widgets/offroad_alerts.py:320 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 #, python-format msgid "No release notes available." msgstr "無可用發行說明。" -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "離線" -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "確定" -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 msgid "ONLINE" msgstr "線上" -#: selfdrive/ui/widgets/setup.py:20 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Open" msgstr "開啟" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "PAIR" msgstr "配對" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "PREVIEW" msgstr "預覽" -#: selfdrive/ui/widgets/prime.py:44 +#: openpilot/selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "PRIME 功能:" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "Pair Device" msgstr "配對裝置" -#: selfdrive/ui/widgets/setup.py:19 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format msgid "Pair device" msgstr "配對裝置" -#: selfdrive/ui/widgets/pairing_dialog.py:103 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "將裝置配對至您的 comma 帳號" -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." -msgstr "" -"將裝置與 comma connect(connect.comma.ai)配對,領取您的 comma prime 優惠。" +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." +msgstr "將裝置與 comma connect(connect.comma.ai)配對,領取您的 comma prime 優惠。" -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "請連線至 Wi‑Fi 以完成初始化配對" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Power Off" msgstr "關機" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "在計量制 Wi‑Fi 連線時避免大量上傳" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "在計量制行動網路時避免大量上傳" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" msgstr "預覽車內鏡頭以確保駕駛監控視野良好。(車輛須熄火)" -#: selfdrive/ui/widgets/pairing_dialog.py:161 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "QR 碼錯誤" -#: selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "移除" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "RESET" msgstr "重設" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "REVIEW" msgstr "檢視" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Reboot" msgstr "重新啟動" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "重新啟動裝置" -#: selfdrive/ui/widgets/offroad_alerts.py:112 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "重新啟動並更新" -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" -"當車輛以超過 31 mph(50 km/h)行駛且未打方向燈越過偵測到的車道線時,接收轉向" -"回車道的警示。" - -#: selfdrive/ui/layouts/settings/toggles.py:76 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "錄製並上傳車內鏡頭" -#: selfdrive/ui/layouts/settings/toggles.py:82 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "錄製並上傳麥克風音訊" -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." -msgstr "" -"行車時錄製並儲存麥克風音訊。音訊將包含在 comma connect 的行車紀錄影片中。" +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." +msgstr "行車時錄製並儲存麥克風音訊。音訊將包含在 comma connect 的行車紀錄影片中。" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Regulatory" msgstr "法規" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "從容" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "遠端存取" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "遠端擷圖" -#: selfdrive/ui/widgets/ssh_key.py:123 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 #, python-format msgid "Request timed out" msgstr "要求逾時" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Reset" msgstr "重設" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "Reset Calibration" msgstr "重設校正" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "Review Training Guide" msgstr "檢視訓練指南" -#: selfdrive/ui/layouts/settings/device.py:27 +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 msgid "Review the rules, features, and limitations of openpilot" msgstr "檢視 openpilot 的規則、功能與限制" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "選取" -#: selfdrive/ui/layouts/settings/developer.py:53 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "SSH 金鑰" -#: system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:316 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "正在掃描 Wi‑Fi 網路…" -#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:37 #, python-format msgid "Select" msgstr "選取" -#: selfdrive/ui/layouts/settings/software.py:183 +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 #, python-format msgid "Select a branch" msgstr "選取分支" -#: selfdrive/ui/layouts/settings/device.py:91 +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 #, python-format msgid "Select a language" msgstr "選取語言" -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "Serial" msgstr "序號" -#: selfdrive/ui/widgets/offroad_alerts.py:106 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "延後更新" -#: selfdrive/ui/layouts/settings/settings.py:65 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 msgid "Software" msgstr "軟體" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "標準" -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" -"建議使用標準模式。積極模式下,openpilot 會更貼近前車,油門與煞車反應更積極;" -"從容模式下,會與前車保持更遠距離。於支援車款,可用方向盤距離按鈕切換這些風" -"格。" - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "系統無回應" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "請立刻接手控制" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "溫度" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "目標分支" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "網路共享密碼" -#: selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 msgid "Toggles" msgstr "切換" -#: selfdrive/ui/layouts/settings/software.py:72 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 +#, python-format +msgid "UI Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "UNINSTALL" msgstr "解除安裝" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "更新" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "Uninstall" msgstr "解除安裝" -#: selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "未知" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "僅在車輛熄火時下載更新。" -#: selfdrive/ui/widgets/prime.py:33 +#: openpilot/selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "立即升級" -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." msgstr "上傳車內鏡頭資料,協助改善駕駛監控演算法。" -#: selfdrive/ui/layouts/settings/toggles.py:88 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "使用公制" -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" -"使用 openpilot 進行 ACC 與車道維持輔助。使用此功能時,您必須始終保持專注。" - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "車輛" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "VIEW" msgstr "檢視" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "等待開始" -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" -"警告:這將授予對您 GitHub 設定中所有公開金鑰的 SSH 存取權。請勿輸入非您本人" -"的 GitHub 帳號。comma 員工絕不會要求您新增他們的帳號。" - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "歡迎使用 openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "啟用後,踩下加速踏板將會脫離 openpilot。" -#: selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Wi-Fi Network Metered" msgstr "Wi‑Fi 計量網路" -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "密碼錯誤" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "您必須接受條款與細則才能使用 openpilot。" -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." -msgstr "" -"您必須接受條款與細則才能使用 openpilot。繼續前請閱讀 https://comma.ai/terms " -"上的最新條款。" +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." +msgstr "您必須接受條款與細則才能使用 openpilot。繼續前請閱讀 https://comma.ai/terms 上的最新條款。" -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 #, python-format msgid "camera starting" msgstr "相機啟動中" -#: selfdrive/ui/widgets/prime.py:63 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "checking..." +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "default" msgstr "預設" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "down" msgstr "下" -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "downloading..." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "檢查更新失敗" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 +#, python-format +msgid "finalizing update..." +msgstr "" + +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 #, python-format msgid "for \"{}\"" msgstr "適用於「{}」" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "leave blank for automatic configuration" msgstr "留空以自動設定" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "left" msgstr "左" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "計量" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "從不" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "現在" -#: selfdrive/ui/layouts/settings/developer.py:71 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "openpilot 縱向控制(Alpha)" -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot 無法使用" -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." -msgstr "" -"openpilot 預設以安穩模式行駛。實驗模式啟用尚未準備好進入安穩模式的 Alpha 等級" -"功能。實驗功能如下:

端到端縱向控制


讓駕駛模型控制油門與煞車。" -"openpilot 會如同人類駕駛般行駛,包括在紅燈與停車標誌前停車。由於駕駛模型決定" -"行駛速度,設定速度僅作為上限。此為 Alpha 品質功能;預期會有失誤。

全新" -"駕駛視覺化


在低速時,駕駛視覺化將切換至面向道路的廣角鏡頭以更好呈現部" -"分轉彎。右上角亦會顯示實驗模式圖示。" - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" -"openpilot 會持續校正,通常不需重設。若車輛通電,重設校正將重新啟動 " -"openpilot。" - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" -"openpilot 透過觀察人類(也就是您)的駕駛方式來學習。\n" -"\n" -"Firehose 模式可讓您最大化上傳訓練資料,以改進 openpilot 的駕駛模型。更多資料" -"代表更大的模型,也就代表更好的實驗模式。" - -#: selfdrive/ui/layouts/settings/toggles.py:183 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "openpilot 縱向控制可能於未來更新提供。" -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." msgstr "openpilot 要求裝置安裝在左右 4°、上 5° 或下 9° 以內。" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "right" msgstr "右" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "不限流量" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "up" msgstr "上" -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "已為最新,最後檢查:從未" -#: selfdrive/ui/layouts/settings/software.py:115 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format msgid "up to date, last checked {}" msgstr "已為最新,最後檢查:{}" -#: selfdrive/ui/layouts/settings/software.py:109 +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "有可用更新" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} 則警示" msgstr[1] "{} 則警示" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{} 天前" msgstr[1] "{} 天前" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{} 小時前" msgstr[1] "{} 小時前" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{} 分鐘前" msgstr[1] "{} 分鐘前" -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "目前已有 {} 個您的駕駛片段納入訓練資料集。" msgstr[1] "目前已有 {} 個您的駕駛片段納入訓練資料集。" -#: selfdrive/ui/widgets/prime.py:62 +#: openpilot/selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ 已訂閱" -#: selfdrive/ui/widgets/setup.py:22 +#: openpilot/selfdrive/ui/widgets/setup.py:21 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehose 模式 🔥" + diff --git a/selfdrive/ui/widgets/exp_mode_button.py b/selfdrive/ui/widgets/exp_mode_button.py index faa3bf877f..0b5bff7da4 100644 --- a/selfdrive/ui/widgets/exp_mode_button.py +++ b/selfdrive/ui/widgets/exp_mode_button.py @@ -20,6 +20,7 @@ class ExperimentalModeButton(Widget): self.experimental_pixmap = gui_app.texture("icons/experimental_grey.png", self.img_width, self.img_width) def show_event(self): + super().show_event() self.experimental_mode = self.params.get_bool("ExperimentalMode") def _get_gradient_colors(self): diff --git a/selfdrive/ui/widgets/offroad_alerts.py b/selfdrive/ui/widgets/offroad_alerts.py index a87727e27f..110ca714a9 100644 --- a/selfdrive/ui/widgets/offroad_alerts.py +++ b/selfdrive/ui/widgets/offroad_alerts.py @@ -118,6 +118,7 @@ class AbstractAlert(Widget, ABC): self.scroll_panel = GuiScrollPanel() def show_event(self): + super().show_event() self.scroll_panel.set_offset(0) def set_dismiss_callback(self, callback: Callable): diff --git a/selfdrive/ui/widgets/ssh_key.py b/selfdrive/ui/widgets/ssh_key.py index b31a9eb3bd..b3ff5c1711 100644 --- a/selfdrive/ui/widgets/ssh_key.py +++ b/selfdrive/ui/widgets/ssh_key.py @@ -1,7 +1,6 @@ import pyray as rl import requests import threading -import copy from collections.abc import Callable from enum import Enum @@ -25,6 +24,51 @@ from openpilot.system.ui.widgets.list_view import ( VALUE_FONT_SIZE = 48 +class SshKeyFetcher: + HTTP_TIMEOUT = 15 # seconds + + def __init__(self, params: Params): + self._params = params + self._on_response: Callable[[str | None], None] | None = None + self._done: bool = False + self._error: str | None = None + + def fetch(self, username: str, on_response: Callable[[str | None], None]): + self._error = None + self._on_response = on_response + threading.Thread(target=self._fetch_thread, args=(username,), daemon=True).start() + + def update(self): + if not self._done: + return + self._done = False + if self._error is not None: + self.clear() + if self._on_response: + self._on_response(self._error) + + def clear(self): + self._params.remove("GithubUsername") + self._params.remove("GithubSshKeys") + + def _fetch_thread(self, username: str): + try: + response = requests.get(f"https://github.com/{username}.keys", timeout=self.HTTP_TIMEOUT) + response.raise_for_status() + keys = response.text.strip() + if not keys: + raise requests.exceptions.HTTPError("No SSH keys found") + + self._params.put("GithubUsername", username) + self._params.put("GithubSshKeys", keys) + except requests.exceptions.Timeout: + self._error = tr("Request timed out") + except Exception: + self._error = tr("No SSH keys found for user '{}'").format(username) + finally: + self._done = True + + class SshKeyActionState(Enum): LOADING = tr_noop("LOADING") ADD = tr_noop("ADD") @@ -32,7 +76,6 @@ class SshKeyActionState(Enum): class SshKeyAction(ItemAction): - HTTP_TIMEOUT = 15 # seconds MAX_WIDTH = 500 def __init__(self): @@ -40,7 +83,7 @@ class SshKeyAction(ItemAction): self._keyboard = Keyboard(min_text_size=1) self._params = Params() - self._error_message: str = "" + self._fetcher = SshKeyFetcher(self._params) self._text_font = gui_app.font(FontWeight.NORMAL) self._button = Button("", click_callback=self._handle_button_click, button_style=ButtonStyle.LIST_ACTION, border_radius=BUTTON_BORDER_RADIUS, font_size=BUTTON_FONT_SIZE) @@ -55,14 +98,11 @@ class SshKeyAction(ItemAction): self._username = self._params.get("GithubUsername") self._state = SshKeyActionState.REMOVE if self._params.get("GithubSshKeys") else SshKeyActionState.ADD - def _render(self, rect: rl.Rectangle) -> bool: - # Show error dialog if there's an error - if self._error_message: - message = copy.copy(self._error_message) - gui_app.push_widget(alert_dialog(message)) - self._username = "" - self._error_message = "" + def _update_state(self): + super()._update_state() + self._fetcher.update() + def _render(self, rect: rl.Rectangle) -> bool: # Draw username if exists if self._username: text_size = measure_text_cached(self._text_font, self._username, VALUE_FONT_SIZE) @@ -90,8 +130,7 @@ class SshKeyAction(ItemAction): self._keyboard.set_callback(self._on_username_submit) gui_app.push_widget(self._keyboard) elif self._state == SshKeyActionState.REMOVE: - self._params.remove("GithubUsername") - self._params.remove("GithubSshKeys") + self._fetcher.clear() self._refresh_state() def _on_username_submit(self, result: DialogResult): @@ -103,29 +142,16 @@ class SshKeyAction(ItemAction): return self._state = SshKeyActionState.LOADING - threading.Thread(target=lambda: self._fetch_ssh_key(username), daemon=True).start() + self._fetcher.fetch(username, self._on_fetch_response) - def _fetch_ssh_key(self, username: str): - try: - url = f"https://github.com/{username}.keys" - response = requests.get(url, timeout=self.HTTP_TIMEOUT) - response.raise_for_status() - keys = response.text.strip() - if not keys: - raise requests.exceptions.HTTPError(tr("No SSH keys found")) - - # Success - save keys - self._params.put("GithubUsername", username) - self._params.put("GithubSshKeys", keys) + def _on_fetch_response(self, error: str | None): + if error is None: self._state = SshKeyActionState.REMOVE - self._username = username - - except requests.exceptions.Timeout: - self._error_message = tr("Request timed out") - self._state = SshKeyActionState.ADD - except Exception: - self._error_message = tr("No SSH keys found for user '{}'").format(username) + self._username = self._params.get("GithubUsername") + else: self._state = SshKeyActionState.ADD + self._username = "" + gui_app.push_widget(alert_dialog(error)) def ssh_key_item(title: str | Callable[[], str], description: str | Callable[[], str]) -> ListItem: diff --git a/sunnypilot/models/fetcher.py b/sunnypilot/models/fetcher.py index 5990ee2e41..452c59e06b 100644 --- a/sunnypilot/models/fetcher.py +++ b/sunnypilot/models/fetcher.py @@ -116,7 +116,7 @@ class ModelCache: class ModelFetcher: """Handles fetching and caching of model data from remote source""" - MODEL_URL = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-docs/refs/heads/gh-pages/docs/driving_models_v15.json" + MODEL_URL = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-models/refs/heads/gh-pages/docs/driving_models_v15.json" def __init__(self, params: Params): self.params = params diff --git a/sunnypilot/selfdrive/pandad/rivian_long_flasher.py b/sunnypilot/selfdrive/pandad/rivian_long_flasher.py index 24191a73a2..305b994c78 100755 --- a/sunnypilot/selfdrive/pandad/rivian_long_flasher.py +++ b/sunnypilot/selfdrive/pandad/rivian_long_flasher.py @@ -72,9 +72,10 @@ def _flash_panda(panda: Panda) -> None: _flash_static(panda._handle, code) panda.reconnect() + cloudlog.info(f"Successfully flashed xnor's Rivian Longitudinal Upgrade Kit: {panda.get_usb_serial()}") -def flash_rivian_long(panda: Panda) -> None: +def flash_rivian_long(panda_serials: list[str]) -> None: if not os.path.isfile(FW_PATH): cloudlog.error(f"Rivian longitudinal upgrade firmware not found at {FW_PATH}") return @@ -83,13 +84,22 @@ def flash_rivian_long(panda: Panda) -> None: cloudlog.info("Not a Rivian, skipping longitudinal upgrade...") return - # only flash external black pandas (HW_TYPE_BLACK = 0x03) - if panda.get_type() == b'\x03' and not panda.is_internal(): - try: - _flash_panda(panda) - except Exception: - cloudlog.exception(f"Failed to flash F4 panda {panda.get_usb_serial()}") + # only check USB connected pandas, internal panda uses SPI and is never an external panda + usb_serials = set(Panda.usb_list()) + for serial in panda_serials: + if serial not in usb_serials: + continue + panda = Panda(serial) + # only flash external black pandas (HW_TYPE_BLACK = 0x03) + if panda.get_type() == b'\x03' and not panda.is_internal(): + try: + _flash_panda(panda) + except Exception: + cloudlog.exception(f"Failed to flash xnor's Rivian Longitudinal Upgrade Kit: {serial}") + panda.close() + + return if __name__ == '__main__': - flash_rivian_long(Panda()) + flash_rivian_long(Panda.list()) diff --git a/sunnypilot/sunnylink/athena/sunnylinkd.py b/sunnypilot/sunnylink/athena/sunnylinkd.py index e8066dca49..39fe5679ce 100755 --- a/sunnypilot/sunnylink/athena/sunnylinkd.py +++ b/sunnypilot/sunnylink/athena/sunnylinkd.py @@ -229,6 +229,41 @@ def getParamsAllKeysV1() -> dict[str, str]: raise +@dispatcher.add_method +def getParamsMetadata() -> str: + """Compressed equivalent of getParamsAllKeysV1 — same struct, gzipped + base64.""" + try: + with open(METADATA_PATH) as f: + metadata = json.load(f) + except Exception: + cloudlog.exception("sunnylinkd.getParamsMetadata.exception") + metadata = {} + + try: + available_keys: list[str] = [k.decode('utf-8') for k in Params().all_keys()] + + params_list: list[dict] = [] + for key in available_keys: + value = get_param_as_byte(key, get_default=True) + + param_entry: dict = { + "key": key, + "type": int(params.get_type(key).value), + "default_value": base64.b64encode(value).decode('utf-8') if value else None, + } + + if key in metadata: + param_entry["_extra"] = metadata[key] + + params_list.append(param_entry) + + raw = json.dumps(params_list, separators=(',', ':')).encode('utf-8') + return base64.b64encode(gzip.compress(raw)).decode('utf-8') + except Exception: + cloudlog.exception("sunnylinkd.getParamsMetadata.exception") + raise + + @dispatcher.add_method def getParams(params_keys: list[str], compression: bool = False) -> str | dict[str, str]: params = Params() diff --git a/sunnypilot/sunnylink/params_metadata.json b/sunnypilot/sunnylink/params_metadata.json index c8c9839cd0..4392602d7d 100644 --- a/sunnypilot/sunnylink/params_metadata.json +++ b/sunnypilot/sunnylink/params_metadata.json @@ -941,6 +941,22 @@ "title": "Onroad Brightness Delay", "description": "", "options": [ + { + "value": 3, + "label": "3s" + }, + { + "value": 5, + "label": "5s" + }, + { + "value": 7, + "label": "7s" + }, + { + "value": 10, + "label": "10s" + }, { "value": 15, "label": "15s" @@ -991,6 +1007,10 @@ } ] }, + "OnroadScreenOffTimerMigrated": { + "title": "Onroad Brightness Delay Migration Version", + "description": "This param is to track whether OnroadScreenOffTimer needs to be migrated." + }, "OnroadUploads": { "title": "Onroad Uploads", "description": "" @@ -1294,7 +1314,7 @@ "min": 0.1, "max": 5.0, "step": 0.1, - "unit": "m/s²" + "unit": "m/s\u00b2" }, "ToyotaEnforceStockLongitudinal": { "title": "Toyota: Enforce Factory Longitudinal Control", diff --git a/sunnypilot/sunnylink/tools/update_params_metadata.py b/sunnypilot/sunnylink/tools/update_params_metadata.py index 013544005b..ea3765420d 100755 --- a/sunnypilot/sunnylink/tools/update_params_metadata.py +++ b/sunnypilot/sunnylink/tools/update_params_metadata.py @@ -10,6 +10,7 @@ import os from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params +from openpilot.sunnypilot.system.params_migration import ONROAD_BRIGHTNESS_TIMER_VALUES METADATA_PATH = os.path.join(os.path.dirname(__file__), "../params_metadata.json") TORQUE_VERSIONS_JSON = os.path.join(BASEDIR, "sunnypilot", "selfdrive", "controls", "lib", "latcontrol_torque_versions.json") @@ -56,6 +57,9 @@ def main(): # update onroad screen brightness params update_onroad_brightness_param() + # update onroad screen brightness timer params + update_onroad_brightness_timer_param() + # update torque versions param update_torque_versions_param() @@ -81,6 +85,24 @@ def update_onroad_brightness_param(): print(f"Failed to update OnroadScreenOffBrightness versions in params_metadata.json: {e}") +def update_onroad_brightness_timer_param(): + try: + with open(METADATA_PATH) as f: + params_metadata = json.load(f) + if "OnroadScreenOffTimer" in params_metadata: + options = [] + for _index, seconds in sorted(ONROAD_BRIGHTNESS_TIMER_VALUES.items()): + label = f"{seconds}s" if seconds < 60 else f"{seconds // 60}m" + options.append({"value": seconds, "label": label}) + params_metadata["OnroadScreenOffTimer"]["options"] = options + with open(METADATA_PATH, 'w') as f: + json.dump(params_metadata, f, indent=2) + f.write('\n') + print(f"Updated OnroadScreenOffTimer options in params_metadata.json with {len(options)} options.") + except Exception as e: + print(f"Failed to update OnroadScreenOffTimer options in params_metadata.json: {e}") + + def update_torque_versions_param(): with open(TORQUE_VERSIONS_JSON) as f: current_versions = json.load(f) diff --git a/sunnypilot/system/params_migration.py b/sunnypilot/system/params_migration.py index 6a82f1866d..5e524de06e 100644 --- a/sunnypilot/system/params_migration.py +++ b/sunnypilot/system/params_migration.py @@ -7,13 +7,18 @@ See the LICENSE.md file in the root directory for more details. from openpilot.common.swaglog import cloudlog ONROAD_BRIGHTNESS_MIGRATION_VERSION: str = "1.0" +ONROAD_BRIGHTNESS_TIMER_MIGRATION_VERSION: str = "1.0" + +# index → seconds mapping for OnroadScreenOffTimer (SSoT) +ONROAD_BRIGHTNESS_TIMER_VALUES = {0: 3, 1: 5, 2: 7, 3: 10, 4: 15, 5: 30, **{i: (i - 5) * 60 for i in range(6, 16)}} +VALID_TIMER_VALUES = set(ONROAD_BRIGHTNESS_TIMER_VALUES.values()) def run_migration(_params): # migrate OnroadScreenOffBrightness if _params.get("OnroadScreenOffBrightnessMigrated") != ONROAD_BRIGHTNESS_MIGRATION_VERSION: try: - val = _params.get("OnroadScreenOffBrightness") + val = _params.get("OnroadScreenOffBrightness", return_default=True) if val >= 2: # old: 5%, new: Screen Off new_val = val + 1 _params.put("OnroadScreenOffBrightness", new_val) @@ -25,3 +30,18 @@ def run_migration(_params): cloudlog.info(log_str + f" Setting OnroadScreenOffBrightnessMigrated to {ONROAD_BRIGHTNESS_MIGRATION_VERSION}") except Exception as e: cloudlog.exception(f"Error migrating OnroadScreenOffBrightness: {e}") + + # migrate OnroadScreenOffTimer + if _params.get("OnroadScreenOffTimerMigrated") != ONROAD_BRIGHTNESS_TIMER_MIGRATION_VERSION: + try: + val = _params.get("OnroadScreenOffTimer", return_default=True) + if val not in VALID_TIMER_VALUES: + _params.put("OnroadScreenOffTimer", 15) + log_str = f"Successfully migrated OnroadScreenOffTimer from {val} to 15 (default)." + else: + log_str = "Migration not required for OnroadScreenOffTimer." + + _params.put("OnroadScreenOffTimerMigrated", ONROAD_BRIGHTNESS_TIMER_MIGRATION_VERSION) + cloudlog.info(log_str + f" Setting OnroadScreenOffTimerMigrated to {ONROAD_BRIGHTNESS_TIMER_MIGRATION_VERSION}") + except Exception as e: + cloudlog.exception(f"Error migrating OnroadScreenOffTimer: {e}") diff --git a/system/camerad/cameras/hw.h b/system/camerad/cameras/hw.h index f20a1b3ade..defe878e89 100644 --- a/system/camerad/cameras/hw.h +++ b/system/camerad/cameras/hw.h @@ -25,6 +25,7 @@ struct CameraConfig { uint32_t phy; bool vignetting_correction; SpectraOutputType output_type; + bool staggered_sof; // SOF is staggered (half-period offset) from other cameras }; // NOTE: to be able to disable road and wide road, we still have to configure the sensor over i2c @@ -39,6 +40,7 @@ const CameraConfig WIDE_ROAD_CAMERA_CONFIG = { .phy = CAM_ISP_IFE_IN_RES_PHY_0, .vignetting_correction = false, .output_type = ISP_IFE_PROCESSED, + .staggered_sof = false, }; const CameraConfig ROAD_CAMERA_CONFIG = { @@ -51,6 +53,7 @@ const CameraConfig ROAD_CAMERA_CONFIG = { .phy = CAM_ISP_IFE_IN_RES_PHY_1, .vignetting_correction = true, .output_type = ISP_IFE_PROCESSED, + .staggered_sof = false, }; const CameraConfig DRIVER_CAMERA_CONFIG = { @@ -63,6 +66,7 @@ const CameraConfig DRIVER_CAMERA_CONFIG = { .phy = CAM_ISP_IFE_IN_RES_PHY_2, .vignetting_correction = false, .output_type = ISP_BPS_PROCESSED, + .staggered_sof = true, }; const CameraConfig ALL_CAMERA_CONFIGS[] = {WIDE_ROAD_CAMERA_CONFIG, ROAD_CAMERA_CONFIG, DRIVER_CAMERA_CONFIG}; diff --git a/system/camerad/cameras/spectra.cc b/system/camerad/cameras/spectra.cc index 73e0a78da3..ab9d8e069a 100644 --- a/system/camerad/cameras/spectra.cc +++ b/system/camerad/cameras/spectra.cc @@ -1436,7 +1436,7 @@ bool SpectraCamera::waitForFrameReady(uint64_t request_id) { } bool SpectraCamera::processFrame(int buf_idx, uint64_t request_id, uint64_t frame_id_raw, uint64_t timestamp) { - if (!syncFirstFrame(cc.camera_num, request_id, frame_id_raw, timestamp)) { + if (!syncFirstFrame(cc.camera_num, request_id, frame_id_raw, timestamp, cc.staggered_sof)) { return false; } @@ -1455,23 +1455,31 @@ bool SpectraCamera::processFrame(int buf_idx, uint64_t request_id, uint64_t fram return true; } -bool SpectraCamera::syncFirstFrame(int camera_id, uint64_t request_id, uint64_t raw_id, uint64_t timestamp) { +bool SpectraCamera::syncFirstFrame(int camera_id, uint64_t request_id, uint64_t raw_id, uint64_t timestamp, bool staggered) { if (first_frame_synced) return true; // Store the frame data for this camera - camera_sync_data[camera_id] = SyncData{timestamp, raw_id + 1}; + camera_sync_data[camera_id] = SyncData{timestamp, raw_id + 1, staggered}; // Ensure all cameras are up int enabled_camera_count = std::count_if(std::begin(ALL_CAMERA_CONFIGS), std::end(ALL_CAMERA_CONFIGS), [](const auto &config) { return config.enabled; }); bool all_cams_up = camera_sync_data.size() == enabled_camera_count; - // Wait until the timestamps line up + // Check that camera timestamps are properly aligned: + // - non-staggered cameras should be within 0.2ms of each other + // - staggered cameras should be within 0.2ms of a 25ms offset from non-staggered cameras + const uint64_t half_period_ns = 25 * 1000000ULL; // 25ms + const uint64_t tolerance_ns = 200000ULL; // 0.2ms bool all_cams_synced = true; - for (const auto &[_, sync_data] : camera_sync_data) { + for (const auto &[cam, sync_data] : camera_sync_data) { + if (cam == camera_id) continue; uint64_t diff = std::max(timestamp, sync_data.timestamp) - std::min(timestamp, sync_data.timestamp); - if (diff > 0.2*1e6) { // milliseconds + bool pair_staggered = staggered != sync_data.staggered; + uint64_t expected_offset = pair_staggered ? half_period_ns : 0; + uint64_t error = (diff > expected_offset) ? diff - expected_offset : expected_offset - diff; + if (error > tolerance_ns) { all_cams_synced = false; } } diff --git a/system/camerad/cameras/spectra.h b/system/camerad/cameras/spectra.h index a02b8a6cac..7dd1135254 100644 --- a/system/camerad/cameras/spectra.h +++ b/system/camerad/cameras/spectra.h @@ -194,10 +194,11 @@ private: bool validateEvent(uint64_t request_id, uint64_t frame_id_raw); bool waitForFrameReady(uint64_t request_id); bool processFrame(int buf_idx, uint64_t request_id, uint64_t frame_id_raw, uint64_t timestamp); - static bool syncFirstFrame(int camera_id, uint64_t request_id, uint64_t raw_id, uint64_t timestamp); + static bool syncFirstFrame(int camera_id, uint64_t request_id, uint64_t raw_id, uint64_t timestamp, bool staggered); struct SyncData { uint64_t timestamp; uint64_t frame_id_offset = 0; + bool staggered = false; }; inline static std::map camera_sync_data; inline static bool first_frame_synced = false; diff --git a/system/camerad/test/test_camerad.py b/system/camerad/test/test_camerad.py index 5f8de86899..3abe4db654 100644 --- a/system/camerad/test/test_camerad.py +++ b/system/camerad/test/test_camerad.py @@ -105,17 +105,23 @@ class TestCamerad: assert set(np.diff(logs[c]['frameId'])) == {1, }, f"{c} has frame skips" def test_frame_sync(self, logs): + SYNCED_CAMS = ('roadCameraState', 'wideRoadCameraState') n = range(len(logs['roadCameraState']['t'][:-10])) frame_ids = {i: [logs[cam]['frameId'][i] for cam in CAMERAS] for i in n} assert all(len(set(v)) == 1 for v in frame_ids.values()), "frame IDs not aligned" - frame_times = {i: [logs[cam]['timestampSof'][i] for cam in CAMERAS] for i in n} - diffs = {i: (max(ts) - min(ts))/1e6 for i, ts in frame_times.items()} - + # road and wide cameras should be synced within 1.1ms + synced_times = {i: [logs[cam]['timestampSof'][i] for cam in SYNCED_CAMS] for i in n} + diffs = {i: (max(ts) - min(ts))/1e6 for i, ts in synced_times.items()} laggy_frames = {k: v for k, v in diffs.items() if v > 1.1} assert len(laggy_frames) == 0, f"Frames not synced properly: {laggy_frames=}" + # driver camera should be staggered ~25ms from road camera + for i in n: + offset_ms = abs(logs['driverCameraState']['timestampSof'][i] - logs['roadCameraState']['timestampSof'][i]) / 1e6 + assert 20 < offset_ms < 30, f"driver camera stagger out of range at frame {i}: {offset_ms:.1f}ms (expected ~25ms)" + def test_sanity_checks(self, logs): self._sanity_checks(logs) diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json index e33a26bb2e..295b0279d9 100644 --- a/system/hardware/tici/agnos.json +++ b/system/hardware/tici/agnos.json @@ -56,28 +56,28 @@ }, { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-a0185fa5ffc860de2179e4d0fec703fef6d560eacd730f79f60891ca79c72756.img.xz", - "hash": "a0185fa5ffc860de2179e4d0fec703fef6d560eacd730f79f60891ca79c72756", - "hash_raw": "a0185fa5ffc860de2179e4d0fec703fef6d560eacd730f79f60891ca79c72756", - "size": 17496064, + "url": "https://commadist.azureedge.net/agnosupdate/boot-d726315cf98a43e1090e5b49297404cf3d084cfbd42ad8bb7d8afb68136b9f51.img.xz", + "hash": "d726315cf98a43e1090e5b49297404cf3d084cfbd42ad8bb7d8afb68136b9f51", + "hash_raw": "d726315cf98a43e1090e5b49297404cf3d084cfbd42ad8bb7d8afb68136b9f51", + "size": 17500160, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "0ee1ab104bb46d0f72e7d0b7d3e94629a7644a368896c6d4c558554fb955a08a" + "ondevice_hash": "2454108de1161289bc4a75449ad6421f1772b13b3e5cba68a84fca7530557699" }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd.img.xz", - "hash": "7c58308be461126677ba02e9c9739556520ee02958934733867d86ecfe2e58e9", - "hash_raw": "0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd", + "url": "https://commadist.azureedge.net/agnosupdate/system-dcdea6bd675d0276a63c25151727829620794baf42ada2e5e19a3f77b3f583a5.img.xz", + "hash": "5f319030ad05942267b77f1a4686c4ca24cc09b2c2a4688e57342ffc9720fd49", + "hash_raw": "dcdea6bd675d0276a63c25151727829620794baf42ada2e5e19a3f77b3f583a5", "size": 4718592000, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "826790516410c325aa30265846946d06a556f0a7b23c957f65fd11c055a663da", + "ondevice_hash": "c12f1b7d790a418aea17424accf4cd59c575e5745cad82bdc9452f384483648c", "alt": { - "hash": "0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd", - "url": "https://commadist.azureedge.net/agnosupdate/system-0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd.img", + "hash": "dcdea6bd675d0276a63c25151727829620794baf42ada2e5e19a3f77b3f583a5", + "url": "https://commadist.azureedge.net/agnosupdate/system-dcdea6bd675d0276a63c25151727829620794baf42ada2e5e19a3f77b3f583a5.img", "size": 4718592000 } } diff --git a/system/hardware/tici/all-partitions.json b/system/hardware/tici/all-partitions.json index b6718fe97d..0801907a2d 100644 --- a/system/hardware/tici/all-partitions.json +++ b/system/hardware/tici/all-partitions.json @@ -339,62 +339,51 @@ }, { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-a0185fa5ffc860de2179e4d0fec703fef6d560eacd730f79f60891ca79c72756.img.xz", - "hash": "a0185fa5ffc860de2179e4d0fec703fef6d560eacd730f79f60891ca79c72756", - "hash_raw": "a0185fa5ffc860de2179e4d0fec703fef6d560eacd730f79f60891ca79c72756", - "size": 17496064, + "url": "https://commadist.azureedge.net/agnosupdate/boot-d726315cf98a43e1090e5b49297404cf3d084cfbd42ad8bb7d8afb68136b9f51.img.xz", + "hash": "d726315cf98a43e1090e5b49297404cf3d084cfbd42ad8bb7d8afb68136b9f51", + "hash_raw": "d726315cf98a43e1090e5b49297404cf3d084cfbd42ad8bb7d8afb68136b9f51", + "size": 17500160, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "0ee1ab104bb46d0f72e7d0b7d3e94629a7644a368896c6d4c558554fb955a08a" + "ondevice_hash": "2454108de1161289bc4a75449ad6421f1772b13b3e5cba68a84fca7530557699" }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd.img.xz", - "hash": "7c58308be461126677ba02e9c9739556520ee02958934733867d86ecfe2e58e9", - "hash_raw": "0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd", + "url": "https://commadist.azureedge.net/agnosupdate/system-dcdea6bd675d0276a63c25151727829620794baf42ada2e5e19a3f77b3f583a5.img.xz", + "hash": "5f319030ad05942267b77f1a4686c4ca24cc09b2c2a4688e57342ffc9720fd49", + "hash_raw": "dcdea6bd675d0276a63c25151727829620794baf42ada2e5e19a3f77b3f583a5", "size": 4718592000, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "826790516410c325aa30265846946d06a556f0a7b23c957f65fd11c055a663da", + "ondevice_hash": "c12f1b7d790a418aea17424accf4cd59c575e5745cad82bdc9452f384483648c", "alt": { - "hash": "0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd", - "url": "https://commadist.azureedge.net/agnosupdate/system-0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd.img", + "hash": "dcdea6bd675d0276a63c25151727829620794baf42ada2e5e19a3f77b3f583a5", + "url": "https://commadist.azureedge.net/agnosupdate/system-dcdea6bd675d0276a63c25151727829620794baf42ada2e5e19a3f77b3f583a5.img", "size": 4718592000 } }, { "name": "userdata_90", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-ec31b8116125a95755adb32853c401c462a14a74f538535532bf2c34d72c60eb.img.xz", - "hash": "aa0f0fe32187493e6135aee9e984d3f9705fc58560d537b34687bb6b51a38428", - "hash_raw": "ec31b8116125a95755adb32853c401c462a14a74f538535532bf2c34d72c60eb", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-a7b25ea29255f4fd3a2da99e037f40b4ca10bd4afd57dd96563353b8dfb0f634.img.xz", + "hash": "7ea9d7d4685ec36bbfdf06afe0b51650d567416c3092fef96bd97158ed322742", + "hash_raw": "a7b25ea29255f4fd3a2da99e037f40b4ca10bd4afd57dd96563353b8dfb0f634", "size": 96636764160, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "9c916b7d05543d4608b0401bc867639f44ce9671639a1a6da83b6d58b4eaa1b4" + "ondevice_hash": "79ed653c1679d84b13ee23083a511b0e668454e4af9b0db99a3279072ed041c1" }, { "name": "userdata_89", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-7f092cc841124c10300e43574e90e3367e983bfbe4faa0969024e79e5ce90b11.img.xz", - "hash": "fa83d4b7096857136820b0b0a8785c90677256b054c5c14039cd7b9b1065a90b", - "hash_raw": "7f092cc841124c10300e43574e90e3367e983bfbe4faa0969024e79e5ce90b11", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-8e428632c967aa609cac184bff938a90240e53ffd3b4fca40bc94c33c81202ba.img.xz", + "hash": "7104cdb0384e4ecb1ebfa6136a2330251bc8aa829b9ec48c4b740f656252d382", + "hash_raw": "8e428632c967aa609cac184bff938a90240e53ffd3b4fca40bc94c33c81202ba", "size": 95563022336, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "1699e38de769eb32c21dfa6a5ac21eb3ad620a362c7b8abf1a2c0afe0f717530" - }, - { - "name": "userdata_30", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-3df2dcd5e1f426c90b090fdbcd1a95b035d96a4bdaf88d5517245db5ee84f5ed.img.xz", - "hash": "890910f20b1ad88a728ee822a47b1234eb3d70cab28ca8a935679c8c2d33cbe9", - "hash_raw": "3df2dcd5e1f426c90b090fdbcd1a95b035d96a4bdaf88d5517245db5ee84f5ed", - "size": 32212254720, - "sparse": true, - "full_check": true, - "has_ab": false, - "ondevice_hash": "8e7cb392dd6e49c7d59fa850be7d1f44901314c86ba9c88be5bb27a0cd1123c9" + "ondevice_hash": "fbede3b0831dbc4a4edd336e5f547f4978902b9421fb1484e86c416192c59165" } ] \ No newline at end of file diff --git a/system/hardware/tici/updater_magic b/system/hardware/tici/updater_magic index ec586dbcb3..44b82d0c54 100755 --- a/system/hardware/tici/updater_magic +++ b/system/hardware/tici/updater_magic @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c44fb88b3b1643b6b44ae8ac9880348bd0257ff90f4084cbe889de91d71653fe -size 25111329 +oid sha256:3a94ab8395f20d20a9d5a2a2bacca0694f072df8421cf13adca6250d28065bdc +size 24709205 diff --git a/system/manager/manager.py b/system/manager/manager.py index 8c219909e4..3ec554f904 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -51,7 +51,8 @@ def manager_init() -> None: if params.get_bool("RecordFrontLock"): params.put_bool("RecordFront", True) - run_migration(params) + if not PC: + run_migration(params) # set unset params to their default value for k in params.all_keys(): diff --git a/system/qcomgpsd/qcomgpsd.py b/system/qcomgpsd/qcomgpsd.py index 59f5ac0b50..47d64ff4e9 100755 --- a/system/qcomgpsd/qcomgpsd.py +++ b/system/qcomgpsd/qcomgpsd.py @@ -7,7 +7,7 @@ import math import time import requests import shutil -import subprocess +from serial import Serial import datetime from multiprocessing import Process, Event from typing import NoReturn @@ -90,9 +90,24 @@ measurementStatusGlonassFields = { def try_setup_logs(diag, logs): return setup_logs(diag, logs) -@retry(attempts=3, delay=1.0) -def at_cmd(cmd: str) -> str | None: - return subprocess.check_output(f"mmcli -m any --timeout 30 --command='{cmd}'", shell=True, encoding='utf8') +AT_PORT = "/dev/modem_at0" + +@retry(attempts=5, delay=1.0) +def at_cmd(cmd: str) -> str: + with Serial(AT_PORT, baudrate=115200, timeout=5) as ser: + ser.reset_input_buffer() + ser.write(f"{cmd}\r".encode()) + lines = [] + while True: + line = ser.readline() + if not line: + raise RuntimeError(f"AT command timeout: {cmd}") + line = line.decode('utf-8', errors='replace').strip() + if line in ("OK", "ERROR") or line.startswith("+CME ERROR"): + break + if line and line != cmd: + lines.append(line) + return '\n'.join(lines) def gps_enabled() -> bool: return "QGPS: 1" in at_cmd("AT+QGPS?") @@ -131,6 +146,7 @@ def downloader_loop(event): @retry(attempts=5, delay=0.2, ignore_failure=True) def inject_assistance(): + import subprocess cmd = f"mmcli -m any --timeout 30 --location-inject-assistance-data={ASSIST_DATA_FILE}" subprocess.check_output(cmd, stderr=subprocess.PIPE, shell=True) cloudlog.info("successfully loaded assistance data") @@ -207,13 +223,19 @@ def teardown_quectel(diag): try_setup_logs(diag, []) -def wait_for_modem(cmd="AT+QGPS?"): +def wait_for_modem(): cloudlog.warning("waiting for modem to come up") + while not os.path.exists(AT_PORT): + time.sleep(0.5) + # wait until the modem GNSS subsystem responds while True: - ret = subprocess.call(f"mmcli -m any --timeout 10 --command=\"{cmd}\"", stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=True) - if ret == 0: - return - time.sleep(0.1) + try: + resp = at_cmd("AT+QGPS?") + if "+QGPS:" in resp: + return + except Exception: + pass + time.sleep(0.5) def main() -> NoReturn: @@ -335,6 +357,9 @@ def main() -> NoReturn: report = unpack_position(log_payload) if report["u_PosSource"] != 2: continue + # uint16_t max is an invalid sentinel value from the modem + if report['w_GpsWeekNumber'] >= 0xFFFF: + continue vNED = [report["q_FltVelEnuMps[1]"], report["q_FltVelEnuMps[0]"], -report["q_FltVelEnuMps[2]"]] vNEDsigma = [report["q_FltVelSigmaMps[1]"], report["q_FltVelSigmaMps[0]"], -report["q_FltVelSigmaMps[2]"]] diff --git a/system/qcomgpsd/tests/test_qcomgpsd.py b/system/qcomgpsd/tests/test_qcomgpsd.py index 2fc6205ea4..75e8707591 100644 --- a/system/qcomgpsd/tests/test_qcomgpsd.py +++ b/system/qcomgpsd/tests/test_qcomgpsd.py @@ -1,9 +1,7 @@ import os import pytest -import json import time import datetime -import subprocess import cereal.messaging as messaging from openpilot.system.qcomgpsd.qcomgpsd import at_cmd, wait_for_modem @@ -44,15 +42,13 @@ class TestRawgpsd: return self.sm.updated['qcomGnss'] def test_no_crash_double_command(self): + wait_for_modem() at_cmd("AT+QGPSDEL=0") at_cmd("AT+QGPSDEL=0") def test_wait_for_modem(self): os.system("sudo systemctl stop ModemManager") managed_processes['qcomgpsd'].start() - assert not self._wait_for_output(5) - - os.system("sudo systemctl restart ModemManager") assert self._wait_for_output(30) def test_startup_time(self, subtests): @@ -61,7 +57,7 @@ class TestRawgpsd: os.system("sudo systemctl stop systemd-resolved") with subtests.test(internet=internet): managed_processes['qcomgpsd'].start() - assert self._wait_for_output(7) + assert self._wait_for_output(30) managed_processes['qcomgpsd'].stop() def test_turns_off_gnss(self, subtests): @@ -71,14 +67,15 @@ class TestRawgpsd: time.sleep(s) managed_processes['qcomgpsd'].stop() - ls = subprocess.check_output("mmcli -m any --location-status --output-json", shell=True, encoding='utf-8') - loc_status = json.loads(ls) - assert set(loc_status['modem']['location']['enabled']) <= {'3gpp-lac-ci'} + wait_for_modem() + resp = at_cmd("AT+QGPS?") + assert "+QGPS: 0" in resp def check_assistance(self, should_be_loaded): # after QGPSDEL: '+QGPSXTRADATA: 0,"1980/01/05,19:00:00"' # after loading: '+QGPSXTRADATA: 10080,"2023/06/24,19:00:00"' + wait_for_modem() out = at_cmd("AT+QGPSXTRADATA?") out = out.split("+QGPSXTRADATA:")[1].split("'")[0].strip() valid_duration, injected_time_str = out.split(",", 1) @@ -92,20 +89,23 @@ class TestRawgpsd: assert injected_time_str[:] == '1980/01/05,19:00:00'[:] assert valid_duration == '0' + @pytest.mark.skip(reason="XTRA injection via QMI needs debugging on AGNOS 17") def test_assistance_loading(self): managed_processes['qcomgpsd'].start() - assert self._wait_for_output(10) + assert self._wait_for_output(30) managed_processes['qcomgpsd'].stop() self.check_assistance(True) + @pytest.mark.skip(reason="XTRA injection via QMI needs debugging on AGNOS 17") def test_no_assistance_loading(self): os.system("sudo systemctl stop systemd-resolved") managed_processes['qcomgpsd'].start() - assert self._wait_for_output(10) + assert self._wait_for_output(30) managed_processes['qcomgpsd'].stop() self.check_assistance(False) + @pytest.mark.skip(reason="XTRA injection via QMI needs debugging on AGNOS 17") def test_late_assistance_loading(self): os.system("sudo systemctl stop systemd-resolved") diff --git a/system/timed.py b/system/timed.py index b7131b04c0..c74ba51da5 100755 --- a/system/timed.py +++ b/system/timed.py @@ -5,7 +5,7 @@ import time from typing import NoReturn import cereal.messaging as messaging -from openpilot.common.time_helpers import min_date, system_time_valid +from openpilot.common.time_helpers import min_date, MAX_DATE, system_time_valid from openpilot.common.swaglog import cloudlog from openpilot.common.params import Params from openpilot.common.gps import get_gps_location_service @@ -52,7 +52,7 @@ def main() -> NoReturn: continue if not gps.hasFix: continue - if gps_time < min_date(): + if gps_time < min_date() or gps_time > MAX_DATE: continue set_time(gps_time) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index df4153067c..58edc0061a 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -1,5 +1,6 @@ import atexit import cffi +import math import os import queue import time @@ -96,7 +97,6 @@ FONT_DIR = ASSETS_DIR.joinpath("fonts") class FontWeight(StrEnum): - LIGHT = "Inter-Light.fnt" NORMAL = "Inter-Regular.fnt" if BIG_UI else "Inter-Medium.fnt" MEDIUM = "Inter-Medium.fnt" BOLD = "Inter-Bold.fnt" @@ -172,6 +172,10 @@ class MouseState: self._rk.keep_time() def _handle_mouse_event(self): + # TODO: read touch events from evdev directly to get real kernel timestamps. + # Polling at 140Hz with time.monotonic() causes timing jitter that makes scroll + # velocity oscillate (alternating high/low). Real timestamps would also let us + # detect swipe-stop-lift via event gaps instead of the fragile decel heuristic. for slot in range(MAX_TOUCH_SLOTS): mouse_pos = rl.get_touch_position(slot) x = mouse_pos.x / self._scale if self._scale != 1.0 else mouse_pos.x @@ -283,7 +287,7 @@ class GuiApplication(GuiApplicationExt): if self._scale != 1.0: rl.set_mouse_scale(1 / self._scale, 1 / self._scale) if needs_render_texture: - self._render_texture = rl.load_render_texture(self._width, self._height) + self._render_texture = rl.load_render_texture(self._scaled_width, self._scaled_height) rl.set_texture_filter(self._render_texture.texture, rl.TextureFilter.TEXTURE_FILTER_BILINEAR) if RECORD: @@ -294,13 +298,13 @@ class GuiApplication(GuiApplicationExt): '-nostats', # Suppress encoding progress '-f', 'rawvideo', # Input format '-pix_fmt', 'rgba', # Input pixel format - '-s', f'{self._width}x{self._height}', # Input resolution + '-s', f'{self._scaled_width}x{self._scaled_height}', # Input resolution '-r', str(fps), # Input frame rate '-i', 'pipe:0', # Input from stdin '-vf', 'vflip,format=yuv420p', # Flip vertically and convert to yuv420p '-r', str(output_fps), # Output frame rate (for speed multiplier) '-c:v', 'libx264', - '-preset', 'ultrafast', + '-preset', 'veryfast', '-crf', str(RECORD_QUALITY) ] if RECORD_BITRATE: @@ -324,6 +328,7 @@ class GuiApplication(GuiApplicationExt): self._set_styles() self._load_fonts() self._patch_text_functions() + self._patch_scissor_mode() if BURN_IN_MODE and self._burn_in_shader is None: self._burn_in_shader = rl.load_shader_from_memory(BURN_IN_VERTEX_SHADER, BURN_IN_FRAGMENT_SHADER) @@ -436,6 +441,9 @@ class GuiApplication(GuiApplicationExt): return self._nav_stack[-1] return None + def widget_in_stack(self, widget: object) -> bool: + return widget in self._nav_stack + def add_nav_stack_tick(self, tick_function: Callable[[], None]): if tick_function not in self._nav_stack_ticks: self._nav_stack_ticks.append(tick_function) @@ -456,6 +464,12 @@ class GuiApplication(GuiApplicationExt): with as_file(ASSETS_DIR.joinpath(asset_path)) as fspath: image_obj = self._load_image_from_path(fspath.as_posix(), width, height, alpha_premultiply, keep_aspect_ratio, flip_x) texture_obj = self._load_texture_from_image(image_obj) + + # Set logical size so widget layout math stays at 1x coordinates + if self._scale != 1.0 and width is not None and height is not None: + texture_obj.width = width + texture_obj.height = height + self._textures[cache_key] = texture_obj return texture_obj @@ -467,6 +481,11 @@ class GuiApplication(GuiApplicationExt): if alpha_premultiply: rl.image_alpha_premultiply(image) + # Scale up load size for sharper rendering, capped at source resolution + if self._scale != 1.0 and width is not None and height is not None: + width = min(int(width * self._scale), image.width) + height = min(int(height * self._scale), image.height) + if width is not None and height is not None: same_dimensions = image.width == width and image.height == height @@ -590,6 +609,10 @@ class GuiApplication(GuiApplicationExt): rl.begin_drawing() rl.clear_background(rl.BLACK) + if self._scale != 1.0: + rl.rl_push_matrix() + rl.rl_scalef(self._scale, self._scale, 1.0) + # Allow a Widget to still run a function regardless of the stack depth for tick in self._nav_stack_ticks: tick() @@ -600,11 +623,14 @@ class GuiApplication(GuiApplicationExt): yield True + if self._scale != 1.0: + rl.rl_pop_matrix() + if self._render_texture: rl.end_texture_mode() rl.begin_drawing() rl.clear_background(rl.BLACK) - src_rect = rl.Rectangle(0, 0, float(self._width), -float(self._height)) + src_rect = rl.Rectangle(0, 0, float(self._scaled_width), -float(self._scaled_height)) dst_rect = rl.Rectangle(0, 0, float(self._scaled_width), float(self._scaled_height)) texture = self._render_texture.texture if texture: @@ -661,7 +687,8 @@ class GuiApplication(GuiApplicationExt): fnt_path = fspath / font_weight_file font = rl.load_font(fnt_path.as_posix()) if font_weight_file != FontWeight.UNIFONT: - rl.set_texture_filter(font.texture, rl.TextureFilter.TEXTURE_FILTER_BILINEAR) + rl.gen_texture_mipmaps(font.texture) + rl.set_texture_filter(font.texture, rl.TextureFilter.TEXTURE_FILTER_TRILINEAR) self._fonts[font_weight_file] = font rl.gui_set_font(self._fonts[FontWeight.NORMAL]) @@ -683,6 +710,20 @@ class GuiApplication(GuiApplicationExt): rl.draw_text_ex = _draw_text_ex_scaled + def _patch_scissor_mode(self): + if self._scale == 1.0: + return + + if not hasattr(rl, "_orig_begin_scissor_mode"): + rl._orig_begin_scissor_mode = rl.begin_scissor_mode + + def _begin_scissor_mode_scaled(x, y, width, height): + return rl._orig_begin_scissor_mode( + int(x * self._scale), int(y * self._scale), + int(math.ceil(width * self._scale)), int(math.ceil(height * self._scale))) + + rl.begin_scissor_mode = _begin_scissor_mode_scaled + def _set_log_callback(self): ffi_libc = cffi.FFI() ffi_libc.cdef(""" diff --git a/system/ui/lib/emoji.py b/system/ui/lib/emoji.py index 37228e2d45..ad4c272c8d 100644 --- a/system/ui/lib/emoji.py +++ b/system/ui/lib/emoji.py @@ -1,12 +1,13 @@ import io import re +import functools +from importlib.resources import as_file from PIL import Image, ImageDraw, ImageFont import pyray as rl from openpilot.system.ui.lib.application import FONT_DIR -_emoji_font: ImageFont.FreeTypeFont | None = None _cache: dict[str, rl.Texture] = {} EMOJI_REGEX = re.compile( @@ -33,11 +34,10 @@ EMOJI_REGEX = re.compile( flags=re.UNICODE ) -def _load_emoji_font() -> ImageFont.FreeTypeFont | None: - global _emoji_font - if _emoji_font is None: - _emoji_font = ImageFont.truetype(str(FONT_DIR.joinpath("NotoColorEmoji.ttf")), 109) - return _emoji_font +@functools.cache +def _load_emoji_font() -> ImageFont.FreeTypeFont: + with as_file(FONT_DIR.joinpath("NotoColorEmoji.ttf")) as font_path: + return ImageFont.truetype(io.BytesIO(font_path.read_bytes()), 109) def find_emoji(text): return [(m.start(), m.end(), m.group()) for m in EMOJI_REGEX.finditer(text)] diff --git a/system/ui/lib/multilang.py b/system/ui/lib/multilang.py index 506fc13b0a..9d25427867 100644 --- a/system/ui/lib/multilang.py +++ b/system/ui/lib/multilang.py @@ -74,7 +74,7 @@ def load_translations(path) -> tuple[dict[str, str], dict[str, list[str]]]: translations: msgid -> msgstr plurals: msgid -> [msgstr[0], msgstr[1], ...] """ - with open(str(path), encoding='utf-8') as f: + with path.open(encoding='utf-8') as f: lines = f.readlines() translations: dict[str, str] = {} diff --git a/system/ui/lib/scroll_panel2.py b/system/ui/lib/scroll_panel2.py index e2a548ba26..18fd8a9a67 100644 --- a/system/ui/lib/scroll_panel2.py +++ b/system/ui/lib/scroll_panel2.py @@ -20,6 +20,21 @@ MAX_SPEED = 10000.0 # px/s DEBUG = os.getenv("DEBUG_SCROLL", "0") == "1" +# Weights older (steadier) velocity samples more heavily on release. +# Finger-lift samples are noisy; trusting earlier samples gives consistent fling velocity. +# Reverse-engineered from iOS UIScrollView (tuned at 120Hz touch) by Flutter team: +# https://github.com/flutter/flutter/pull/60501 +# 3 samples ≈ 25ms at 120Hz (iOS) / ~21ms at 140Hz (comma). Scale if touch rate changes. +def weighted_velocity(buffer: deque) -> float: + if len(buffer) >= 3: + return buffer[-3] * 0.6 + buffer[-2] * 0.35 + buffer[-1] * 0.05 + elif len(buffer) == 2: + return buffer[-2] * 0.7 + buffer[-1] * 0.3 + elif len(buffer) == 1: + return buffer[-1] + return 0.0 + + # from https://ariya.io/2011/10/flick-list-with-its-momentum-scrolling-and-deceleration class ScrollState(Enum): STEADY = 0 @@ -151,7 +166,13 @@ class GuiScrollPanel2: # Touch rejection: when releasing finger after swiping and stopping, panel # reports a few erroneous touch events with high velocity, try to ignore. - # If velocity decelerates very quickly, assume user doesn't intend to auto scroll + # If velocity decelerates very quickly, assume user doesn't intend to auto scroll. + # Catches two cases: 1) swipe, stop finger, then lift (stale high velocity in buffer) + # 2) dirty finger lift where finger rotates/slides producing spurious velocity spike. + # TODO: this heuristic false-positives on fast swipes because 140Hz touch polling + # jitter causes velocity to oscillate (not real deceleration). Better approaches: + # - Use evdev kernel timestamps to eliminate velocity oscillation at the source + # - Replace with a time-since-last-event check (40ms timeout) for swipe-stop-lift high_decel = False if len(self._velocity_buffer) > 2: # We limit max to first half since final few velocities can surpass first few @@ -166,6 +187,8 @@ class GuiScrollPanel2: print('deceleration too high, going to STEADY') high_decel = True + self._velocity = weighted_velocity(self._velocity_buffer) + # If final velocity is below some threshold, switch to steady state too low_speed = abs(self._velocity) <= MIN_VELOCITY_FOR_CLICKING * 1.5 # plus some margin diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 1084e9e533..b83f7bacc9 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -854,8 +854,12 @@ class WifiManager: # NOTE: AccessPoints property may exclude hidden APs (use GetAllAccessPoints method if needed) wifi_addr = DBusAddress(self._wifi_device, NM, interface=NM_WIRELESS_IFACE) - wifi_props = self._router_main.send_and_get_reply(Properties(wifi_addr).get_all()).body[0] - ap_paths = wifi_props.get('AccessPoints', ('ao', []))[1] + wifi_props_reply = self._router_main.send_and_get_reply(Properties(wifi_addr).get_all()) + if wifi_props_reply.header.message_type == MessageType.error: + cloudlog.warning(f"Failed to get WiFi properties: {wifi_props_reply}") + return + + ap_paths = wifi_props_reply.body[0].get('AccessPoints', ('ao', []))[1] aps: dict[str, list[AccessPoint]] = {} diff --git a/system/ui/mici_reset.py b/system/ui/mici_reset.py index a459927eeb..9cc6e7f3f8 100755 --- a/system/ui/mici_reset.py +++ b/system/ui/mici_reset.py @@ -1,18 +1,17 @@ #!/usr/bin/env python3 import os import sys -import threading import time +import threading from enum import IntEnum import pyray as rl -from openpilot.system.hardware import PC -from openpilot.system.ui.lib.application import gui_app, FontWeight -from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.widgets.slider import SmallSlider -from openpilot.system.ui.widgets.button import SmallButton, FullRoundedButton -from openpilot.system.ui.widgets.label import gui_label, gui_text_box +from openpilot.system.hardware import HARDWARE, PC +from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.ui.mici_setup import GreyBigButton, FailedPage +from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationCircleButton USERDATA = "/dev/disk/by-partlabel/userdata" TIMEOUT = 3*60 @@ -21,35 +20,85 @@ TIMEOUT = 3*60 class ResetMode(IntEnum): USER_RESET = 0 # user initiated a factory reset from openpilot RECOVER = 1 # userdata is corrupt for some reason, give a chance to recover - FORMAT = 2 # finish up a factory reset from a tool that doesn't flash an empty partition to userdata + TAP_RESET = 2 # user initiated a factory reset by tapping the screen during boot -class ResetState(IntEnum): - NONE = 0 - RESETTING = 1 - FAILED = 2 +class ResetFailedPage(FailedPage): + def __init__(self): + super().__init__(None, "reset failed", "reboot to try again", icon="icons_mici/setup/reset_failed.png") + + def show_event(self): + super().show_event() + self._nav_bar._alpha = 0.0 # not dismissable + + def _back_enabled(self) -> bool: + return False -class Reset(Widget): +class ResettingPage(BigDialog): + DOT_STEP = 0.6 + + def __init__(self): + super().__init__("resetting device", "this may take up to\na minute...", + gui_app.texture("icons_mici/setup/factory_reset.png", 64, 64)) + self._show_time = 0.0 + + def show_event(self): + super().show_event() + self._nav_bar._alpha = 0.0 # not dismissable + self._show_time = rl.get_time() + + def _back_enabled(self) -> bool: + return False + + def _render(self, _): + t = (rl.get_time() - self._show_time) % (self.DOT_STEP * 2) + dots = "." * min(int(t / (self.DOT_STEP / 4)), 3) + self._card.set_value(f"this may take up to\na minute{dots}") + super()._render(_) + + +class Reset(Scroller): def __init__(self, mode): super().__init__() self._mode = mode - self._previous_reset_state = None - self._reset_state = ResetState.NONE + self._previous_active_widget = None + self._reset_failed = False + self._timeout_st = time.monotonic() - self._cancel_button = SmallButton("cancel") - self._cancel_button.set_click_callback(gui_app.request_close) + self._resetting_page = ResettingPage() + self._reset_failed_page = ResetFailedPage() - self._reboot_button = FullRoundedButton("reboot") - self._reboot_button.set_click_callback(self._do_reboot) + self._reset_button = BigConfirmationCircleButton("reset &\nerase", gui_app.texture("icons_mici/settings/device/uninstall.png", 70, 70), + self._start_reset, exit_on_confirm=False, red=True) + self._cancel_button = BigConfirmationCircleButton("cancel", gui_app.texture("icons_mici/setup/cancel.png", 64, 64), + gui_app.request_close, exit_on_confirm=False) + self._reboot_button = BigConfirmationCircleButton("reboot\ndevice", gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70), + HARDWARE.reboot, exit_on_confirm=False) - self._confirm_slider = SmallSlider("reset", self._confirm) + # show reboot button if in recover mode + self._cancel_button.set_visible(mode != ResetMode.RECOVER) + self._reboot_button.set_visible(mode == ResetMode.RECOVER) - def _do_reboot(self): - if PC: - return + main_card = GreyBigButton("factory reset", "resetting erases\nall user content & data", + gui_app.texture("icons_mici/setup/factory_reset.png", 64, 64)) + self._scroller.add_widget(main_card) - os.system("sudo reboot") + if mode != ResetMode.USER_RESET: + self._scroller.add_widget(GreyBigButton("", "Resetting erases all user content & data.")) + if mode == ResetMode.RECOVER: + main_card.set_value("user data partition\ncould not be mounted") + elif mode == ResetMode.TAP_RESET: + main_card.set_value("reset triggered by\ntapping the screen") + + self._scroller.add_widgets([ + GreyBigButton("", "For a deeper reset, go to\nhttps://flash.comma.ai"), + self._cancel_button, + self._reboot_button, + self._reset_button, + ]) + + gui_app.add_nav_stack_tick(self._nav_stack_tick) def _do_erase(self): if PC: @@ -63,86 +112,38 @@ class Reset(Widget): if rm == 0 or fmt == 0: os.system("sudo reboot") else: - self._reset_state = ResetState.FAILED + self._reset_failed = True - def start_reset(self): - self._reset_state = ResetState.RESETTING - threading.Timer(0.1, self._do_erase).start() + def _start_reset(self): + def do_erase_thread(): + threading.Thread(target=self._do_erase, daemon=True).start() - def _update_state(self): - if self._reset_state != self._previous_reset_state: - self._previous_reset_state = self._reset_state + self._resetting_page.set_shown_callback(do_erase_thread) + gui_app.push_widget(self._resetting_page) + + def _nav_stack_tick(self): + if self._reset_failed: + self._reset_failed = False + gui_app.pop_widgets_to(self, lambda: gui_app.push_widget(self._reset_failed_page)) + + active_widget = gui_app.get_active_widget() + if active_widget != self._previous_active_widget: + self._previous_active_widget = active_widget self._timeout_st = time.monotonic() - elif self._reset_state != ResetState.RESETTING and (time.monotonic() - self._timeout_st) > TIMEOUT: + elif self._mode != ResetMode.RECOVER and active_widget != self._resetting_page and (time.monotonic() - self._timeout_st) > TIMEOUT: exit(0) - def _render(self, rect: rl.Rectangle): - label_rect = rl.Rectangle(rect.x + 8, rect.y + 8, rect.width, 50) - gui_label(label_rect, "factory reset", 48, font_weight=FontWeight.BOLD, - color=rl.Color(255, 255, 255, int(255 * 0.9))) - - text_rect = rl.Rectangle(rect.x + 8, rect.y + 56, rect.width - 8 * 2, rect.height - 80) - gui_text_box(text_rect, self._get_body_text(), 36, font_weight=FontWeight.ROMAN, line_scale=0.9) - - if self._reset_state != ResetState.RESETTING: - # fade out cancel button as slider is moved, set visible to prevent pressing invisible cancel - self._cancel_button.set_opacity(1.0 - self._confirm_slider.slider_percentage) - self._cancel_button.set_visible(self._confirm_slider.slider_percentage < 0.8) - - if self._mode == ResetMode.RECOVER: - self._cancel_button.set_text("reboot") - self._cancel_button.render(rl.Rectangle( - rect.x + 8, - rect.y + rect.height - self._cancel_button.rect.height, - self._cancel_button.rect.width, - self._cancel_button.rect.height)) - elif self._mode == ResetMode.USER_RESET and self._reset_state != ResetState.FAILED: - self._cancel_button.render(rl.Rectangle( - rect.x + 8, - rect.y + rect.height - self._cancel_button.rect.height, - self._cancel_button.rect.width, - self._cancel_button.rect.height)) - - if self._reset_state != ResetState.FAILED: - self._confirm_slider.render(rl.Rectangle( - rect.x + rect.width - self._confirm_slider.rect.width, - rect.y + rect.height - self._confirm_slider.rect.height, - self._confirm_slider.rect.width, - self._confirm_slider.rect.height)) - else: - self._reboot_button.render(rl.Rectangle( - rect.x + 8, - rect.y + rect.height - self._reboot_button.rect.height, - self._reboot_button.rect.width, - self._reboot_button.rect.height)) - - def _confirm(self): - self.start_reset() - - def _get_body_text(self): - if self._reset_state == ResetState.RESETTING: - return "Resetting device... This may take up to a minute." - if self._reset_state == ResetState.FAILED: - return "Reset failed. Reboot to try again." - if self._mode == ResetMode.RECOVER: - return "Unable to mount data partition. It may be corrupted." - return "All content and settings will be erased." - def main(): mode = ResetMode.USER_RESET if len(sys.argv) > 1: if sys.argv[1] == '--recover': mode = ResetMode.RECOVER - elif sys.argv[1] == "--format": - mode = ResetMode.FORMAT + elif sys.argv[1] == '--tap-reset': + mode = ResetMode.TAP_RESET gui_app.init_window("System Reset") reset = Reset(mode) - - if mode == ResetMode.FORMAT: - reset.start_reset() - gui_app.push_widget(reset) for _ in gui_app.render(): diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 76fdfd8c68..d04f08ca37 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -1,61 +1,52 @@ #!/usr/bin/env python3 -from abc import abstractmethod import os import re +import ssl import threading import time import urllib.request import urllib.error from urllib.parse import urlparse -import shutil from collections.abc import Callable import pyray as rl from cereal import log -from openpilot.common.filter_simple import FirstOrderFilter +from openpilot.common.filter_simple import BounceFilter +from openpilot.system.hardware import HARDWARE, TICI from openpilot.common.realtime import config_realtime_process, set_core_affinity from openpilot.common.swaglog import cloudlog +from openpilot.common.time_helpers import system_time_valid from openpilot.common.utils import run_cmd -from openpilot.system.hardware import HARDWARE, TICI from openpilot.system.ui.lib.application import gui_app, FontWeight -from openpilot.system.ui.lib.wifi_manager import WifiManager -from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2 +from openpilot.system.ui.lib.wifi_manager import WifiManager, ConnectStatus from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.nav_widget import NavWidget -from openpilot.system.ui.widgets.button import (IconButton, SmallButton, WideRoundedButton, SmallerRoundedButton, - SmallCircleIconButton, WidishRoundedButton, FullRoundedButton) from openpilot.system.ui.widgets.label import UnifiedLabel -from openpilot.system.ui.widgets.slider import LargerSlider, SmallSlider -from openpilot.selfdrive.ui.mici.layouts.settings.network import WifiUIMici -from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog +from openpilot.system.ui.widgets.scroller import Scroller, NavScroller, ITEM_SPACING +from openpilot.system.ui.widgets.slider import LargerSlider +from openpilot.selfdrive.ui.mici.layouts.settings.network import WifiNetworkButton +from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici +from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog, BigConfirmationCircleButton +from openpilot.selfdrive.ui.mici.widgets.button import BigButton, GreyBigButton NetworkType = log.DeviceState.NetworkType OPENPILOT_URL = "https://openpilot.comma.ai" USER_AGENT = f"AGNOSSetup-{HARDWARE.get_os_version()}" -CONTINUE_PATH = "/data/continue.sh" -TMP_CONTINUE_PATH = "/data/continue.sh.new" -INSTALL_PATH = "/data/openpilot" -VALID_CACHE_PATH = "/data/.openpilot_cache" -INSTALLER_SOURCE_PATH = "/usr/comma/installer" INSTALLER_DESTINATION_PATH = "/tmp/installer" INSTALLER_URL_PATH = "/tmp/installer_url" -CONTINUE = """#!/usr/bin/env bash - -cd /data/openpilot -exec ./launch_openpilot.sh -""" - class NetworkConnectivityMonitor: def __init__(self, should_check: Callable[[], bool] | None = None): self.network_connected = threading.Event() self.wifi_connected = threading.Event() + self.recheck_event = threading.Event() self._should_check = should_check or (lambda: True) self._stop_event = threading.Event() + self._last_timesyncd_restart = 0.0 self._thread: threading.Thread | None = None def start(self): @@ -74,15 +65,32 @@ class NetworkConnectivityMonitor: self.network_connected.clear() self.wifi_connected.clear() + def invalidate(self): + self.recheck_event.set() + self.reset() + def _run(self): while not self._stop_event.is_set(): if self._should_check(): try: request = urllib.request.Request(OPENPILOT_URL, method="HEAD") urllib.request.urlopen(request, timeout=2.0) + + # Discard stale result if invalidated during request + if self.recheck_event.is_set(): + self.recheck_event.clear() + continue + self.network_connected.set() if HARDWARE.get_network_type() == NetworkType.wifi: self.wifi_connected.set() + except urllib.error.URLError as e: + if (isinstance(e.reason, ssl.SSLCertVerificationError) and + not system_time_valid() and + time.monotonic() - self._last_timesyncd_restart > 5): + self._last_timesyncd_restart = time.monotonic() + run_cmd(["sudo", "systemctl", "restart", "systemd-timesyncd"]) + self.reset() except Exception: self.reset() else: @@ -102,7 +110,7 @@ class StartPage(Widget): self._start_bg_txt = gui_app.texture("icons_mici/setup/start_button.png", 500, 224, keep_aspect_ratio=False) self._start_bg_pressed_txt = gui_app.texture("icons_mici/setup/start_button_pressed.png", 500, 224, keep_aspect_ratio=False) - self._scale_filter = FirstOrderFilter(1.0, 0.1, 1 / gui_app.target_fps) + self._scale_filter = BounceFilter(1.0, 0.1, 1 / gui_app.target_fps) self._click_delay = 0.075 def _render(self, rect: rl.Rectangle): @@ -122,9 +130,9 @@ class SoftwareSelectionPage(NavWidget): use_custom_software_callback: Callable): super().__init__() - self._openpilot_slider = LargerSlider("slide to use\nopenpilot", use_openpilot_callback) + self._openpilot_slider = self._child(LargerSlider("slide to install\nopenpilot", use_openpilot_callback)) self._openpilot_slider.set_enabled(lambda: self.enabled and not self.is_dismissing) - self._custom_software_slider = LargerSlider("slide to use\ncustom software", use_custom_software_callback, green=False) + self._custom_software_slider = self._child(LargerSlider("slide to install\ncustom software", use_custom_software_callback, green=False, shimmer_offset=0.4)) self._custom_software_slider.set_enabled(lambda: self.enabled and not self.is_dismissing) def show_event(self): @@ -161,202 +169,28 @@ class SoftwareSelectionPage(NavWidget): self._custom_software_slider.render(custom_software_rect) -class TermsHeader(Widget): - def __init__(self, text: str, icon_texture: rl.Texture): - super().__init__() - - self._title = UnifiedLabel(text, 36, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), - font_weight=FontWeight.BOLD, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE, - line_height=0.8) - self._icon_texture = icon_texture - - self.set_rect(rl.Rectangle(0, 0, gui_app.width - 16 * 2, self._icon_texture.height)) - - def set_title(self, text: str): - self._title.set_text(text) - - def set_icon(self, icon_texture: rl.Texture): - self._icon_texture = icon_texture - - def _render(self, _): - rl.draw_texture_ex(self._icon_texture, rl.Vector2(self._rect.x, self._rect.y), - 0.0, 1.0, rl.WHITE) - - # May expand outside parent rect - title_content_height = self._title.get_content_height(int(self._rect.width - self._icon_texture.width - 16)) - title_rect = rl.Rectangle( - self._rect.x + self._icon_texture.width + 16, - self._rect.y + (self._rect.height - title_content_height) / 2, - self._rect.width - self._icon_texture.width - 16, - title_content_height, - ) - self._title.render(title_rect) - - -class TermsPage(Widget): - ITEM_SPACING = 20 - - def __init__(self, continue_callback: Callable, back_callback: Callable | None = None, - back_text: str = "back", continue_text: str = "accept"): - super().__init__() - - # TODO: use Scroller - self._scroll_panel = GuiScrollPanel2(horizontal=False) - - self._continue_text = continue_text - self._continue_slider: bool = continue_text in ("reboot", "power off") - self._continue_button: WideRoundedButton | FullRoundedButton | SmallSlider - if self._continue_slider: - self._continue_button = SmallSlider(continue_text, confirm_callback=continue_callback) - self._scroll_panel.set_enabled(lambda: not self._continue_button.is_pressed) - elif back_callback is not None: - self._continue_button = WideRoundedButton(continue_text) - else: - self._continue_button = FullRoundedButton(continue_text) - self._continue_button.set_enabled(False) - self._continue_button.set_opacity(0.0) - self._continue_button.set_touch_valid_callback(self._scroll_panel.is_touch_valid) - if not self._continue_slider: - self._continue_button.set_click_callback(continue_callback) - - self._enable_back = back_callback is not None - self._back_button = SmallButton(back_text) - self._back_button.set_opacity(0.0) - self._back_button.set_touch_valid_callback(self._scroll_panel.is_touch_valid) - self._back_button.set_click_callback(back_callback) - - self._scroll_down_indicator = IconButton(gui_app.texture("icons_mici/setup/scroll_down_indicator.png", 64, 78)) - self._scroll_down_indicator.set_enabled(False) - - def reset(self): - self._scroll_panel.set_offset(0) - self._continue_button.set_enabled(False) - self._continue_button.set_opacity(0.0) - self._back_button.set_enabled(False) - self._back_button.set_opacity(0.0) - self._scroll_down_indicator.set_opacity(1.0) - - def show_event(self): - super().show_event() - self.reset() - - @property - @abstractmethod - def _content_height(self): - pass - - @property - def _scrolled_down_offset(self): - return -self._content_height + (self._continue_button.rect.height + 16 + 30) - - @abstractmethod - def _render_content(self, scroll_offset): - pass - - def _render(self, _): - rl.draw_rectangle_rec(self._rect, rl.BLACK) - scroll_offset = round(self._scroll_panel.update(self._rect, self._content_height + self._continue_button.rect.height + 16)) - - if scroll_offset <= self._scrolled_down_offset: - # don't show back if not enabled - if self._enable_back: - self._back_button.set_enabled(True) - self._back_button.set_opacity(1.0, smooth=True) - self._continue_button.set_enabled(True) - self._continue_button.set_opacity(1.0, smooth=True) - self._scroll_down_indicator.set_opacity(0.0, smooth=True) - else: - self._back_button.set_enabled(False) - self._back_button.set_opacity(0.0, smooth=True) - self._continue_button.set_enabled(False) - self._continue_button.set_opacity(0.0, smooth=True) - self._scroll_down_indicator.set_opacity(1.0, smooth=True) - - # Render content - self._render_content(scroll_offset) - - # black gradient at top and bottom for scrolling content - rl.draw_rectangle_gradient_v(int(self._rect.x), int(self._rect.y), - int(self._rect.width), 20, rl.BLACK, rl.BLANK) - rl.draw_rectangle_gradient_v(int(self._rect.x), int(self._rect.y + self._rect.height - 20), - int(self._rect.width), 20, rl.BLANK, rl.BLACK) - - # fade out back button as slider is moved - if self._continue_slider and scroll_offset <= self._scrolled_down_offset: - self._back_button.set_opacity(1.0 - self._continue_button.slider_percentage) - self._back_button.set_visible(self._continue_button.slider_percentage < 0.99) - - self._back_button.render(rl.Rectangle( - self._rect.x + 8, - self._rect.y + self._rect.height - self._back_button.rect.height, - self._back_button.rect.width, - self._back_button.rect.height, - )) - - continue_x = self._rect.x + 8 - if self._enable_back: - continue_x = self._rect.x + self._rect.width - self._continue_button.rect.width - 8 - if self._continue_slider: - continue_x += 8 - self._continue_button.render(rl.Rectangle( - continue_x, - self._rect.y + self._rect.height - self._continue_button.rect.height, - self._continue_button.rect.width, - self._continue_button.rect.height, - )) - - self._scroll_down_indicator.render(rl.Rectangle( - self._rect.x + self._rect.width - self._scroll_down_indicator.rect.width - 8, - self._rect.y + self._rect.height - self._scroll_down_indicator.rect.height - 8, - self._scroll_down_indicator.rect.width, - self._scroll_down_indicator.rect.height, - )) - - -class CustomSoftwareWarningPage(TermsPage): +class CustomSoftwareWarningPage(NavScroller): def __init__(self, continue_callback: Callable, back_callback: Callable): - super().__init__(continue_callback, back_callback) + super().__init__() + self.set_back_callback(back_callback) - self._title_header = TermsHeader("use caution installing\n3rd party software", - gui_app.texture("icons_mici/setup/warning.png", 66, 60)) - self._body = UnifiedLabel("• It has not been tested by comma.\n" + - "• It may not comply with relevant safety standards.\n" + - "• It may cause damage to your device and/or vehicle.\n", 36, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), - font_weight=FontWeight.ROMAN) + self._continue_button = BigPillButton("next") + self._continue_button.set_click_callback(continue_callback) - self._restore_header = TermsHeader("how to backup &\nrestore", gui_app.texture("icons_mici/setup/restore.png", 60, 60)) - self._restore_body = UnifiedLabel("To restore your device to a factory state later, use https://flash.comma.ai", - 36, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), - font_weight=FontWeight.ROMAN) - - @property - def _content_height(self): - return self._restore_body.rect.y + self._restore_body.rect.height - self._scroll_panel.get_offset() - - def _render_content(self, scroll_offset): - self._title_header.set_position(self._rect.x + 16, self._rect.y + 8 + scroll_offset) - self._title_header.render() - - body_rect = rl.Rectangle( - self._rect.x + 8, - self._title_header.rect.y + self._title_header.rect.height + self.ITEM_SPACING, - self._rect.width - 50, - self._body.get_content_height(int(self._rect.width - 50)), - ) - self._body.render(body_rect) - - self._restore_header.set_position(self._rect.x + 16, self._body.rect.y + self._body.rect.height + self.ITEM_SPACING) - self._restore_header.render() - - self._restore_body.render(rl.Rectangle( - self._rect.x + 8, - self._restore_header.rect.y + self._restore_header.rect.height + self.ITEM_SPACING, - self._rect.width - 50, - self._restore_body.get_content_height(int(self._rect.width - 50)), - )) + self._scroller.add_widgets([ + GreyBigButton("caution: installing\n3rd party software", "swipe down to go back", + gui_app.texture("icons_mici/setup/warning.png", 64, 58)), + GreyBigButton("", "• It has not been tested by comma."), + GreyBigButton("", "• It may not comply with safety standards."), + GreyBigButton("", "• It may damage your device and/or vehicle."), + GreyBigButton("how to restore to a\nfactory state later", "https://flash.comma.ai", + gui_app.texture("icons_mici/setup/restore.png", 64, 64)), + self._continue_button, + ]) -class DownloadingPage(Widget): +# TODO: unifi with updater's progress page +class DownloadingPage(NavWidget): def __init__(self): super().__init__() @@ -366,8 +200,12 @@ class DownloadingPage(Widget): font_weight=FontWeight.ROMAN, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM) self._progress = 0 + def _back_enabled(self) -> bool: + return False + def show_event(self): super().show_event() + self._nav_bar._alpha = 0.0 # not dismissable self.set_progress(0) def set_progress(self, progress: int): @@ -391,151 +229,204 @@ class DownloadingPage(Widget): )) -class FailedPage(NavWidget): - def __init__(self, reboot_callback: Callable, retry_callback: Callable, title: str = "download failed"): +class FailedPage(NavScroller): + def __init__(self, retry_callback: Callable | None, title: str = "download failed", + description: str | None = None, icon: str = "icons_mici/setup/warning.png"): super().__init__() self.set_back_callback(retry_callback) - self._title_label = UnifiedLabel(title, 64, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), - font_weight=FontWeight.DISPLAY) - self._reason_label = UnifiedLabel("", 36, text_color=rl.Color(255, 255, 255, int(255 * 0.9 * 0.65)), - font_weight=FontWeight.ROMAN) + self._reason_card = GreyBigButton("", "") + self._reason_card.set_visible(False) - self._reboot_slider = SmallSlider("reboot", reboot_callback) - self._reboot_slider.set_enabled(lambda: self.enabled) # for nav stack - - self._retry_button = SmallButton("retry") - self._retry_button.set_click_callback(retry_callback) - self._retry_button.set_enabled(lambda: self.enabled) # for nav stack + self._scroller.add_widgets([ + GreyBigButton(title, description or "swipe down to go\nback and try again", + gui_app.texture(icon, 64, 58)), + self._reason_card, + BigConfirmationCircleButton("reboot\ndevice", gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70), + HARDWARE.reboot, exit_on_confirm=False), + ]) def set_reason(self, reason: str): - self._reason_label.set_text(reason) - - def show_event(self): - super().show_event() - self._reboot_slider.reset() - - def _render(self, rect: rl.Rectangle): - self._title_label.render(rl.Rectangle( - rect.x + 8, - rect.y + 10, - rect.width, - 64, - )) - - self._reason_label.render(rl.Rectangle( - rect.x + 8, - rect.y + 10 + 64, - rect.width, - 36, - )) - - self._retry_button.set_opacity(1 - self._reboot_slider.slider_percentage) - self._retry_button.render(rl.Rectangle( - self._rect.x + 8, - self._rect.y + self._rect.height - self._retry_button.rect.height, - self._retry_button.rect.width, - self._retry_button.rect.height, - )) - - self._reboot_slider.render(rl.Rectangle( - self._rect.x + self._rect.width - self._reboot_slider.rect.width, - self._rect.y + self._rect.height - self._reboot_slider.rect.height, - self._reboot_slider.rect.width, - self._reboot_slider.rect.height, - )) + if reason: + self._reason_card.set_value(reason) + self._reason_card.set_visible(True) + else: + self._reason_card.set_visible(False) -class NetworkSetupPage(NavWidget): +class BigPillButton(BigButton): + def __init__(self, *args, green: bool = False, disabled_background: bool = False, **kwargs): + self._green = green + self._disabled_background = disabled_background + super().__init__(*args, **kwargs) + + self._label.set_font_size(48) + self._label.set_alignment(rl.GuiTextAlignment.TEXT_ALIGN_CENTER) + self._label.set_alignment_vertical(rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) + + def _load_images(self): + if self._green: + self._txt_default_bg = gui_app.texture("icons_mici/setup/start_button.png", 402, 180) + self._txt_pressed_bg = gui_app.texture("icons_mici/setup/start_button_pressed.png", 402, 180) + else: + self._txt_default_bg = gui_app.texture("icons_mici/setup/continue.png", 402, 180) + self._txt_pressed_bg = gui_app.texture("icons_mici/setup/continue_pressed.png", 402, 180) + self._txt_disabled_bg = gui_app.texture("icons_mici/setup/continue_disabled.png", 402, 180) + + def set_green(self, green: bool): + if self._green != green: + self._green = green + self._load_images() + + def _update_label_layout(self): + # Don't change label text size + pass + + def _handle_background(self) -> tuple[rl.Texture, float, float, float]: + txt_bg, btn_x, btn_y, scale = super()._handle_background() + + if self._disabled_background: + txt_bg = self._txt_disabled_bg + return txt_bg, btn_x, btn_y, scale + + +class NetworkSetupPageBase(Scroller): def __init__(self, network_monitor: NetworkConnectivityMonitor, continue_callback: Callable[[bool], None], - back_callback: Callable[[], None] | None): + disable_connect_hint: bool = False): super().__init__() - self.set_back_callback(back_callback) self._wifi_manager = WifiManager() self._wifi_manager.set_active(True) self._network_monitor = network_monitor self._custom_software = False - self._prev_has_internet = False self._wifi_ui = WifiUIMici(self._wifi_manager) - self._no_wifi_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_slash.png", 58, 50) - self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 58, 50) - self._waiting_text = "waiting for internet..." - self._network_header = TermsHeader(self._waiting_text, self._no_wifi_txt) + self._connect_button = GreyBigButton("connect to\ninternet", "swipe down to go back", + gui_app.texture("icons_mici/setup/small_slider/slider_arrow.png", 64, 56, flip_x=True)) + self._connect_button.set_visible(not disable_connect_hint) - back_txt = gui_app.texture("icons_mici/setup/back_new.png", 37, 32) - self._back_button = SmallCircleIconButton(back_txt) - self._back_button.set_click_callback(back_callback) - self._back_button.set_enabled(lambda: self.enabled) # for nav stack - - self._wifi_button = SmallerRoundedButton("wifi") + self._wifi_button = WifiNetworkButton(self._wifi_manager) self._wifi_button.set_click_callback(lambda: gui_app.push_widget(self._wifi_ui)) - self._wifi_button.set_enabled(lambda: self.enabled) - self._continue_button = WidishRoundedButton("continue") - self._continue_button.set_enabled(False) + self._prev_has_internet = False + self._prev_wifi_connected = False + self._pending_has_internet_scroll: float | None = None # stores time to use as delay + self._pending_continue_grow_animation = False + self._pending_wifi_grow_animation = False + + def on_waiting_click(): + offset = (self._wifi_button.rect.x + self._wifi_button.rect.width / 2) - (self._rect.x + self._rect.width / 2) + self._scroller.scroll_to(offset, smooth=True, block_interaction=True) + # trigger grow when wifi button in view + self._pending_wifi_grow_animation = True + + self._waiting_button = BigPillButton("connect to\ncontinue", disabled_background=True) + self._waiting_button.set_click_callback(on_waiting_click) + self._continue_button = BigPillButton("install openpilot", green=True) self._continue_button.set_click_callback(lambda: continue_callback(self._custom_software)) + self._scroller.add_widgets([ + self._connect_button, + self._wifi_button, + self._continue_button, + self._waiting_button, + ]) + gui_app.add_nav_stack_tick(self._nav_stack_tick) def show_event(self): super().show_event() - self._prev_has_internet = False - self._network_monitor.reset() - self._set_has_internet(False) + # make sure we populate strength and ip immediately if already have wifi + self._wifi_manager.set_active(True) + self._prev_has_internet = self._has_internet + self._prev_wifi_connected = self._wifi_manager.wifi_state.status == ConnectStatus.CONNECTED + self._pending_has_internet_scroll = None + self._pending_continue_grow_animation = False + self._pending_wifi_grow_animation = False + + if self._prev_has_internet or self._prev_wifi_connected: + self.set_shown_callback(lambda: self._scroll_to_end_and_grow()) + + @property + def _has_internet(self) -> bool: + network_changing = self._wifi_ui.any_network_forgetting or self._wifi_manager.wifi_state.status == ConnectStatus.CONNECTING + if network_changing: + self._network_monitor.invalidate() + + has_internet = (self._network_monitor.network_connected.is_set() and + not network_changing and + not self._network_monitor.recheck_event.is_set()) + return has_internet def _nav_stack_tick(self): + # Only run tick when this page or its WiFi UI is on the stack + if gui_app.get_active_widget() is not self and not gui_app.widget_in_stack(self._wifi_ui): + self._wifi_manager.process_callbacks() + return + + # Check network state before processing callbacks so forgetting flag + # is still set on the frame the forgotten callback fires + has_internet = self._has_internet + wifi_connected = self._wifi_manager.wifi_state.status == ConnectStatus.CONNECTED + + self._continue_button.set_visible(has_internet) + self._waiting_button.set_visible(not has_internet) + + # TODO: fire show/hide events on visibility changes + if not has_internet: + self._pending_continue_grow_animation = False + self._waiting_button.set_text("waiting for\ninternet..." if wifi_connected else "connect to\ncontinue") + self._wifi_manager.process_callbacks() - has_internet = self._network_monitor.network_connected.is_set() - if has_internet != self._prev_has_internet: - self._set_has_internet(has_internet) - if has_internet: - gui_app.pop_widgets_to(self) - self._prev_has_internet = has_internet + # Dismiss WiFi UI and scroll on WiFi connect or internet gain + if (has_internet and not self._prev_has_internet) or (wifi_connected and not self._prev_wifi_connected): + # TODO: cancel if connect is transient + self._pending_has_internet_scroll = rl.get_time() - def _set_has_internet(self, has_internet: bool): - if has_internet: - self._network_header.set_title("connected to internet") - self._network_header.set_icon(self._wifi_full_txt) - self._continue_button.set_enabled(lambda: self.enabled) - else: - self._network_header.set_title(self._waiting_text) - self._network_header.set_icon(self._no_wifi_txt) - self._continue_button.set_enabled(False) + self._prev_has_internet = has_internet + self._prev_wifi_connected = wifi_connected + + if self._pending_has_internet_scroll is not None: + # Scrolls over to continue button, then grows once in view + elapsed = rl.get_time() - self._pending_has_internet_scroll + if elapsed > 0.7 or gui_app.get_active_widget() is self: # instant scroll + grow if not popping + # Animate WifiUi down first before scroll + self._pending_has_internet_scroll = None + gui_app.pop_widgets_to(self, self._scroll_to_end_and_grow) + + def _scroll_to_end_and_grow(self): + self._scroller._layout() + end_offset = -(self._scroller.content_size - self._rect.width) + remaining = self._scroller.scroll_panel.get_offset() - end_offset + self._scroller.scroll_to(remaining, smooth=True, block_interaction=True) + self._pending_continue_grow_animation = True def set_custom_software(self, custom_software: bool): self._custom_software = custom_software + self._continue_button.set_text("install openpilot" if not custom_software else "choose software") + self._continue_button.set_green(not custom_software) - def _render(self, _): - self._network_header.render(rl.Rectangle( - self._rect.x + 16, - self._rect.y + 16, - self._rect.width - 32, - self._network_header.rect.height, - )) + def _update_state(self): + super()._update_state() - self._back_button.render(rl.Rectangle( - self._rect.x + 8, - self._rect.y + self._rect.height - self._back_button.rect.height, - self._back_button.rect.width, - self._back_button.rect.height, - )) + if self._pending_continue_grow_animation: + btn_right = self._continue_button.rect.x + self._continue_button.rect.width + visible_right = self._rect.x + self._rect.width + if btn_right < visible_right + 50: + self._pending_continue_grow_animation = False + self._continue_button.trigger_grow_animation() - self._wifi_button.render(rl.Rectangle( - self._rect.x + 8 + self._back_button.rect.width + 10, - self._rect.y + self._rect.height - self._wifi_button.rect.height, - self._wifi_button.rect.width, - self._wifi_button.rect.height, - )) + if self._pending_wifi_grow_animation and abs(self._wifi_button.rect.x - ITEM_SPACING) < 50: + self._pending_wifi_grow_animation = False + self._wifi_button.trigger_grow_animation() - self._continue_button.render(rl.Rectangle( - self._rect.x + self._rect.width - self._continue_button.rect.width - 8, - self._rect.y + self._rect.height - self._continue_button.rect.height, - self._continue_button.rect.width, - self._continue_button.rect.height, - )) + +class NetworkSetupPage(NetworkSetupPageBase, NavScroller): + def __init__(self, network_monitor: NetworkConnectivityMonitor, continue_callback: Callable[[bool], None], + back_callback: Callable[[], None] | None): + super().__init__(network_monitor, continue_callback) + self.set_back_callback(back_callback) class Setup(Widget): @@ -550,20 +441,19 @@ class Setup(Widget): self._network_monitor.start() def getting_started_button_callback(): - self._software_selection_page.reset() gui_app.push_widget(self._software_selection_page) self._start_page = StartPage() self._start_page.set_click_callback(getting_started_button_callback) self._start_page.set_enabled(lambda: self.enabled) # for nav stack - self._network_setup_page = NetworkSetupPage(self._network_monitor, self._network_setup_continue_button_callback, - self._pop_to_software_selection) - self._software_selection_page = SoftwareSelectionPage(self._use_openpilot, lambda: gui_app.push_widget(self._custom_software_warning_page)) + self._network_setup_page = NetworkSetupPage(self._network_monitor, self._network_setup_continue_callback, self._pop_to_software_selection) - self._download_failed_page = FailedPage(HARDWARE.reboot, self._pop_to_software_selection) + self._software_selection_page = SoftwareSelectionPage(self._push_network_setup, lambda: gui_app.push_widget(self._custom_software_warning_page)) - self._custom_software_warning_page = CustomSoftwareWarningPage(self._software_selection_custom_software_continue, self._pop_to_software_selection) + self._download_failed_page = FailedPage(self._pop_to_software_selection, icon="icons_mici/setup/red_warning.png") + + self._custom_software_warning_page = CustomSoftwareWarningPage(lambda: self._push_network_setup(True), self._pop_to_software_selection) self._downloading_page = DownloadingPage() @@ -576,8 +466,7 @@ class Setup(Widget): reason = self._download_failed_reason self._download_failed_reason = None self._download_failed_page.set_reason(reason) - gui_app.pop_widgets_to(self._software_selection_page, instant=True) # don't reset sliders - gui_app.push_widget(self._download_failed_page) + gui_app.pop_widgets_to(self._software_selection_page, lambda: gui_app.push_widget(self._download_failed_page)) def _render(self, rect: rl.Rectangle): self._start_page.render(rect) @@ -589,41 +478,21 @@ class Setup(Widget): # reset sliders after dismiss completes gui_app.pop_widgets_to(self._software_selection_page, self._software_selection_page.reset) - def _use_openpilot(self): - if os.path.isdir(INSTALL_PATH) and os.path.isfile(VALID_CACHE_PATH): - os.remove(VALID_CACHE_PATH) - with open(TMP_CONTINUE_PATH, "w") as f: - f.write(CONTINUE) - run_cmd(["chmod", "+x", TMP_CONTINUE_PATH]) - shutil.move(TMP_CONTINUE_PATH, CONTINUE_PATH) - shutil.copyfile(INSTALLER_SOURCE_PATH, INSTALLER_DESTINATION_PATH) - - # give time for installer UI to take over - time.sleep(0.1) - gui_app.request_close() - else: - self._push_network_setup(custom_software=False) - - def _push_network_setup(self, custom_software: bool): + def _push_network_setup(self, custom_software: bool = False): + # to fire the correct continue callback later self._network_setup_page.set_custom_software(custom_software) gui_app.push_widget(self._network_setup_page) - def _software_selection_custom_software_continue(self): - gui_app.pop_widgets_to(self._software_selection_page, instant=True) # don't reset sliders - self._push_network_setup(custom_software=True) - - def _network_setup_continue_button_callback(self, custom_software): + def _network_setup_continue_callback(self, custom_software: bool): if not custom_software: - gui_app.pop_widgets_to(self._software_selection_page, instant=True) # don't reset sliders self._download(OPENPILOT_URL) else: def handle_keyboard_result(text): url = text.strip() if url: - gui_app.pop_widgets_to(self._software_selection_page, instant=True) # don't reset sliders self._download(url) - keyboard = BigInputDialog("custom software URL", confirm_callback=handle_keyboard_result) + keyboard = BigInputDialog("custom software URL...", confirm_callback=handle_keyboard_result, auto_return_to_letters="./") gui_app.push_widget(keyboard) def _download(self, url: str): @@ -635,10 +504,12 @@ class Setup(Widget): self.download_url = (urlparse(f"https://{url}") if not parsed.netloc else parsed).geturl() self.download_progress = 0 - gui_app.push_widget(self._downloading_page) + def start_download(): + self.download_thread = threading.Thread(target=self._download_thread, daemon=True) + self.download_thread.start() - self.download_thread = threading.Thread(target=self._download_thread, daemon=True) - self.download_thread.start() + self._downloading_page.set_shown_callback(start_download) + gui_app.push_widget(self._downloading_page) def _download_thread(self): try: @@ -673,26 +544,27 @@ class Setup(Widget): is_elf = header == b'\x7fELF' if not is_elf: - self._download_failed_reason = "No custom software found at this URL." + self._download_failed_reason = "No custom software found at this URL: " + self.download_url.replace("https://", "", 1) return + # NOTE: currently unused, for future logging + with open(INSTALLER_URL_PATH, "w") as f: + f.write(self.download_url) + # AGNOS might try to execute the installer before this process exits. # Therefore, important to close the fd before renaming the installer. os.close(fd) os.rename(tmpfile, INSTALLER_DESTINATION_PATH) - with open(INSTALLER_URL_PATH, "w") as f: - f.write(self.download_url) - # give time for installer UI to take over time.sleep(0.1) gui_app.request_close() except urllib.error.HTTPError as e: if e.code == 409: - self._download_failed_reason = "Incompatible sunnypilot version" + self._download_failed_reason = "Incompatible sunnypilot version." except Exception: - self._download_failed_reason = "Invalid URL" + self._download_failed_reason = "Invalid URL: " + self.download_url.replace("https://", "", 1) def main(): diff --git a/system/ui/mici_updater.py b/system/ui/mici_updater.py index c98b310709..8437e6fa60 100755 --- a/system/ui/mici_updater.py +++ b/system/ui/mici_updater.py @@ -3,57 +3,28 @@ import sys import subprocess import threading import pyray as rl -from enum import IntEnum -from openpilot.system.hardware import HARDWARE +from openpilot.common.realtime import config_realtime_process, set_core_affinity +from openpilot.system.hardware import HARDWARE, TICI +from openpilot.common.swaglog import cloudlog from openpilot.system.ui.lib.application import gui_app, FontWeight -from openpilot.system.ui.lib.wifi_manager import WifiManager -from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.nav_widget import NavWidget +from openpilot.system.ui.widgets.scroller import Scroller from openpilot.system.ui.widgets.label import UnifiedLabel -from openpilot.system.ui.widgets.button import FullRoundedButton -from openpilot.system.ui.mici_setup import NetworkSetupPage, FailedPage, NetworkConnectivityMonitor +from openpilot.system.ui.mici_setup import (NetworkSetupPage, FailedPage, NetworkConnectivityMonitor, + GreyBigButton, BigPillButton) -class Screen(IntEnum): - PROMPT = 0 - WIFI = 1 - PROGRESS = 2 - FAILED = 3 +class UpdaterNetworkSetupPage(NetworkSetupPage): + def __init__(self, network_monitor, continue_callback): + super().__init__(network_monitor, continue_callback, back_callback=None) + self._continue_button.set_text("download\n& install") + self._continue_button.set_green(False) -class Updater(Widget): - def __init__(self, updater_path, manifest_path): +class ProgressPage(NavWidget): + def __init__(self): super().__init__() - self.updater = updater_path - self.manifest = manifest_path - self.current_screen = Screen.PROMPT - - self.progress_value = 0 - self.progress_text = "loading" - self.process = None - self.update_thread = None - self._wifi_manager = WifiManager() - self._wifi_manager.set_active(True) - - self._network_setup_page = NetworkSetupPage(self._wifi_manager, self._network_setup_continue_callback, - self._network_setup_back_callback) - self._network_setup_page.set_enabled(lambda: self.enabled) # for nav stack - - self._network_monitor = NetworkConnectivityMonitor() - self._network_monitor.start() - - # Buttons - self._continue_button = FullRoundedButton("continue") - self._continue_button.set_click_callback(lambda: self.set_current_screen(Screen.WIFI)) - - self._title_label = UnifiedLabel("update required", 48, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), - font_weight=FontWeight.DISPLAY) - self._subtitle_label = UnifiedLabel("The download size is approximately 1GB.", 36, - text_color=rl.Color(255, 255, 255, int(255 * 0.9)), - font_weight=FontWeight.ROMAN) - - self._update_failed_page = FailedPage(HARDWARE.reboot, self._update_failed_retry_callback, - title="update failed") self._progress_title_label = UnifiedLabel("", 64, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), font_weight=FontWeight.DISPLAY, line_height=0.8) @@ -61,47 +32,102 @@ class Updater(Widget): font_weight=FontWeight.ROMAN, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM) - def _network_setup_back_callback(self): - self.set_current_screen(Screen.PROMPT) + def _back_enabled(self) -> bool: + return False - def _network_setup_continue_callback(self): + def set_progress(self, text: str, value: int): + self._progress_title_label.set_text(text.replace("_", "_\n") + "...") + self._progress_percent_label.set_text(f"{value}%") + + def show_event(self): + super().show_event() + self._nav_bar._alpha = 0.0 # not dismissable + self.set_progress("downloading", 0) + + def _render(self, rect: rl.Rectangle): + rl.draw_rectangle_rec(rect, rl.BLACK) + self._progress_title_label.render(rl.Rectangle( + rect.x + 12, + rect.y + 2, + rect.width, + self._progress_title_label.get_content_height(int(rect.width - 20)), + )) + + self._progress_percent_label.render(rl.Rectangle( + rect.x + 12, + rect.y + 18, + rect.width, + rect.height, + )) + + +class Updater(Scroller): + def __init__(self, updater_path, manifest_path): + super().__init__() + self.updater = updater_path + self.manifest = manifest_path + + self.progress_value = 0 + self.progress_text = "loading" + self.process = None + self.update_thread = None + self._update_failed = False + + self._network_monitor = NetworkConnectivityMonitor() + self._network_monitor.start() + + self._network_setup_page = UpdaterNetworkSetupPage(self._network_monitor, self._network_setup_continue_callback) + + self._progress_page = ProgressPage() + + self._failed_page = FailedPage(self._retry, title="update failed") + + self._continue_button = BigPillButton("next") + self._continue_button.set_click_callback(lambda: gui_app.push_widget(self._network_setup_page)) + + self._scroller.add_widgets([ + GreyBigButton("update required", "the download size\nis approximately 1 GB", + gui_app.texture("icons_mici/offroad_alerts/green_wheel.png", 64, 64)), + self._continue_button, + ]) + + gui_app.add_nav_stack_tick(self._nav_stack_tick) + + def _network_setup_continue_callback(self, _): self.install_update() - def _update_failed_retry_callback(self): - self.set_current_screen(Screen.PROMPT) + def _retry(self): + gui_app.pop_widgets_to(self) - def set_current_screen(self, screen: Screen): - if self.current_screen != screen: - if screen == Screen.PROGRESS: - if self._network_setup_page: - self._network_setup_page.hide_event() - elif screen == Screen.WIFI: - if self._network_setup_page: - self._network_setup_page.show_event() - elif screen == Screen.PROMPT: - if self._network_setup_page: - self._network_setup_page.hide_event() - elif screen == Screen.FAILED: - if self._network_setup_page: - self._network_setup_page.hide_event() + def _nav_stack_tick(self): + self._progress_page.set_progress(self.progress_text, self.progress_value) - self.current_screen = screen + if self._update_failed: + self._update_failed = False + self.show_event() + gui_app.pop_widgets_to(self, lambda: gui_app.push_widget(self._failed_page)) def install_update(self): - self.set_current_screen(Screen.PROGRESS) self.progress_value = 0 self.progress_text = "downloading" - # Start the update process in a separate thread - self.update_thread = threading.Thread(target=self._run_update_process) - self.update_thread.daemon = True - self.update_thread.start() + def start_update(): + self.update_thread = threading.Thread(target=self._run_update_process, daemon=True) + self.update_thread.start() + + # Start the update process in a separate thread *after* show animation completes + self._progress_page.set_shown_callback(start_update) + gui_app.push_widget(self._progress_page) def _run_update_process(self): # TODO: just import it and run in a thread without a subprocess - cmd = [self.updater, "--swap", self.manifest] - self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - text=True, bufsize=1, universal_newlines=True) + try: + cmd = [self.updater, "--swap", self.manifest] + self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, + text=True, bufsize=1, universal_newlines=True) + except Exception: + self._update_failed = True + return if self.process.stdout is not None: for line in self.process.stdout: @@ -117,68 +143,21 @@ class Updater(Widget): if exit_code == 0: HARDWARE.reboot() else: - self.set_current_screen(Screen.FAILED) - - def render_prompt_screen(self, rect: rl.Rectangle): - self._title_label.render(rl.Rectangle( - rect.x + 8, - rect.y - 5, - rect.width, - 48, - )) - - subtitle_width = rect.width - 16 - subtitle_height = self._subtitle_label.get_content_height(int(subtitle_width)) - self._subtitle_label.render(rl.Rectangle( - rect.x + 8, - rect.y + 48, - subtitle_width, - subtitle_height, - )) - - self._continue_button.render(rl.Rectangle( - rect.x + 8, - rect.y + rect.height - self._continue_button.rect.height, - self._continue_button.rect.width, - self._continue_button.rect.height, - )) - - def render_progress_screen(self, rect: rl.Rectangle): - self._progress_title_label.set_text(self.progress_text.replace("_", "_\n") + "...") - self._progress_title_label.render(rl.Rectangle( - rect.x + 12, - rect.y + 2, - rect.width, - self._progress_title_label.get_content_height(int(rect.width - 20)), - )) - - self._progress_percent_label.set_text(f"{self.progress_value}%") - self._progress_percent_label.render(rl.Rectangle( - rect.x + 12, - rect.y + 18, - rect.width, - rect.height, - )) - - def _update_state(self): - self._wifi_manager.process_callbacks() - - def _render(self, rect: rl.Rectangle): - if self.current_screen == Screen.PROMPT: - self.render_prompt_screen(rect) - elif self.current_screen == Screen.WIFI: - self._network_setup_page.set_has_internet(self._network_monitor.network_connected.is_set()) - self._network_setup_page.render(rect) - elif self.current_screen == Screen.PROGRESS: - self.render_progress_screen(rect) - elif self.current_screen == Screen.FAILED: - self._update_failed_page.render(rect) + self._update_failed = True def close(self): self._network_monitor.stop() def main(): + config_realtime_process(0, 51) + # attempt to affine. AGNOS will start setup with all cores, should only fail when manually launching with screen off + if TICI: + try: + set_core_affinity([5]) + except OSError: + cloudlog.exception("Failed to set core affinity for updater process") + if len(sys.argv) < 3: print("Usage: updater.py ") sys.exit(1) diff --git a/system/ui/tici_reset.py b/system/ui/tici_reset.py index 23f6b344ec..a6603d547e 100755 --- a/system/ui/tici_reset.py +++ b/system/ui/tici_reset.py @@ -20,7 +20,6 @@ TIMEOUT = 3*60 class ResetMode(IntEnum): USER_RESET = 0 # user initiated a factory reset from openpilot RECOVER = 1 # userdata is corrupt for some reason, give a chance to recover - FORMAT = 2 # finish up a factory reset from a tool that doesn't flash an empty partition to userdata class ResetState(IntEnum): @@ -54,7 +53,7 @@ class Reset(Widget): else: self._reset_state = ResetState.FAILED - def start_reset(self): + def _start_reset(self): self._reset_state = ResetState.RESETTING threading.Timer(0.1, self._do_erase).start() @@ -92,7 +91,7 @@ class Reset(Widget): def _confirm(self): if self._reset_state == ResetState.CONFIRM: - self.start_reset() + self._start_reset() else: self._reset_state = ResetState.CONFIRM @@ -113,15 +112,10 @@ def main(): if len(sys.argv) > 1: if sys.argv[1] == '--recover': mode = ResetMode.RECOVER - elif sys.argv[1] == "--format": - mode = ResetMode.FORMAT gui_app.init_window("System Reset", 20) reset = Reset(mode) - if mode == ResetMode.FORMAT: - reset.start_reset() - gui_app.push_widget(reset) for _ in gui_app.render(): diff --git a/system/ui/tici_setup.py b/system/ui/tici_setup.py index 8098e9ea27..9eefb6af53 100755 --- a/system/ui/tici_setup.py +++ b/system/ui/tici_setup.py @@ -7,12 +7,10 @@ import urllib.request import urllib.error from urllib.parse import urlparse from enum import IntEnum -import shutil import pyray as rl from cereal import log -from openpilot.common.utils import run_cmd from openpilot.system.hardware import HARDWARE from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE @@ -35,20 +33,9 @@ BUTTON_SPACING = 50 OPENPILOT_URL = "https://openpilot.comma.ai" USER_AGENT = f"AGNOSSetup-{HARDWARE.get_os_version()}" -CONTINUE_PATH = "/data/continue.sh" -TMP_CONTINUE_PATH = "/data/continue.sh.new" -INSTALL_PATH = "/data/openpilot" -VALID_CACHE_PATH = "/data/.openpilot_cache" -INSTALLER_SOURCE_PATH = "/usr/comma/installer" INSTALLER_DESTINATION_PATH = "/tmp/installer" INSTALLER_URL_PATH = "/tmp/installer_url" -CONTINUE = """#!/usr/bin/env bash - -cd /data/openpilot -exec ./launch_openpilot.sh -""" - class SetupState(IntEnum): LOW_VOLTAGE = 0 @@ -176,7 +163,9 @@ class Setup(Widget): def _software_selection_continue_button_callback(self): if self._software_selection_openpilot_button.selected: - self.use_openpilot() + self.state = SetupState.NETWORK_SETUP + self.stop_network_check_thread.clear() + self.start_network_check() else: self.state = SetupState.CUSTOM_SOFTWARE_WARNING @@ -194,7 +183,7 @@ class Setup(Widget): self.state = SetupState.CUSTOM_SOFTWARE def render_low_voltage(self, rect: rl.Rectangle): - rl.draw_texture(self.warning, int(rect.x + 150), int(rect.y + 110), rl.WHITE) + rl.draw_texture_ex(self.warning, rl.Vector2(rect.x + 150, rect.y + 110), 0.0, 1.0, rl.WHITE) self._low_voltage_title_label.render(rl.Rectangle(rect.x + 150, rect.y + 110 + 150 + 100, rect.width - 500 - 150, TITLE_FONT_SIZE * FONT_SCALE)) self._low_voltage_body_label.render(rl.Rectangle(rect.x + 150, rect.y + 110 + 150 + 150, rect.width - 500, BODY_FONT_SIZE * FONT_SCALE * 3)) @@ -342,23 +331,6 @@ class Setup(Widget): self.keyboard.set_callback(handle_keyboard_result) gui_app.push_widget(self.keyboard) - def use_openpilot(self): - if os.path.isdir(INSTALL_PATH) and os.path.isfile(VALID_CACHE_PATH): - os.remove(VALID_CACHE_PATH) - with open(TMP_CONTINUE_PATH, "w") as f: - f.write(CONTINUE) - run_cmd(["chmod", "+x", TMP_CONTINUE_PATH]) - shutil.move(TMP_CONTINUE_PATH, CONTINUE_PATH) - shutil.copyfile(INSTALLER_SOURCE_PATH, INSTALLER_DESTINATION_PATH) - - # give time for installer UI to take over - time.sleep(0.1) - gui_app.request_close() - else: - self.state = SetupState.NETWORK_SETUP - self.stop_network_check_thread.clear() - self.start_network_check() - def download(self, url: str): # autocomplete incomplete URLs if re.match("^([^/.]+)/([^/]+)$", url): diff --git a/system/ui/tici_updater.py b/system/ui/tici_updater.py index 9824638cd0..3a3b0987d0 100755 --- a/system/ui/tici_updater.py +++ b/system/ui/tici_updater.py @@ -67,9 +67,14 @@ class Updater(Widget): def _run_update_process(self): # TODO: just import it and run in a thread without a subprocess - cmd = [self.updater, "--swap", self.manifest] - self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - text=True, bufsize=1, universal_newlines=True) + try: + cmd = [self.updater, "--swap", self.manifest] + self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, + text=True, bufsize=1, universal_newlines=True) + except Exception: + self.progress_text = "Update failed" + self.show_reboot_button = True + return if self.process.stdout is not None: for line in self.process.stdout: diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index 568f58b985..4ce1c1b694 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -3,6 +3,7 @@ from __future__ import annotations import abc import pyray as rl from enum import IntEnum +from typing import TypeVar from collections.abc import Callable from openpilot.system.ui.lib.application import gui_app, MousePos, MAX_TOUCH_SLOTS, MouseEvent @@ -13,6 +14,10 @@ except ImportError: awake = True device = Device() +W = TypeVar('W', bound='Widget') + +DEBUG = False + class DialogResult(IntEnum): CANCEL = 0 @@ -24,11 +29,14 @@ class Widget(abc.ABC): def __init__(self): self._rect: rl.Rectangle = rl.Rectangle(0, 0, 0, 0) self._parent_rect: rl.Rectangle | None = None + self._children: list[Widget] = [] + + self._enabled: bool | Callable[[], bool] = True + self._is_visible: bool | Callable[[], bool] = True + self.__is_pressed = [False] * MAX_TOUCH_SLOTS # if current mouse/touch down started within the widget's rectangle self.__tracking_is_pressed = [False] * MAX_TOUCH_SLOTS - self._enabled: bool | Callable[[], bool] = True - self._is_visible: bool | Callable[[], bool] = True self._touch_valid_callback: Callable[[], bool] | None = None self._click_delay: float | None = None # seconds to hold is_pressed after release self._click_release_time: float | None = None @@ -197,12 +205,37 @@ class Widget(abc.ABC): """Optionally handle mouse events. This is called before rendering.""" # Default implementation does nothing, can be overridden by subclasses + def _child(self, widget: W) -> W: + """ + Register a widget as a child. Lifecycle events (show/hide) propagate to registered children. + - If the widget is pushed onto the nav stack, do NOT register it (gui_app manages its lifecycle). + - If the widget is rendered inline in _render(), register it. + """ + assert widget not in self._children, f"{type(widget).__name__} already a child of {type(self).__name__}" + self._children.append(widget) + return widget + + _show_hide_depth = 0 + def show_event(self): - """Optionally handle show event. Parent must manually call this""" - # TODO: iterate through all child objects, check for subclassing from Widget/Layout (Scroller) + """Called when widget becomes visible. Propagates to registered children.""" + if DEBUG: + print(f"{' ' * Widget._show_hide_depth}show_event: {type(self).__name__}") + Widget._show_hide_depth += 1 + for child in self._children: + child.show_event() + if DEBUG: + Widget._show_hide_depth -= 1 def hide_event(self): - """Optionally handle hide event. Parent must manually call this""" + """Called when widget is hidden. Propagates to registered children.""" + if DEBUG: + print(f"{' ' * Widget._show_hide_depth}hide_event: {type(self).__name__}") + Widget._show_hide_depth += 1 + for child in self._children: + child.hide_event() + if DEBUG: + Widget._show_hide_depth -= 1 def dismiss(self, callback: Callable[[], None] | None = None): """Immediately dismiss the widget, firing the callback after.""" diff --git a/system/ui/widgets/button.py b/system/ui/widgets/button.py index 67125d7091..36ef3bedab 100644 --- a/system/ui/widgets/button.py +++ b/system/ui/widgets/button.py @@ -5,7 +5,7 @@ import pyray as rl from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.widgets.label import Label, UnifiedLabel +from openpilot.system.ui.widgets.label import Label from openpilot.common.filter_simple import FirstOrderFilter @@ -191,7 +191,7 @@ class IconButton(Widget): color = rl.Color(255, 255, 255, int(255 * 0.9 * 0.35 * self._opacity_filter.x)) draw_x = rect.x + (rect.width - self._texture.width) / 2 draw_y = rect.y + (rect.height - self._texture.height) / 2 - rl.draw_texture(self._texture, int(draw_x), int(draw_y), color) + rl.draw_texture_ex(self._texture, rl.Vector2(draw_x, draw_y), 0.0, 1.0, color) class SmallCircleIconButton(Widget): @@ -219,85 +219,7 @@ class SmallCircleIconButton(Widget): bg_txt = self._icon_bg_pressed_txt if self.is_pressed else self._icon_bg_txt icon_white = white - rl.draw_texture(bg_txt, int(self.rect.x), int(self.rect.y), white) + rl.draw_texture_ex(bg_txt, rl.Vector2(self.rect.x, self.rect.y), 0.0, 1.0, white) icon_x = self.rect.x + (self.rect.width - self._icon_txt.width) / 2 icon_y = self.rect.y + (self.rect.height - self._icon_txt.height) / 2 - rl.draw_texture(self._icon_txt, int(icon_x), int(icon_y), icon_white) - - -class SmallButton(Widget): - def __init__(self, text: str): - super().__init__() - self._opacity_filter = FirstOrderFilter(1.0, 0.1, 1 / gui_app.target_fps) - - self._load_assets() - - self._label = UnifiedLabel(text, 36, font_weight=FontWeight.SEMI_BOLD, - text_color=rl.Color(255, 255, 255, int(255 * 0.9)), - alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, - alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) - - self._bg_disabled_txt = None - - def _load_assets(self): - self.set_rect(rl.Rectangle(0, 0, 194, 100)) - self._bg_txt = gui_app.texture("icons_mici/setup/reset/small_button.png", 194, 100) - self._bg_pressed_txt = gui_app.texture("icons_mici/setup/reset/small_button_pressed.png", 194, 100) - - def set_text(self, text: str): - self._label.set_text(text) - - def set_opacity(self, opacity: float, smooth: bool = False): - if smooth: - self._opacity_filter.update(opacity) - else: - self._opacity_filter.x = opacity - - def _render(self, _): - if not self.enabled and self._bg_disabled_txt is not None: - rl.draw_texture(self._bg_disabled_txt, int(self.rect.x), int(self.rect.y), rl.Color(255, 255, 255, int(255 * self._opacity_filter.x))) - elif self.is_pressed: - rl.draw_texture(self._bg_pressed_txt, int(self.rect.x), int(self.rect.y), rl.Color(255, 255, 255, int(255 * self._opacity_filter.x))) - else: - rl.draw_texture(self._bg_txt, int(self.rect.x), int(self.rect.y), rl.Color(255, 255, 255, int(255 * self._opacity_filter.x))) - - opacity = 0.9 if self.enabled else 0.35 - self._label.set_color(rl.Color(255, 255, 255, int(255 * opacity * self._opacity_filter.x))) - self._label.render(self._rect) - - -class SmallRedPillButton(SmallButton): - def _load_assets(self): - self.set_rect(rl.Rectangle(0, 0, 194, 100)) - self._bg_txt = gui_app.texture("icons_mici/setup/small_red_pill.png", 194, 100) - self._bg_pressed_txt = gui_app.texture("icons_mici/setup/small_red_pill_pressed.png", 194, 100) - - -class SmallerRoundedButton(SmallButton): - def _load_assets(self): - self.set_rect(rl.Rectangle(0, 0, 150, 100)) - self._bg_txt = gui_app.texture("icons_mici/setup/smaller_button.png", 150, 100) - self._bg_disabled_txt = gui_app.texture("icons_mici/setup/smaller_button_disabled.png", 150, 100) - self._bg_pressed_txt = gui_app.texture("icons_mici/setup/smaller_button_pressed.png", 150, 100) - - -class WideRoundedButton(SmallButton): - def _load_assets(self): - self.set_rect(rl.Rectangle(0, 0, 316, 100)) - self._bg_txt = gui_app.texture("icons_mici/setup/medium_button_bg.png", 316, 100) - self._bg_pressed_txt = gui_app.texture("icons_mici/setup/medium_button_pressed_bg.png", 316, 100) - - -class WidishRoundedButton(SmallButton): - def _load_assets(self): - self.set_rect(rl.Rectangle(0, 0, 250, 100)) - self._bg_txt = gui_app.texture("icons_mici/setup/widish_button.png", 250, 100) - self._bg_pressed_txt = gui_app.texture("icons_mici/setup/widish_button_pressed.png", 250, 100) - self._bg_disabled_txt = gui_app.texture("icons_mici/setup/widish_button_disabled.png", 250, 100) - - -class FullRoundedButton(SmallButton): - def _load_assets(self): - self.set_rect(rl.Rectangle(0, 0, 520, 100)) - self._bg_txt = gui_app.texture("icons_mici/setup/reset/wide_button.png", 520, 100) - self._bg_pressed_txt = gui_app.texture("icons_mici/setup/reset/wide_button_pressed.png", 520, 100) + rl.draw_texture_ex(self._icon_txt, rl.Vector2(icon_x, icon_y), 0.0, 1.0, icon_white) diff --git a/system/ui/widgets/label.py b/system/ui/widgets/label.py index 8ed9ec62f5..7fe25ab51d 100644 --- a/system/ui/widgets/label.py +++ b/system/ui/widgets/label.py @@ -1,3 +1,4 @@ +import math from enum import IntEnum from collections.abc import Callable from itertools import zip_longest @@ -26,166 +27,6 @@ class ScrollState(IntEnum): SCROLLING = 1 -# TODO: merge anything new here to master -class MiciLabel(Widget): - def __init__(self, - text: str, - font_size: int = DEFAULT_TEXT_SIZE, - width: int | None = None, - color: rl.Color = DEFAULT_TEXT_COLOR, - font_weight: FontWeight = FontWeight.NORMAL, - alignment: int = rl.GuiTextAlignment.TEXT_ALIGN_LEFT, - alignment_vertical: int = rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP, - spacing: int = 0, - line_height: int | None = None, - elide_right: bool = True, - wrap_text: bool = False, - scroll: bool = False): - super().__init__() - self.text = text - self.wrapped_text: list[str] = [] - self.font_size = font_size - self.width = width - self.color = color - self.font_weight = font_weight - self.alignment = alignment - self.alignment_vertical = alignment_vertical - self.spacing = spacing - self.line_height = line_height if line_height is not None else font_size - self.elide_right = elide_right - self.wrap_text = wrap_text - self._height = 0 - - # Scroll state - self.scroll = scroll - self._needs_scroll = False - self._scroll_offset = 0 - self._scroll_pause_t: float | None = None - self._scroll_state: ScrollState = ScrollState.STARTING - - assert not (self.scroll and self.wrap_text), "Cannot enable both scroll and wrap_text" - assert not (self.scroll and self.elide_right), "Cannot enable both scroll and elide_right" - - self.set_text(text) - - @property - def text_height(self): - return self._height - - def set_font_size(self, font_size: int): - self.font_size = font_size - self.set_text(self.text) - - def set_width(self, width: int): - self.width = width - self._rect.width = width - self.set_text(self.text) - - def set_text(self, txt: str): - self.text = txt - text_size = measure_text_cached(gui_app.font(self.font_weight), self.text, self.font_size, self.spacing) - if self.width is not None: - self._rect.width = self.width - else: - self._rect.width = text_size.x - - if self.wrap_text: - self.wrapped_text = wrap_text(gui_app.font(self.font_weight), self.text, self.font_size, int(self._rect.width)) - self._height = len(self.wrapped_text) * self.line_height - elif self.scroll: - self._needs_scroll = self.scroll and text_size.x > self._rect.width - self._rect.height = text_size.y - - def set_color(self, color: rl.Color): - self.color = color - - def set_font_weight(self, font_weight: FontWeight): - self.font_weight = font_weight - self.set_text(self.text) - - def _render(self, rect: rl.Rectangle): - # Only scissor when we know there is a single scrolling line - if self._needs_scroll: - rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height)) - - font = gui_app.font(self.font_weight) - - text_y_offset = 0 - # Draw the text in the specified rectangle - lines = self.wrapped_text or [self.text] - if self.alignment_vertical == rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM: - lines = lines[::-1] - - for display_text in lines: - text_size = measure_text_cached(font, display_text, self.font_size, self.spacing) - - # Elide text to fit within the rectangle - if self.elide_right and text_size.x > rect.width: - ellipsis = "..." - left, right = 0, len(display_text) - while left < right: - mid = (left + right) // 2 - candidate = display_text[:mid] + ellipsis - candidate_size = measure_text_cached(font, candidate, self.font_size, self.spacing) - if candidate_size.x <= rect.width: - left = mid + 1 - else: - right = mid - display_text = display_text[: left - 1] + ellipsis if left > 0 else ellipsis - text_size = measure_text_cached(font, display_text, self.font_size, self.spacing) - - # Handle scroll state - elif self.scroll and self._needs_scroll: - if self._scroll_state == ScrollState.STARTING: - if self._scroll_pause_t is None: - self._scroll_pause_t = rl.get_time() + 2.0 - if rl.get_time() >= self._scroll_pause_t: - self._scroll_state = ScrollState.SCROLLING - self._scroll_pause_t = None - - elif self._scroll_state == ScrollState.SCROLLING: - self._scroll_offset -= 0.8 / 60. * gui_app.target_fps - # don't fully hide - if self._scroll_offset <= -text_size.x - self._rect.width / 3: - self._scroll_offset = 0 - self._scroll_state = ScrollState.STARTING - self._scroll_pause_t = None - - # Calculate horizontal position based on alignment - text_x = rect.x + { - rl.GuiTextAlignment.TEXT_ALIGN_LEFT: 0, - rl.GuiTextAlignment.TEXT_ALIGN_CENTER: (rect.width - text_size.x) / 2, - rl.GuiTextAlignment.TEXT_ALIGN_RIGHT: rect.width - text_size.x, - }.get(self.alignment, 0) + self._scroll_offset - - # Calculate vertical position based on alignment - text_y = rect.y + { - rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP: 0, - rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE: (rect.height - text_size.y) / 2, - rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM: rect.height - text_size.y, - }.get(self.alignment_vertical, 0) - text_y += text_y_offset - - rl.draw_text_ex(font, display_text, rl.Vector2(round(text_x), text_y), self.font_size, self.spacing, self.color) - # Draw 2nd instance for scrolling - if self._needs_scroll and self._scroll_state != ScrollState.STARTING: - text2_scroll_offset = text_size.x + self._rect.width / 3 - rl.draw_text_ex(font, display_text, rl.Vector2(round(text_x + text2_scroll_offset), text_y), self.font_size, self.spacing, self.color) - if self.alignment_vertical == rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM: - text_y_offset -= self.line_height - else: - text_y_offset += self.line_height - - if self._needs_scroll: - # draw black fade on left and right - fade_width = 20 - rl.draw_rectangle_gradient_h(int(rect.x + rect.width - fade_width), int(rect.y), fade_width, int(rect.height), rl.BLANK, rl.BLACK) - if self._scroll_state != ScrollState.STARTING: - rl.draw_rectangle_gradient_h(int(rect.x), int(rect.y), fade_width, int(rect.height), rl.BLACK, rl.BLANK) - - rl.end_scissor_mode() - - # TODO: This should be a Widget class def gui_label( rect: rl.Rectangle, @@ -392,7 +233,7 @@ class Label(Widget): class UnifiedLabel(Widget): """ - Unified label widget that combines functionality from gui_label, gui_text_box, Label, and MiciLabel. + Unified label widget that combines functionality from gui_label, gui_text_box, and Label. Supports: - Emoji rendering @@ -401,6 +242,13 @@ class UnifiedLabel(Widget): - Proper multiline vertical alignment - Height calculation for layout purposes """ + # Shimmer constants + SHIMMER_BAND_WIDTH = 0.3 # shimmer width as fraction of text width + SHIMMER_BLUR_RADIUS = 0.12 # gaussian blur as fraction of text width + SHIMMER_CYCLE_PERIOD = 2.5 # seconds per full shimmer cycle + SHIMMER_SWEEP_FRACTION = 0.9 # fraction of cycle spent sweeping (rest is pause) + SHIMMER_LOW_OPACITY = 0.65 # text opacity at rest, shimmer brings to 1.0 + def __init__(self, text: str | Callable[[], str], font_size: int = DEFAULT_TEXT_SIZE, @@ -414,7 +262,8 @@ class UnifiedLabel(Widget): wrap_text: bool = True, scroll: bool = False, line_height: float = 1.0, - letter_spacing: float = 0.0): + letter_spacing: float = 0.0, + shimmer: bool = False): super().__init__() self._text = text self._font_size = font_size @@ -432,6 +281,10 @@ class UnifiedLabel(Widget): self._letter_spacing = letter_spacing # 0.1 = 10% self._spacing_pixels = font_size * letter_spacing + # Shimmer state + self._shimmer = shimmer + self._shimmer_start_time = 0.0 + # Scroll state self._scroll = scroll self._needs_scroll = False @@ -467,6 +320,14 @@ class UnifiedLabel(Widget): """Get the current text content.""" return str(_resolve_value(self._text)) + @property + def font_size(self) -> int: + return self._font_size + + @property + def text_width(self) -> float: + return max((s.x for s in self._cached_line_sizes), default=0.0) + def set_text_color(self, color: rl.Color): """Update the text color.""" self._text_color = color @@ -517,6 +378,15 @@ class UnifiedLabel(Widget): self._scroll_pause_t = None self._scroll_state = ScrollState.STARTING + def show_event(self): + super().show_event() + if self._shimmer: + self.reset_shimmer() + + def reset_shimmer(self, offset: float = 0.0): + """Reset shimmer animation timing.""" + self._shimmer_start_time = rl.get_time() + offset + def set_max_width(self, max_width: int | None): """Set the maximum width constraint for wrapping/eliding.""" if self._max_width != max_width: @@ -770,6 +640,24 @@ class UnifiedLabel(Widget): rl.end_scissor_mode() + def _shimmer_alpha(self, char_x: float, shimmer_left: float, shimmer_width: float) -> float: + """Compute shimmer opacity multiplier for a character at the given x position.""" + sigma = shimmer_width * self.SHIMMER_BLUR_RADIUS + if sigma <= 0: + return self.SHIMMER_LOW_OPACITY + + elapsed = rl.get_time() - self._shimmer_start_time + t_raw = (elapsed % self.SHIMMER_CYCLE_PERIOD) / self.SHIMMER_CYCLE_PERIOD + t_clamped = max(0.0, min(t_raw / self.SHIMMER_SWEEP_FRACTION, 1.0)) + t = t_clamped * t_clamped * (3.0 - 2.0 * t_clamped) # smoothstep + + margin = shimmer_width * self.SHIMMER_BAND_WIDTH + center = shimmer_left + shimmer_width + margin - t * (shimmer_width + 2.0 * margin) + + d = char_x - center + shimmer = math.exp(-0.5 * d * d / (sigma * sigma)) + return self.SHIMMER_LOW_OPACITY + (1.0 - self.SHIMMER_LOW_OPACITY) * shimmer + def _render_line(self, line, size, emojis, current_y, x_offset=0.0): # Calculate horizontal position if self._alignment == rl.GuiTextAlignment.TEXT_ALIGN_LEFT: @@ -782,7 +670,13 @@ class UnifiedLabel(Widget): line_x = self._rect.x + self._text_padding line_x += self._scroll_offset + x_offset - # Render line with emojis + if self._shimmer: + self._render_line_shimmer(line, line_x, current_y) + else: + # Render line with emojis + self._render_line_normal(line, emojis, line_x, current_y) + + def _render_line_normal(self, line, emojis, line_x, current_y): line_pos = rl.Vector2(line_x, current_y) prev_index = 0 @@ -806,3 +700,23 @@ class UnifiedLabel(Widget): text_after = line[prev_index:] if text_after: rl.draw_text_ex(self._font, text_after, line_pos, self._font_size, self._spacing_pixels, self._text_color) + + def _render_line_shimmer(self, line, line_x, current_y): + # Shimmer range based on widest line so sweep is even across all lines + max_width = self.text_width + if self._alignment == rl.GuiTextAlignment.TEXT_ALIGN_RIGHT: + shimmer_left = self._rect.x + self._rect.width - self._text_padding - max_width + elif self._alignment == rl.GuiTextAlignment.TEXT_ALIGN_CENTER: + shimmer_left = self._rect.x + (self._rect.width - max_width) / 2 + else: + shimmer_left = self._rect.x + self._text_padding + + base_a = self._text_color.a / 255.0 + cursor_x = line_x + for ch in line: + char_width = measure_text_cached(self._font, ch, self._font_size, self._spacing_pixels).x + char_center_x = cursor_x + char_width / 2.0 + alpha = int(255 * self._shimmer_alpha(char_center_x, shimmer_left, max_width) * base_a) + color = rl.Color(self._text_color.r, self._text_color.g, self._text_color.b, alpha) + rl.draw_text_ex(self._font, ch, rl.Vector2(cursor_x, current_y), self._font_size, 0, color) + cursor_x += char_width + self._spacing_pixels diff --git a/system/ui/widgets/layouts.py b/system/ui/widgets/layouts.py index 6f97fe5ed8..6bbc49e927 100644 --- a/system/ui/widgets/layouts.py +++ b/system/ui/widgets/layouts.py @@ -21,7 +21,6 @@ class HBoxLayout(Widget): def __init__(self, widgets: list[Widget] | None = None, spacing: int = 0, alignment: Alignment = Alignment.LEFT | Alignment.V_CENTER): super().__init__() - self._widgets: list[Widget] = [] self._spacing = spacing self._alignment = alignment @@ -31,13 +30,13 @@ class HBoxLayout(Widget): @property def widgets(self) -> list[Widget]: - return self._widgets + return self._children def add_widget(self, widget: Widget) -> None: - self._widgets.append(widget) + self._child(widget) def _render(self, _): - visible_widgets = [w for w in self._widgets if w.is_visible] + visible_widgets = [w for w in self._children if w.is_visible] cur_offset_x = 0 @@ -55,6 +54,6 @@ class HBoxLayout(Widget): y = self._rect.y + (self._rect.height - widget.rect.height) / 2 # Update widget position and render - widget.set_position(round(x), round(y)) + widget.set_position(x, y) widget.set_parent_rect(self._rect) widget.render() diff --git a/system/ui/widgets/list_view.py b/system/ui/widgets/list_view.py index 32bf01cfc8..82613c37c8 100644 --- a/system/ui/widgets/list_view.py +++ b/system/ui/widgets/list_view.py @@ -293,6 +293,7 @@ class ListItem(Widget): self._prev_description: str | None = self.description def show_event(self): + super().show_event() self._set_description_visible(False) def set_description_opened_callback(self, callback: Callable) -> None: @@ -354,7 +355,7 @@ class ListItem(Widget): if self.title: # Draw icon if present if self.icon: - rl.draw_texture(self._icon_texture, int(content_x), int(self._rect.y + (ITEM_BASE_HEIGHT - self._icon_texture.height) // 2), rl.WHITE) + rl.draw_texture_ex(self._icon_texture, rl.Vector2(content_x, self._rect.y + (ITEM_BASE_HEIGHT - self._icon_texture.height) / 2), 0.0, 1.0, rl.WHITE) text_x += ICON_SIZE + ITEM_PADDING # Draw main text diff --git a/system/ui/widgets/mici_keyboard.py b/system/ui/widgets/mici_keyboard.py index 18384fd905..75a3c29e6b 100644 --- a/system/ui/widgets/mici_keyboard.py +++ b/system/ui/widgets/mici_keyboard.py @@ -95,7 +95,7 @@ class Key(Widget): self._size_filter.update(size) def _get_font_size(self) -> int: - return int(round(self._size_filter.x)) + return round(self._size_filter.x) class SmallKey(Key): @@ -146,8 +146,9 @@ class CapsState(IntEnum): class MiciKeyboard(Widget): - def __init__(self): + def __init__(self, auto_return_to_letters: str = ""): super().__init__() + self._auto_return_to_letters = auto_return_to_letters lower_chars = [ "qwertyuiop", @@ -305,6 +306,10 @@ class MiciKeyboard(Widget): if self._caps_state == CapsState.UPPER: self._set_uppercase(False) + # Switch back to letters after common URL delimiters + if self._closest_key[0].char in self._auto_return_to_letters and self._current_keys in (self._special_keys, self._super_special_keys): + self._set_uppercase(False) + # ensure minimum selected animation time key_selected_dt = rl.get_time() - (self._selected_key_t or 0) cur_t = rl.get_time() diff --git a/system/ui/widgets/nav_widget.py b/system/ui/widgets/nav_widget.py index 67203d53f4..11770bbe5d 100644 --- a/system/ui/widgets/nav_widget.py +++ b/system/ui/widgets/nav_widget.py @@ -63,11 +63,13 @@ class NavWidget(Widget, abc.ABC): self._playing_dismiss_animation = False # released and animating away self._y_pos_filter = BounceFilter(0.0, 0.1, 1 / gui_app.target_fps, bounce=1) - self._back_callback: Callable[[], None] | None = None # persistent callback for any back navigation + self._back_callback: Callable[[], None] | None = None # persistent callback for user-initiated back navigation self._dismiss_callback: Callable[[], None] | None = None # transient callback for programmatic dismiss + # TODO: add this functionality to push_widget + self._shown_callback: Callable[[], None] | None = None # transient callback fired after show animation completes # TODO: move this state into NavBar - self._nav_bar = NavBar() + self._nav_bar = self._child(NavBar()) self._nav_bar_show_time = 0.0 self._nav_bar_y_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps) @@ -79,6 +81,9 @@ class NavWidget(Widget, abc.ABC): def set_back_callback(self, callback: Callable[[], None]) -> None: self._back_callback = callback + def set_shown_callback(self, callback: Callable[[], None] | None) -> None: + self._shown_callback = callback + def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: super()._handle_mouse_event(mouse_event) @@ -143,19 +148,24 @@ class NavWidget(Widget, abc.ABC): if self._playing_dismiss_animation: new_y = self._rect.height + DISMISS_PUSH_OFFSET - new_y = round(self._y_pos_filter.update(new_y)) - if abs(new_y) < 1 and self._y_pos_filter.velocity.x == 0.0: + new_y = self._y_pos_filter.update(new_y) + if abs(new_y) < 1 and abs(self._y_pos_filter.velocity.x) < 0.5: new_y = self._y_pos_filter.x = 0.0 + self._y_pos_filter.velocity.x = 0.0 + + if self._shown_callback is not None: + self._shown_callback() + self._shown_callback = None if new_y > self._rect.height + DISMISS_PUSH_OFFSET - 10: gui_app.pop_widget() - if self._back_callback is not None: - self._back_callback() - + # Only one callback should ever be fired if self._dismiss_callback is not None: self._dismiss_callback() self._dismiss_callback = None + elif self._back_callback is not None: + self._back_callback() self._playing_dismiss_animation = False self._drag_start_pos = None @@ -166,10 +176,10 @@ class NavWidget(Widget, abc.ABC): def _layout(self): # Dim whatever is behind this widget, fading with position (runs after _update_state so position is correct) overlay_alpha = int(200 * max(0.0, min(1.0, 1.0 - self._rect.y / self._rect.height))) if self._rect.height > 0 else 0 - rl.draw_rectangle(0, 0, int(self._rect.width), int(self._rect.height), rl.Color(0, 0, 0, overlay_alpha)) + rl.draw_rectangle_rec(rl.Rectangle(0, 0, self._rect.width, self._rect.height), rl.Color(0, 0, 0, overlay_alpha)) bounce_height = 20 - rl.draw_rectangle(int(self._rect.x), int(self._rect.y), int(self._rect.width), int(self._rect.height + bounce_height), rl.BLACK) + rl.draw_rectangle_rec(rl.Rectangle(self._rect.x, self._rect.y, self._rect.width, self._rect.height + bounce_height), rl.BLACK) def render(self, rect: rl.Rectangle | None = None) -> bool | int | None: ret = super().render(rect) @@ -186,7 +196,7 @@ class NavWidget(Widget, abc.ABC): else: self._nav_bar_y_filter.update(NAV_BAR_MARGIN) - self._nav_bar.set_position(bar_x, round(self._nav_bar_y_filter.x)) + self._nav_bar.set_position(bar_x, self._nav_bar_y_filter.x) self._nav_bar.render() return ret @@ -204,7 +214,6 @@ class NavWidget(Widget, abc.ABC): def show_event(self): super().show_event() - self._nav_bar.show_event() # Reset state self._drag_start_pos = None @@ -214,6 +223,7 @@ class NavWidget(Widget, abc.ABC): # Start NavWidget off-screen, no matter how tall it is self._y_pos_filter.update_alpha(0.1) self._y_pos_filter.x = gui_app.height + self._y_pos_filter.velocity.x = 0.0 self._nav_bar_y_filter.x = -NAV_BAR_MARGIN - NAV_BAR_HEIGHT self._nav_bar_show_time = rl.get_time() diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 6427a2f203..69be42f502 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -75,17 +75,14 @@ class NetworkUI(Widget): super().__init__() self._wifi_manager = wifi_manager self._current_panel: PanelType = PanelType.WIFI - self._wifi_panel = WifiManagerUI(wifi_manager) - self._advanced_panel = AdvancedNetworkSettings(wifi_manager) - self._nav_button = NavButton(tr("Advanced")) + self._wifi_panel = self._child(WifiManagerUI(wifi_manager)) + self._advanced_panel = self._child(AdvancedNetworkSettings(wifi_manager)) + self._nav_button = self._child(NavButton(tr("Advanced"))) self._nav_button.set_click_callback(self._cycle_panel) def show_event(self): + super().show_event() self._set_current_panel(PanelType.WIFI) - self._wifi_panel.show_event() - - def hide_event(self): - self._wifi_panel.hide_event() def _cycle_panel(self): if self._current_panel == PanelType.WIFI: @@ -305,10 +302,12 @@ class WifiManagerUI(Widget): disconnected=self._on_disconnected) def show_event(self): + super().show_event() # start/stop scanning when widget is visible self._wifi_manager.set_active(True) def hide_event(self): + super().hide_event() self._wifi_manager.set_active(False) def _load_icons(self): diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index c48be6b80b..a3a0d2b38f 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -47,7 +47,7 @@ class ScrollIndicator(Widget): # position based on scroll ratio slide_range = self._viewport.width - indicator_w max_scroll = self._content_size - self._viewport.width - scroll_ratio = -self._scroll_offset / max_scroll + scroll_ratio = (-self._scroll_offset / abs(max_scroll)) if abs(max_scroll) > 1e-3 else 0.0 x = self._viewport.x + scroll_ratio * slide_range # don't bounce up when NavWidget shows y = max(self._viewport.y, 0) + self._viewport.height - self._txt_scroll_indicator.height / 2 @@ -190,7 +190,7 @@ class _Scroller(Widget): self.scroll_panel.set_enabled(scroll_enabled and self.enabled and not self._scrolling_to[1]) self.scroll_panel.update(self._rect, content_size) if not self._snap_items: - return round(self.scroll_panel.get_offset()) + return self.scroll_panel.get_offset() # Snap closest item to center center_pos = self._rect.x + self._rect.width / 2 if self._horizontal else self._rect.y + self._rect.height / 2 @@ -341,7 +341,7 @@ class _Scroller(Widget): x, y = self._do_move_animation(item, x, y) # Update item state - item.set_position(round(x), round(y)) # round to prevent jumping when settling + item.set_position(x, y) item.set_parent_rect(self._rect) def _render_item(self, item: Widget): @@ -422,18 +422,10 @@ class Scroller(Widget): """Wrapper for _Scroller so that children do not need to call events or pass down enabled for nav stack.""" def __init__(self, **kwargs): super().__init__() - self._scroller = _Scroller([], **kwargs) + self._scroller = self._child(_Scroller([], **kwargs)) # pass down enabled to child widget for nav stack self._scroller.set_enabled(lambda: self.enabled) - def show_event(self): - super().show_event() - self._scroller.show_event() - - def hide_event(self): - super().hide_event() - self._scroller.hide_event() - def _render(self, _): self._scroller.render(self._rect) diff --git a/system/ui/widgets/slider.py b/system/ui/widgets/slider.py index 8f4bbfc011..bf965954f2 100644 --- a/system/ui/widgets/slider.py +++ b/system/ui/widgets/slider.py @@ -1,3 +1,4 @@ +import abc from collections.abc import Callable import pyray as rl @@ -5,19 +6,23 @@ import pyray as rl from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.label import UnifiedLabel -from openpilot.common.filter_simple import FirstOrderFilter +from openpilot.common.filter_simple import FirstOrderFilter, BounceFilter -class SmallSlider(Widget): +class SliderBase(Widget, abc.ABC): HORIZONTAL_PADDING = 8 CONFIRM_DELAY = 0.2 + PRESSED_SCALE = 1.07 - def __init__(self, title: str, confirm_callback: Callable | None = None): - # TODO: unify this with BigConfirmationDialogV2 + _bg_txt: rl.Texture + _circle_bg_txt: rl.Texture + _circle_bg_pressed_txt: rl.Texture + _circle_arrow_txt: rl.Texture + + def __init__(self, title: str, confirm_callback: Callable | None = None, shimmer_offset: float = 0.0): super().__init__() self._confirm_callback = confirm_callback - - self._font = gui_app.font(FontWeight.DISPLAY) + self._shimmer_offset = shimmer_offset self._load_assets() @@ -30,30 +35,34 @@ class SmallSlider(Widget): self._start_x_circle = 0.0 self._scroll_x_circle = 0.0 self._scroll_x_circle_filter = FirstOrderFilter(0, 0.05, 1 / gui_app.target_fps) + self._circle_scale_filter = BounceFilter(1.0, 0.1, 1 / gui_app.target_fps) + self._circle_press_time: float | None = None self._is_dragging_circle = False - self._label = UnifiedLabel(title, font_size=36, font_weight=FontWeight.SEMI_BOLD, text_color=rl.Color(255, 255, 255, int(255 * 0.65)), - alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT, - alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE, line_height=0.9) + self._label = self._child(UnifiedLabel(title, font_size=36, font_weight=FontWeight.SEMI_BOLD, text_color=rl.WHITE, + alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT, + alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE, line_height=0.9, shimmer=True)) + @abc.abstractmethod def _load_assets(self): - self.set_rect(rl.Rectangle(0, 0, 316 + self.HORIZONTAL_PADDING * 2, 100)) - - self._bg_txt = gui_app.texture("icons_mici/setup/small_slider/slider_bg.png", 316, 100) - self._circle_bg_txt = gui_app.texture("icons_mici/setup/small_slider/slider_red_circle.png", 100, 100) - self._circle_bg_pressed_txt = gui_app.texture("icons_mici/setup/small_slider/slider_red_circle_pressed.png", 100, 100) - self._circle_arrow_txt = gui_app.texture("icons_mici/setup/small_slider/slider_arrow.png", 37, 32) + ... @property def confirmed(self) -> bool: return self._confirmed_time > 0.0 + def show_event(self): + super().show_event() + self.reset() + def reset(self): # reset all slider state self._is_dragging_circle = False + self._circle_press_time = None self._confirmed_time = 0.0 self._confirm_callback_called = False + self._label.reset_shimmer(self._shimmer_offset) def set_opacity(self, opacity: float, smooth: bool = False): if smooth: @@ -84,6 +93,7 @@ class SmallSlider(Widget): if rl.check_collision_point_rec(mouse_event.pos, circle_button_rect): self._start_x_circle = mouse_event.pos.x self._is_dragging_circle = True + self._circle_press_time = rl.get_time() elif mouse_event.left_released: # swiped to left @@ -101,7 +111,7 @@ class SmallSlider(Widget): activated_pos = int(-self._bg_txt.width + self._circle_bg_txt.width) self._scroll_x_circle = max(min(self._scroll_x_circle, 0), activated_pos) - if self._confirmed_time > 0: + if self.confirmed: # swiped left to confirm self._scroll_x_circle_filter.update(activated_pos) @@ -119,8 +129,6 @@ class SmallSlider(Widget): self._scroll_x_circle_filter.x = self._scroll_x_circle def _render(self, _): - # TODO: iOS text shimmering animation - white = rl.Color(255, 255, 255, int(255 * self._opacity_filter.x)) bg_txt_x = self._rect.x + (self._rect.width - self._bg_txt.width) / 2 @@ -130,8 +138,9 @@ class SmallSlider(Widget): btn_x = bg_txt_x + self._bg_txt.width - self._circle_bg_txt.width + self._scroll_x_circle_filter.x btn_y = self._rect.y + (self._rect.height - self._circle_bg_txt.height) / 2 - if self._confirmed_time == 0.0 or self._scroll_x_circle > 0: - self._label.set_text_color(rl.Color(255, 255, 255, int(255 * 0.65 * (1.0 - self.slider_percentage) * self._opacity_filter.x))) + label_alpha = int(255 * (1.0 - self.slider_percentage) * self._opacity_filter.x) + if label_alpha > 0: + self._label.set_text_color(rl.Color(255, 255, 255, label_alpha)) label_rect = rl.Rectangle( self._rect.x + 20, self._rect.y, @@ -140,19 +149,23 @@ class SmallSlider(Widget): ) self._label.render(label_rect) - # circle and arrow - circle_bg_txt = self._circle_bg_pressed_txt if self._is_dragging_circle or self._confirmed_time > 0 else self._circle_bg_txt - rl.draw_texture_ex(circle_bg_txt, rl.Vector2(btn_x, btn_y), 0.0, 1.0, white) + # circle and arrow with grow animation + circle_pressed = self._is_dragging_circle or self.confirmed or (self._circle_press_time is not None and rl.get_time() - self._circle_press_time < 0.075) + circle_bg_txt = self._circle_bg_pressed_txt if circle_pressed else self._circle_bg_txt + scale = self._circle_scale_filter.update(self.PRESSED_SCALE if circle_pressed else 1.0) + scaled_btn_x = btn_x + (self._circle_bg_txt.width * (1 - scale)) / 2 + scaled_btn_y = btn_y + (self._circle_bg_txt.height * (1 - scale)) / 2 + rl.draw_texture_ex(circle_bg_txt, rl.Vector2(scaled_btn_x, scaled_btn_y), 0.0, scale, white) arrow_x = btn_x + (self._circle_bg_txt.width - self._circle_arrow_txt.width) / 2 - arrow_y = btn_y + (self._circle_bg_txt.height - self._circle_arrow_txt.height) / 2 + arrow_y = scaled_btn_y + (self._circle_bg_txt.height - self._circle_arrow_txt.height) / 2 rl.draw_texture_ex(self._circle_arrow_txt, rl.Vector2(arrow_x, arrow_y), 0.0, 1.0, white) -class LargerSlider(SmallSlider): - def __init__(self, title: str, confirm_callback: Callable | None = None, green: bool = True): +class LargerSlider(SliderBase): + def __init__(self, title: str, confirm_callback: Callable | None = None, green: bool = True, shimmer_offset: float = 0.0): self._green = green - super().__init__(title, confirm_callback=confirm_callback) + super().__init__(title, confirm_callback=confirm_callback, shimmer_offset=shimmer_offset) def _load_assets(self): self.set_rect(rl.Rectangle(0, 0, 520 + self.HORIZONTAL_PADDING * 2, 115)) @@ -164,13 +177,13 @@ class LargerSlider(SmallSlider): self._circle_arrow_txt = gui_app.texture("icons_mici/setup/small_slider/slider_arrow.png", 64, 55) -class BigSlider(SmallSlider): +class BigSlider(SliderBase): def __init__(self, title: str, icon: rl.Texture, confirm_callback: Callable | None = None): self._icon = icon super().__init__(title, confirm_callback=confirm_callback) - self._label = UnifiedLabel(title, font_size=48, font_weight=FontWeight.DISPLAY, text_color=rl.Color(255, 255, 255, int(255 * 0.65)), - alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE, - line_height=0.875) + self._label.set_font_size(48) + self._label.set_font_weight(FontWeight.DISPLAY) + self._label.set_line_height(0.875) def _load_assets(self): self.set_rect(rl.Rectangle(0, 0, 520 + self.HORIZONTAL_PADDING * 2, 180)) diff --git a/third_party/.gitignore b/third_party/.gitignore deleted file mode 100644 index 0d20b6487c..0000000000 --- a/third_party/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.pyc diff --git a/third_party/json11/json11.cpp b/third_party/json11/json11.cpp index bc4045f07d..3bd4fde2f2 100644 --- a/third_party/json11/json11.cpp +++ b/third_party/json11/json11.cpp @@ -25,6 +25,7 @@ #include #include #include +#include namespace json11 { diff --git a/tools/README.md b/tools/README.md index d52c8f4522..90696ab4e6 100644 --- a/tools/README.md +++ b/tools/README.md @@ -38,7 +38,7 @@ scons -u -j$(nproc) Follow [these instructions](https://docs.microsoft.com/en-us/windows/wsl/install) to setup the WSL and install the `Ubuntu-24.04` distribution. Once your Ubuntu WSL environment is setup, follow the Linux setup instructions to finish setting up your environment. See [these instructions](https://learn.microsoft.com/en-us/windows/wsl/tutorials/gui-apps) for running GUI apps. -**NOTE**: If you are running WSL and any GUIs are failing (segfaulting or other strange issues) even after following the steps above, you may need to enable software rendering with `LIBGL_ALWAYS_SOFTWARE=1`, e.g. `LIBGL_ALWAYS_SOFTWARE=1 selfdrive/ui/ui`. +**NOTE**: If you are running WSL 2 and experiencing performance issues with the UI or simulator, you may need to explicitly enable hardware acceleration by setting `GALLIUM_DRIVER=d3d12` before commands. Add `export GALLIUM_DRIVER=d3d12` to your `~/.bashrc` file to make it automatic for future sessions. ## CTF Learn about the openpilot ecosystem and tools by playing our [CTF](/tools/CTF.md). diff --git a/tools/bodyteleop/.gitignore b/tools/bodyteleop/.gitignore deleted file mode 100644 index adeab99a95..0000000000 --- a/tools/bodyteleop/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -av -av-10.0.0/* -key.pem -cert.pem \ No newline at end of file diff --git a/tools/cabana/.gitignore b/tools/cabana/.gitignore index 3d64f83204..1ee6c92236 100644 --- a/tools/cabana/.gitignore +++ b/tools/cabana/.gitignore @@ -1,6 +1,8 @@ moc_* *.moc +assets.cc + _cabana dbc/car_fingerprint_to_dbc.json tests/test_cabana diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index cecb5ed8d9..dbe4dbc659 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -2,6 +2,8 @@ import subprocess import os import shutil +import libusb + Import('env', 'arch', 'common', 'messaging', 'visionipc', 'cereal') # Detect Qt - skip build if not available @@ -13,7 +15,6 @@ if arch == "Darwin": has_qt = False else: has_qt = shutil.which('qmake') is not None - if not has_qt: Return() @@ -21,7 +22,7 @@ SConscript(['#tools/replay/SConscript']) Import('replay_lib') qt_env = env.Clone() -qt_modules = ["Widgets", "Gui", "Core", "Network", "Concurrent", "DBus", "Xml"] +qt_modules = ["Widgets", "Gui", "Core"] qt_libs = [] if arch == "Darwin": @@ -51,7 +52,7 @@ else: qt_env['QT3DIR'] = qt_env['QTDIR'] qt_env.Tool('qt3') -qt_env['CPPPATH'] += qt_dirs + ["#third_party/qrcode"] +qt_env['CPPPATH'] += qt_dirs qt_flags = [ "-D_REENTRANT", "-DQT_NO_DEBUG", @@ -65,23 +66,18 @@ qt_env['LIBPATH'] += ['#selfdrive/ui', ] qt_env['LIBS'] = qt_libs base_frameworks = qt_env['FRAMEWORKS'] -base_libs = [common, messaging, cereal, visionipc, 'm', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"] +base_libs = [common, messaging, cereal, visionipc, 'm', 'pthread'] + qt_env["LIBS"] if arch == "Darwin": base_frameworks.append('QtCharts') - base_frameworks.append('QtSerialBus') else: base_libs.append('Qt5Charts') - base_libs.append('Qt5SerialBus') - -qt_libs = base_libs cabana_env = qt_env.Clone() -if arch == "Darwin": - cabana_env['CPPPATH'] += [f"{brew_prefix}/include"] - cabana_env['LIBPATH'] += [f"{brew_prefix}/lib"] +cabana_env['CPPPATH'] += [libusb.INCLUDE_DIR] +cabana_env['LIBPATH'] += [libusb.LIB_DIR] -cabana_libs = [cereal, messaging, visionipc, replay_lib, 'avformat', 'avcodec', 'swresample', 'avutil', 'x264', 'z', 'bz2', 'zstd', 'yuv', 'usb-1.0'] + qt_libs +cabana_libs = [cereal, messaging, visionipc, replay_lib, 'avformat', 'avcodec', 'swresample', 'avutil', 'x264', 'z', 'bz2', 'zstd', 'yuv', 'usb-1.0'] + base_libs opendbc_path = '-DOPENDBC_FILE_PATH=\'"%s"\'' % (cabana_env.Dir("../../opendbc/dbc").abspath) cabana_env['CXXFLAGS'] += [opendbc_path] @@ -91,12 +87,15 @@ assets_src = "assets/assets.qrc" cabana_env.Command(assets, assets_src, f"rcc $SOURCES -o $TARGET") cabana_env.Depends(assets, Glob('/assets/*', exclude=[assets, assets_src, "assets/assets.o"])) -cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'streams/socketcanstream.cc', 'streams/pandastream.cc', 'streams/devicestream.cc', 'streams/livestream.cc', 'streams/abstractstream.cc', 'streams/replaystream.cc', 'binaryview.cc', 'historylog.cc', 'videowidget.cc', 'signalview.cc', - 'streams/routes.cc', 'dbc/dbc.cc', 'dbc/dbcfile.cc', 'dbc/dbcmanager.cc', - 'utils/export.cc', 'utils/util.cc', 'utils/elidedlabel.cc', - 'chart/chartswidget.cc', 'chart/chart.cc', 'chart/signalselector.cc', 'chart/tiplabel.cc', 'chart/sparkline.cc', - 'commands.cc', 'messageswidget.cc', 'streamselector.cc', 'settings.cc', 'panda.cc', - 'cameraview.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc', 'tools/findsignal.cc', 'tools/routeinfo.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) +cabana_srcs = ['mainwin.cc', 'streams/pandastream.cc', 'streams/devicestream.cc', 'streams/livestream.cc', 'streams/abstractstream.cc', 'streams/replaystream.cc', 'binaryview.cc', 'historylog.cc', 'videowidget.cc', 'signalview.cc', + 'streams/routes.cc', 'dbc/dbc.cc', 'dbc/dbcfile.cc', 'dbc/dbcmanager.cc', + 'utils/export.cc', 'utils/util.cc', 'utils/elidedlabel.cc', + 'chart/chartswidget.cc', 'chart/chart.cc', 'chart/signalselector.cc', 'chart/tiplabel.cc', 'chart/sparkline.cc', + 'commands.cc', 'messageswidget.cc', 'streamselector.cc', 'settings.cc', 'panda.cc', + 'cameraview.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc', 'tools/findsignal.cc', 'tools/routeinfo.cc'] +if arch != "Darwin": + cabana_srcs += ['streams/socketcanstream.cc'] +cabana_lib = cabana_env.Library("cabana_lib", cabana_srcs, LIBS=cabana_libs, FRAMEWORKS=base_frameworks) cabana_env.Program('_cabana', ['cabana.cc', cabana_lib, assets], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) if GetOption('extras'): diff --git a/tools/cabana/assets/.gitignore b/tools/cabana/assets/.gitignore deleted file mode 100644 index 283034ca8b..0000000000 --- a/tools/cabana/assets/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.cc diff --git a/tools/cabana/binaryview.cc b/tools/cabana/binaryview.cc index b5a68c6b26..0be28f06b7 100644 --- a/tools/cabana/binaryview.cc +++ b/tools/cabana/binaryview.cc @@ -111,7 +111,8 @@ void BinaryView::highlight(const cabana::Signal *sig) { if (sig != hovered_sig) { for (int i = 0; i < model->items.size(); ++i) { auto &item_sigs = model->items[i].sigs; - if ((sig && item_sigs.contains(sig)) || (hovered_sig && item_sigs.contains(hovered_sig))) { + auto has = [](const auto &v, auto p) { return std::find(v.begin(), v.end(), p) != v.end(); }; + if ((sig && has(item_sigs, sig)) || (hovered_sig && has(item_sigs, hovered_sig))) { auto index = model->index(i / model->columnCount(), i % model->columnCount()); emit model->dataChanged(index, index, {Qt::DisplayRole}); } @@ -157,7 +158,7 @@ void BinaryView::mousePressEvent(QMouseEvent *event) { void BinaryView::highlightPosition(const QPoint &pos) { if (auto index = indexAt(viewport()->mapFromGlobal(pos)); index.isValid()) { auto item = (BinaryViewModel::Item *)index.internalPointer(); - const cabana::Signal *sig = item->sigs.isEmpty() ? nullptr : item->sigs.back(); + const cabana::Signal *sig = item->sigs.empty() ? nullptr : item->sigs.back(); highlight(sig); } } @@ -208,12 +209,12 @@ void BinaryView::refresh() { highlightPosition(QCursor::pos()); } -QSet BinaryView::getOverlappingSignals() const { - QSet overlapping; +std::set BinaryView::getOverlappingSignals() const { + std::set overlapping; for (const auto &item : model->items) { if (item.sigs.size() > 1) { for (auto s : item.sigs) { - if (s->type == cabana::Signal::Type::Normal) overlapping += s; + if (s->type == cabana::Signal::Type::Normal) overlapping.insert(s); } } } @@ -258,7 +259,7 @@ void BinaryViewModel::refresh() { int pos = sig->is_little_endian ? flipBitPos(sig->start_bit + j) : flipBitPos(sig->start_bit) + j; int idx = column_count * (pos / 8) + pos % 8; if (idx >= items.size()) { - qWarning() << "signal " << sig->name << "out of bounds.start_bit:" << sig->start_bit << "size:" << sig->size; + qWarning() << "signal " << sig->name.c_str() << "out of bounds.start_bit:" << sig->start_bit << "size:" << sig->size; break; } if (j == 0) sig->is_little_endian ? items[idx].is_lsb = true : items[idx].is_msb = true; @@ -404,7 +405,9 @@ bool BinaryItemDelegate::hasSignal(const QModelIndex &index, int dx, int dy, con if (!index.isValid()) return false; auto model = (const BinaryViewModel*)(index.model()); int idx = (index.row() + dy) * model->columnCount() + index.column() + dx; - return (idx >=0 && idx < model->items.size()) ? model->items[idx].sigs.contains(sig) : false; + if (idx < 0 || idx >= (int)model->items.size()) return false; + auto &s = model->items[idx].sigs; + return std::find(s.begin(), s.end(), sig) != s.end(); } void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { @@ -421,7 +424,7 @@ void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op auto color = bin_view->resize_sig ? bin_view->resize_sig->color : option.palette.color(QPalette::Active, QPalette::Highlight); painter->fillRect(option.rect, color); painter->setPen(option.palette.color(QPalette::BrightText)); - } else if (!bin_view->selectionModel()->hasSelection() || !item->sigs.contains(bin_view->resize_sig)) { // not resizing + } else if (!bin_view->selectionModel()->hasSelection() || std::find(item->sigs.begin(), item->sigs.end(), bin_view->resize_sig) == item->sigs.end()) { // not resizing if (item->sigs.size() > 0) { for (auto &s : item->sigs) { if (s == bin_view->hovered_sig) { @@ -433,7 +436,7 @@ void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op } else if (item->valid && item->bg_color.alpha() > 0) { painter->fillRect(option.rect, item->bg_color); } - auto color_role = item->sigs.contains(bin_view->hovered_sig) ? QPalette::BrightText : QPalette::Text; + auto color_role = (std::find(item->sigs.begin(), item->sigs.end(), bin_view->hovered_sig) != item->sigs.end()) ? QPalette::BrightText : QPalette::Text; painter->setPen(option.palette.color(bin_view->is_message_active ? QPalette::Normal : QPalette::Disabled, color_role)); } diff --git a/tools/cabana/binaryview.h b/tools/cabana/binaryview.h index 920deb0018..e568228b37 100644 --- a/tools/cabana/binaryview.h +++ b/tools/cabana/binaryview.h @@ -1,10 +1,9 @@ #pragma once +#include #include #include -#include -#include #include #include @@ -51,7 +50,7 @@ public: bool is_msb = false; bool is_lsb = false; uint8_t val; - QList sigs; + std::vector sigs; bool valid = false; }; std::vector items; @@ -68,7 +67,7 @@ public: BinaryView(QWidget *parent = nullptr); void setMessage(const MessageId &message_id); void highlight(const cabana::Signal *sig); - QSet getOverlappingSignals() const; + std::set getOverlappingSignals() const; void updateState() { model->updateState(); } void paintEvent(QPaintEvent *event) override { is_message_active = can->isMessageActive(model->msg_id); diff --git a/tools/cabana/cabana.cc b/tools/cabana/cabana.cc index bc50afc03a..db26b4067a 100644 --- a/tools/cabana/cabana.cc +++ b/tools/cabana/cabana.cc @@ -5,7 +5,9 @@ #include "tools/cabana/streams/devicestream.h" #include "tools/cabana/streams/pandastream.h" #include "tools/cabana/streams/replaystream.h" +#ifdef __linux__ #include "tools/cabana/streams/socketcanstream.h" +#endif int main(int argc, char *argv[]) { QCoreApplication::setApplicationName("Cabana"); @@ -29,9 +31,11 @@ int main(int argc, char *argv[]) { cmd_parser.addOption({"msgq", "read can messages from the msgq"}); cmd_parser.addOption({"panda", "read can messages from panda"}); cmd_parser.addOption({"panda-serial", "read can messages from panda with given serial", "panda-serial"}); +#ifdef __linux__ if (SocketCanStream::available()) { cmd_parser.addOption({"socketcan", "read can messages from given SocketCAN device", "socketcan"}); } +#endif cmd_parser.addOption({"zmq", "read can messages from zmq at the specified ip-address", "ip-address"}); cmd_parser.addOption({"data_dir", "local directory with routes", "data_dir"}); cmd_parser.addOption({"no-vipc", "do not output video"}); @@ -46,13 +50,15 @@ int main(int argc, char *argv[]) { stream = new DeviceStream(&app, cmd_parser.value("zmq")); } else if (cmd_parser.isSet("panda") || cmd_parser.isSet("panda-serial")) { try { - stream = new PandaStream(&app, {.serial = cmd_parser.value("panda-serial")}); + stream = new PandaStream(&app, {.serial = cmd_parser.value("panda-serial").toStdString()}); } catch (std::exception &e) { qWarning() << e.what(); return 0; } +#ifdef __linux__ } else if (SocketCanStream::available() && cmd_parser.isSet("socketcan")) { - stream = new SocketCanStream(&app, {.device = cmd_parser.value("socketcan")}); + stream = new SocketCanStream(&app, {.device = cmd_parser.value("socketcan").toStdString()}); +#endif } else { uint32_t replay_flags = REPLAY_FLAG_NONE; if (cmd_parser.isSet("ecam")) replay_flags |= REPLAY_FLAG_ECAM; @@ -70,7 +76,7 @@ int main(int argc, char *argv[]) { if (!route.isEmpty()) { auto replay_stream = std::make_unique(&app); bool auto_source = cmd_parser.isSet("auto"); - if (!replay_stream->loadRoute(route, cmd_parser.value("data_dir"), replay_flags, auto_source)) { + if (!replay_stream->loadRoute(route.toStdString(), cmd_parser.value("data_dir").toStdString(), replay_flags, auto_source)) { return 0; } stream = replay_stream.release(); diff --git a/tools/cabana/chart/chart.cc b/tools/cabana/chart/chart.cc index 14491b1eff..9dfdc595f0 100644 --- a/tools/cabana/chart/chart.cc +++ b/tools/cabana/chart/chart.cc @@ -237,8 +237,8 @@ void ChartView::updateTitle() { for (auto &s : sigs) { auto decoration = s.series->isVisible() ? "none" : "line-through"; s.series->setName(QString("%3 %5 %6") - .arg(decoration, titleColorCss, s.sig->name, - msgColorCss, msgName(s.msg_id), s.msg_id.toString())); + .arg(decoration, titleColorCss, QString::fromStdString(s.sig->name), + msgColorCss, QString::fromStdString(msgName(s.msg_id)), QString::fromStdString(s.msg_id.toString()))); } split_chart_act->setEnabled(sigs.size() > 1); resetChartCache(); @@ -339,13 +339,13 @@ void ChartView::updateAxisY() { double min = std::numeric_limits::max(); double max = std::numeric_limits::lowest(); - QString unit = sigs[0].sig->unit; + QString unit = QString::fromStdString(sigs[0].sig->unit); for (auto &s : sigs) { if (!s.series->isVisible()) continue; // Only show unit when all signals have the same unit - if (unit != s.sig->unit) { + if (unit != QString::fromStdString(s.sig->unit)) { unit.clear(); } @@ -573,11 +573,11 @@ void ChartView::showTip(double sec) { // use reverse iterator to find last item <= sec. auto it = std::lower_bound(s.vals.crbegin(), s.vals.crend(), sec, [](auto &p, double v) { return p.x() > v; }); if (it != s.vals.crend() && it->x() >= axis_x->min()) { - value = s.sig->formatValue(it->y(), false); + value = QString::fromStdString(s.sig->formatValue(it->y(), false)); s.track_pt = *it; x = std::max(x, chart()->mapToPosition(*it).x()); } - QString name = sigs.size() > 1 ? s.sig->name + ": " : ""; + QString name = sigs.size() > 1 ? QString::fromStdString(s.sig->name) + ": " : ""; QString min = s.min == std::numeric_limits::max() ? "--" : QString::number(s.min); QString max = s.max == std::numeric_limits::lowest() ? "--" : QString::number(s.max); text_list << QString("%2%3 (%4, %5)") @@ -766,7 +766,7 @@ void ChartView::drawSignalValue(QPainter *painter) { for (auto &s : sigs) { auto it = std::lower_bound(s.vals.crbegin(), s.vals.crend(), cur_sec, [](auto &p, double x) { return p.x() > x + EPSILON; }); - QString value = (it != s.vals.crend() && it->x() >= axis_x->min()) ? s.sig->formatValue(it->y()) : "--"; + QString value = (it != s.vals.crend() && it->x() >= axis_x->min()) ? QString::fromStdString(s.sig->formatValue(it->y())) : "--"; QRectF marker_rect = legend_markers[i++]->sceneBoundingRect(); QRectF value_rect(marker_rect.bottomLeft() - QPoint(0, 1), marker_rect.size()); QString elided_val = painter->fontMetrics().elidedText(value, Qt::ElideRight, value_rect.width()); diff --git a/tools/cabana/chart/chartswidget.cc b/tools/cabana/chart/chartswidget.cc index aba25dcf83..44dca42152 100644 --- a/tools/cabana/chart/chartswidget.cc +++ b/tools/cabana/chart/chartswidget.cc @@ -1,13 +1,13 @@ #include "tools/cabana/chart/chartswidget.h" #include +#include #include -#include #include +#include #include #include -#include #include "tools/cabana/chart/chart.h" @@ -166,15 +166,16 @@ void ChartsWidget::removeTab(int index) { void ChartsWidget::updateTabBar() { for (int i = 0; i < tabbar->count(); ++i) { const auto &charts_in_tab = tab_charts[tabbar->tabData(i).toInt()]; - tabbar->setTabText(i, QString("Tab %1 (%2)").arg(i + 1).arg(charts_in_tab.count())); + tabbar->setTabText(i, QString("Tab %1 (%2)").arg(i + 1).arg((int)charts_in_tab.size())); } } void ChartsWidget::eventsMerged(const MessageEventsMap &new_events) { - QFutureSynchronizer future_synchronizer; + std::vector> futures; for (auto c : charts) { - future_synchronizer.addFuture(QtConcurrent::run(c, &ChartView::updateSeries, nullptr, &new_events)); + futures.push_back(std::async(std::launch::async, &ChartView::updateSeries, c, nullptr, &new_events)); } + for (auto &f : futures) f.get(); } void ChartsWidget::timeRangeChanged(const std::optional> &time_range) { @@ -203,7 +204,7 @@ void ChartsWidget::showValueTip(double sec) { } void ChartsWidget::updateState() { - if (charts.isEmpty()) return; + if (charts.empty()) return; const auto &time_range = can->timeRange(); const double cur_sec = can->currentSec(); @@ -247,7 +248,7 @@ void ChartsWidget::updateToolBar() { redo_zoom_action->setVisible(is_zoomed); reset_zoom_action->setVisible(is_zoomed); reset_zoom_btn->setText(is_zoomed ? tr("%1-%2").arg(can->timeRange()->first, 0, 'f', 2).arg(can->timeRange()->second, 0, 'f', 2) : ""); - remove_all_btn->setEnabled(!charts.isEmpty()); + remove_all_btn->setEnabled(!charts.empty()); } void ChartsWidget::settingChanged() { @@ -281,9 +282,9 @@ ChartView *ChartsWidget::createChart(int pos) { chart->setMinimumWidth(CHART_MIN_WIDTH); chart->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); QObject::connect(chart, &ChartView::axisYLabelWidthChanged, align_timer, qOverload<>(&QTimer::start)); - pos = std::clamp(pos, 0, charts.size()); - charts.insert(pos, chart); - currentCharts().insert(pos, chart); + pos = std::clamp(pos, 0, (int)charts.size()); + charts.insert(charts.begin() + pos, chart); + currentCharts().insert(currentCharts().begin() + pos, chart); updateLayout(true); updateToolBar(); return chart; @@ -302,7 +303,7 @@ void ChartsWidget::showChart(const MessageId &id, const cabana::Signal *sig, boo void ChartsWidget::splitChart(ChartView *src_chart) { if (src_chart->sigs.size() > 1) { - int pos = charts.indexOf(src_chart) + 1; + int pos = std::find(charts.begin(), charts.end(), src_chart) - charts.begin() + 1; for (auto it = src_chart->sigs.begin() + 1; it != src_chart->sigs.end(); /**/) { auto c = createChart(pos); src_chart->chart()->removeSeries(it->series); @@ -327,7 +328,7 @@ QStringList ChartsWidget::serializeChartIds() const { for (auto c : charts) { QStringList ids; for (const auto& s : c->sigs) - ids += QString("%1|%2").arg(s.msg_id.toString(), s.sig->name); + ids += QString("%1|%2").arg(QString::fromStdString(s.msg_id.toString()), QString::fromStdString(s.sig->name)); chart_ids += ids.join(','); } std::reverse(chart_ids.begin(), chart_ids.end()); @@ -340,9 +341,9 @@ void ChartsWidget::restoreChartsFromIds(const QStringList& chart_ids) { for (const auto& part : chart_id.split(',')) { const auto sig_parts = part.split('|'); if (sig_parts.size() != 2) continue; - MessageId msg_id = MessageId::fromString(sig_parts[0]); + MessageId msg_id = MessageId::fromString(sig_parts[0].toStdString()); if (auto* msg = dbc()->msg(msg_id)) - if (auto* sig = msg->sig(sig_parts[1])) + if (auto* sig = msg->sig(sig_parts[1].toStdString())) showChart(msg_id, sig, true, index++ > 0); } } @@ -426,14 +427,14 @@ void ChartsWidget::doAutoScroll() { } QSize ChartsWidget::minimumSizeHint() const { - return QSize(CHART_MIN_WIDTH * 1.5 * qApp->devicePixelRatio(), QWidget::minimumSizeHint().height()); + return QSize(CHART_MIN_WIDTH * 1.5, QWidget::minimumSizeHint().height()); } void ChartsWidget::newChart() { SignalSelector dlg(tr("New Chart"), this); if (dlg.exec() == QDialog::Accepted) { auto items = dlg.seletedItems(); - if (!items.isEmpty()) { + if (!items.empty()) { auto c = createChart(); for (auto it : items) { c->addSignal(it->msg_id, it->sig); @@ -443,10 +444,10 @@ void ChartsWidget::newChart() { } void ChartsWidget::removeChart(ChartView *chart) { - charts.removeOne(chart); + charts.erase(std::remove(charts.begin(), charts.end(), chart), charts.end()); chart->deleteLater(); for (auto &[_, list] : tab_charts) { - list.removeOne(chart); + list.erase(std::remove(list.begin(), list.end(), chart), list.end()); } updateToolBar(); updateLayout(true); @@ -460,7 +461,7 @@ void ChartsWidget::removeAll() { } tab_charts.clear(); - if (!charts.isEmpty()) { + if (!charts.empty()) { for (auto c : charts) { delete c; } @@ -560,10 +561,11 @@ void ChartsContainer::dropEvent(QDropEvent *event) { auto chart = qobject_cast(event->source()); if (w != chart) { for (auto &[_, list] : charts_widget->tab_charts) { - list.removeOne(chart); + list.erase(std::remove(list.begin(), list.end(), chart), list.end()); } - int to = w ? charts_widget->currentCharts().indexOf(w) + 1 : 0; - charts_widget->currentCharts().insert(to, chart); + auto &cur = charts_widget->currentCharts(); + int to = w ? std::find(cur.begin(), cur.end(), w) - cur.begin() + 1 : 0; + cur.insert(cur.begin() + to, chart); charts_widget->updateLayout(true); charts_widget->updateTabBar(); event->acceptProposedAction(); diff --git a/tools/cabana/chart/chartswidget.h b/tools/cabana/chart/chartswidget.h index f87b1276c5..ef3fbc471a 100644 --- a/tools/cabana/chart/chartswidget.h +++ b/tools/cabana/chart/chartswidget.h @@ -81,7 +81,7 @@ private: bool eventFilter(QObject *obj, QEvent *event) override; void newTab(); void removeTab(int index); - inline QList ¤tCharts() { return tab_charts[tabbar->tabData(tabbar->currentIndex()).toInt()]; } + inline std::vector ¤tCharts() { return tab_charts[tabbar->tabData(tabbar->currentIndex()).toInt()]; } ChartView *findChart(const MessageId &id, const cabana::Signal *sig); QLabel *title_label; @@ -100,8 +100,8 @@ private: QUndoStack *zoom_undo_stack; ToolButton *remove_all_btn; - QList charts; - std::unordered_map> tab_charts; + std::vector charts; + std::unordered_map> tab_charts; TabBar *tabbar; ChartsContainer *charts_container; QScrollArea *charts_scroll; diff --git a/tools/cabana/chart/signalselector.cc b/tools/cabana/chart/signalselector.cc index 63f3a7d575..6f2fd8de46 100644 --- a/tools/cabana/chart/signalselector.cc +++ b/tools/cabana/chart/signalselector.cc @@ -46,7 +46,7 @@ SignalSelector::SignalSelector(QString title, QWidget *parent) : QDialog(parent) for (const auto &[id, _] : can->lastMessages()) { if (auto m = dbc()->msg(id)) { - msgs_combo->addItem(QString("%1 (%2)").arg(m->name).arg(id.toString()), QVariant::fromValue(id)); + msgs_combo->addItem(QString("%1 (%2)").arg(QString::fromStdString(m->name)).arg(QString::fromStdString(id.toString())), QVariant::fromValue(id)); } } msgs_combo->model()->sort(0); @@ -92,8 +92,8 @@ void SignalSelector::updateAvailableList(int index) { } void SignalSelector::addItemToList(QListWidget *parent, const MessageId id, const cabana::Signal *sig, bool show_msg_name) { - QString text = QString(" %1").arg(sig->color.name(), sig->name); - if (show_msg_name) text += QString(" %0 %1").arg(msgName(id), id.toString()); + QString text = QString(" %1").arg(sig->color.name(), QString::fromStdString(sig->name)); + if (show_msg_name) text += QString(" %0 %1").arg(QString::fromStdString(msgName(id)), QString::fromStdString(id.toString())); QLabel *label = new QLabel(text); label->setContentsMargins(5, 0, 5, 0); @@ -102,8 +102,8 @@ void SignalSelector::addItemToList(QListWidget *parent, const MessageId id, cons parent->setItemWidget(new_item, label); } -QList SignalSelector::seletedItems() { - QList ret; +std::vector SignalSelector::seletedItems() { + std::vector ret; for (int i = 0; i < selected_list->count(); ++i) ret.push_back((ListItem *)selected_list->item(i)); return ret; } diff --git a/tools/cabana/chart/signalselector.h b/tools/cabana/chart/signalselector.h index f46779f044..5b6e37e56a 100644 --- a/tools/cabana/chart/signalselector.h +++ b/tools/cabana/chart/signalselector.h @@ -15,7 +15,7 @@ public: }; SignalSelector(QString title, QWidget *parent); - QList seletedItems(); + std::vector seletedItems(); inline void addSelected(const MessageId &id, const cabana::Signal *sig) { addItemToList(selected_list, id, sig, true); } private: diff --git a/tools/cabana/commands.cc b/tools/cabana/commands.cc index 52861723f4..f158528b51 100644 --- a/tools/cabana/commands.cc +++ b/tools/cabana/commands.cc @@ -4,22 +4,22 @@ // EditMsgCommand -EditMsgCommand::EditMsgCommand(const MessageId &id, const QString &name, int size, - const QString &node, const QString &comment, QUndoCommand *parent) +EditMsgCommand::EditMsgCommand(const MessageId &id, const std::string &name, int size, + const std::string &node, const std::string &comment, QUndoCommand *parent) : id(id), new_name(name), new_size(size), new_node(node), new_comment(comment), QUndoCommand(parent) { if (auto msg = dbc()->msg(id)) { old_name = msg->name; old_size = msg->size; old_node = msg->transmitter; old_comment = msg->comment; - setText(QObject::tr("edit message %1:%2").arg(name).arg(id.address)); + setText(QObject::tr("edit message %1:%2").arg(QString::fromStdString(name)).arg(id.address)); } else { - setText(QObject::tr("new message %1:%2").arg(name).arg(id.address)); + setText(QObject::tr("new message %1:%2").arg(QString::fromStdString(name)).arg(id.address)); } } void EditMsgCommand::undo() { - if (old_name.isEmpty()) + if (old_name.empty()) dbc()->removeMsg(id); else dbc()->updateMsg(id, old_name, old_size, old_node, old_comment); @@ -34,12 +34,12 @@ void EditMsgCommand::redo() { RemoveMsgCommand::RemoveMsgCommand(const MessageId &id, QUndoCommand *parent) : id(id), QUndoCommand(parent) { if (auto msg = dbc()->msg(id)) { message = *msg; - setText(QObject::tr("remove message %1:%2").arg(message.name).arg(id.address)); + setText(QObject::tr("remove message %1:%2").arg(QString::fromStdString(message.name)).arg(id.address)); } } void RemoveMsgCommand::undo() { - if (!message.name.isEmpty()) { + if (!message.name.empty()) { dbc()->updateMsg(id, message.name, message.size, message.transmitter, message.comment); for (auto s : message.getSignals()) dbc()->addSignal(id, *s); @@ -47,7 +47,7 @@ void RemoveMsgCommand::undo() { } void RemoveMsgCommand::redo() { - if (!message.name.isEmpty()) + if (!message.name.empty()) dbc()->removeMsg(id); } @@ -55,7 +55,7 @@ void RemoveMsgCommand::redo() { AddSigCommand::AddSigCommand(const MessageId &id, const cabana::Signal &sig, QUndoCommand *parent) : id(id), signal(sig), QUndoCommand(parent) { - setText(QObject::tr("add signal %1 to %2:%3").arg(sig.name).arg(msgName(id)).arg(id.address)); + setText(QObject::tr("add signal %1 to %2:%3").arg(QString::fromStdString(sig.name)).arg(QString::fromStdString(msgName(id))).arg(id.address)); } void AddSigCommand::undo() { @@ -85,7 +85,7 @@ RemoveSigCommand::RemoveSigCommand(const MessageId &id, const cabana::Signal *si } } } - setText(QObject::tr("remove signal %1 from %2:%3").arg(sig->name).arg(msgName(id)).arg(id.address)); + setText(QObject::tr("remove signal %1 from %2:%3").arg(QString::fromStdString(sig->name)).arg(QString::fromStdString(msgName(id))).arg(id.address)); } void RemoveSigCommand::undo() { for (const auto &s : sigs) dbc()->addSignal(id, s); } @@ -108,7 +108,7 @@ EditSignalCommand::EditSignalCommand(const MessageId &id, const cabana::Signal * } } } - setText(QObject::tr("edit signal %1 in %2:%3").arg(sig->name).arg(msgName(id)).arg(id.address)); + setText(QObject::tr("edit signal %1 in %2:%3").arg(QString::fromStdString(sig->name)).arg(QString::fromStdString(msgName(id))).arg(id.address)); } void EditSignalCommand::undo() { for (const auto &s : sigs) dbc()->updateSignal(id, s.second.name, s.first); } diff --git a/tools/cabana/commands.h b/tools/cabana/commands.h index 0736d9b83f..4081f86985 100644 --- a/tools/cabana/commands.h +++ b/tools/cabana/commands.h @@ -1,6 +1,8 @@ #pragma once +#include #include +#include #include #include @@ -10,14 +12,14 @@ class EditMsgCommand : public QUndoCommand { public: - EditMsgCommand(const MessageId &id, const QString &name, int size, const QString &node, - const QString &comment, QUndoCommand *parent = nullptr); + EditMsgCommand(const MessageId &id, const std::string &name, int size, const std::string &node, + const std::string &comment, QUndoCommand *parent = nullptr); void undo() override; void redo() override; private: const MessageId id; - QString old_name, new_name, old_comment, new_comment, old_node, new_node; + std::string old_name, new_name, old_comment, new_comment, old_node, new_node; int old_size = 0, new_size = 0; }; @@ -52,7 +54,7 @@ public: private: const MessageId id; - QList sigs; + std::vector sigs; }; class EditSignalCommand : public QUndoCommand { @@ -63,7 +65,7 @@ public: private: const MessageId id; - QList> sigs; // QList<{old_sig, new_sig}> + std::vector> sigs; // {old_sig, new_sig} }; namespace UndoStack { diff --git a/tools/cabana/dbc/dbc.cc b/tools/cabana/dbc/dbc.cc index 9b0de92218..8e41cf54e3 100644 --- a/tools/cabana/dbc/dbc.cc +++ b/tools/cabana/dbc/dbc.cc @@ -4,10 +4,6 @@ #include "tools/cabana/utils/util.h" -uint qHash(const MessageId &item) { - return qHash(item.source) ^ qHash(item.address); -} - // cabana::Msg cabana::Msg::~Msg() { @@ -22,7 +18,7 @@ cabana::Signal *cabana::Msg::addSignal(const cabana::Signal &sig) { return s; } -cabana::Signal *cabana::Msg::updateSignal(const QString &sig_name, const cabana::Signal &new_sig) { +cabana::Signal *cabana::Msg::updateSignal(const std::string &sig_name, const cabana::Signal &new_sig) { auto s = sig(sig_name); if (s) { *s = new_sig; @@ -31,7 +27,7 @@ cabana::Signal *cabana::Msg::updateSignal(const QString &sig_name, const cabana: return s; } -void cabana::Msg::removeSignal(const QString &sig_name) { +void cabana::Msg::removeSignal(const std::string &sig_name) { auto it = std::find_if(sigs.begin(), sigs.end(), [&](auto &s) { return s->name == sig_name; }); if (it != sigs.end()) { delete *it; @@ -57,7 +53,7 @@ cabana::Msg &cabana::Msg::operator=(const cabana::Msg &other) { return *this; } -cabana::Signal *cabana::Msg::sig(const QString &sig_name) const { +cabana::Signal *cabana::Msg::sig(const std::string &sig_name) const { auto it = std::find_if(sigs.begin(), sigs.end(), [&](auto &s) { return s->name == sig_name; }); return it != sigs.end() ? *it : nullptr; } @@ -69,17 +65,17 @@ int cabana::Msg::indexOf(const cabana::Signal *sig) const { return -1; } -QString cabana::Msg::newSignalName() { - QString new_name; +std::string cabana::Msg::newSignalName() { + std::string new_name; for (int i = 1; /**/; ++i) { - new_name = QString("NEW_SIGNAL_%1").arg(i); + new_name = "NEW_SIGNAL_" + std::to_string(i); if (sig(new_name) == nullptr) break; } return new_name; } void cabana::Msg::update() { - if (transmitter.isEmpty()) { + if (transmitter.empty()) { transmitter = DEFAULT_NODE_NAME; } mask.assign(size, 0x00); @@ -129,13 +125,13 @@ void cabana::Msg::update() { void cabana::Signal::update() { updateMsbLsb(*this); - if (receiver_name.isEmpty()) { + if (receiver_name.empty()) { receiver_name = DEFAULT_NODE_NAME; } float h = 19 * (float)lsb / 64.0; h = fmod(h, 1.0); - size_t hash = qHash(name); + size_t hash = std::hash{}(name); float s = 0.25 + 0.25 * (float)(hash & 0xff) / 255.0; float v = 0.75 + 0.25 * (float)((hash >> 8) & 0xff) / 255.0; @@ -143,7 +139,7 @@ void cabana::Signal::update() { precision = std::max(num_decimals(factor), num_decimals(offset)); } -QString cabana::Signal::formatValue(double value, bool with_unit) const { +std::string cabana::Signal::formatValue(double value, bool with_unit) const { // Show enum string int64_t raw_value = round((value - offset) / factor); for (const auto &[val, desc] : val_desc) { @@ -152,8 +148,10 @@ QString cabana::Signal::formatValue(double value, bool with_unit) const { } } - QString val_str = QString::number(value, 'f', precision); - if (with_unit && !unit.isEmpty()) { + char buf[64]; + snprintf(buf, sizeof(buf), "%.*f", precision, value); + std::string val_str(buf); + if (with_unit && !unit.empty()) { val_str += " " + unit; } return val_str; diff --git a/tools/cabana/dbc/dbc.h b/tools/cabana/dbc/dbc.h index 134d88a919..a10e7871fe 100644 --- a/tools/cabana/dbc/dbc.h +++ b/tools/cabana/dbc/dbc.h @@ -1,29 +1,35 @@ #pragma once +#include +#include +#include #include +#include #include #include #include #include -#include -const QString UNTITLED = "untitled"; -const QString DEFAULT_NODE_NAME = "XXX"; +const std::string UNTITLED = "untitled"; +const std::string DEFAULT_NODE_NAME = "XXX"; constexpr int CAN_MAX_DATA_BYTES = 64; struct MessageId { uint8_t source = 0; uint32_t address = 0; - QString toString() const { - return QString("%1:%2").arg(source).arg(QString::number(address, 16).toUpper()); + std::string toString() const { + char buf[64]; + snprintf(buf, sizeof(buf), "%u:%X", source, address); + return buf; } - inline static MessageId fromString(const QString &str) { - auto parts = str.split(':'); - if (parts.size() != 2) return {}; - return MessageId{.source = uint8_t(parts[0].toUInt()), .address = parts[1].toUInt(nullptr, 16)}; + inline static MessageId fromString(const std::string &str) { + auto pos = str.find(':'); + if (pos == std::string::npos) return {}; + return MessageId{.source = uint8_t(std::stoul(str.substr(0, pos))), + .address = uint32_t(std::stoul(str.substr(pos + 1), nullptr, 16))}; } bool operator==(const MessageId &other) const { @@ -43,15 +49,17 @@ struct MessageId { } }; -uint qHash(const MessageId &item); Q_DECLARE_METATYPE(MessageId); template <> struct std::hash { - std::size_t operator()(const MessageId &k) const noexcept { return qHash(k); } + std::size_t operator()(const MessageId &k) const noexcept { + return std::hash{}(k.source) ^ (std::hash{}(k.address) << 1); + } }; -typedef std::vector> ValueDescription; +typedef std::vector> ValueDescription; +Q_DECLARE_METATYPE(ValueDescription); namespace cabana { @@ -61,7 +69,7 @@ public: Signal(const Signal &other) = default; void update(); bool getValue(const uint8_t *data, size_t data_size, double *val) const; - QString formatValue(double value, bool with_unit = true) const; + std::string formatValue(double value, bool with_unit = true) const; bool operator==(const cabana::Signal &other) const; inline bool operator!=(const cabana::Signal &other) const { return !(*this == other); } @@ -72,16 +80,16 @@ public: }; Type type = Type::Normal; - QString name; + std::string name; int start_bit, msb, lsb, size; double factor = 1.0; double offset = 0; bool is_signed; bool is_little_endian; double min, max; - QString unit; - QString comment; - QString receiver_name; + std::string unit; + std::string comment; + std::string receiver_name; ValueDescription val_desc; int precision = 0; QColor color; @@ -97,20 +105,20 @@ public: Msg(const Msg &other) { *this = other; } ~Msg(); cabana::Signal *addSignal(const cabana::Signal &sig); - cabana::Signal *updateSignal(const QString &sig_name, const cabana::Signal &sig); - void removeSignal(const QString &sig_name); + cabana::Signal *updateSignal(const std::string &sig_name, const cabana::Signal &sig); + void removeSignal(const std::string &sig_name); Msg &operator=(const Msg &other); int indexOf(const cabana::Signal *sig) const; - cabana::Signal *sig(const QString &sig_name) const; - QString newSignalName(); + cabana::Signal *sig(const std::string &sig_name) const; + std::string newSignalName(); void update(); inline const std::vector &getSignals() const { return sigs; } uint32_t address; - QString name; + std::string name; uint32_t size; - QString comment; - QString transmitter; + std::string comment; + std::string transmitter; std::vector sigs; std::vector mask; @@ -123,4 +131,8 @@ public: double get_raw_value(const uint8_t *data, size_t data_size, const cabana::Signal &sig); void updateMsbLsb(cabana::Signal &s); inline int flipBitPos(int start_bit) { return 8 * (start_bit / 8) + 7 - start_bit % 8; } -inline QString doubleToString(double value) { return QString::number(value, 'g', std::numeric_limits::digits10); } +inline std::string doubleToString(double value) { + char buf[64]; + snprintf(buf, sizeof(buf), "%.*g", std::numeric_limits::digits10, value); + return buf; +} diff --git a/tools/cabana/dbc/dbcfile.cc b/tools/cabana/dbc/dbcfile.cc index 1c03c8a0aa..d9c129ee81 100644 --- a/tools/cabana/dbc/dbcfile.cc +++ b/tools/cabana/dbc/dbcfile.cc @@ -3,11 +3,12 @@ #include #include #include +#include -DBCFile::DBCFile(const QString &dbc_file_name) { - QFile file(dbc_file_name); +DBCFile::DBCFile(const std::string &dbc_file_name) { + QFile file(QString::fromStdString(dbc_file_name)); if (file.open(QIODevice::ReadOnly)) { - name_ = QFileInfo(dbc_file_name).baseName(); + name_ = QFileInfo(QString::fromStdString(dbc_file_name)).baseName().toStdString(); filename = dbc_file_name; parse(file.readAll()); } else { @@ -15,34 +16,35 @@ DBCFile::DBCFile(const QString &dbc_file_name) { } } -DBCFile::DBCFile(const QString &name, const QString &content) : name_(name), filename("") { - parse(content); +DBCFile::DBCFile(const std::string &name, const std::string &content) : name_(name), filename("") { + parse(QString::fromStdString(content)); } bool DBCFile::save() { - assert(!filename.isEmpty()); + assert(!filename.empty()); return writeContents(filename); } -bool DBCFile::saveAs(const QString &new_filename) { +bool DBCFile::saveAs(const std::string &new_filename) { filename = new_filename; return save(); } -bool DBCFile::writeContents(const QString &fn) { - QFile file(fn); +bool DBCFile::writeContents(const std::string &fn) { + QFile file(QString::fromStdString(fn)); if (file.open(QIODevice::WriteOnly)) { - return file.write(generateDBC().toUtf8()) >= 0; + std::string content = generateDBC(); + return file.write(content.c_str(), content.size()) >= 0; } return false; } -void DBCFile::updateMsg(const MessageId &id, const QString &name, uint32_t size, const QString &node, const QString &comment) { +void DBCFile::updateMsg(const MessageId &id, const std::string &name, uint32_t size, const std::string &node, const std::string &comment) { auto &m = msgs[id.address]; m.address = id.address; m.name = name; m.size = size; - m.transmitter = node.isEmpty() ? DEFAULT_NODE_NAME : node; + m.transmitter = node.empty() ? DEFAULT_NODE_NAME : node; m.comment = comment; } @@ -51,12 +53,12 @@ cabana::Msg *DBCFile::msg(uint32_t address) { return it != msgs.end() ? &it->second : nullptr; } -cabana::Msg *DBCFile::msg(const QString &name) { +cabana::Msg *DBCFile::msg(const std::string &name) { auto it = std::find_if(msgs.begin(), msgs.end(), [&name](auto &m) { return m.second.name == name; }); return it != msgs.end() ? &(it->second) : nullptr; } -cabana::Signal *DBCFile::signal(uint32_t address, const QString &name) { +cabana::Signal *DBCFile::signal(uint32_t address, const std::string &name) { auto m = msg(address); return m ? (cabana::Signal *)m->sig(name) : nullptr; } @@ -93,13 +95,13 @@ void DBCFile::parse(const QString &content) { seen = false; } } catch (std::exception &e) { - throw std::runtime_error(QString("[%1:%2]%3: %4").arg(filename).arg(line_num).arg(e.what()).arg(line).toStdString()); + throw std::runtime_error(QString("[%1:%2]%3: %4").arg(QString::fromStdString(filename)).arg(line_num).arg(e.what()).arg(line).toStdString()); } if (seen) { seen_first = true; } else if (!seen_first) { - header += raw_line + "\n"; + header += raw_line.toStdString() + "\n"; } } @@ -122,9 +124,9 @@ cabana::Msg *DBCFile::parseBO(const QString &line) { // Create a new message object cabana::Msg *msg = &msgs[address]; msg->address = address; - msg->name = match.captured("name"); + msg->name = match.captured("name").toStdString(); msg->size = match.captured("size").toULong(); - msg->transmitter = match.captured("transmitter").trimmed(); + msg->transmitter = match.captured("transmitter").trimmed().toStdString(); return msg; } @@ -141,7 +143,7 @@ void DBCFile::parseCM_BO(const QString &line, const QString &content, const QStr throw std::runtime_error("Invalid message comment format"); if (auto m = (cabana::Msg *)msg(match.captured("address").toUInt())) - m->comment = match.captured("comment").trimmed().replace("\\\"", "\""); + m->comment = match.captured("comment").trimmed().replace("\\\"", "\"").toStdString(); } void DBCFile::parseSG(const QString &line, cabana::Msg *current_msg, int &multiplexor_cnt) { @@ -160,7 +162,7 @@ void DBCFile::parseSG(const QString &line, cabana::Msg *current_msg, int &multip if (!match.hasMatch()) throw std::runtime_error("Invalid SG_ line format"); - QString name = match.captured(1); + std::string name = match.captured(1).toStdString(); if (current_msg->sig(name) != nullptr) throw std::runtime_error("Duplicate signal name"); @@ -188,8 +190,8 @@ void DBCFile::parseSG(const QString &line, cabana::Msg *current_msg, int &multip s.offset = match.captured(offset + 7).toDouble(); s.min = match.captured(8 + offset).toDouble(); s.max = match.captured(9 + offset).toDouble(); - s.unit = match.captured(10 + offset); - s.receiver_name = match.captured(11 + offset).trimmed(); + s.unit = match.captured(10 + offset).toStdString(); + s.receiver_name = match.captured(11 + offset).trimmed().toStdString(); current_msg->sigs.push_back(new cabana::Signal(s)); } @@ -205,8 +207,8 @@ void DBCFile::parseCM_SG(const QString &line, const QString &content, const QStr if (!match.hasMatch()) throw std::runtime_error("Invalid CM_ SG_ line format"); - if (auto s = signal(match.captured(1).toUInt(), match.captured(2))) { - s->comment = match.captured(3).trimmed().replace("\\\"", "\""); + if (auto s = signal(match.captured(1).toUInt(), match.captured(2).toStdString())) { + s->comment = match.captured(3).trimmed().replace("\\\"", "\"").toStdString(); } } @@ -217,55 +219,60 @@ void DBCFile::parseVAL(const QString &line) { if (!match.hasMatch()) throw std::runtime_error("invalid VAL_ line format"); - if (auto s = signal(match.captured(1).toUInt(), match.captured(2))) { + if (auto s = signal(match.captured(1).toUInt(), match.captured(2).toStdString())) { QStringList desc_list = match.captured(3).trimmed().split('"'); for (int i = 0; i < desc_list.size(); i += 2) { auto val = desc_list[i].trimmed(); if (!val.isEmpty() && (i + 1) < desc_list.size()) { auto desc = desc_list[i + 1].trimmed(); - s->val_desc.push_back({val.toDouble(), desc}); + s->val_desc.push_back({val.toDouble(), desc.toStdString()}); } } } } -QString DBCFile::generateDBC() { - QString dbc_string, comment, val_desc; +std::string DBCFile::generateDBC() { + std::string dbc_string, comment, val_desc; for (const auto &[address, m] : msgs) { - const QString transmitter = m.transmitter.isEmpty() ? DEFAULT_NODE_NAME : m.transmitter; - dbc_string += QString("BO_ %1 %2: %3 %4\n").arg(address).arg(m.name).arg(m.size).arg(transmitter); - if (!m.comment.isEmpty()) { - comment += QString("CM_ BO_ %1 \"%2\";\n").arg(address).arg(QString(m.comment).replace("\"", "\\\"")); + const std::string &transmitter = m.transmitter.empty() ? DEFAULT_NODE_NAME : m.transmitter; + dbc_string += "BO_ " + std::to_string(address) + " " + m.name + ": " + std::to_string(m.size) + " " + transmitter + "\n"; + if (!m.comment.empty()) { + std::string escaped_comment = m.comment; + // Replace " with \" + for (size_t pos = 0; (pos = escaped_comment.find('"', pos)) != std::string::npos; pos += 2) + escaped_comment.replace(pos, 1, "\\\""); + comment += "CM_ BO_ " + std::to_string(address) + " \"" + escaped_comment + "\";\n"; } for (auto sig : m.getSignals()) { - QString multiplexer_indicator; + std::string multiplexer_indicator; if (sig->type == cabana::Signal::Type::Multiplexor) { multiplexer_indicator = "M "; } else if (sig->type == cabana::Signal::Type::Multiplexed) { - multiplexer_indicator = QString("m%1 ").arg(sig->multiplex_value); + multiplexer_indicator = "m" + std::to_string(sig->multiplex_value) + " "; } - dbc_string += QString(" SG_ %1 %2: %3|%4@%5%6 (%7,%8) [%9|%10] \"%11\" %12\n") - .arg(sig->name) - .arg(multiplexer_indicator) - .arg(sig->start_bit) - .arg(sig->size) - .arg(sig->is_little_endian ? '1' : '0') - .arg(sig->is_signed ? '-' : '+') - .arg(doubleToString(sig->factor)) - .arg(doubleToString(sig->offset)) - .arg(doubleToString(sig->min)) - .arg(doubleToString(sig->max)) - .arg(sig->unit) - .arg(sig->receiver_name.isEmpty() ? DEFAULT_NODE_NAME : sig->receiver_name); - if (!sig->comment.isEmpty()) { - comment += QString("CM_ SG_ %1 %2 \"%3\";\n").arg(address).arg(sig->name).arg(QString(sig->comment).replace("\"", "\\\"")); + const std::string &recv = sig->receiver_name.empty() ? DEFAULT_NODE_NAME : sig->receiver_name; + dbc_string += " SG_ " + sig->name + " " + multiplexer_indicator + ": " + + std::to_string(sig->start_bit) + "|" + std::to_string(sig->size) + "@" + + std::string(1, sig->is_little_endian ? '1' : '0') + + std::string(1, sig->is_signed ? '-' : '+') + + " (" + doubleToString(sig->factor) + "," + doubleToString(sig->offset) + ")" + + " [" + doubleToString(sig->min) + "|" + doubleToString(sig->max) + "]" + + " \"" + sig->unit + "\" " + recv + "\n"; + if (!sig->comment.empty()) { + std::string escaped_comment = sig->comment; + for (size_t pos = 0; (pos = escaped_comment.find('"', pos)) != std::string::npos; pos += 2) + escaped_comment.replace(pos, 1, "\\\""); + comment += "CM_ SG_ " + std::to_string(address) + " " + sig->name + " \"" + escaped_comment + "\";\n"; } if (!sig->val_desc.empty()) { - QStringList text; + std::string text; for (auto &[val, desc] : sig->val_desc) { - text << QString("%1 \"%2\"").arg(val).arg(desc); + if (!text.empty()) text += " "; + char val_buf[64]; + snprintf(val_buf, sizeof(val_buf), "%g", val); + text += std::string(val_buf) + " \"" + desc + "\""; } - val_desc += QString("VAL_ %1 %2 %3;\n").arg(address).arg(sig->name).arg(text.join(" ")); + val_desc += "VAL_ " + std::to_string(address) + " " + sig->name + " " + text + ";\n"; } } dbc_string += "\n"; diff --git a/tools/cabana/dbc/dbcfile.h b/tools/cabana/dbc/dbcfile.h index bd267898f9..decb566abd 100644 --- a/tools/cabana/dbc/dbcfile.h +++ b/tools/cabana/dbc/dbcfile.h @@ -1,34 +1,35 @@ #pragma once #include +#include #include #include "tools/cabana/dbc/dbc.h" class DBCFile { public: - DBCFile(const QString &dbc_file_name); - DBCFile(const QString &name, const QString &content); + DBCFile(const std::string &dbc_file_name); + DBCFile(const std::string &name, const std::string &content); ~DBCFile() {} bool save(); - bool saveAs(const QString &new_filename); - bool writeContents(const QString &fn); - QString generateDBC(); + bool saveAs(const std::string &new_filename); + bool writeContents(const std::string &fn); + std::string generateDBC(); - void updateMsg(const MessageId &id, const QString &name, uint32_t size, const QString &node, const QString &comment); + void updateMsg(const MessageId &id, const std::string &name, uint32_t size, const std::string &node, const std::string &comment); inline void removeMsg(const MessageId &id) { msgs.erase(id.address); } inline const std::map &getMessages() const { return msgs; } cabana::Msg *msg(uint32_t address); - cabana::Msg *msg(const QString &name); + cabana::Msg *msg(const std::string &name); inline cabana::Msg *msg(const MessageId &id) { return msg(id.address); } - cabana::Signal *signal(uint32_t address, const QString &name); + cabana::Signal *signal(uint32_t address, const std::string &name); - inline QString name() const { return name_.isEmpty() ? "untitled" : name_; } - inline bool isEmpty() const { return msgs.empty() && name_.isEmpty(); } + inline std::string name() const { return name_.empty() ? "untitled" : name_; } + inline bool isEmpty() const { return msgs.empty() && name_.empty(); } - QString filename; + std::string filename; private: void parse(const QString &content); @@ -38,7 +39,7 @@ private: void parseCM_SG(const QString &line, const QString &content, const QString &raw_line, const QTextStream &stream); void parseVAL(const QString &line); - QString header; + std::string header; std::map msgs; - QString name_; + std::string name_; }; diff --git a/tools/cabana/dbc/dbcmanager.cc b/tools/cabana/dbc/dbcmanager.cc index 8e98d95322..2236a93da1 100644 --- a/tools/cabana/dbc/dbcmanager.cc +++ b/tools/cabana/dbc/dbcmanager.cc @@ -1,10 +1,9 @@ #include "tools/cabana/dbc/dbcmanager.h" -#include #include -#include +#include -bool DBCManager::open(const SourceSet &sources, const QString &dbc_file_name, QString *error) { +bool DBCManager::open(const SourceSet &sources, const std::string &dbc_file_name, QString *error) { try { auto it = std::find_if(dbc_files.begin(), dbc_files.end(), [&](auto &f) { return f.second && f.second->filename == dbc_file_name; }); @@ -21,7 +20,7 @@ bool DBCManager::open(const SourceSet &sources, const QString &dbc_file_name, QS return true; } -bool DBCManager::open(const SourceSet &sources, const QString &name, const QString &content, QString *error) { +bool DBCManager::open(const SourceSet &sources, const std::string &name, const std::string &content, QString *error) { try { auto file = std::make_shared(name, content); for (auto s : sources) { @@ -64,7 +63,7 @@ void DBCManager::addSignal(const MessageId &id, const cabana::Signal &sig) { } } -void DBCManager::updateSignal(const MessageId &id, const QString &sig_name, const cabana::Signal &sig) { +void DBCManager::updateSignal(const MessageId &id, const std::string &sig_name, const cabana::Signal &sig) { if (auto m = msg(id)) { if (auto s = m->updateSignal(sig_name, sig)) { emit signalUpdated(s); @@ -73,7 +72,7 @@ void DBCManager::updateSignal(const MessageId &id, const QString &sig_name, cons } } -void DBCManager::removeSignal(const MessageId &id, const QString &sig_name) { +void DBCManager::removeSignal(const MessageId &id, const std::string &sig_name) { if (auto m = msg(id)) { if (auto s = m->sig(sig_name)) { emit signalRemoved(s); @@ -83,7 +82,7 @@ void DBCManager::removeSignal(const MessageId &id, const QString &sig_name) { } } -void DBCManager::updateMsg(const MessageId &id, const QString &name, uint32_t size, const QString &node, const QString &comment) { +void DBCManager::updateMsg(const MessageId &id, const std::string &name, uint32_t size, const std::string &node, const std::string &comment) { auto dbc_file = findDBCFile(id); assert(dbc_file); // This should be impossible dbc_file->updateMsg(id, name, size, node, comment); @@ -98,11 +97,13 @@ void DBCManager::removeMsg(const MessageId &id) { emit maskUpdated(); } -QString DBCManager::newMsgName(const MessageId &id) { - return QString("NEW_MSG_") + QString::number(id.address, 16).toUpper(); +std::string DBCManager::newMsgName(const MessageId &id) { + char buf[64]; + snprintf(buf, sizeof(buf), "NEW_MSG_%X", id.address); + return buf; } -QString DBCManager::newSignalName(const MessageId &id) { +std::string DBCManager::newSignalName(const MessageId &id) { auto m = msg(id); return m ? m->newSignalName() : ""; } @@ -118,14 +119,14 @@ cabana::Msg *DBCManager::msg(const MessageId &id) { return dbc_file ? dbc_file->msg(id) : nullptr; } -cabana::Msg *DBCManager::msg(uint8_t source, const QString &name) { +cabana::Msg *DBCManager::msg(uint8_t source, const std::string &name) { auto dbc_file = findDBCFile(source); return dbc_file ? dbc_file->msg(name) : nullptr; } -QStringList DBCManager::signalNames() { +std::vector DBCManager::signalNames() { // Used for autocompletion - QSet names; + std::set names; for (auto &f : allDBCFiles()) { for (auto &[_, m] : f->getMessages()) { for (auto sig : m.getSignals()) { @@ -133,8 +134,8 @@ QStringList DBCManager::signalNames() { } } } - QStringList ret = names.values(); - ret.sort(); + std::vector ret(names.begin(), names.end()); + std::sort(ret.begin(), ret.end()); return ret; } @@ -165,11 +166,13 @@ const SourceSet DBCManager::sources(const DBCFile *dbc_file) const { return sources; } -QString toString(const SourceSet &ss) { - return std::accumulate(ss.cbegin(), ss.cend(), QString(), [](QString str, int source) { - if (!str.isEmpty()) str += ", "; - return str + (source == -1 ? QStringLiteral("all") : QString::number(source)); - }); +std::string toString(const SourceSet &ss) { + std::string result; + for (int source : ss) { + if (!result.empty()) result += ", "; + result += (source == -1) ? "all" : std::to_string(source); + } + return result; } DBCManager *dbc() { diff --git a/tools/cabana/dbc/dbcmanager.h b/tools/cabana/dbc/dbcmanager.h index 5f183752d2..4a122073ea 100644 --- a/tools/cabana/dbc/dbcmanager.h +++ b/tools/cabana/dbc/dbcmanager.h @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include "tools/cabana/dbc/dbcfile.h" @@ -18,27 +20,27 @@ class DBCManager : public QObject { public: DBCManager(QObject *parent) : QObject(parent) {} ~DBCManager() {} - bool open(const SourceSet &sources, const QString &dbc_file_name, QString *error = nullptr); - bool open(const SourceSet &sources, const QString &name, const QString &content, QString *error = nullptr); + bool open(const SourceSet &sources, const std::string &dbc_file_name, QString *error = nullptr); + bool open(const SourceSet &sources, const std::string &name, const std::string &content, QString *error = nullptr); void close(const SourceSet &sources); void close(DBCFile *dbc_file); void closeAll(); void addSignal(const MessageId &id, const cabana::Signal &sig); - void updateSignal(const MessageId &id, const QString &sig_name, const cabana::Signal &sig); - void removeSignal(const MessageId &id, const QString &sig_name); + void updateSignal(const MessageId &id, const std::string &sig_name, const cabana::Signal &sig); + void removeSignal(const MessageId &id, const std::string &sig_name); - void updateMsg(const MessageId &id, const QString &name, uint32_t size, const QString &node, const QString &comment); + void updateMsg(const MessageId &id, const std::string &name, uint32_t size, const std::string &node, const std::string &comment); void removeMsg(const MessageId &id); - QString newMsgName(const MessageId &id); - QString newSignalName(const MessageId &id); + std::string newMsgName(const MessageId &id); + std::string newSignalName(const MessageId &id); const std::map &getMessages(uint8_t source); cabana::Msg *msg(const MessageId &id); - cabana::Msg* msg(uint8_t source, const QString &name); + cabana::Msg* msg(uint8_t source, const std::string &name); - QStringList signalNames(); + std::vector signalNames(); inline int dbcCount() { return allDBCFiles().size(); } int nonEmptyDBCCount(); @@ -62,8 +64,8 @@ private: DBCManager *dbc(); -QString toString(const SourceSet &ss); -inline QString msgName(const MessageId &id) { +std::string toString(const SourceSet &ss); +inline std::string msgName(const MessageId &id) { auto msg = dbc()->msg(id); return msg ? msg->name : UNTITLED; } diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index 35492c8efa..148b059e5b 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -124,9 +124,9 @@ int DetailWidget::findOrAddTab(const MessageId& message_id) { if (tabbar->tabData(index).value() == message_id) break; } if (index == -1) { - index = tabbar->addTab(message_id.toString()); + index = tabbar->addTab(QString::fromStdString(message_id.toString())); tabbar->setTabData(index, QVariant::fromValue(message_id)); - tabbar->setTabToolTip(index, msgName(message_id)); + tabbar->setTabToolTip(index, QString::fromStdString(msgName(message_id))); } return index; } @@ -151,21 +151,21 @@ std::pair DetailWidget::serializeMessageIds() const { QStringList msgs; for (int i = 0; i < tabbar->count(); ++i) { MessageId id = tabbar->tabData(i).value(); - msgs.append(id.toString()); + msgs.append(QString::fromStdString(id.toString())); } - return std::make_pair(msg_id.toString(), msgs); + return std::make_pair(QString::fromStdString(msg_id.toString()), msgs); } void DetailWidget::restoreTabs(const QString active_msg_id, const QStringList& msg_ids) { tabbar->blockSignals(true); for (const auto& str_id : msg_ids) { - MessageId id = MessageId::fromString(str_id); + MessageId id = MessageId::fromString(str_id.toStdString()); if (dbc()->msg(id) != nullptr) findOrAddTab(id); } tabbar->blockSignals(false); - auto active_id = MessageId::fromString(active_msg_id); + auto active_id = MessageId::fromString(active_msg_id.toStdString()); if (dbc()->msg(active_id) != nullptr) setMessage(active_id); } @@ -180,10 +180,10 @@ void DetailWidget::refresh() { warnings.push_back(tr("Message size (%1) is incorrect.").arg(msg->size)); } for (auto s : binary_view->getOverlappingSignals()) { - warnings.push_back(tr("%1 has overlapping bits.").arg(s->name)); + warnings.push_back(tr("%1 has overlapping bits.").arg(QString::fromStdString(s->name))); } } - QString msg_name = msg ? QString("%1 (%2)").arg(msg->name, msg->transmitter) : msgName(msg_id); + QString msg_name = msg ? QString("%1 (%2)").arg(QString::fromStdString(msg->name), QString::fromStdString(msg->transmitter)) : QString::fromStdString(msgName(msg_id)); name_label->setText(msg_name); name_label->setToolTip(msg_name); action_remove_msg->setEnabled(msg != nullptr); @@ -208,10 +208,10 @@ void DetailWidget::updateState(const std::set *msgs) { void DetailWidget::editMsg() { auto msg = dbc()->msg(msg_id); int size = msg ? msg->size : can->lastMessage(msg_id).dat.size(); - EditMessageDialog dlg(msg_id, msgName(msg_id), size, this); + EditMessageDialog dlg(msg_id, QString::fromStdString(msgName(msg_id)), size, this); if (dlg.exec()) { - UndoStack::push(new EditMsgCommand(msg_id, dlg.name_edit->text().trimmed(), dlg.size_spin->value(), - dlg.node->text().trimmed(), dlg.comment_edit->toPlainText().trimmed())); + UndoStack::push(new EditMsgCommand(msg_id, dlg.name_edit->text().trimmed().toStdString(), dlg.size_spin->value(), + dlg.node->text().trimmed().toStdString(), dlg.comment_edit->toPlainText().trimmed().toStdString())); } } @@ -223,7 +223,7 @@ void DetailWidget::removeMsg() { EditMessageDialog::EditMessageDialog(const MessageId &msg_id, const QString &title, int size, QWidget *parent) : original_name(title), msg_id(msg_id), QDialog(parent) { - setWindowTitle(tr("Edit message: %1").arg(msg_id.toString())); + setWindowTitle(tr("Edit message: %1").arg(QString::fromStdString(msg_id.toString()))); QFormLayout *form_layout = new QFormLayout(this); form_layout->addRow("", error_label = new QLabel); @@ -241,8 +241,8 @@ EditMessageDialog::EditMessageDialog(const MessageId &msg_id, const QString &tit form_layout->addRow(btn_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel)); if (auto msg = dbc()->msg(msg_id)) { - node->setText(msg->transmitter); - comment_edit->setText(msg->comment); + node->setText(QString::fromStdString(msg->transmitter)); + comment_edit->setText(QString::fromStdString(msg->comment)); } validateName(name_edit->text()); setFixedWidth(parent->width() * 0.9); @@ -252,10 +252,10 @@ EditMessageDialog::EditMessageDialog(const MessageId &msg_id, const QString &tit } void EditMessageDialog::validateName(const QString &text) { - bool valid = text.compare(UNTITLED, Qt::CaseInsensitive) != 0; + bool valid = text.compare(QString::fromStdString(UNTITLED), Qt::CaseInsensitive) != 0; error_label->setVisible(false); if (!text.isEmpty() && valid && text != original_name) { - valid = dbc()->msg(msg_id.source, text) == nullptr; + valid = dbc()->msg(msg_id.source, text.toStdString()) == nullptr; if (!valid) { error_label->setText(tr("Name already exists")); error_label->setVisible(true); diff --git a/tools/cabana/historylog.cc b/tools/cabana/historylog.cc index 3dbdf5a7cd..fb79ff9cea 100644 --- a/tools/cabana/historylog.cc +++ b/tools/cabana/historylog.cc @@ -14,7 +14,7 @@ QVariant HistoryLogModel::data(const QModelIndex &index, int role) const { const int col = index.column(); if (role == Qt::DisplayRole) { if (col == 0) return QString::number(can->toSeconds(m.mono_time), 'f', 3); - if (!isHexMode()) return sigs[col - 1]->formatValue(m.sig_values[col - 1], false); + if (!isHexMode()) return QString::fromStdString(sigs[col - 1]->formatValue(m.sig_values[col - 1], false)); } else if (role == Qt::TextAlignmentRole) { return (uint32_t)(Qt::AlignRight | Qt::AlignVCenter); } @@ -49,8 +49,8 @@ QVariant HistoryLogModel::headerData(int section, Qt::Orientation orientation, i if (section == 0) return "Time"; if (isHexMode()) return "Data"; - QString name = sigs[section - 1]->name; - QString unit = sigs[section - 1]->unit; + QString name = QString::fromStdString(sigs[section - 1]->name); + QString unit = QString::fromStdString(sigs[section - 1]->unit); return unit.isEmpty() ? name : QString("%1 (%2)").arg(name, unit); } else if (role == Qt::BackgroundRole && section > 0 && !isHexMode()) { // Alpha-blend the signal color with the background to ensure contrast @@ -216,7 +216,7 @@ LogsWidget::LogsWidget(QWidget *parent) : QFrame(parent) { void LogsWidget::modelReset() { signals_cb->clear(); for (auto s : model->sigs) { - signals_cb->addItem(s->name); + signals_cb->addItem(QString::fromStdString(s->name)); } export_btn->setEnabled(false); value_edit->clear(); @@ -238,8 +238,8 @@ void LogsWidget::filterChanged() { } void LogsWidget::exportToCSV() { - QString dir = QString("%1/%2_%3.csv").arg(settings.last_dir).arg(can->routeName()).arg(msgName(model->msg_id)); - QString fn = QFileDialog::getSaveFileName(this, QString("Export %1 to CSV file").arg(msgName(model->msg_id)), + QString dir = QString("%1/%2_%3.csv").arg(settings.last_dir).arg(QString::fromStdString(can->routeName())).arg(QString::fromStdString(msgName(model->msg_id))); + QString fn = QFileDialog::getSaveFileName(this, QString("Export %1 to CSV file").arg(QString::fromStdString(msgName(model->msg_id))), dir, tr("csv (*.csv)")); if (!fn.isEmpty()) { model->isHexMode() ? utils::exportToCSV(fn, model->msg_id) diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index a7040f891e..39fb979c79 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -234,7 +234,7 @@ void MainWindow::DBCFileChanged() { QStringList title; for (auto f : dbc()->allDBCFiles()) { - title.push_back(tr("(%1) %2").arg(toString(dbc()->sources(f)), f->name())); + title.push_back(tr("(%1) %2").arg(QString::fromStdString(toString(dbc()->sources(f))), QString::fromStdString(f->name()))); } setWindowFilePath(title.join(" | ")); @@ -259,7 +259,7 @@ void MainWindow::closeStream() { } void MainWindow::exportToCSV() { - QString dir = QString("%1/%2.csv").arg(settings.last_dir).arg(can->routeName()); + QString dir = QString("%1/%2.csv").arg(settings.last_dir).arg(QString::fromStdString(can->routeName())); QString fn = QFileDialog::getSaveFileName(this, "Export stream to CSV file", dir, tr("csv (*.csv)")); if (!fn.isEmpty()) { utils::exportToCSV(fn); @@ -268,7 +268,7 @@ void MainWindow::exportToCSV() { void MainWindow::newFile(SourceSet s) { closeFile(s); - dbc()->open(s, "", ""); + dbc()->open(s, std::string(""), std::string("")); } void MainWindow::openFile(SourceSet s) { @@ -284,7 +284,7 @@ void MainWindow::loadFile(const QString &fn, SourceSet s) { closeFile(s); QString error; - if (dbc()->open(s, fn, &error)) { + if (dbc()->open(s, fn.toStdString(), &error)) { updateRecentFiles(fn); statusBar()->showMessage(tr("DBC File %1 loaded").arg(fn), 2000); } else { @@ -304,7 +304,7 @@ void MainWindow::loadFromClipboard(SourceSet s, bool close_all) { QString dbc_str = QGuiApplication::clipboard()->text(); QString error; - bool ret = dbc()->open(s, "", dbc_str, &error); + bool ret = dbc()->open(s, std::string(""), dbc_str.toStdString(), &error); if (ret && dbc()->nonEmptyDBCCount() > 0) { QMessageBox::information(this, tr("Load From Clipboard"), tr("DBC Successfully Loaded!")); } else { @@ -333,7 +333,7 @@ void MainWindow::startStream(AbstractStream *stream, QString dbc_file) { can->start(); loadFile(dbc_file); - statusBar()->showMessage(tr("Stream [%1] started").arg(can->routeName()), 2000); + statusBar()->showMessage(tr("Stream [%1] started").arg(QString::fromStdString(can->routeName())), 2000); bool has_stream = dynamic_cast(can) == nullptr; close_stream_act->setEnabled(has_stream); @@ -341,7 +341,7 @@ void MainWindow::startStream(AbstractStream *stream, QString dbc_file) { tools_menu->setEnabled(has_stream); createDockWidgets(); - video_dock->setWindowTitle(can->routeName()); + video_dock->setWindowTitle(QString::fromStdString(can->routeName())); if (can->liveStreaming() || video_splitter->sizes()[0] == 0) { // display video at minimum size. video_splitter->setSizes({1, 1}); @@ -368,9 +368,9 @@ void MainWindow::startStream(AbstractStream *stream, QString dbc_file) { } void MainWindow::eventsMerged() { - if (!can->liveStreaming() && std::exchange(car_fingerprint, can->carFingerprint()) != car_fingerprint) { + if (!can->liveStreaming() && std::exchange(car_fingerprint, QString::fromStdString(can->carFingerprint())) != car_fingerprint) { video_dock->setWindowTitle(tr("ROUTE: %1 FINGERPRINT: %2") - .arg(can->routeName()) + .arg(QString::fromStdString(can->routeName())) .arg(car_fingerprint.isEmpty() ? tr("Unknown Car") : car_fingerprint)); // Don't overwrite already loaded DBC if (!dbc()->nonEmptyDBCCount() && fingerprint_to_dbc.object().contains(car_fingerprint)) { @@ -416,7 +416,7 @@ void MainWindow::closeFile(DBCFile *dbc_file) { void MainWindow::saveFile(DBCFile *dbc_file) { assert(dbc_file != nullptr); - if (!dbc_file->filename.isEmpty()) { + if (!dbc_file->filename.empty()) { dbc_file->save(); UndoStack::instance()->setClean(); statusBar()->showMessage(tr("File saved"), 2000); @@ -426,10 +426,10 @@ void MainWindow::saveFile(DBCFile *dbc_file) { } void MainWindow::saveFileAs(DBCFile *dbc_file) { - QString title = tr("Save File (bus: %1)").arg(toString(dbc()->sources(dbc_file))); + QString title = tr("Save File (bus: %1)").arg(QString::fromStdString(toString(dbc()->sources(dbc_file)))); QString fn = QFileDialog::getSaveFileName(this, title, QDir::cleanPath(settings.last_dir + "/untitled.dbc"), tr("DBC (*.dbc)")); if (!fn.isEmpty()) { - dbc_file->saveAs(fn); + dbc_file->saveAs(fn.toStdString()); UndoStack::instance()->setClean(); statusBar()->showMessage(tr("File saved as %1").arg(fn), 2000); updateRecentFiles(fn); @@ -446,7 +446,7 @@ void MainWindow::saveToClipboard() { void MainWindow::saveFileToClipboard(DBCFile *dbc_file) { assert(dbc_file != nullptr); - QGuiApplication::clipboard()->setText(dbc_file->generateDBC()); + QGuiApplication::clipboard()->setText(QString::fromStdString(dbc_file->generateDBC())); QMessageBox::information(this, tr("Copy To Clipboard"), tr("DBC Successfully copied!")); } @@ -467,14 +467,14 @@ void MainWindow::updateLoadSaveMenus() { auto dbc_file = dbc()->findDBCFile(source); if (dbc_file) { bus_menu->addSeparator(); - bus_menu->addAction(dbc_file->name() + " (" + toString(dbc()->sources(dbc_file)) + ")")->setEnabled(false); + bus_menu->addAction(QString::fromStdString(dbc_file->name()) + " (" + QString::fromStdString(toString(dbc()->sources(dbc_file))) + ")")->setEnabled(false); bus_menu->addAction(tr("Save..."), [=]() { saveFile(dbc_file); }); bus_menu->addAction(tr("Save As..."), [=]() { saveFileAs(dbc_file); }); bus_menu->addAction(tr("Copy to Clipboard..."), [=]() { saveFileToClipboard(dbc_file); }); bus_menu->addAction(tr("Remove from this bus..."), [=]() { closeFile(ss); }); bus_menu->addAction(tr("Remove from all buses..."), [=]() { closeFile(dbc_file); }); } - bus_menu->setTitle(tr("Bus %1 (%2)").arg(source).arg(dbc_file ? dbc_file->name() : "No DBCs loaded")); + bus_menu->setTitle(tr("Bus %1 (%2)").arg(source).arg(dbc_file ? QString::fromStdString(dbc_file->name()) : "No DBCs loaded")); manage_dbcs_menu->addMenu(bus_menu); } @@ -627,7 +627,7 @@ void MainWindow::saveSessionState() { settings.active_charts.clear(); for (auto &f : dbc()->allDBCFiles()) - if (!f->isEmpty()) { settings.recent_dbc_file = f->filename; break; } + if (!f->isEmpty()) { settings.recent_dbc_file = QString::fromStdString(f->filename); break; } if (auto *detail = center_widget->getDetailWidget()) { auto [active_id, ids] = detail->serializeMessageIds(); @@ -643,7 +643,7 @@ void MainWindow::restoreSessionState() { QString dbc_file; for (auto& f : dbc()->allDBCFiles()) - if (!f->isEmpty()) { dbc_file = f->filename; break; } + if (!f->isEmpty()) { dbc_file = QString::fromStdString(f->filename); break; } if (dbc_file != settings.recent_dbc_file) return; if (!settings.selected_msg_ids.isEmpty()) diff --git a/tools/cabana/messageswidget.cc b/tools/cabana/messageswidget.cc index ed9aeaf311..ec8c82dd98 100644 --- a/tools/cabana/messageswidget.cc +++ b/tools/cabana/messageswidget.cc @@ -205,7 +205,7 @@ QVariant MessageListModel::data(const QModelIndex &index, int role) const { } else if (role == Qt::ToolTipRole && index.column() == Column::NAME) { auto msg = dbc()->msg(item.id); auto tooltip = item.name; - if (msg && !msg->comment.isEmpty()) tooltip += "
" + msg->comment + ""; + if (msg && !msg->comment.empty()) tooltip += "
" + QString::fromStdString(msg->comment) + ""; return tooltip; } return {}; @@ -277,7 +277,7 @@ bool MessageListModel::match(const MessageListModel::Item &item) { if (!match) { const auto m = dbc()->msg(item.id); match = m && std::any_of(m->sigs.cbegin(), m->sigs.cend(), - [&txt](const auto &s) { return s->name.contains(txt, Qt::CaseInsensitive); }); + [&txt](const auto &s) { return QString::fromStdString(s->name).contains(txt, Qt::CaseInsensitive); }); } break; } @@ -323,8 +323,8 @@ bool MessageListModel::filterAndSort() { if (show_inactive_messages || can->isMessageActive(id)) { auto msg = dbc()->msg(id); Item item = {.id = id, - .name = msg ? msg->name : UNTITLED, - .node = msg ? msg->transmitter : QString()}; + .name = msg ? QString::fromStdString(msg->name) : QString::fromStdString(UNTITLED), + .node = msg ? QString::fromStdString(msg->transmitter) : QString()}; if (match(item)) items.emplace_back(item); } diff --git a/tools/cabana/signalview.cc b/tools/cabana/signalview.cc index a9ceacd806..15baabe512 100644 --- a/tools/cabana/signalview.cc +++ b/tools/cabana/signalview.cc @@ -1,6 +1,7 @@ #include "tools/cabana/signalview.h" #include +#include #include #include @@ -11,7 +12,6 @@ #include #include #include -#include #include #include "tools/cabana/commands.h" @@ -34,8 +34,8 @@ SignalModel::SignalModel(QObject *parent) : root(new Item), QAbstractItemModel(p } void SignalModel::insertItem(SignalModel::Item *root_item, int pos, const cabana::Signal *sig) { - Item *parent_item = new Item{.type = Item::Sig, .parent = root_item, .sig = sig, .title = sig->name}; - root_item->children.insert(pos, parent_item); + Item *parent_item = new Item{.type = Item::Sig, .parent = root_item, .sig = sig, .title = QString::fromStdString(sig->name)}; + root_item->children.insert(root_item->children.begin() + pos, parent_item); QString titles[]{"Name", "Size", "Receiver Nodes", "Little Endian", "Signed", "Offset", "Factor", "Type", "Multiplex Value", "Extra Info", "Unit", "Comment", "Minimum Value", "Maximum Value", "Value Table"}; for (int i = 0; i < std::size(titles); ++i) { @@ -63,7 +63,7 @@ void SignalModel::refresh() { root.reset(new SignalModel::Item); if (auto msg = dbc()->msg(msg_id)) { for (auto s : msg->getSignals()) { - if (filter_str.isEmpty() || s->name.contains(filter_str, Qt::CaseInsensitive)) { + if (filter_str.isEmpty() || QString::fromStdString(s->name).contains(filter_str, Qt::CaseInsensitive)) { insertItem(root.get(), root->children.size(), s); } } @@ -124,25 +124,25 @@ QVariant SignalModel::data(const QModelIndex &index, int role) const { const Item *item = getItem(index); if (role == Qt::DisplayRole || role == Qt::EditRole) { if (index.column() == 0) { - return item->type == Item::Sig ? item->sig->name : item->title; + return item->type == Item::Sig ? QString::fromStdString(item->sig->name) : item->title; } else { switch (item->type) { case Item::Sig: return item->sig_val; - case Item::Name: return item->sig->name; + case Item::Name: return QString::fromStdString(item->sig->name); case Item::Size: return item->sig->size; - case Item::Node: return item->sig->receiver_name; + case Item::Node: return QString::fromStdString(item->sig->receiver_name); case Item::SignalType: return signalTypeToString(item->sig->type); case Item::MultiplexValue: return item->sig->multiplex_value; - case Item::Offset: return doubleToString(item->sig->offset); - case Item::Factor: return doubleToString(item->sig->factor); - case Item::Unit: return item->sig->unit; - case Item::Comment: return item->sig->comment; - case Item::Min: return doubleToString(item->sig->min); - case Item::Max: return doubleToString(item->sig->max); + case Item::Offset: return QString::fromStdString(doubleToString(item->sig->offset)); + case Item::Factor: return QString::fromStdString(doubleToString(item->sig->factor)); + case Item::Unit: return QString::fromStdString(item->sig->unit); + case Item::Comment: return QString::fromStdString(item->sig->comment); + case Item::Min: return QString::fromStdString(doubleToString(item->sig->min)); + case Item::Max: return QString::fromStdString(doubleToString(item->sig->max)); case Item::Desc: { QStringList val_desc; for (auto &[val, desc] : item->sig->val_desc) { - val_desc << QString("%1 \"%2\"").arg(val).arg(desc); + val_desc << QString("%1 \"%2\"").arg(val).arg(QString::fromStdString(desc)); } return val_desc.join(" "); } @@ -165,17 +165,17 @@ bool SignalModel::setData(const QModelIndex &index, const QVariant &value, int r Item *item = getItem(index); cabana::Signal s = *item->sig; switch (item->type) { - case Item::Name: s.name = value.toString(); break; + case Item::Name: s.name = value.toString().toStdString(); break; case Item::Size: s.size = value.toInt(); break; - case Item::Node: s.receiver_name = value.toString().trimmed(); break; + case Item::Node: s.receiver_name = value.toString().trimmed().toStdString(); break; case Item::SignalType: s.type = (cabana::Signal::Type)value.toInt(); break; case Item::MultiplexValue: s.multiplex_value = value.toInt(); break; case Item::Endian: s.is_little_endian = value.toBool(); break; case Item::Signed: s.is_signed = value.toBool(); break; case Item::Offset: s.offset = value.toDouble(); break; case Item::Factor: s.factor = value.toDouble(); break; - case Item::Unit: s.unit = value.toString(); break; - case Item::Comment: s.comment = value.toString(); break; + case Item::Unit: s.unit = value.toString().toStdString(); break; + case Item::Comment: s.comment = value.toString().toStdString(); break; case Item::Min: s.min = value.toDouble(); break; case Item::Max: s.max = value.toDouble(); break; case Item::Desc: s.val_desc = value.value(); break; @@ -189,7 +189,7 @@ bool SignalModel::setData(const QModelIndex &index, const QVariant &value, int r bool SignalModel::saveSignal(const cabana::Signal *origin_s, cabana::Signal &s) { auto msg = dbc()->msg(msg_id); if (s.name != origin_s->name && msg->sig(s.name) != nullptr) { - QString text = tr("There is already a signal with the same name '%1'").arg(s.name); + QString text = tr("There is already a signal with the same name '%1'").arg(QString::fromStdString(s.name)); QMessageBox::warning(nullptr, tr("Failed to save signal"), text); return false; } @@ -214,7 +214,7 @@ void SignalModel::handleSignalAdded(MessageId id, const cabana::Signal *sig) { beginInsertRows({}, i, i); insertItem(root.get(), i, sig); endInsertRows(); - } else if (sig->name.contains(filter_str, Qt::CaseInsensitive)) { + } else if (QString::fromStdString(sig->name).contains(filter_str, Qt::CaseInsensitive)) { refresh(); } } @@ -229,7 +229,9 @@ void SignalModel::handleSignalUpdated(const cabana::Signal *sig) { int to = dbc()->msg(msg_id)->indexOf(sig); if (to != row) { beginMoveRows({}, row, row, {}, to > row ? to + 1 : to); - root->children.move(row, to); + auto item = root->children[row]; + root->children.erase(root->children.begin() + row); + root->children.insert(root->children.begin() + to, item); endMoveRows(); } } @@ -239,7 +241,8 @@ void SignalModel::handleSignalUpdated(const cabana::Signal *sig) { void SignalModel::handleSignalRemoved(const cabana::Signal *sig) { if (int row = signalRow(sig); row != -1) { beginRemoveRows({}, row, row); - delete root->children.takeAt(row); + delete root->children[row]; + root->children.erase(root->children.begin() + row); endRemoveRows(); } } @@ -373,7 +376,10 @@ QWidget *SignalItemDelegate::createEditor(QWidget *parent, const QStyleOptionVie else e->setValidator(double_validator); if (item->type == SignalModel::Item::Name) { - QCompleter *completer = new QCompleter(dbc()->signalNames(), e); + auto names = dbc()->signalNames(); + QStringList qnames; + for (const auto &n : names) qnames.push_back(QString::fromStdString(n)); + QCompleter *completer = new QCompleter(qnames, e); completer->setCaseSensitivity(Qt::CaseInsensitive); completer->setFilterMode(Qt::MatchContains); e->setCompleter(completer); @@ -395,7 +401,7 @@ QWidget *SignalItemDelegate::createEditor(QWidget *parent, const QStyleOptionVie return c; } else if (item->type == SignalModel::Item::Desc) { ValueDescriptionDlg dlg(item->sig->val_desc, parent); - dlg.setWindowTitle(item->sig->name); + dlg.setWindowTitle(QString::fromStdString(item->sig->name)); if (dlg.exec()) { ((QAbstractItemModel *)index.model())->setData(index, QVariant::fromValue(dlg.val_desc)); } @@ -621,7 +627,7 @@ void SignalView::updateState(const std::set *msgs) { for (auto item : model->root->children) { double value = 0; if (item->sig->getValue(last_msg.dat.data(), last_msg.dat.size(), &value)) { - item->sig_val = item->sig->formatValue(value); + item->sig_val = QString::fromStdString(item->sig->formatValue(value)); max_value_width = std::max(max_value_width, fontMetrics().horizontalAdvance(item->sig_val)); } } @@ -635,13 +641,13 @@ void SignalView::updateState(const std::set *msgs) { delegate->button_size.height() - style()->pixelMetric(QStyle::PM_FocusFrameVMargin) * 2); auto [first, last] = can->eventsInRange(model->msg_id, std::make_pair(last_msg.ts -settings.sparkline_range, last_msg.ts)); - QFutureSynchronizer synchronizer; + std::vector> futures; for (int i = first_visible.row(); i <= last_visible.row(); ++i) { auto item = model->getItem(model->index(i, 1)); - synchronizer.addFuture(QtConcurrent::run( - &item->sparkline, &Sparkline::update, item->sig, first, last, settings.sparkline_range, size)); + futures.push_back(std::async(std::launch::async, + &Sparkline::update, &item->sparkline, item->sig, first, last, settings.sparkline_range, size)); } - synchronizer.waitForFinished(); + for (auto &f : futures) f.get(); } for (int i = 0; i < model->rowCount(); ++i) { @@ -677,7 +683,7 @@ ValueDescriptionDlg::ValueDescriptionDlg(const ValueDescription &descriptions, Q int row = 0; for (auto &[val, desc] : descriptions) { table->setItem(row, 0, new QTableWidgetItem(QString::number(val))); - table->setItem(row, 1, new QTableWidgetItem(desc)); + table->setItem(row, 1, new QTableWidgetItem(QString::fromStdString(desc))); ++row; } @@ -706,7 +712,7 @@ void ValueDescriptionDlg::save() { QString val = table->item(i, 0)->text().trimmed(); QString desc = table->item(i, 1)->text().trimmed(); if (!val.isEmpty() && !desc.isEmpty()) { - val_desc.push_back({val.toDouble(), desc}); + val_desc.push_back({val.toDouble(), desc.toStdString()}); } } QDialog::accept(); diff --git a/tools/cabana/signalview.h b/tools/cabana/signalview.h index 4e746ea105..42db830df7 100644 --- a/tools/cabana/signalview.h +++ b/tools/cabana/signalview.h @@ -20,12 +20,15 @@ class SignalModel : public QAbstractItemModel { public: struct Item { enum Type {Root, Sig, Name, Size, Node, Endian, Signed, Offset, Factor, SignalType, MultiplexValue, ExtraInfo, Unit, Comment, Min, Max, Desc }; - ~Item() { qDeleteAll(children); } - inline int row() { return parent->children.indexOf(this); } + ~Item() { for (auto c : children) delete c; } + inline int row() { + auto it = std::find(parent->children.begin(), parent->children.end(), this); + return it != parent->children.end() ? std::distance(parent->children.begin(), it) : -1; + } Type type = Type::Root; Item *parent = nullptr; - QList children; + std::vector children; const cabana::Signal *sig = nullptr; QString title; diff --git a/tools/cabana/streams/abstractstream.h b/tools/cabana/streams/abstractstream.h index f35b19d34f..7d66a420dc 100644 --- a/tools/cabana/streams/abstractstream.h +++ b/tools/cabana/streams/abstractstream.h @@ -65,8 +65,8 @@ public: virtual void start() = 0; virtual bool liveStreaming() const { return true; } virtual void seekTo(double ts) {} - virtual QString routeName() const = 0; - virtual QString carFingerprint() const { return ""; } + virtual std::string routeName() const = 0; + virtual std::string carFingerprint() const { return ""; } virtual QDateTime beginDateTime() const { return {}; } virtual uint64_t beginMonoTime() const { return 0; } virtual double minSeconds() const { return 0; } @@ -149,7 +149,7 @@ class DummyStream : public AbstractStream { Q_OBJECT public: DummyStream(QObject *parent) : AbstractStream(parent) {} - QString routeName() const override { return tr("No Stream"); } + std::string routeName() const override { return "No Stream"; } void start() override {} }; diff --git a/tools/cabana/streams/devicestream.h b/tools/cabana/streams/devicestream.h index 6beb300d7a..4bcdb5351d 100644 --- a/tools/cabana/streams/devicestream.h +++ b/tools/cabana/streams/devicestream.h @@ -9,8 +9,8 @@ class DeviceStream : public LiveStream { public: DeviceStream(QObject *parent, QString address = {}); ~DeviceStream(); - inline QString routeName() const override { - return QString("Live Streaming From %1").arg(zmq_address.isEmpty() ? "127.0.0.1" : zmq_address); + inline std::string routeName() const override { + return "Live Streaming From " + (zmq_address.isEmpty() ? std::string("127.0.0.1") : zmq_address.toStdString()); } protected: diff --git a/tools/cabana/streams/pandastream.cc b/tools/cabana/streams/pandastream.cc index a2430c665f..3692f71a11 100644 --- a/tools/cabana/streams/pandastream.cc +++ b/tools/cabana/streams/pandastream.cc @@ -16,8 +16,8 @@ PandaStream::PandaStream(QObject *parent, PandaStreamConfig config_) : config(co bool PandaStream::connect() { try { - qDebug() << "Connecting to panda " << config.serial; - panda.reset(new Panda(config.serial.toStdString())); + qDebug() << "Connecting to panda " << config.serial.c_str(); + panda.reset(new Panda(config.serial)); config.bus_config.resize(3); qDebug() << "Connected"; } catch (const std::exception& e) { @@ -81,7 +81,7 @@ void PandaStream::streamThread() { OpenPandaWidget::OpenPandaWidget(QWidget *parent) : AbstractOpenStreamWidget(parent) { form_layout = new QFormLayout(this); if (can && dynamic_cast(can) != nullptr) { - form_layout->addWidget(new QLabel(tr("Already connected to %1.").arg(can->routeName()))); + form_layout->addWidget(new QLabel(tr("Already connected to %1.").arg(QString::fromStdString(can->routeName())))); form_layout->addWidget(new QLabel("Close the current connection via [File menu -> Close Stream] before connecting to another Panda.")); QTimer::singleShot(0, [this]() { emit enableOpenButton(false); }); return; @@ -129,7 +129,7 @@ void OpenPandaWidget::buildConfigForm() { } if (has_panda) { - config.serial = serial; + config.serial = serial.toStdString(); config.bus_config.resize(3); for (int i = 0; i < config.bus_config.size(); i++) { QHBoxLayout *bus_layout = new QHBoxLayout; diff --git a/tools/cabana/streams/pandastream.h b/tools/cabana/streams/pandastream.h index e17ad887fc..f8847f65e5 100644 --- a/tools/cabana/streams/pandastream.h +++ b/tools/cabana/streams/pandastream.h @@ -19,7 +19,7 @@ struct BusConfig { }; struct PandaStreamConfig { - QString serial = ""; + std::string serial = ""; std::vector bus_config; }; @@ -28,8 +28,8 @@ class PandaStream : public LiveStream { public: PandaStream(QObject *parent, PandaStreamConfig config_ = {}); ~PandaStream() { stop(); } - inline QString routeName() const override { - return QString("Panda: %1").arg(config.serial); + inline std::string routeName() const override { + return "Panda: " + config.serial; } protected: diff --git a/tools/cabana/streams/replaystream.cc b/tools/cabana/streams/replaystream.cc index b8cf1be299..f42bf2601c 100644 --- a/tools/cabana/streams/replaystream.cc +++ b/tools/cabana/streams/replaystream.cc @@ -46,9 +46,9 @@ void ReplayStream::mergeSegments() { } } -bool ReplayStream::loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags, bool auto_source) { - replay.reset(new Replay(route.toStdString(), {"can", "roadEncodeIdx", "driverEncodeIdx", "wideRoadEncodeIdx", "carParams"}, - {}, nullptr, replay_flags, data_dir.toStdString(), auto_source)); +bool ReplayStream::loadRoute(const std::string &route, const std::string &data_dir, uint32_t replay_flags, bool auto_source) { + replay.reset(new Replay(route, {"can", "roadEncodeIdx", "driverEncodeIdx", "wideRoadEncodeIdx", "carParams"}, + {}, nullptr, replay_flags, data_dir, auto_source)); replay->setSegmentCacheLimit(settings.max_cached_minutes); replay->installEventFilter([this](const Event *event) { return eventFilter(event); }); @@ -72,17 +72,17 @@ bool ReplayStream::loadRoute(const QString &route, const QString &data_dir, uint "This will grant access to routes from your comma account."; } else { message = tr("Access Denied. You do not have permission to access route:\n\n%1\n\n" - "This is likely a private route.").arg(route); + "This is likely a private route.").arg(QString::fromStdString(route)); } QMessageBox::warning(nullptr, tr("Access Denied"), message); } else if (replay->lastRouteError() == RouteLoadError::NetworkError) { QMessageBox::warning(nullptr, tr("Network Error"), - tr("Unable to load the route:\n\n %1.\n\nPlease check your network connection and try again.").arg(route)); + tr("Unable to load the route:\n\n %1.\n\nPlease check your network connection and try again.").arg(QString::fromStdString(route))); } else if (replay->lastRouteError() == RouteLoadError::FileNotFound) { QMessageBox::warning(nullptr, tr("Route Not Found"), - tr("The specified route could not be found:\n\n %1.\n\nPlease check the route name and try again.").arg(route)); + tr("The specified route could not be found:\n\n %1.\n\nPlease check the route name and try again.").arg(QString::fromStdString(route))); } else { - QMessageBox::warning(nullptr, tr("Route Load Failed"), tr("Failed to load route: '%1'").arg(route)); + QMessageBox::warning(nullptr, tr("Route Load Failed"), tr("Failed to load route: '%1'").arg(QString::fromStdString(route))); } } return success; @@ -168,7 +168,7 @@ AbstractStream *OpenReplayWidget::open() { if (cameras[2]->isChecked()) flags |= REPLAY_FLAG_ECAM; if (flags == REPLAY_FLAG_NONE && !cameras[0]->isChecked()) flags = REPLAY_FLAG_NO_VIPC; - if (replay_stream->loadRoute(route, data_dir, flags)) { + if (replay_stream->loadRoute(route.toStdString(), data_dir.toStdString(), flags)) { return replay_stream.release(); } } diff --git a/tools/cabana/streams/replaystream.h b/tools/cabana/streams/replaystream.h index d429ed1f95..40f8ec8cfb 100644 --- a/tools/cabana/streams/replaystream.h +++ b/tools/cabana/streams/replaystream.h @@ -18,12 +18,12 @@ class ReplayStream : public AbstractStream { public: ReplayStream(QObject *parent); void start() override { replay->start(); } - bool loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags = REPLAY_FLAG_NONE, bool auto_source = false); + bool loadRoute(const std::string &route, const std::string &data_dir, uint32_t replay_flags = REPLAY_FLAG_NONE, bool auto_source = false); bool eventFilter(const Event *event); void seekTo(double ts) override { replay->seekTo(std::max(double(0), ts), false); } bool liveStreaming() const override { return false; } - inline QString routeName() const override { return QString::fromStdString(replay->route().name()); } - inline QString carFingerprint() const override { return replay->carFingerprint().c_str(); } + inline std::string routeName() const override { return replay->route().name(); } + inline std::string carFingerprint() const override { return replay->carFingerprint(); } double minSeconds() const override { return replay->minSeconds(); } double maxSeconds() const { return replay->maxSeconds(); } inline QDateTime beginDateTime() const { return QDateTime::fromSecsSinceEpoch(replay->routeDateTime()); } diff --git a/tools/cabana/streams/routes.cc b/tools/cabana/streams/routes.cc index 8539a00b5b..1e69a45cea 100644 --- a/tools/cabana/streams/routes.cc +++ b/tools/cabana/streams/routes.cc @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include "tools/replay/py_downloader.h" @@ -72,13 +72,12 @@ RoutesDialog::RoutesDialog(QWidget *parent) : QDialog(parent) { // Fetch devices QPointer self = this; - QtConcurrent::run([self]() { + std::thread([self]() { std::string result = PyDownloader::getDevices(); - auto [success, error_code] = checkApiResponse(result); - QMetaObject::invokeMethod(qApp, [self, r = QString::fromStdString(result), success, error_code]() { - if (self) self->parseDeviceList(r, success, error_code); + QMetaObject::invokeMethod(qApp, [self, r = QString::fromStdString(result), response = checkApiResponse(result)]() { + if (self) self->parseDeviceList(r, response.first, response.second); }, Qt::QueuedConnection); - }); + }).detach(); } void RoutesDialog::parseDeviceList(const QString &json, bool success, int error_code) { @@ -114,14 +113,13 @@ void RoutesDialog::fetchRoutes() { int request_id = ++fetch_id_; QPointer self = this; - QtConcurrent::run([self, did, start_ms, end_ms, preserved, request_id]() { + std::thread([self, did, start_ms, end_ms, preserved, request_id]() { std::string result = PyDownloader::getDeviceRoutes(did, start_ms, end_ms, preserved); if (!self || self->fetch_id_ != request_id) return; - auto [success, error_code] = checkApiResponse(result); - QMetaObject::invokeMethod(qApp, [self, r = QString::fromStdString(result), success, error_code, request_id]() { - if (self && self->fetch_id_ == request_id) self->parseRouteList(r, success, error_code); + QMetaObject::invokeMethod(qApp, [self, r = QString::fromStdString(result), response = checkApiResponse(result), request_id]() { + if (self && self->fetch_id_ == request_id) self->parseRouteList(r, response.first, response.second); }, Qt::QueuedConnection); - }); + }).detach(); } void RoutesDialog::parseRouteList(const QString &json, bool success, int error_code) { diff --git a/tools/cabana/streams/socketcanstream.cc b/tools/cabana/streams/socketcanstream.cc index e4801df9c0..768465d5a3 100644 --- a/tools/cabana/streams/socketcanstream.cc +++ b/tools/cabana/streams/socketcanstream.cc @@ -1,6 +1,14 @@ #include "tools/cabana/streams/socketcanstream.h" +#include +#include +#include +#include +#include +#include + #include +#include #include #include #include @@ -9,59 +17,82 @@ SocketCanStream::SocketCanStream(QObject *parent, SocketCanStreamConfig config_) : config(config_), LiveStream(parent) { if (!available()) { - throw std::runtime_error("SocketCAN plugin not available"); + throw std::runtime_error("SocketCAN not available"); } - qDebug() << "Connecting to SocketCAN device" << config.device; + qDebug() << "Connecting to SocketCAN device" << config.device.c_str(); if (!connect()) { throw std::runtime_error("Failed to connect to SocketCAN device"); } } +SocketCanStream::~SocketCanStream() { + stop(); + if (sock_fd >= 0) { + ::close(sock_fd); + sock_fd = -1; + } +} + bool SocketCanStream::available() { - return QCanBus::instance()->plugins().contains("socketcan"); + int fd = socket(PF_CAN, SOCK_RAW, CAN_RAW); + if (fd < 0) return false; + ::close(fd); + return true; } bool SocketCanStream::connect() { - // Connecting might generate some warnings about missing socketcan/libsocketcan libraries - // These are expected and can be ignored, we don't need the advanced features of libsocketcan - QString errorString; - device.reset(QCanBus::instance()->createDevice("socketcan", config.device, &errorString)); - device->setConfigurationParameter(QCanBusDevice::CanFdKey, true); - - if (!device) { - qDebug() << "Failed to create SocketCAN device" << errorString; + sock_fd = socket(PF_CAN, SOCK_RAW, CAN_RAW); + if (sock_fd < 0) { + qDebug() << "Failed to create CAN socket"; return false; } - if (!device->connectDevice()) { - qDebug() << "Failed to connect to device"; + // Enable CAN-FD + int fd_enable = 1; + setsockopt(sock_fd, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &fd_enable, sizeof(fd_enable)); + + struct ifreq ifr = {}; + strncpy(ifr.ifr_name, config.device.c_str(), IFNAMSIZ - 1); + if (ioctl(sock_fd, SIOCGIFINDEX, &ifr) < 0) { + qDebug() << "Failed to get interface index for" << config.device.c_str(); + ::close(sock_fd); + sock_fd = -1; return false; } + struct sockaddr_can addr = {}; + addr.can_family = AF_CAN; + addr.can_ifindex = ifr.ifr_ifindex; + if (bind(sock_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + qDebug() << "Failed to bind CAN socket"; + ::close(sock_fd); + sock_fd = -1; + return false; + } + + // Set read timeout so the thread can check for interruption + struct timeval tv = {.tv_sec = 0, .tv_usec = 100000}; // 100ms + setsockopt(sock_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + return true; } void SocketCanStream::streamThread() { - while (!QThread::currentThread()->isInterruptionRequested()) { - QThread::msleep(1); + struct canfd_frame frame; - auto frames = device->readAllFrames(); - if (frames.size() == 0) continue; + while (!QThread::currentThread()->isInterruptionRequested()) { + ssize_t nbytes = read(sock_fd, &frame, sizeof(frame)); + if (nbytes <= 0) continue; + + uint8_t len = (nbytes == CAN_MTU) ? frame.len : frame.len; // works for both CAN and CAN-FD MessageBuilder msg; auto evt = msg.initEvent(); - auto canData = evt.initCan(frames.size()); - - for (uint i = 0; i < frames.size(); i++) { - if (!frames[i].isValid()) continue; - - canData[i].setAddress(frames[i].frameId()); - canData[i].setSrc(0); - - auto payload = frames[i].payload(); - canData[i].setDat(kj::arrayPtr((uint8_t*)payload.data(), payload.size())); - } + auto canData = evt.initCan(1); + canData[0].setAddress(frame.can_id & CAN_EFF_MASK); + canData[0].setSrc(0); + canData[0].setDat(kj::arrayPtr(frame.data, len)); handleEvent(capnp::messageToFlatArray(msg)); } @@ -87,7 +118,7 @@ OpenSocketCanWidget::OpenSocketCanWidget(QWidget *parent) : AbstractOpenStreamWi main_layout->addStretch(1); QObject::connect(refresh, &QPushButton::clicked, this, &OpenSocketCanWidget::refreshDevices); - QObject::connect(device_edit, &QComboBox::currentTextChanged, this, [=]{ config.device = device_edit->currentText(); }); + QObject::connect(device_edit, &QComboBox::currentTextChanged, this, [=]{ config.device = device_edit->currentText().toStdString(); }); // Populate devices refreshDevices(); @@ -95,12 +126,19 @@ OpenSocketCanWidget::OpenSocketCanWidget(QWidget *parent) : AbstractOpenStreamWi void OpenSocketCanWidget::refreshDevices() { device_edit->clear(); - for (auto device : QCanBus::instance()->availableDevices(QStringLiteral("socketcan"))) { - device_edit->addItem(device.name()); + // Scan /sys/class/net/ for CAN interfaces (type 280 = ARPHRD_CAN) + QDir net_dir("/sys/class/net"); + for (const auto &iface : net_dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { + QFile type_file(net_dir.filePath(iface) + "/type"); + if (type_file.open(QIODevice::ReadOnly)) { + int type = type_file.readAll().trimmed().toInt(); + if (type == 280) { + device_edit->addItem(iface); + } + } } } - AbstractStream *OpenSocketCanWidget::open() { try { return new SocketCanStream(qApp, config); diff --git a/tools/cabana/streams/socketcanstream.h b/tools/cabana/streams/socketcanstream.h index 8083b687e9..3c5cd184f7 100644 --- a/tools/cabana/streams/socketcanstream.h +++ b/tools/cabana/streams/socketcanstream.h @@ -1,27 +1,22 @@ #pragma once -#include - -#include -#include -#include #include #include "tools/cabana/streams/livestream.h" struct SocketCanStreamConfig { - QString device = ""; // TODO: support multiple devices/buses at once + std::string device = ""; // TODO: support multiple devices/buses at once }; class SocketCanStream : public LiveStream { Q_OBJECT public: SocketCanStream(QObject *parent, SocketCanStreamConfig config_ = {}); - ~SocketCanStream() { stop(); } + ~SocketCanStream(); static bool available(); - inline QString routeName() const override { - return QString("Live Streaming From Socket CAN %1").arg(config.device); + inline std::string routeName() const override { + return "Live Streaming From Socket CAN " + config.device; } protected: @@ -29,7 +24,7 @@ protected: bool connect(); SocketCanStreamConfig config = {}; - std::unique_ptr device; + int sock_fd = -1; }; class OpenSocketCanWidget : public AbstractOpenStreamWidget { diff --git a/tools/cabana/streamselector.cc b/tools/cabana/streamselector.cc index efd00d3985..4ad552d4b4 100644 --- a/tools/cabana/streamselector.cc +++ b/tools/cabana/streamselector.cc @@ -4,11 +4,12 @@ #include #include -#include "streams/socketcanstream.h" #include "tools/cabana/streams/devicestream.h" #include "tools/cabana/streams/pandastream.h" #include "tools/cabana/streams/replaystream.h" +#ifdef __linux__ #include "tools/cabana/streams/socketcanstream.h" +#endif StreamSelector::StreamSelector(QWidget *parent) : QDialog(parent) { setWindowTitle(tr("Open stream")); @@ -35,9 +36,11 @@ StreamSelector::StreamSelector(QWidget *parent) : QDialog(parent) { addStreamWidget(new OpenReplayWidget, tr("&Replay")); addStreamWidget(new OpenPandaWidget, tr("&Panda")); +#ifdef __linux__ if (SocketCanStream::available()) { addStreamWidget(new OpenSocketCanWidget, tr("&SocketCAN")); } +#endif addStreamWidget(new OpenDeviceWidget, tr("&Device")); QObject::connect(btn_box, &QDialogButtonBox::rejected, this, &QDialog::reject); diff --git a/tools/cabana/tests/test_cabana.cc b/tools/cabana/tests/test_cabana.cc index d9fcae6f21..833cfbe4b5 100644 --- a/tools/cabana/tests/test_cabana.cc +++ b/tools/cabana/tests/test_cabana.cc @@ -8,7 +8,7 @@ const std::string TEST_RLOG_URL = "https://commadataci.blob.core.windows.net/openpilotci/0c94aa1e1296d7c6/2021-05-05--19-48-37/0/rlog.bz2"; TEST_CASE("DBCFile::generateDBC") { - QString fn = QString("%1/%2.dbc").arg(OPENDBC_FILE_PATH, "tesla_can"); + std::string fn = std::string(OPENDBC_FILE_PATH) + "/tesla_can.dbc"; DBCFile dbc_origin(fn); DBCFile dbc_from_generated("", dbc_origin.generateDBC()); @@ -30,7 +30,7 @@ TEST_CASE("DBCFile::generateDBC") { TEST_CASE("DBCFile::generateDBC - comment order") { // Ensure that message comments are followed by signal comments and in the correct order - auto content = R"(BO_ 160 message_1: 8 EON + std::string content = R"(BO_ 160 message_1: 8 EON SG_ signal_1 : 0|12@1+ (1,0) [0|4095] "unit" XXX BO_ 162 message_2: 8 EON @@ -46,7 +46,7 @@ CM_ SG_ 162 signal_2 "signal comment"; } TEST_CASE("DBCFile::generateDBC -- preserve original header") { - QString content = R"(VERSION "1.0" + std::string content = R"(VERSION "1.0" NS_ : CM_ @@ -66,7 +66,7 @@ CM_ SG_ 160 signal_1 "signal comment"; } TEST_CASE("DBCFile::generateDBC - escaped quotes") { - QString content = R"(BO_ 160 message_1: 8 EON + std::string content = R"(BO_ 160 message_1: 8 EON SG_ signal_1 : 0|12@1+ (1,0) [0|4095] "unit" XXX CM_ BO_ 160 "message comment with \"escaped quotes\""; @@ -77,7 +77,7 @@ CM_ SG_ 160 signal_1 "signal comment with \"escaped quotes\""; } TEST_CASE("parse_dbc") { - QString content = R"( + std::string content = R"( BO_ 160 message_1: 8 EON SG_ signal_1 : 0|12@1+ (1,0) [0|4095] "unit" XXX SG_ signal_2 : 12|1@1+ (1.0,0.0) [0.0|1] "" XXX @@ -119,9 +119,9 @@ CM_ SG_ 162 signal_1 "signal comment with \"escaped quotes\""; REQUIRE(sig_1->comment == "signal comment"); REQUIRE(sig_1->receiver_name == "XXX"); REQUIRE(sig_1->val_desc.size() == 3); - REQUIRE(sig_1->val_desc[0] == std::pair{0, "disabled"}); - REQUIRE(sig_1->val_desc[1] == std::pair{1.2, "initializing"}); - REQUIRE(sig_1->val_desc[2] == std::pair{2, "fault"}); + REQUIRE(sig_1->val_desc[0] == std::pair{0, "disabled"}); + REQUIRE(sig_1->val_desc[1] == std::pair{1.2, "initializing"}); + REQUIRE(sig_1->val_desc[2] == std::pair{2, "fault"}); auto &sig_2 = msg->sigs[1]; REQUIRE(sig_2->comment == "multiple line comment \n1\n2"); @@ -147,7 +147,7 @@ TEST_CASE("parse_opendbc") { QStringList errors; for (auto fn : dir.entryList({"*.dbc"}, QDir::Files, QDir::Name)) { try { - auto dbc = DBCFile(dir.filePath(fn)); + auto dbc = DBCFile(dir.filePath(fn).toStdString()); } catch (std::exception &e) { errors.push_back(e.what()); } diff --git a/tools/cabana/tools/findsignal.cc b/tools/cabana/tools/findsignal.cc index ec56fcaac0..b131893942 100644 --- a/tools/cabana/tools/findsignal.cc +++ b/tools/cabana/tools/findsignal.cc @@ -1,10 +1,11 @@ #include "tools/cabana/tools/findsignal.h" +#include + #include #include #include #include -#include #include #include @@ -20,7 +21,7 @@ QVariant FindSignalModel::data(const QModelIndex &index, int role) const { if (role == Qt::DisplayRole) { const auto &s = filtered_signals[index.row()]; switch (index.column()) { - case 0: return s.id.toString(); + case 0: return QString::fromStdString(s.id.toString()); case 1: return QString("%1, %2").arg(s.sig.start_bit).arg(s.sig.size); case 2: return s.values.join(" "); } @@ -32,36 +33,49 @@ void FindSignalModel::search(std::function cmp) { beginResetModel(); std::mutex lock; - const auto prev_sigs = !histories.isEmpty() ? histories.back() : initial_signals; + const auto prev_sigs = !histories.empty() ? histories.back() : initial_signals; filtered_signals.clear(); filtered_signals.reserve(prev_sigs.size()); - QtConcurrent::blockingMap(prev_sigs, [&](auto &s) { - const auto &events = can->events(s.id); - auto first = std::upper_bound(events.cbegin(), events.cend(), s.mono_time, CompareCanEvent()); - auto last = events.cend(); - if (last_time < std::numeric_limits::max()) { - last = std::upper_bound(events.cbegin(), events.cend(), last_time, CompareCanEvent()); - } - auto it = std::find_if(first, last, [&](const CanEvent *e) { return cmp(get_raw_value(e->dat, e->size, s.sig)); }); - if (it != last) { - auto values = s.values; - values += QString("(%1, %2)").arg(can->toSeconds((*it)->mono_time), 0, 'f', 3).arg(get_raw_value((*it)->dat, (*it)->size, s.sig)); - std::lock_guard lk(lock); - filtered_signals.push_back({.id = s.id, .mono_time = (*it)->mono_time, .sig = s.sig, .values = values}); - } - }); + unsigned int num_threads = std::max(1u, std::thread::hardware_concurrency()); + size_t chunk = (prev_sigs.size() + num_threads - 1) / num_threads; + std::vector threads; + for (unsigned int t = 0; t < num_threads && t * chunk < (size_t)prev_sigs.size(); ++t) { + size_t start = t * chunk; + size_t end = std::min(start + chunk, (size_t)prev_sigs.size()); + threads.emplace_back([&, start, end]() { + for (size_t i = start; i < end; ++i) { + const auto &s = prev_sigs[i]; + const auto &events = can->events(s.id); + auto first = std::upper_bound(events.cbegin(), events.cend(), s.mono_time, CompareCanEvent()); + auto last = events.cend(); + if (last_time < std::numeric_limits::max()) { + last = std::upper_bound(events.cbegin(), events.cend(), last_time, CompareCanEvent()); + } + + auto it = std::find_if(first, last, [&](const CanEvent *e) { return cmp(get_raw_value(e->dat, e->size, s.sig)); }); + if (it != last) { + auto values = s.values; + values += QString("(%1, %2)").arg(can->toSeconds((*it)->mono_time), 0, 'f', 3).arg(get_raw_value((*it)->dat, (*it)->size, s.sig)); + std::lock_guard lk(lock); + filtered_signals.push_back({.id = s.id, .mono_time = (*it)->mono_time, .sig = s.sig, .values = values}); + } + } + }); + } + for (auto &th : threads) th.join(); + histories.push_back(filtered_signals); endResetModel(); } void FindSignalModel::undo() { - if (!histories.isEmpty()) { + if (!histories.empty()) { beginResetModel(); histories.pop_back(); filtered_signals.clear(); - if (!histories.isEmpty()) filtered_signals = histories.back(); + if (!histories.empty()) filtered_signals = histories.back(); endResetModel(); } } @@ -172,7 +186,7 @@ FindSignalDlg::FindSignalDlg(QWidget *parent) : QDialog(parent, Qt::WindowFlags( } void FindSignalDlg::search() { - if (model->histories.isEmpty()) { + if (model->histories.empty()) { setInitialSignals(); } auto v1 = value1->text().toDouble(); @@ -246,12 +260,12 @@ void FindSignalDlg::setInitialSignals() { } void FindSignalDlg::modelReset() { - properties_group->setEnabled(model->histories.isEmpty()); - message_group->setEnabled(model->histories.isEmpty()); - search_btn->setText(model->histories.isEmpty() ? tr("Find") : tr("Find Next")); - reset_btn->setEnabled(!model->histories.isEmpty()); + properties_group->setEnabled(model->histories.empty()); + message_group->setEnabled(model->histories.empty()); + search_btn->setText(model->histories.empty() ? tr("Find") : tr("Find Next")); + reset_btn->setEnabled(!model->histories.empty()); undo_btn->setEnabled(model->histories.size() > 1); - search_btn->setEnabled(model->rowCount() > 0 || model->histories.isEmpty()); + search_btn->setEnabled(model->rowCount() > 0 || model->histories.empty()); stats_label->setVisible(true); stats_label->setText(tr("%1 matches. right click on an item to create signal. double click to open message").arg(model->filtered_signals.size())); } diff --git a/tools/cabana/tools/findsignal.h b/tools/cabana/tools/findsignal.h index 5ef7461fee..239a08c9c4 100644 --- a/tools/cabana/tools/findsignal.h +++ b/tools/cabana/tools/findsignal.h @@ -2,6 +2,8 @@ #include #include +#include +#include #include #include @@ -26,14 +28,14 @@ public: QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override { return 3; } - int rowCount(const QModelIndex &parent = QModelIndex()) const override { return std::min(filtered_signals.size(), 300); } + int rowCount(const QModelIndex &parent = QModelIndex()) const override { return std::min((int)filtered_signals.size(), 300); } void search(std::function cmp); void reset(); void undo(); - QList filtered_signals; - QList initial_signals; - QList> histories; + std::vector filtered_signals; + std::vector initial_signals; + std::vector> histories; uint64_t last_time = std::numeric_limits::max(); }; diff --git a/tools/cabana/tools/findsimilarbits.cc b/tools/cabana/tools/findsimilarbits.cc index c3c659791a..8062b61199 100644 --- a/tools/cabana/tools/findsimilarbits.cc +++ b/tools/cabana/tools/findsimilarbits.cc @@ -1,6 +1,7 @@ #include "tools/cabana/tools/findsimilarbits.h" #include +#include #include #include @@ -31,7 +32,7 @@ FindSimilarBitsDlg::FindSimilarBitsDlg(QWidget *parent) : QDialog(parent, Qt::Wi msg_cb = new QComboBox(this); // TODO: update when src_bus_combo changes for (auto &[address, msg] : dbc()->getMessages(-1)) { - msg_cb->addItem(msg.name, address); + msg_cb->addItem(QString::fromStdString(msg.name), address); } msg_cb->model()->sort(0); msg_cb->setCurrentIndex(0); @@ -114,10 +115,10 @@ void FindSimilarBitsDlg::find() { search_btn->setEnabled(true); } -QList FindSimilarBitsDlg::calcBits(uint8_t bus, uint32_t selected_address, int byte_idx, - int bit_idx, uint8_t find_bus, bool equal, int min_msgs_cnt) { - QHash> mismatches; - QHash msg_count; +std::vector FindSimilarBitsDlg::calcBits(uint8_t bus, uint32_t selected_address, int byte_idx, + int bit_idx, uint8_t find_bus, bool equal, int min_msgs_cnt) { + std::unordered_map> mismatches; + std::unordered_map msg_count; const auto &events = can->allEvents(); int bit_to_find = -1; for (const CanEvent *e : events) { @@ -143,14 +144,14 @@ QList FindSimilarBitsDlg::calcBits(uint8_ } } - QList result; + std::vector result; result.reserve(mismatches.size()); for (auto it = mismatches.begin(); it != mismatches.end(); ++it) { - if (auto cnt = msg_count[it.key()]; cnt > min_msgs_cnt) { - auto &mismatched = it.value(); - for (int i = 0; i < mismatched.size(); ++i) { + if (auto cnt = msg_count[it->first]; cnt > (uint32_t)min_msgs_cnt) { + auto &mismatched = it->second; + for (int i = 0; i < (int)mismatched.size(); ++i) { if (float perc = (mismatched[i] / (double)cnt) * 100; perc < 50) { - result.push_back({it.key(), (uint32_t)i / 8, (uint32_t)i % 8, mismatched[i], cnt, perc}); + result.push_back({it->first, (uint32_t)i / 8, (uint32_t)i % 8, mismatched[i], cnt, perc}); } } } diff --git a/tools/cabana/tools/findsimilarbits.h b/tools/cabana/tools/findsimilarbits.h index 77bfac19ca..3451360654 100644 --- a/tools/cabana/tools/findsimilarbits.h +++ b/tools/cabana/tools/findsimilarbits.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include @@ -22,7 +24,7 @@ private: uint32_t address, byte_idx, bit_idx, mismatches, total; float perc; }; - QList calcBits(uint8_t bus, uint32_t selected_address, int byte_idx, int bit_idx, uint8_t find_bus, + std::vector calcBits(uint8_t bus, uint32_t selected_address, int byte_idx, int bit_idx, uint8_t find_bus, bool equal, int min_msgs_cnt); void find(); diff --git a/tools/cabana/utils/export.cc b/tools/cabana/utils/export.cc index 79ca97ba8f..a7f910193f 100644 --- a/tools/cabana/utils/export.cc +++ b/tools/cabana/utils/export.cc @@ -26,7 +26,7 @@ void exportSignalsToCSV(const QString &file_name, const MessageId &msg_id) { QTextStream stream(&file); stream << "time,addr,bus"; for (auto s : msg->sigs) - stream << "," << s->name; + stream << "," << s->name.c_str(); stream << "\n"; for (auto e : can->events(msg_id)) { diff --git a/tools/cabana/utils/util.cc b/tools/cabana/utils/util.cc index 61e4ee0514..50ab764423 100644 --- a/tools/cabana/utils/util.cc +++ b/tools/cabana/utils/util.cc @@ -17,8 +17,7 @@ #include #include #include -#include -#include +#include #include "common/util.h" // SegmentTree @@ -278,7 +277,7 @@ QString signalToolTip(const cabana::Signal *sig) { Start Bit: %2 Size: %3
MSB: %4 LSB: %5
Little Endian: %6 Signed: %7 - )").arg(sig->name).arg(sig->start_bit).arg(sig->size).arg(sig->msb).arg(sig->lsb) + )").arg(QString::fromStdString(sig->name)).arg(sig->start_bit).arg(sig->size).arg(sig->msb).arg(sig->lsb) .arg(sig->is_little_endian ? "Y" : "N").arg(sig->is_signed ? "Y" : "N"); } @@ -325,36 +324,49 @@ void initApp(int argc, char *argv[], bool disable_hidpi) { setSurfaceFormat(); } -static QHash load_bootstrap_icons() { - QHash icons; +static std::unordered_map load_bootstrap_icons() { + std::unordered_map icons; QFile f(":/bootstrap-icons.svg"); if (f.open(QIODevice::ReadOnly | QIODevice::Text)) { - QDomDocument xml; - xml.setContent(&f); - QDomNode n = xml.documentElement().firstChild(); - while (!n.isNull()) { - QDomElement e = n.toElement(); - if (!e.isNull() && e.hasAttribute("id")) { - QString svg_str; - QTextStream stream(&svg_str); - n.save(stream, 0); - svg_str.replace("", ""); - icons[e.attribute("id")] = svg_str.toUtf8(); + std::string content = f.readAll().toStdString(); + const std::string sym_open = " with + svg_str.replace(0, 7, " ""); // "" (9) -> "" (6) + icons[id] = std::move(svg_str); + } } - n = n.nextSibling(); + pos = end; } } return icons; } QPixmap bootstrapPixmap(const QString &id) { - static QHash icons = load_bootstrap_icons(); + static auto icons = load_bootstrap_icons(); QPixmap pixmap; - if (auto it = icons.find(id); it != icons.end()) { - pixmap.loadFromData(it.value(), "svg"); + auto it = icons.find(id.toStdString()); + if (it != icons.end()) { + pixmap.loadFromData((const uchar *)it->second.data(), it->second.size(), "svg"); } return pixmap; } diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index a05bf24b4a..f203ec663e 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -1,6 +1,7 @@ #include "tools/cabana/videowidget.h" #include +#include #include #include @@ -9,7 +10,6 @@ #include #include #include -#include #include "tools/cabana/tools/routeinfo.h" @@ -334,19 +334,31 @@ StreamCameraView::StreamCameraView(std::string stream_name, VisionStreamType str void StreamCameraView::parseQLog(std::shared_ptr qlog) { std::mutex mutex; - QtConcurrent::blockingMap(qlog->events.cbegin(), qlog->events.cend(), [this, &mutex](const Event &e) { - if (e.which == cereal::Event::Which::THUMBNAIL) { - capnp::FlatArrayMessageReader reader(e.data); - auto thumb_data = reader.getRoot().getThumbnail(); - auto image_data = thumb_data.getThumbnail(); - if (QPixmap thumb; thumb.loadFromData(image_data.begin(), image_data.size(), "jpeg")) { - QPixmap generated_thumb = generateThumbnail(thumb, can->toSeconds(thumb_data.getTimestampEof())); - std::lock_guard lock(mutex); - thumbnails[thumb_data.getTimestampEof()] = generated_thumb; - big_thumbnails[thumb_data.getTimestampEof()] = thumb; + const auto &events = qlog->events; + unsigned int num_threads = std::max(1u, std::thread::hardware_concurrency()); + size_t chunk = (events.size() + num_threads - 1) / num_threads; + std::vector threads; + for (unsigned int t = 0; t < num_threads && t * chunk < events.size(); ++t) { + size_t start = t * chunk; + size_t end = std::min(start + chunk, events.size()); + threads.emplace_back([this, &mutex, &events, start, end]() { + for (size_t i = start; i < end; ++i) { + const Event &e = events[i]; + if (e.which == cereal::Event::Which::THUMBNAIL) { + capnp::FlatArrayMessageReader reader(e.data); + auto thumb_data = reader.getRoot().getThumbnail(); + auto image_data = thumb_data.getThumbnail(); + if (QPixmap thumb; thumb.loadFromData(image_data.begin(), image_data.size(), "jpeg")) { + QPixmap generated_thumb = generateThumbnail(thumb, can->toSeconds(thumb_data.getTimestampEof())); + std::lock_guard lock(mutex); + thumbnails[thumb_data.getTimestampEof()] = generated_thumb; + big_thumbnails[thumb_data.getTimestampEof()] = thumb; + } + } } - } - }); + }); + } + for (auto &th : threads) th.join(); update(); } @@ -384,9 +396,9 @@ QPixmap StreamCameraView::generateThumbnail(QPixmap thumb, double seconds) { void StreamCameraView::drawScrubThumbnail(QPainter &p) { p.fillRect(rect(), Qt::black); - auto it = big_thumbnails.lowerBound(can->toMonoTime(thumbnail_dispaly_time)); + auto it = big_thumbnails.lower_bound(can->toMonoTime(thumbnail_dispaly_time)); if (it != big_thumbnails.end()) { - QPixmap scaled_thumb = it.value().scaled(rect().size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); + QPixmap scaled_thumb = it->second.scaled(rect().size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); QRect thumb_rect(rect().center() - scaled_thumb.rect().center(), scaled_thumb.size()); p.drawPixmap(thumb_rect.topLeft(), scaled_thumb); drawTime(p, thumb_rect, thumbnail_dispaly_time); @@ -394,9 +406,9 @@ void StreamCameraView::drawScrubThumbnail(QPainter &p) { } void StreamCameraView::drawThumbnail(QPainter &p) { - auto it = thumbnails.lowerBound(can->toMonoTime(thumbnail_dispaly_time)); + auto it = thumbnails.lower_bound(can->toMonoTime(thumbnail_dispaly_time)); if (it != thumbnails.end()) { - const QPixmap &thumb = it.value(); + const QPixmap &thumb = it->second; auto [min_sec, max_sec] = can->timeRange().value_or(std::make_pair(can->minSeconds(), can->maxSeconds())); int pos = (thumbnail_dispaly_time - min_sec) * width() / (max_sec - min_sec); int x = std::clamp(pos - thumb.width() / 2, THUMBNAIL_MARGIN, width() - thumb.width() - THUMBNAIL_MARGIN + 1); diff --git a/tools/cabana/videowidget.h b/tools/cabana/videowidget.h index 6da0023123..e52e92ebd1 100644 --- a/tools/cabana/videowidget.h +++ b/tools/cabana/videowidget.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -47,8 +48,8 @@ private: void drawTime(QPainter &p, const QRect &rect, double seconds); QPropertyAnimation *fade_animation; - QMap big_thumbnails; - QMap thumbnails; + std::map big_thumbnails; + std::map thumbnails; double thumbnail_dispaly_time = -1; friend class VideoWidget; }; diff --git a/tools/tuning/measure_steering_accuracy.py b/tools/car_porting/measure_steering_accuracy.py similarity index 100% rename from tools/tuning/measure_steering_accuracy.py rename to tools/car_porting/measure_steering_accuracy.py diff --git a/tools/clip/run.py b/tools/clip/run.py index a5587f239b..ed2a5075b4 100755 --- a/tools/clip/run.py +++ b/tools/clip/run.py @@ -63,8 +63,10 @@ def parse_args(): return args -def setup_env(output_path: str, big: bool = False, speed: int = 1, target_mb: float = 0, duration: int = 0): - os.environ.update({"RECORD": "1", "OFFSCREEN": "1", "RECORD_OUTPUT": str(Path(output_path).with_suffix(".mp4"))}) +def setup_env(output_path: str, big: bool = False, speed: int = 1, target_mb: float = 0, duration: int = 0, headless: bool = True): + os.environ.update({"RECORD": "1", "RECORD_OUTPUT": str(Path(output_path).with_suffix(".mp4"))}) + if headless: + os.environ["OFFSCREEN"] = "1" if speed > 1: os.environ["RECORD_SPEED"] = str(speed) if target_mb > 0 and duration > 0: @@ -302,11 +304,11 @@ def clip(route: Route, output: str, start: int, end: int, headless: bool = True, logger.error("No messages to render") sys.exit(1) - metadata = load_route_metadata(route) if show_metadata else None if headless: rl.set_config_flags(rl.ConfigFlags.FLAG_WINDOW_HIDDEN) with OpenpilotPrefix(shared_download_cache=True): + metadata = load_route_metadata(route) if show_metadata else None camera_paths = route.qcamera_paths() if use_qcam else route.camera_paths() frame_queue = FrameQueue(camera_paths, start, end, fps=FRAMERATE, use_qcam=use_qcam) @@ -349,8 +351,9 @@ def main(): logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s\t%(message)s") args = parse_args() - setup_env(args.output, big=args.big, speed=args.speed, target_mb=args.file_size, duration=args.end - args.start) - clip(Route(args.route, data_dir=args.data_dir), args.output, args.start, args.end, not args.windowed, + headless = not args.windowed + setup_env(args.output, big=args.big, speed=args.speed, target_mb=args.file_size, duration=args.end - args.start, headless=headless) + clip(Route(args.route, data_dir=args.data_dir), args.output, args.start, args.end, headless, args.big, args.title, not args.no_metadata, not args.no_time_overlay, args.qcam) diff --git a/tools/jotpluggler/README.md b/tools/jotpluggler/README.md deleted file mode 100644 index d5e4b8ab0f..0000000000 --- a/tools/jotpluggler/README.md +++ /dev/null @@ -1,67 +0,0 @@ -# JotPluggler - -JotPluggler is a tool to quickly visualize openpilot logs. - -## Usage - -``` -$ ./jotpluggler/pluggle.py -h -usage: pluggle.py [-h] [--demo] [--layout LAYOUT] [route] - -A tool for visualizing openpilot logs. - -positional arguments: - route Optional route name to load on startup. - -options: - -h, --help show this help message and exit - --demo Use the demo route instead of providing one - --layout LAYOUT Path to YAML layout file to load on startup -``` - -Example using route name: - -`./pluggle.py "5beb9b58bd12b691/0000010a--a51155e496"` - -Examples using segment: - -`./pluggle.py "5beb9b58bd12b691/0000010a--a51155e496/1"` - -`./pluggle.py "5beb9b58bd12b691/0000010a--a51155e496/1/q" # use qlogs` - -Example using segment range: - -`./pluggle.py "5beb9b58bd12b691/0000010a--a51155e496/0:1"` - -## Demo - -For a quick demo, run this command: - -`./pluggle.py --demo --layout=layouts/torque-controller.yaml` - - -## Basic Usage/Features: -- The text box to load a route is a the top left of the page, accepts standard openpilot format routes (e.g. `5beb9b58bd12b691/0000010a--a51155e496/0:1`, `https://connect.comma.ai/5beb9b58bd12b691/0000010a--a51155e496/`) -- The Play/Pause button is at the bottom of the screen, you can drag the bottom slider to seek. The timeline in timeseries plots are synced with the slider. -- The Timeseries List sidebar has several dropdowns, the fields each show the field name and value, synced with the timeline (will show N/A until the time of the first message in that field is reached). -- There is a search bar for the timeseries list, you can search for structs or fields, or both by separating with a "/" -- You can drag and drop any numeric/boolean field from the timeseries list into a timeseries panel. -- You can create more panels with the split buttons (buttons with two rectangles, either horizontal or vertical). You can resize the panels by dragging the grip in between any panel. -- You can load and save layouts with the corresponding buttons. Layouts will save all tabs, panels, titles, timeseries, etc. - -## Layouts - -If you create a layout that's useful for others, consider upstreaming it. - -## Plot Interaction Controls - -- **Left click and drag within the plot area** to pan X - - Left click and drag on an axis to pan an individual axis (disabled for Y-axis) -- **Scroll in the plot area** to zoom in X axes, Y-axis is autofit - - Scroll on an axis to zoom an individual axis -- **Right click and drag** to select data and zoom into the selected data - - Left click while box selecting to cancel the selection -- **Double left click** to fit all visible data - - Double left click on an axis to fit the individual axis (disabled for Y-axis, always autofit) -- **Double right click** to open the plot context menu -- **Click legend label icons** to show/hide plot items diff --git a/tools/jotpluggler/assets/pause.png b/tools/jotpluggler/assets/pause.png deleted file mode 100644 index 8040099831..0000000000 --- a/tools/jotpluggler/assets/pause.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3ea96d8193eb9067a5efdc5d88a3099730ecafa40efcd09d7402bb3efd723603 -size 2305 diff --git a/tools/jotpluggler/assets/play.png b/tools/jotpluggler/assets/play.png deleted file mode 100644 index b1556cf0ab..0000000000 --- a/tools/jotpluggler/assets/play.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:53097ac5403b725ff1841dfa186ea770b4bb3714205824bde36ec3c2a0fb5dba -size 2758 diff --git a/tools/jotpluggler/assets/plus.png b/tools/jotpluggler/assets/plus.png deleted file mode 100644 index 6f8388b24d..0000000000 --- a/tools/jotpluggler/assets/plus.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:248b71eafd1b42b0861da92114da3d625221cd88121fff01e0514bf3d79ff3b1 -size 1364 diff --git a/tools/jotpluggler/assets/split_h.png b/tools/jotpluggler/assets/split_h.png deleted file mode 100644 index 4fd88806e1..0000000000 --- a/tools/jotpluggler/assets/split_h.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:54dd035ff898d881509fa686c402a61af8ef5fb408b92414722da01f773b0d33 -size 2900 diff --git a/tools/jotpluggler/assets/split_v.png b/tools/jotpluggler/assets/split_v.png deleted file mode 100644 index 752e62a4ae..0000000000 --- a/tools/jotpluggler/assets/split_v.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:adbd4e5df1f58694dca9dde46d1d95b4e7471684e42e6bca9f41ea5d346e67c5 -size 3669 diff --git a/tools/jotpluggler/assets/x.png b/tools/jotpluggler/assets/x.png deleted file mode 100644 index 3b2eabd447..0000000000 --- a/tools/jotpluggler/assets/x.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a6d9c90cb0dd906e0b15e1f7f3fd9f0dfad3c3b0b34eeed7a7882768dc5f3961 -size 2053 diff --git a/tools/jotpluggler/data.py b/tools/jotpluggler/data.py deleted file mode 100644 index 756b87bd20..0000000000 --- a/tools/jotpluggler/data.py +++ /dev/null @@ -1,360 +0,0 @@ -import numpy as np -import threading -import multiprocessing -import bisect -from collections import defaultdict -from tqdm import tqdm -from openpilot.common.swaglog import cloudlog -from openpilot.selfdrive.test.process_replay.migration import migrate_all -from openpilot.tools.lib.logreader import _LogFileReader, LogReader - - -def flatten_dict(d: dict, sep: str = "/", prefix: str | None = None) -> dict: - result = {} - stack: list[tuple] = [(d, prefix)] - - while stack: - obj, current_prefix = stack.pop() - - if isinstance(obj, dict): - for key, val in obj.items(): - new_prefix = key if current_prefix is None else f"{current_prefix}{sep}{key}" - if isinstance(val, (dict, list)): - stack.append((val, new_prefix)) - else: - result[new_prefix] = val - elif isinstance(obj, list): - for i, item in enumerate(obj): - new_prefix = f"{current_prefix}{sep}{i}" - if isinstance(item, (dict, list)): - stack.append((item, new_prefix)) - else: - result[new_prefix] = item - else: - if current_prefix is not None: - result[current_prefix] = obj - return result - - -def extract_field_types(schema, prefix, field_types_dict): - stack = [(schema, prefix)] - - while stack: - current_schema, current_prefix = stack.pop() - - for field in current_schema.fields_list: - field_name = field.proto.name - field_path = f"{current_prefix}/{field_name}" - field_proto = field.proto - field_which = field_proto.which() - - field_type = field_proto.slot.type.which() if field_which == 'slot' else field_which - field_types_dict[field_path] = field_type - - if field_which == 'slot': - slot_type = field_proto.slot.type - type_which = slot_type.which() - - if type_which == 'list': - element_type = slot_type.list.elementType.which() - list_path = f"{field_path}/*" - field_types_dict[list_path] = element_type - - if element_type == 'struct': - stack.append((field.schema.elementType, list_path)) - - elif type_which == 'struct': - stack.append((field.schema, field_path)) - - elif field_which == 'group': - stack.append((field.schema, field_path)) - - -def _convert_to_optimal_dtype(values_list, capnp_type): - dtype_mapping = { - 'bool': np.bool_, 'int8': np.int8, 'int16': np.int16, 'int32': np.int32, 'int64': np.int64, - 'uint8': np.uint8, 'uint16': np.uint16, 'uint32': np.uint32, 'uint64': np.uint64, - 'float32': np.float32, 'float64': np.float64, 'text': object, 'data': object, - 'enum': object, 'anyPointer': object, - } - - target_dtype = dtype_mapping.get(capnp_type, object) - return np.array(values_list, dtype=target_dtype) - - -def _match_field_type(field_path, field_types): - if field_path in field_types: - return field_types[field_path] - - path_parts = field_path.split('/') - template_parts = [p if not p.isdigit() else '*' for p in path_parts] - template_path = '/'.join(template_parts) - return field_types.get(template_path) - - -def _get_field_times_values(segment, field_name): - if field_name not in segment: - return None, None - - field_data = segment[field_name] - segment_times = segment['t'] - - if field_data['sparse']: - if len(field_data['t_index']) == 0: - return None, None - return segment_times[field_data['t_index']], field_data['values'] - else: - return segment_times, field_data['values'] - - -def msgs_to_time_series(msgs): - """Extract scalar fields and return (time_series_data, start_time, end_time).""" - collected_data = defaultdict(lambda: {'timestamps': [], 'columns': defaultdict(list), 'sparse_fields': set()}) - field_types = {} - extracted_schemas = set() - min_time = max_time = None - - for msg in msgs: - typ = msg.which() - timestamp = msg.logMonoTime * 1e-9 - if typ != 'initData': - if min_time is None: - min_time = timestamp - max_time = timestamp - - sub_msg = getattr(msg, typ) - if not hasattr(sub_msg, 'to_dict'): - continue - - if hasattr(sub_msg, 'schema') and typ not in extracted_schemas: - extract_field_types(sub_msg.schema, typ, field_types) - extracted_schemas.add(typ) - - try: - msg_dict = sub_msg.to_dict(verbose=True) - except Exception as e: - cloudlog.warning(f"Failed to convert sub_msg.to_dict() for message of type: {typ}: {e}") - continue - - flat_dict = flatten_dict(msg_dict) - flat_dict['_valid'] = msg.valid - field_types[f"{typ}/_valid"] = 'bool' - - type_data = collected_data[typ] - columns, sparse_fields = type_data['columns'], type_data['sparse_fields'] - known_fields = set(columns.keys()) - missing_fields = known_fields - flat_dict.keys() - - for field, value in flat_dict.items(): - if field not in known_fields and type_data['timestamps']: - sparse_fields.add(field) - columns[field].append(value) - if value is None: - sparse_fields.add(field) - - for field in missing_fields: - columns[field].append(None) - sparse_fields.add(field) - - type_data['timestamps'].append(timestamp) - - final_result = {} - for typ, data in collected_data.items(): - if not data['timestamps']: - continue - - typ_result = {'t': np.array(data['timestamps'], dtype=np.float64)} - sparse_fields = data['sparse_fields'] - - for field_name, values in data['columns'].items(): - if len(values) < len(data['timestamps']): - values = [None] * (len(data['timestamps']) - len(values)) + values - sparse_fields.add(field_name) - - capnp_type = _match_field_type(f"{typ}/{field_name}", field_types) - - if field_name in sparse_fields: # extract non-None values and their indices - non_none_indices = [] - non_none_values = [] - for i, value in enumerate(values): - if value is not None: - non_none_indices.append(i) - non_none_values.append(value) - - if non_none_values: # check if indices > uint16 max, currently would require a 1000+ Hz signal since indices are within segments - assert max(non_none_indices) <= 65535, f"Sparse field {typ}/{field_name} has timestamp indices exceeding uint16 max. Max: {max(non_none_indices)}" - - typ_result[field_name] = { - 'values': _convert_to_optimal_dtype(non_none_values, capnp_type), - 'sparse': True, - 't_index': np.array(non_none_indices, dtype=np.uint16), - } - else: # dense representation - typ_result[field_name] = {'values': _convert_to_optimal_dtype(values, capnp_type), 'sparse': False} - - final_result[typ] = typ_result - - return final_result, min_time or 0.0, max_time or 0.0 - - -def _process_segment(segment_identifier: str): - try: - lr = _LogFileReader(segment_identifier, sort_by_time=True) - migrated_msgs = migrate_all(lr) - return msgs_to_time_series(migrated_msgs) - except Exception as e: - cloudlog.warning(f"Warning: Failed to process segment {segment_identifier}: {e}") - return {}, 0.0, 0.0 - - -class DataManager: - def __init__(self): - self._segments = [] - self._segment_starts = [] - self._start_time = 0.0 - self._duration = 0.0 - self._paths = set() - self._observers = [] - self._loading = False - self._lock = threading.RLock() - - def load_route(self, route: str) -> None: - if self._loading: - return - self._reset() - threading.Thread(target=self._load_async, args=(route,), daemon=True).start() - - def get_timeseries(self, path: str): - with self._lock: - msg_type, field = path.split('/', 1) - times, values = [], [] - - for segment in self._segments: - if msg_type in segment: - field_times, field_values = _get_field_times_values(segment[msg_type], field) - if field_times is not None: - times.append(field_times) - values.append(field_values) - - if not times: - return np.array([]), np.array([]) - - combined_times = np.concatenate(times) - self._start_time - - if len(values) > 1: - first_dtype = values[0].dtype - if all(arr.dtype == first_dtype for arr in values): # check if all arrays have compatible dtypes - combined_values = np.concatenate(values) - else: - combined_values = np.concatenate([arr.astype(object) for arr in values]) - else: - combined_values = values[0] if values else np.array([]) - - return combined_times, combined_values - - def get_value_at(self, path: str, time: float): - with self._lock: - MAX_LOOKBACK = 5.0 # seconds - absolute_time = self._start_time + time - message_type, field = path.split('/', 1) - current_index = bisect.bisect_right(self._segment_starts, absolute_time) - 1 - for index in (current_index, current_index - 1): - if not 0 <= index < len(self._segments): - continue - segment = self._segments[index].get(message_type) - if not segment: - continue - times, values = _get_field_times_values(segment, field) - if times is None or len(times) == 0 or (index != current_index and absolute_time - times[-1] > MAX_LOOKBACK): - continue - position = np.searchsorted(times, absolute_time, 'right') - 1 - if position >= 0 and absolute_time - times[position] <= MAX_LOOKBACK: - return values[position] - return None - - def get_all_paths(self): - with self._lock: - return sorted(self._paths) - - def get_duration(self): - with self._lock: - return self._duration - - def is_plottable(self, path: str): - _, values = self.get_timeseries(path) - if len(values) == 0: - return False - return np.issubdtype(values.dtype, np.number) or np.issubdtype(values.dtype, np.bool_) - - def add_observer(self, callback): - with self._lock: - self._observers.append(callback) - - def remove_observer(self, callback): - with self._lock: - if callback in self._observers: - self._observers.remove(callback) - - def _reset(self): - with self._lock: - self._loading = True - self._segments.clear() - self._segment_starts.clear() - self._paths.clear() - self._start_time = self._duration = 0.0 - observers = self._observers.copy() - - for callback in observers: - callback({'reset': True}) - - def _load_async(self, route: str): - try: - lr = LogReader(route, sort_by_time=True) - if not lr.logreader_identifiers: - cloudlog.warning(f"Warning: No log segments found for route: {route}") - return - - total_segments = len(lr.logreader_identifiers) - with self._lock: - observers = self._observers.copy() - for callback in observers: - callback({'metadata_loaded': True, 'total_segments': total_segments}) - - num_processes = max(1, multiprocessing.cpu_count() // 2) - with multiprocessing.Pool(processes=num_processes) as pool, tqdm(total=len(lr.logreader_identifiers), desc="Processing Segments") as pbar: - for segment_result, start_time, end_time in pool.imap(_process_segment, lr.logreader_identifiers): - pbar.update(1) - if segment_result: - self._add_segment(segment_result, start_time, end_time) - except Exception: - cloudlog.exception(f"Error loading route {route}:") - finally: - self._finalize_loading() - - def _add_segment(self, segment_data: dict, start_time: float, end_time: float): - with self._lock: - self._segments.append(segment_data) - self._segment_starts.append(start_time) - - if len(self._segments) == 1: - self._start_time = start_time - self._duration = end_time - self._start_time - - for msg_type, data in segment_data.items(): - for field_name in data.keys(): - if field_name != 't': - self._paths.add(f"{msg_type}/{field_name}") - - observers = self._observers.copy() - - for callback in observers: - callback({'segment_added': True, 'duration': self._duration, 'segment_count': len(self._segments)}) - - def _finalize_loading(self): - with self._lock: - self._loading = False - observers = self._observers.copy() - duration = self._duration - - for callback in observers: - callback({'loading_complete': True, 'duration': duration}) diff --git a/tools/jotpluggler/datatree.py b/tools/jotpluggler/datatree.py deleted file mode 100644 index 4f3219dc1b..0000000000 --- a/tools/jotpluggler/datatree.py +++ /dev/null @@ -1,315 +0,0 @@ -import os -import re -import threading -import numpy as np -import dearpygui.dearpygui as dpg - - -class DataTreeNode: - def __init__(self, name: str, full_path: str = "", parent=None): - self.name = name - self.full_path = full_path - self.parent = parent - self.children: dict[str, DataTreeNode] = {} - self.filtered_children: dict[str, DataTreeNode] = {} - self.created_children: dict[str, DataTreeNode] = {} - self.is_leaf = False - self.is_plottable: bool | None = None - self.ui_created = False - self.children_ui_created = False - self.ui_tag: str | None = None - - -class DataTree: - MAX_NODES_PER_FRAME = 50 - - def __init__(self, data_manager, playback_manager): - self.data_manager = data_manager - self.playback_manager = playback_manager - self.current_search = "" - self.data_tree = DataTreeNode(name="root") - self._build_queue: dict[str, tuple[DataTreeNode, DataTreeNode, str | int]] = {} # full_path -> (node, parent, before_tag) - self._current_created_paths: set[str] = set() - self._current_filtered_paths: set[str] = set() - self._path_to_node: dict[str, DataTreeNode] = {} # full_path -> node - self._expanded_tags: set[str] = set() - self._item_handlers: dict[str, str] = {} # ui_tag -> handler_tag - self._char_width = None - self._queued_search = None - self._new_data = False - self._ui_lock = threading.RLock() - self._handlers_to_delete = [] - self.data_manager.add_observer(self._on_data_loaded) - - def create_ui(self, parent_tag: str): - with dpg.child_window(parent=parent_tag, border=False, width=-1, height=-1): - dpg.add_text("Timeseries List") - dpg.add_separator() - dpg.add_input_text(tag="search_input", width=-1, hint="Search fields...", callback=self.search_data) - dpg.add_separator() - with dpg.child_window(border=False, width=-1, height=-1): - with dpg.group(tag="data_tree_container"): - pass - - def _on_data_loaded(self, data: dict): - with self._ui_lock: - if data.get('segment_added') or data.get('reset'): - self._new_data = True - - def update_frame(self, font): - if self._handlers_to_delete: # we need to do everything in main thread, frame callbacks are flaky - dpg.render_dearpygui_frame() # wait a frame to ensure queued callbacks are done - with self._ui_lock: - for handler in self._handlers_to_delete: - dpg.delete_item(handler) - self._handlers_to_delete.clear() - - with self._ui_lock: - if self._char_width is None: - if size := dpg.get_text_size(" ", font=font): - self._char_width = size[0] / 2 # we scale font 2x and downscale to fix hidpi bug - - if self._new_data: - self._process_path_change() - self._new_data = False - return - - if self._queued_search is not None: - self.current_search = self._queued_search - self._process_path_change() - self._queued_search = None - return - - nodes_processed = 0 - while self._build_queue and nodes_processed < self.MAX_NODES_PER_FRAME: - child_node, parent, before_tag = self._build_queue.pop(next(iter(self._build_queue))) - parent_tag = "data_tree_container" if parent.name == "root" else parent.ui_tag - if not child_node.ui_created: - if child_node.is_leaf: - self._create_leaf_ui(child_node, parent_tag, before_tag) - else: - self._create_tree_node_ui(child_node, parent_tag, before_tag) - parent.created_children[child_node.name] = parent.children[child_node.name] - self._current_created_paths.add(child_node.full_path) - nodes_processed += 1 - - def _process_path_change(self): - self._build_queue.clear() - search_term = self.current_search.strip().lower() - all_paths = set(self.data_manager.get_all_paths()) - new_filtered_leafs = {path for path in all_paths if self._should_show_path(path, search_term)} - new_filtered_paths = set(new_filtered_leafs) - for path in new_filtered_leafs: - parts = path.split('/') - for i in range(1, len(parts)): - prefix = '/'.join(parts[:i]) - new_filtered_paths.add(prefix) - created_paths_to_remove = self._current_created_paths - new_filtered_paths - filtered_paths_to_remove = self._current_filtered_paths - new_filtered_leafs - - if created_paths_to_remove or filtered_paths_to_remove: - self._remove_paths_from_tree(created_paths_to_remove, filtered_paths_to_remove) - self._apply_expansion_to_tree(self.data_tree, search_term) - - paths_to_add = new_filtered_leafs - self._current_created_paths - if paths_to_add: - self._add_paths_to_tree(paths_to_add) - self._apply_expansion_to_tree(self.data_tree, search_term) - self._current_filtered_paths = new_filtered_paths - - def _remove_paths_from_tree(self, created_paths_to_remove, filtered_paths_to_remove): - for path in sorted(created_paths_to_remove, reverse=True): - current_node = self._path_to_node[path] - - if len(current_node.created_children) == 0: - self._current_created_paths.remove(current_node.full_path) - if item_handler_tag := self._item_handlers.get(current_node.ui_tag): - dpg.configure_item(item_handler_tag, show=False) - self._handlers_to_delete.append(item_handler_tag) - del self._item_handlers[current_node.ui_tag] - dpg.delete_item(current_node.ui_tag) - current_node.ui_created = False - current_node.ui_tag = None - current_node.children_ui_created = False - del current_node.parent.created_children[current_node.name] - del current_node.parent.filtered_children[current_node.name] - - for path in filtered_paths_to_remove: - parts = path.split('/') - current_node = self._path_to_node[path] - - part_array_index = -1 - while len(current_node.filtered_children) == 0 and part_array_index >= -len(parts): - current_node = current_node.parent - if parts[part_array_index] in current_node.filtered_children: - del current_node.filtered_children[parts[part_array_index]] - part_array_index -= 1 - - def _add_paths_to_tree(self, paths): - parent_nodes_to_recheck = set() - for path in sorted(paths): - parts = path.split('/') - current_node = self.data_tree - current_path_prefix = "" - - for i, part in enumerate(parts): - current_path_prefix = f"{current_path_prefix}/{part}" if current_path_prefix else part - if i < len(parts): - parent_nodes_to_recheck.add(current_node) # for incremental changes from new data - if part not in current_node.children: - current_node.children[part] = DataTreeNode(name=part, full_path=current_path_prefix, parent=current_node) - self._path_to_node[current_path_prefix] = current_node.children[part] - current_node.filtered_children[part] = current_node.children[part] - current_node = current_node.children[part] - - if not current_node.is_leaf: - current_node.is_leaf = True - - for p_node in parent_nodes_to_recheck: - p_node.children_ui_created = False - self._request_children_build(p_node) - - def _get_node_label_and_expand(self, node: DataTreeNode, search_term: str): - label = f"{node.name} ({len(node.filtered_children)} fields)" - expand = len(search_term) > 0 and any(search_term in path for path in self._get_descendant_paths(node)) - if expand and node.parent and len(node.parent.filtered_children) > 100 and len(node.filtered_children) > 2: - label += " (+)" # symbol for large lists which aren't fully expanded for performance (only affects procLog rn) - expand = False - return label, expand - - def _apply_expansion_to_tree(self, node: DataTreeNode, search_term: str): - if node.ui_created and not node.is_leaf and node.ui_tag and dpg.does_item_exist(node.ui_tag): - label, expand = self._get_node_label_and_expand(node, search_term) - if expand: - self._expanded_tags.add(node.ui_tag) - dpg.set_value(node.ui_tag, expand) - elif node.ui_tag in self._expanded_tags: # not expanded and was expanded - self._expanded_tags.remove(node.ui_tag) - dpg.set_value(node.ui_tag, expand) - dpg.delete_item(node.ui_tag, children_only=True) # delete children (not visible since collapsed) - self._reset_ui_state_recursive(node) - node.children_ui_created = False - dpg.set_item_label(node.ui_tag, label) - for child in node.created_children.values(): - self._apply_expansion_to_tree(child, search_term) - - def _reset_ui_state_recursive(self, node: DataTreeNode): - for child in node.created_children.values(): - if child.ui_tag is not None: - if item_handler_tag := self._item_handlers.get(child.ui_tag): - self._handlers_to_delete.append(item_handler_tag) - dpg.configure_item(item_handler_tag, show=False) - del self._item_handlers[child.ui_tag] - self._reset_ui_state_recursive(child) - child.ui_created = False - child.ui_tag = None - child.children_ui_created = False - self._current_created_paths.remove(child.full_path) - node.created_children.clear() - - def search_data(self): - with self._ui_lock: - self._queued_search = dpg.get_value("search_input") - - def _create_tree_node_ui(self, node: DataTreeNode, parent_tag: str, before: str | int): - node.ui_tag = f"tree_{node.full_path}" - search_term = self.current_search.strip().lower() - label, expand = self._get_node_label_and_expand(node, search_term) - if expand: - self._expanded_tags.add(node.ui_tag) - elif node.ui_tag in self._expanded_tags: - self._expanded_tags.remove(node.ui_tag) - - with dpg.tree_node( - label=label, parent=parent_tag, tag=node.ui_tag, default_open=expand, open_on_arrow=True, open_on_double_click=True, before=before, delay_search=True - ): - with dpg.item_handler_registry() as handler_tag: - dpg.add_item_toggled_open_handler(callback=lambda s, a, u: self._request_children_build(node)) - dpg.add_item_visible_handler(callback=lambda s, a, u: self._request_children_build(node)) - dpg.bind_item_handler_registry(node.ui_tag, handler_tag) - self._item_handlers[node.ui_tag] = handler_tag - node.ui_created = True - - def _create_leaf_ui(self, node: DataTreeNode, parent_tag: str, before: str | int): - node.ui_tag = f"leaf_{node.full_path}" - with dpg.group(parent=parent_tag, tag=node.ui_tag, before=before, delay_search=True): - with dpg.table(header_row=False, policy=dpg.mvTable_SizingStretchProp, delay_search=True): - dpg.add_table_column(init_width_or_weight=0.5) - dpg.add_table_column(init_width_or_weight=0.5) - with dpg.table_row(): - dpg.add_text(node.name) - dpg.add_text("N/A", tag=f"value_{node.full_path}") - - if node.is_plottable is None: - node.is_plottable = self.data_manager.is_plottable(node.full_path) - if node.is_plottable: - with dpg.drag_payload(parent=node.ui_tag, drag_data=node.full_path, payload_type="TIMESERIES_PAYLOAD"): - dpg.add_text(f"Plot: {node.full_path}") - - with dpg.item_handler_registry() as handler_tag: - dpg.add_item_visible_handler(callback=self._on_item_visible, user_data=node.full_path) - dpg.bind_item_handler_registry(node.ui_tag, handler_tag) - self._item_handlers[node.ui_tag] = handler_tag - node.ui_created = True - - def _on_item_visible(self, sender, app_data, user_data): - with self._ui_lock: - path = user_data - value_tag = f"value_{path}" - if not dpg.does_item_exist(value_tag): - return - value_column_width = dpg.get_item_rect_size(f"leaf_{path}")[0] // 2 - value = self.data_manager.get_value_at(path, self.playback_manager.current_time_s) - if value is not None: - formatted_value = self.format_and_truncate(value, value_column_width, self._char_width) - dpg.set_value(value_tag, formatted_value) - else: - dpg.set_value(value_tag, "N/A") - - def _request_children_build(self, node: DataTreeNode): - with self._ui_lock: - if not node.children_ui_created and (node.name == "root" or (node.ui_tag is not None and dpg.get_value(node.ui_tag))): # check root or node expanded - sorted_children = sorted(node.filtered_children.values(), key=self._natural_sort_key) - next_existing: list[int | str] = [0] * len(sorted_children) - current_before_tag: int | str = 0 - - for i in range(len(sorted_children) - 1, -1, -1): # calculate "before_tag" for correct ordering when incrementally building tree - child = sorted_children[i] - next_existing[i] = current_before_tag - if child.ui_created: - candidate_tag = f"leaf_{child.full_path}" if child.is_leaf else f"tree_{child.full_path}" - if dpg.does_item_exist(candidate_tag): - current_before_tag = candidate_tag - - for i, child_node in enumerate(sorted_children): - if not child_node.ui_created: - before_tag = next_existing[i] - self._build_queue[child_node.full_path] = (child_node, node, before_tag) - node.children_ui_created = True - - def _should_show_path(self, path: str, search_term: str) -> bool: - if 'DEPRECATED' in path and not os.environ.get('SHOW_DEPRECATED'): - return False - return not search_term or search_term in path.lower() - - def _natural_sort_key(self, node: DataTreeNode): - node_type_key = node.is_leaf - parts = [int(p) if p.isdigit() else p.lower() for p in re.split(r'(\d+)', node.name) if p] - return (node_type_key, parts) - - def _get_descendant_paths(self, node: DataTreeNode): - for child_name, child_node in node.filtered_children.items(): - child_name_lower = child_name.lower() - if child_node.is_leaf: - yield child_name_lower - else: - for path in self._get_descendant_paths(child_node): - yield f"{child_name_lower}/{path}" - - @staticmethod - def format_and_truncate(value, available_width: float, char_width: float) -> str: - s = f"{value:.5f}" if np.issubdtype(type(value), np.floating) else str(value) - max_chars = int(available_width / char_width) - if len(s) > max_chars: - return s[: max(0, max_chars - 3)] + "..." - return s diff --git a/tools/jotpluggler/layout.py b/tools/jotpluggler/layout.py deleted file mode 100644 index 13fbee54e2..0000000000 --- a/tools/jotpluggler/layout.py +++ /dev/null @@ -1,477 +0,0 @@ -import dearpygui.dearpygui as dpg -from openpilot.tools.jotpluggler.data import DataManager -from openpilot.tools.jotpluggler.views import TimeSeriesPanel - -GRIP_SIZE = 4 -MIN_PANE_SIZE = 60 - -class LayoutManager: - def __init__(self, data_manager, playback_manager, worker_manager, scale: float = 1.0): - self.data_manager = data_manager - self.playback_manager = playback_manager - self.worker_manager = worker_manager - self.scale = scale - self.container_tag = "plot_layout_container" - self.tab_bar_tag = "tab_bar_container" - self.tab_content_tag = "tab_content_area" - - self.active_tab = 0 - initial_panel_layout = PanelLayoutManager(data_manager, playback_manager, worker_manager, scale) - self.tabs: dict = {0: {"name": "Tab 1", "panel_layout": initial_panel_layout}} - self._next_tab_id = self.active_tab + 1 - - def to_dict(self) -> dict: - return { - "tabs": { - str(tab_id): { - "name": tab_data["name"], - "panel_layout": tab_data["panel_layout"].to_dict() - } - for tab_id, tab_data in self.tabs.items() - } - } - - def clear_and_load_from_dict(self, data: dict): - tab_ids_to_close = list(self.tabs.keys()) - for tab_id in tab_ids_to_close: - self.close_tab(tab_id, force=True) - - for tab_id_str, tab_data in data["tabs"].items(): - tab_id = int(tab_id_str) - panel_layout = PanelLayoutManager.load_from_dict( - tab_data["panel_layout"], self.data_manager, self.playback_manager, - self.worker_manager, self.scale - ) - self.tabs[tab_id] = { - "name": tab_data["name"], - "panel_layout": panel_layout - } - - self.active_tab = min(self.tabs.keys()) if self.tabs else 0 - self._next_tab_id = max(self.tabs.keys()) + 1 if self.tabs else 1 - - def create_ui(self, parent_tag: str): - if dpg.does_item_exist(self.container_tag): - dpg.delete_item(self.container_tag) - - with dpg.child_window(tag=self.container_tag, parent=parent_tag, border=False, width=-1, height=-1, no_scrollbar=True, no_scroll_with_mouse=True): - self._create_tab_bar() - self._create_tab_content() - dpg.bind_item_theme(self.tab_bar_tag, "tab_bar_theme") - - def _create_tab_bar(self): - text_size = int(13 * self.scale) - with dpg.child_window(tag=self.tab_bar_tag, parent=self.container_tag, height=(text_size + 8), border=False, horizontal_scrollbar=True): - with dpg.group(horizontal=True, tag="tab_bar_group"): - for tab_id, tab_data in self.tabs.items(): - self._create_tab_ui(tab_id, tab_data["name"]) - dpg.add_image_button(texture_tag="plus_texture", callback=self.add_tab, width=text_size, height=text_size, tag="add_tab_button") - dpg.bind_item_theme("add_tab_button", "inactive_tab_theme") - - def _create_tab_ui(self, tab_id: int, tab_name: str): - text_size = int(13 * self.scale) - tab_width = int(140 * self.scale) - with dpg.child_window(width=tab_width, height=-1, border=False, no_scrollbar=True, tag=f"tab_window_{tab_id}", parent="tab_bar_group"): - with dpg.group(horizontal=True, tag=f"tab_group_{tab_id}"): - dpg.add_input_text( - default_value=tab_name, width=tab_width - text_size - 16, callback=lambda s, v, u: self.rename_tab(u, v), user_data=tab_id, tag=f"tab_input_{tab_id}" - ) - dpg.add_image_button( - texture_tag="x_texture", callback=lambda s, a, u: self.close_tab(u), user_data=tab_id, width=text_size, height=text_size, tag=f"tab_close_{tab_id}" - ) - with dpg.item_handler_registry(tag=f"tab_handler_{tab_id}"): - dpg.add_item_clicked_handler(callback=lambda s, a, u: self.switch_tab(u), user_data=tab_id) - dpg.bind_item_handler_registry(f"tab_group_{tab_id}", f"tab_handler_{tab_id}") - - theme_tag = "active_tab_theme" if tab_id == self.active_tab else "inactive_tab_theme" - dpg.bind_item_theme(f"tab_window_{tab_id}", theme_tag) - - def _create_tab_content(self): - with dpg.child_window(tag=self.tab_content_tag, parent=self.container_tag, border=False, width=-1, height=-1, no_scrollbar=True, no_scroll_with_mouse=True): - if self.active_tab in self.tabs: - active_panel_layout = self.tabs[self.active_tab]["panel_layout"] - active_panel_layout.create_ui() - - def add_tab(self): - new_panel_layout = PanelLayoutManager(self.data_manager, self.playback_manager, self.worker_manager, self.scale) - new_tab = {"name": f"Tab {self._next_tab_id + 1}", "panel_layout": new_panel_layout} - self.tabs[self._next_tab_id] = new_tab - self._create_tab_ui(self._next_tab_id, new_tab["name"]) - dpg.move_item("add_tab_button", parent="tab_bar_group") # move plus button to end - self.switch_tab(self._next_tab_id) - self._next_tab_id += 1 - - def close_tab(self, tab_id: int, force = False): - if len(self.tabs) <= 1 and not force: - return # don't allow closing the last tab - - tab_to_close = self.tabs[tab_id] - tab_to_close["panel_layout"].destroy_ui() - for suffix in ["window", "group", "input", "close", "handler"]: - tag = f"tab_{suffix}_{tab_id}" - if dpg.does_item_exist(tag): - dpg.delete_item(tag) - del self.tabs[tab_id] - - if self.active_tab == tab_id and self.tabs: # switch to another tab if we closed the active one - self.active_tab = next(iter(self.tabs.keys())) - self._switch_tab_content() - dpg.bind_item_theme(f"tab_window_{self.active_tab}", "active_tab_theme") - - def switch_tab(self, tab_id: int): - if tab_id == self.active_tab or tab_id not in self.tabs: - return - - current_panel_layout = self.tabs[self.active_tab]["panel_layout"] - current_panel_layout.destroy_ui() - dpg.bind_item_theme(f"tab_window_{self.active_tab}", "inactive_tab_theme") # deactivate old tab - self.active_tab = tab_id - dpg.bind_item_theme(f"tab_window_{tab_id}", "active_tab_theme") # activate new tab - self._switch_tab_content() - - def _switch_tab_content(self): - dpg.delete_item(self.tab_content_tag, children_only=True) - active_panel_layout = self.tabs[self.active_tab]["panel_layout"] - active_panel_layout.create_ui() - active_panel_layout.update_all_panels() - - def rename_tab(self, tab_id: int, new_name: str): - if tab_id in self.tabs: - self.tabs[tab_id]["name"] = new_name - - def update_all_panels(self): - self.tabs[self.active_tab]["panel_layout"].update_all_panels() - - def on_viewport_resize(self): - self.tabs[self.active_tab]["panel_layout"].on_viewport_resize() - -class PanelLayoutManager: - def __init__(self, data_manager: DataManager, playback_manager, worker_manager, scale: float = 1.0): - self.data_manager = data_manager - self.playback_manager = playback_manager - self.worker_manager = worker_manager - self.scale = scale - self.active_panels: list = [] - self.parent_tag = "tab_content_area" - self._queue_resize = False - self._created_handler_tags: set[str] = set() - - self.grip_size = int(GRIP_SIZE * self.scale) - self.min_pane_size = int(MIN_PANE_SIZE * self.scale) - - initial_panel = TimeSeriesPanel(data_manager, playback_manager, worker_manager) - self.layout: dict = {"type": "panel", "panel": initial_panel} - - def to_dict(self) -> dict: - return self._layout_to_dict(self.layout) - - def _layout_to_dict(self, layout: dict) -> dict: - if layout["type"] == "panel": - return { - "type": "panel", - "panel": layout["panel"].to_dict() - } - else: # split - return { - "type": "split", - "orientation": layout["orientation"], - "proportions": layout["proportions"], - "children": [self._layout_to_dict(child) for child in layout["children"]] - } - - @classmethod - def load_from_dict(cls, data: dict, data_manager, playback_manager, worker_manager, scale: float = 1.0): - manager = cls(data_manager, playback_manager, worker_manager, scale) - manager.layout = manager._dict_to_layout(data) - return manager - - def _dict_to_layout(self, data: dict) -> dict: - if data["type"] == "panel": - panel_data = data["panel"] - if panel_data["type"] == "timeseries": - panel = TimeSeriesPanel.load_from_dict( - panel_data, self.data_manager, self.playback_manager, self.worker_manager - ) - return {"type": "panel", "panel": panel} - else: - # Handle future panel types here or make a general mapping - raise ValueError(f"Unknown panel type: {panel_data['type']}") - else: # split - return { - "type": "split", - "orientation": data["orientation"], - "proportions": data["proportions"], - "children": [self._dict_to_layout(child) for child in data["children"]] - } - - def create_ui(self): - self.active_panels.clear() - if dpg.does_item_exist(self.parent_tag): - dpg.delete_item(self.parent_tag, children_only=True) - self._cleanup_all_handlers() - - container_width, container_height = dpg.get_item_rect_size(self.parent_tag) - if container_width == 0 and container_height == 0: - self._queue_resize = True - self._create_ui_recursive(self.layout, self.parent_tag, [], container_width, container_height) - - def destroy_ui(self): - self._cleanup_ui_recursive(self.layout, []) - self._cleanup_all_handlers() - self.active_panels.clear() - - def _cleanup_all_handlers(self): - for handler_tag in list(self._created_handler_tags): - if dpg.does_item_exist(handler_tag): - dpg.delete_item(handler_tag) - self._created_handler_tags.clear() - - def _create_ui_recursive(self, layout: dict, parent_tag: str, path: list[int], width: int, height: int): - if layout["type"] == "panel": - self._create_panel_ui(layout, parent_tag, path, width, height) - else: - self._create_split_ui(layout, parent_tag, path, width, height) - - def _create_panel_ui(self, layout: dict, parent_tag: str, path: list[int], width: int, height: int): - panel_tag = self._path_to_tag(path, "panel") - panel = layout["panel"] - self.active_panels.append(panel) - text_size = int(13 * self.scale) - bar_height = (text_size + 24) if width < int(329 * self.scale + 64) else (text_size + 8) # adjust height to allow for scrollbar - - with dpg.child_window(parent=parent_tag, border=False, width=-1, height=-1, no_scrollbar=True): - with dpg.group(horizontal=True): - with dpg.child_window(tag=panel_tag, width=-(text_size + 16), height=bar_height, horizontal_scrollbar=True, no_scroll_with_mouse=True, border=False): - with dpg.group(horizontal=True): - # if you change the widths make sure to change the sum of widths (currently 329 * scale) - dpg.add_input_text(default_value=panel.title, width=int(150 * self.scale), callback=lambda s, v: setattr(panel, "title", v)) - dpg.add_combo(items=["Time Series"], default_value="Time Series", width=int(100 * self.scale)) - dpg.add_button(label="Clear", callback=lambda: self.clear_panel(panel), width=int(40 * self.scale)) - dpg.add_image_button(texture_tag="split_h_texture", callback=lambda: self.split_panel(path, 0), width=text_size, height=text_size) - dpg.add_image_button(texture_tag="split_v_texture", callback=lambda: self.split_panel(path, 1), width=text_size, height=text_size) - dpg.add_image_button(texture_tag="x_texture", callback=lambda: self.delete_panel(path), width=text_size, height=text_size) - - dpg.add_separator() - - content_tag = self._path_to_tag(path, "content") - with dpg.child_window(tag=content_tag, border=False, height=-1, width=-1, no_scrollbar=True): - panel.create_ui(content_tag) - - def _create_split_ui(self, layout: dict, parent_tag: str, path: list[int], width: int, height: int): - split_tag = self._path_to_tag(path, "split") - orientation, _, pane_sizes = self._get_split_geometry(layout, (width, height)) - - with dpg.group(tag=split_tag, parent=parent_tag, horizontal=orientation == 0): - for i, child_layout in enumerate(layout["children"]): - child_path = path + [i] - container_tag = self._path_to_tag(child_path, "container") - pane_width, pane_height = [(pane_sizes[i], -1), (-1, pane_sizes[i])][orientation] # fill 2nd dim up to the border - with dpg.child_window(tag=container_tag, width=pane_width, height=pane_height, border=False, no_scrollbar=True): - child_width, child_height = [(pane_sizes[i], height), (width, pane_sizes[i])][orientation] - self._create_ui_recursive(child_layout, container_tag, child_path, child_width, child_height) - if i < len(layout["children"]) - 1: - self._create_grip(split_tag, path, i, orientation) - - def clear_panel(self, panel): - panel.clear() - - def delete_panel(self, panel_path: list[int]): - if not panel_path: # Root deletion - old_panel = self.layout["panel"] - old_panel.destroy_ui() - self.active_panels.remove(old_panel) - new_panel = TimeSeriesPanel(self.data_manager, self.playback_manager, self.worker_manager) - self.layout = {"type": "panel", "panel": new_panel} - self._rebuild_ui_at_path([]) - return - - parent, child_index = self._get_parent_and_index(panel_path) - layout_to_delete = parent["children"][child_index] - self._cleanup_ui_recursive(layout_to_delete, panel_path) - - parent["children"].pop(child_index) - parent["proportions"].pop(child_index) - - if len(parent["children"]) == 1: # remove parent and collapse - remaining_child = parent["children"][0] - if len(panel_path) == 1: # parent is at root level - promote remaining child to root - self.layout = remaining_child - self._rebuild_ui_at_path([]) - else: # replace parent with remaining child in grandparent - grandparent_path = panel_path[:-2] - parent_index = panel_path[-2] - self._replace_layout_at_path(grandparent_path + [parent_index], remaining_child) - self._rebuild_ui_at_path(grandparent_path + [parent_index]) - else: # redistribute proportions - equal_prop = 1.0 / len(parent["children"]) - parent["proportions"] = [equal_prop] * len(parent["children"]) - self._rebuild_ui_at_path(panel_path[:-1]) - - def split_panel(self, panel_path: list[int], orientation: int): - current_layout = self._get_layout_at_path(panel_path) - existing_panel = current_layout["panel"] - new_panel = TimeSeriesPanel(self.data_manager, self.playback_manager, self.worker_manager) - parent, child_index = self._get_parent_and_index(panel_path) - - if parent is None: # Root split - self.layout = { - "type": "split", - "orientation": orientation, - "children": [{"type": "panel", "panel": existing_panel}, {"type": "panel", "panel": new_panel}], - "proportions": [0.5, 0.5], - } - self._rebuild_ui_at_path([]) - elif parent["type"] == "split" and parent["orientation"] == orientation: # Same orientation - insert into existing split - parent["children"].insert(child_index + 1, {"type": "panel", "panel": new_panel}) - parent["proportions"] = [1.0 / len(parent["children"])] * len(parent["children"]) - self._rebuild_ui_at_path(panel_path[:-1]) - else: # Different orientation - create new split level - new_split = {"type": "split", "orientation": orientation, "children": [current_layout, {"type": "panel", "panel": new_panel}], "proportions": [0.5, 0.5]} - self._replace_layout_at_path(panel_path, new_split) - self._rebuild_ui_at_path(panel_path) - - def _rebuild_ui_at_path(self, path: list[int]): - layout = self._get_layout_at_path(path) - if path: - container_tag = self._path_to_tag(path, "container") - else: # Root update - container_tag = self.parent_tag - - self._cleanup_ui_recursive(layout, path) - dpg.delete_item(container_tag, children_only=True) - width, height = dpg.get_item_rect_size(container_tag) - self._create_ui_recursive(layout, container_tag, path, width, height) - - def _cleanup_ui_recursive(self, layout: dict, path: list[int]): - if layout["type"] == "panel": - panel = layout["panel"] - panel.destroy_ui() - if panel in self.active_panels: - self.active_panels.remove(panel) - else: - for i in range(len(layout["children"]) - 1): - handler_tag = f"{self._path_to_tag(path, f'grip_{i}')}_handler" - if dpg.does_item_exist(handler_tag): - dpg.delete_item(handler_tag) - self._created_handler_tags.discard(handler_tag) - - for i, child in enumerate(layout["children"]): - self._cleanup_ui_recursive(child, path + [i]) - - def update_all_panels(self): - if self._queue_resize: - if (size := dpg.get_item_rect_size(self.parent_tag)) != [0, 0]: - self._queue_resize = False - self._resize_splits_recursive(self.layout, [], *size) - for panel in self.active_panels: - panel.update() - - def on_viewport_resize(self): - self._resize_splits_recursive(self.layout, []) - - def _resize_splits_recursive(self, layout: dict, path: list[int], width: int | None = None, height: int | None = None): - if layout["type"] == "split": - split_tag = self._path_to_tag(path, "split") - if dpg.does_item_exist(split_tag): - available_sizes = (width, height) if width and height else dpg.get_item_rect_size(dpg.get_item_parent(split_tag)) - orientation, _, pane_sizes = self._get_split_geometry(layout, available_sizes) - size_properties = ("width", "height") - - for i, child_layout in enumerate(layout["children"]): - child_path = path + [i] - container_tag = self._path_to_tag(child_path, "container") - if dpg.does_item_exist(container_tag): - dpg.configure_item(container_tag, **{size_properties[orientation]: pane_sizes[i]}) - child_width, child_height = [(pane_sizes[i], available_sizes[1]), (available_sizes[0], pane_sizes[i])][orientation] - self._resize_splits_recursive(child_layout, child_path, child_width, child_height) - else: # leaf node/panel - adjust bar height to allow for scrollbar - panel_tag = self._path_to_tag(path, "panel") - if width is not None and width < int(329 * self.scale + 64): # scaled widths of the elements in top bar + fixed 8 padding on left and right of each item - dpg.configure_item(panel_tag, height=(int(13 * self.scale) + 24)) - else: - dpg.configure_item(panel_tag, height=(int(13 * self.scale) + 8)) - - def _get_split_geometry(self, layout: dict, available_size: tuple[int, int]) -> tuple[int, int, list[int]]: - orientation = layout["orientation"] - num_grips = len(layout["children"]) - 1 - usable_size = max(self.min_pane_size, available_size[orientation] - (num_grips * (self.grip_size + 8 * (2 - orientation)))) # approximate, scaling is weird - pane_sizes = [max(self.min_pane_size, int(usable_size * prop)) for prop in layout["proportions"]] - return orientation, usable_size, pane_sizes - - def _get_layout_at_path(self, path: list[int]) -> dict: - current = self.layout - for index in path: - current = current["children"][index] - return current - - def _get_parent_and_index(self, path: list[int]) -> tuple: - return (None, -1) if not path else (self._get_layout_at_path(path[:-1]), path[-1]) - - def _replace_layout_at_path(self, path: list[int], new_layout: dict): - if not path: - self.layout = new_layout - else: - parent, index = self._get_parent_and_index(path) - parent["children"][index] = new_layout - - def _path_to_tag(self, path: list[int], prefix: str = "") -> str: - path_str = "_".join(map(str, path)) if path else "root" - return f"{prefix}_{path_str}" if prefix else path_str - - def _create_grip(self, parent_tag: str, path: list[int], grip_index: int, orientation: int): - grip_tag = self._path_to_tag(path, f"grip_{grip_index}") - handler_tag = f"{grip_tag}_handler" - width, height = [(self.grip_size, -1), (-1, self.grip_size)][orientation] - - with dpg.child_window(tag=grip_tag, parent=parent_tag, width=width, height=height, no_scrollbar=True, border=False): - button_tag = dpg.add_button(label="", width=-1, height=-1) - - with dpg.item_handler_registry(tag=handler_tag): - user_data = (path, grip_index, orientation) - dpg.add_item_active_handler(callback=self._on_grip_drag, user_data=user_data) - dpg.add_item_deactivated_handler(callback=self._on_grip_end, user_data=user_data) - dpg.bind_item_handler_registry(button_tag, handler_tag) - self._created_handler_tags.add(handler_tag) - - def _on_grip_drag(self, sender, app_data, user_data): - path, grip_index, orientation = user_data - layout = self._get_layout_at_path(path) - - if "_drag_data" not in layout: - layout["_drag_data"] = {"initial_proportions": layout["proportions"][:], "start_mouse": dpg.get_mouse_pos(local=False)[orientation]} - return - - drag_data = layout["_drag_data"] - split_tag = self._path_to_tag(path, "split") - if not dpg.does_item_exist(split_tag): - return - - _, usable_size, _ = self._get_split_geometry(layout, dpg.get_item_rect_size(split_tag)) - current_coord = dpg.get_mouse_pos(local=False)[orientation] - delta = current_coord - drag_data["start_mouse"] - delta_prop = delta / usable_size - - left_idx = grip_index - right_idx = left_idx + 1 - initial = drag_data["initial_proportions"] - min_prop = self.min_pane_size / usable_size - - new_left = max(min_prop, initial[left_idx] + delta_prop) - new_right = max(min_prop, initial[right_idx] - delta_prop) - - total_available = initial[left_idx] + initial[right_idx] - if new_left + new_right > total_available: - if new_left > new_right: - new_left = total_available - new_right - else: - new_right = total_available - new_left - - layout["proportions"] = initial[:] - layout["proportions"][left_idx] = new_left - layout["proportions"][right_idx] = new_right - - self._resize_splits_recursive(layout, path) - - def _on_grip_end(self, sender, app_data, user_data): - path, _, _ = user_data - self._get_layout_at_path(path).pop("_drag_data", None) diff --git a/tools/jotpluggler/layouts/torque-controller.yaml b/tools/jotpluggler/layouts/torque-controller.yaml deleted file mode 100644 index 5503be9e64..0000000000 --- a/tools/jotpluggler/layouts/torque-controller.yaml +++ /dev/null @@ -1,128 +0,0 @@ -tabs: - '0': - name: Lateral Plan Conformance - panel_layout: - type: split - orientation: 1 - proportions: - - 0.3333333333333333 - - 0.3333333333333333 - - 0.3333333333333333 - children: - - type: panel - panel: - type: timeseries - title: desired vs actual - series_paths: - - controlsState/lateralControlState/torqueState/desiredLateralAccel - - controlsState/lateralControlState/torqueState/actualLateralAccel - - type: panel - panel: - type: timeseries - title: ff vs output - series_paths: - - controlsState/lateralControlState/torqueState/f - - carState/steeringPressed - - carControl/actuators/torque - - type: panel - panel: - type: timeseries - title: vehicle speed - series_paths: - - carState/vEgo - '1': - name: Actuator Performance - panel_layout: - type: split - orientation: 1 - proportions: - - 0.3333333333333333 - - 0.3333333333333333 - - 0.3333333333333333 - children: - - type: panel - panel: - type: timeseries - title: calc vs learned latAccelFactor - series_paths: - - liveTorqueParameters/latAccelFactorFiltered - - liveTorqueParameters/latAccelFactorRaw - - carParams/lateralTuning/torque/latAccelFactor - - type: panel - panel: - type: timeseries - title: learned latAccelOffset - series_paths: - - liveTorqueParameters/latAccelOffsetRaw - - liveTorqueParameters/latAccelOffsetFiltered - - type: panel - panel: - type: timeseries - title: calc vs learned friction - series_paths: - - liveTorqueParameters/frictionCoefficientFiltered - - liveTorqueParameters/frictionCoefficientRaw - - carParams/lateralTuning/torque/friction - '2': - name: Vehicle Dynamics - panel_layout: - type: split - orientation: 1 - proportions: - - 0.3333333333333333 - - 0.3333333333333333 - - 0.3333333333333333 - children: - - type: panel - panel: - type: timeseries - title: initial vs learned steerRatio - series_paths: - - carParams/steerRatio - - liveParameters/steerRatio - - type: panel - panel: - type: timeseries - title: initial vs learned tireStiffnessFactor - series_paths: - - carParams/tireStiffnessFactor - - liveParameters/stiffnessFactor - - type: panel - panel: - type: timeseries - title: live steering angle offsets - series_paths: - - liveParameters/angleOffsetDeg - - liveParameters/angleOffsetAverageDeg - '3': - name: Controller PIF Terms - panel_layout: - type: split - orientation: 1 - proportions: - - 0.3333333333333333 - - 0.3333333333333333 - - 0.3333333333333333 - children: - - type: panel - panel: - type: timeseries - title: ff vs output - series_paths: - - carControl/actuators/torque - - controlsState/lateralControlState/torqueState/f - - carState/steeringPressed - - type: panel - panel: - type: timeseries - title: PIF terms - series_paths: - - controlsState/lateralControlState/torqueState/f - - controlsState/lateralControlState/torqueState/p - - controlsState/lateralControlState/torqueState/i - - type: panel - panel: - type: timeseries - title: road roll angle - series_paths: - - liveParameters/roll diff --git a/tools/jotpluggler/pluggle.py b/tools/jotpluggler/pluggle.py deleted file mode 100755 index 2fb6e3e2f4..0000000000 --- a/tools/jotpluggler/pluggle.py +++ /dev/null @@ -1,368 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import os -import dearpygui.dearpygui as dpg -import multiprocessing -import uuid -import signal -import yaml -from openpilot.common.swaglog import cloudlog -from openpilot.common.basedir import BASEDIR -from openpilot.tools.jotpluggler.data import DataManager -from openpilot.tools.jotpluggler.datatree import DataTree -from openpilot.tools.jotpluggler.layout import LayoutManager - -DEMO_ROUTE = "5beb9b58bd12b691/0000010a--a51155e496" - - -class WorkerManager: - def __init__(self, max_workers=None): - self.pool = multiprocessing.Pool(max_workers or min(4, multiprocessing.cpu_count()), initializer=WorkerManager.worker_initializer) - self.active_tasks = {} - - def submit_task(self, func, args_list, callback=None, task_id=None): - task_id = task_id or str(uuid.uuid4()) - - if task_id in self.active_tasks: - try: - self.active_tasks[task_id].terminate() - except Exception: - pass - - def handle_success(result): - self.active_tasks.pop(task_id, None) - if callback: - try: - callback(result) - except Exception as e: - print(f"Callback for task {task_id} failed: {e}") - - def handle_error(error): - self.active_tasks.pop(task_id, None) - print(f"Task {task_id} failed: {error}") - - async_result = self.pool.starmap_async(func, args_list, callback=handle_success, error_callback=handle_error) - self.active_tasks[task_id] = async_result - return task_id - - @staticmethod - def worker_initializer(): - signal.signal(signal.SIGINT, signal.SIG_IGN) - - def shutdown(self): - for task in self.active_tasks.values(): - try: - task.terminate() - except Exception: - pass - self.pool.terminate() - self.pool.join() - - -class PlaybackManager: - def __init__(self): - self.is_playing = False - self.current_time_s = 0.0 - self.duration_s = 0.0 - self.num_segments = 0 - - self.x_axis_bounds = (0.0, 0.0) # (min_time, max_time) - self.x_axis_observers = [] # callbacks for x-axis changes - self._updating_x_axis = False - - def set_route_duration(self, duration: float): - self.duration_s = duration - self.seek(min(self.current_time_s, duration)) - - def toggle_play_pause(self): - if not self.is_playing and self.current_time_s >= self.duration_s: - self.seek(0.0) - self.is_playing = not self.is_playing - texture_tag = "pause_texture" if self.is_playing else "play_texture" - dpg.configure_item("play_pause_button", texture_tag=texture_tag) - - def seek(self, time_s: float): - self.current_time_s = max(0.0, min(time_s, self.duration_s)) - - def update_time(self, delta_t: float): - if self.is_playing: - self.current_time_s = min(self.current_time_s + delta_t, self.duration_s) - if self.current_time_s >= self.duration_s: - self.is_playing = False - dpg.configure_item("play_pause_button", texture_tag="play_texture") - return self.current_time_s - - def set_x_axis_bounds(self, min_time: float, max_time: float, source_panel=None): - if self._updating_x_axis: - return - - new_bounds = (min_time, max_time) - if new_bounds == self.x_axis_bounds: - return - - self.x_axis_bounds = new_bounds - self._updating_x_axis = True # prevent recursive updates - - try: - for callback in self.x_axis_observers: - try: - callback(min_time, max_time, source_panel) - except Exception as e: - print(f"Error in x-axis sync callback: {e}") - finally: - self._updating_x_axis = False - - def add_x_axis_observer(self, callback): - if callback not in self.x_axis_observers: - self.x_axis_observers.append(callback) - - def remove_x_axis_observer(self, callback): - if callback in self.x_axis_observers: - self.x_axis_observers.remove(callback) - -class MainController: - def __init__(self, scale: float = 1.0): - self.scale = scale - self.data_manager = DataManager() - self.playback_manager = PlaybackManager() - self.worker_manager = WorkerManager() - self._create_global_themes() - self.data_tree = DataTree(self.data_manager, self.playback_manager) - self.layout_manager = LayoutManager(self.data_manager, self.playback_manager, self.worker_manager, scale=self.scale) - self.data_manager.add_observer(self.on_data_loaded) - self._total_segments = 0 - - def _create_global_themes(self): - with dpg.theme(tag="line_theme"): - with dpg.theme_component(dpg.mvLineSeries): - scaled_thickness = max(1.0, self.scale) - dpg.add_theme_style(dpg.mvPlotStyleVar_LineWeight, scaled_thickness, category=dpg.mvThemeCat_Plots) - - with dpg.theme(tag="timeline_theme"): - with dpg.theme_component(dpg.mvInfLineSeries): - scaled_thickness = max(1.0, self.scale) - dpg.add_theme_style(dpg.mvPlotStyleVar_LineWeight, scaled_thickness, category=dpg.mvThemeCat_Plots) - dpg.add_theme_color(dpg.mvPlotCol_Line, (255, 0, 0, 128), category=dpg.mvThemeCat_Plots) - - for tag, color in (("active_tab_theme", (37, 37, 38, 255)), ("inactive_tab_theme", (70, 70, 75, 255))): - with dpg.theme(tag=tag): - for cmp, target in ((dpg.mvChildWindow, dpg.mvThemeCol_ChildBg), (dpg.mvInputText, dpg.mvThemeCol_FrameBg), (dpg.mvImageButton, dpg.mvThemeCol_Button)): - with dpg.theme_component(cmp): - dpg.add_theme_color(target, color) - - with dpg.theme(tag="tab_bar_theme"): - with dpg.theme_component(dpg.mvChildWindow): - dpg.add_theme_color(dpg.mvThemeCol_ChildBg, (51, 51, 55, 255)) - - def on_data_loaded(self, data: dict): - duration = data.get('duration', 0.0) - self.playback_manager.set_route_duration(duration) - - if data.get('metadata_loaded'): - self.playback_manager.num_segments = data.get('total_segments', 0) - self._total_segments = data.get('total_segments', 0) - dpg.set_value("load_status", f"Loading... 0/{self._total_segments} segments processed") - elif data.get('reset'): - self.playback_manager.current_time_s = 0.0 - self.playback_manager.duration_s = 0.0 - self.playback_manager.is_playing = False - self._total_segments = 0 - dpg.set_value("load_status", "Loading...") - dpg.set_value("timeline_slider", 0.0) - dpg.configure_item("timeline_slider", max_value=0.0) - dpg.configure_item("play_pause_button", texture_tag="play_texture") - dpg.configure_item("load_button", enabled=True) - elif data.get('loading_complete'): - num_paths = len(self.data_manager.get_all_paths()) - dpg.set_value("load_status", f"Loaded {num_paths} data paths") - dpg.configure_item("load_button", enabled=True) - elif data.get('segment_added'): - segment_count = data.get('segment_count', 0) - dpg.set_value("load_status", f"Loading... {segment_count}/{self._total_segments} segments processed") - - dpg.configure_item("timeline_slider", max_value=duration) - - def save_layout_to_yaml(self, filepath: str): - layout_dict = self.layout_manager.to_dict() - with open(filepath, 'w') as f: - yaml.dump(layout_dict, f, default_flow_style=False, sort_keys=False) - - def load_layout_from_yaml(self, filepath: str): - with open(filepath) as f: - layout_dict = yaml.safe_load(f) - self.layout_manager.clear_and_load_from_dict(layout_dict) - self.layout_manager.create_ui("main_plot_area") - - def save_layout_dialog(self): - if dpg.does_item_exist("save_layout_dialog"): - dpg.delete_item("save_layout_dialog") - with dpg.file_dialog( - callback=self._save_layout_callback, tag="save_layout_dialog", width=int(700 * self.scale), height=int(400 * self.scale), - default_filename="layout", default_path=os.path.join(os.path.dirname(os.path.realpath(__file__)), "layouts") - ): - dpg.add_file_extension(".yaml") - - def load_layout_dialog(self): - if dpg.does_item_exist("load_layout_dialog"): - dpg.delete_item("load_layout_dialog") - with dpg.file_dialog( - callback=self._load_layout_callback, tag="load_layout_dialog", width=int(700 * self.scale), height=int(400 * self.scale), - default_path=os.path.join(os.path.dirname(os.path.realpath(__file__)), "layouts") - ): - dpg.add_file_extension(".yaml") - - def _save_layout_callback(self, sender, app_data): - filepath = app_data['file_path_name'] - try: - self.save_layout_to_yaml(filepath) - dpg.set_value("load_status", f"Layout saved to {os.path.basename(filepath)}") - except Exception: - dpg.set_value("load_status", "Error saving layout") - cloudlog.exception(f"Error saving layout to {filepath}") - dpg.delete_item("save_layout_dialog") - - def _load_layout_callback(self, sender, app_data): - filepath = app_data['file_path_name'] - try: - self.load_layout_from_yaml(filepath) - dpg.set_value("load_status", f"Layout loaded from {os.path.basename(filepath)}") - except Exception: - dpg.set_value("load_status", "Error loading layout") - cloudlog.exception(f"Error loading layout from {filepath}:") - dpg.delete_item("load_layout_dialog") - - def setup_ui(self): - with dpg.texture_registry(): - script_dir = os.path.dirname(os.path.realpath(__file__)) - for image in ["play", "pause", "x", "split_h", "split_v", "plus"]: - texture = dpg.load_image(os.path.join(script_dir, "assets", f"{image}.png")) - dpg.add_static_texture(width=texture[0], height=texture[1], default_value=texture[3], tag=f"{image}_texture") - - with dpg.window(tag="Primary Window"): - with dpg.group(horizontal=True): - # Left panel - Data tree - with dpg.child_window(label="Sidebar", width=int(300 * self.scale), tag="sidebar_window", border=True, resizable_x=True): - with dpg.group(horizontal=True): - dpg.add_input_text(tag="route_input", width=int(-75 * self.scale), hint="Enter route name...") - dpg.add_button(label="Load", callback=self.load_route, tag="load_button", width=-1) - dpg.add_text("Ready to load route", tag="load_status") - dpg.add_separator() - - with dpg.table(header_row=False, policy=dpg.mvTable_SizingStretchProp): - dpg.add_table_column(init_width_or_weight=0.5) - dpg.add_table_column(init_width_or_weight=0.5) - with dpg.table_row(): - dpg.add_button(label="Save Layout", callback=self.save_layout_dialog, width=-1) - dpg.add_button(label="Load Layout", callback=self.load_layout_dialog, width=-1) - dpg.add_separator() - - self.data_tree.create_ui("sidebar_window") - - # Right panel - Plots and timeline - with dpg.group(tag="right_panel"): - with dpg.child_window(label="Plot Window", border=True, height=int(-(32 + 13 * self.scale)), tag="main_plot_area"): - self.layout_manager.create_ui("main_plot_area") - - with dpg.child_window(label="Timeline", border=True): - with dpg.table(header_row=False): - btn_size = int(13 * self.scale) - dpg.add_table_column(width_fixed=True, init_width_or_weight=(btn_size + 8)) # Play button - dpg.add_table_column(width_stretch=True) # Timeline slider - dpg.add_table_column(width_fixed=True, init_width_or_weight=int(50 * self.scale)) # FPS counter - with dpg.table_row(): - dpg.add_image_button(texture_tag="play_texture", tag="play_pause_button", callback=self.toggle_play_pause, width=btn_size, height=btn_size) - dpg.add_slider_float(tag="timeline_slider", default_value=0.0, label="", width=-1, callback=self.timeline_drag) - dpg.add_text("", tag="fps_counter") - with dpg.item_handler_registry(tag="plot_resize_handler"): - dpg.add_item_resize_handler(callback=self.on_plot_resize) - dpg.bind_item_handler_registry("right_panel", "plot_resize_handler") - - dpg.set_primary_window("Primary Window", True) - - def on_plot_resize(self, sender, app_data, user_data): - self.layout_manager.on_viewport_resize() - - def load_route(self): - route_name = dpg.get_value("route_input").strip() - if route_name: - dpg.set_value("load_status", "Loading route...") - dpg.configure_item("load_button", enabled=False) - self.data_manager.load_route(route_name) - - def toggle_play_pause(self, sender): - self.playback_manager.toggle_play_pause() - - def timeline_drag(self, sender, app_data): - self.playback_manager.seek(app_data) - - def update_frame(self, font): - self.data_tree.update_frame(font) - - new_time = self.playback_manager.update_time(dpg.get_delta_time()) - if not dpg.is_item_active("timeline_slider"): - dpg.set_value("timeline_slider", new_time) - - self.layout_manager.update_all_panels() - - dpg.set_value("fps_counter", f"{dpg.get_frame_rate():.1f} FPS") - - def shutdown(self): - self.worker_manager.shutdown() - - -def main(route_to_load=None, layout_to_load=None): - dpg.create_context() - - # TODO: find better way of calculating display scaling - #try: - # w, h = next(tuple(map(int, l.split()[0].split('x'))) for l in subprocess.check_output(['xrandr']).decode().split('\n') if '*' in l) # actual resolution - # scale = pyautogui.size()[0] / w # scaled resolution - #except Exception: - # scale = 1 - scale = 1 - - with dpg.font_registry(): - default_font = dpg.add_font(os.path.join(BASEDIR, "selfdrive/assets/fonts/JetBrainsMono-Medium.ttf"), int(13 * scale * 2)) # 2x then scale for hidpi - dpg.bind_font(default_font) - dpg.set_global_font_scale(0.5) - - viewport_width, viewport_height = int(1200 * scale), int(800 * scale) - dpg.create_viewport( - title='JotPluggler', width=viewport_width, height=viewport_height, - ) - dpg.setup_dearpygui() - - controller = MainController(scale=scale) - controller.setup_ui() - - if layout_to_load: - try: - controller.load_layout_from_yaml(layout_to_load) - print(f"Loaded layout from {layout_to_load}") - except Exception as e: - print(f"Failed to load layout from {layout_to_load}: {e}") - cloudlog.exception(f"Error loading layout from {layout_to_load}") - - if route_to_load: - dpg.set_value("route_input", route_to_load) - controller.load_route() - - dpg.show_viewport() - - # Main loop - try: - while dpg.is_dearpygui_running(): - controller.update_frame(default_font) - dpg.render_dearpygui_frame() - finally: - controller.shutdown() - dpg.destroy_context() - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="A tool for visualizing openpilot logs.") - parser.add_argument("--demo", action="store_true", help="Use the demo route instead of providing one") - parser.add_argument("--layout", type=str, help="Path to YAML layout file to load on startup") - parser.add_argument("route", nargs='?', default=None, help="Optional route name to load on startup.") - args = parser.parse_args() - route = DEMO_ROUTE if args.demo else args.route - main(route_to_load=route, layout_to_load=args.layout) diff --git a/tools/jotpluggler/views.py b/tools/jotpluggler/views.py deleted file mode 100644 index cd723d161e..0000000000 --- a/tools/jotpluggler/views.py +++ /dev/null @@ -1,294 +0,0 @@ -import uuid -import threading -import numpy as np -from collections import deque -import dearpygui.dearpygui as dpg -from abc import ABC, abstractmethod - - -class ViewPanel(ABC): - """Abstract base class for all view panels that can be displayed in a plot container""" - - def __init__(self, panel_id: str | None = None): - self.panel_id = panel_id or str(uuid.uuid4()) - self.title = "Untitled Panel" - - @abstractmethod - def clear(self): - pass - - @abstractmethod - def create_ui(self, parent_tag: str): - pass - - @abstractmethod - def destroy_ui(self): - pass - - @abstractmethod - def get_panel_type(self) -> str: - pass - - @abstractmethod - def update(self): - pass - - @abstractmethod - def to_dict(self) -> dict: - pass - - @classmethod - @abstractmethod - def load_from_dict(cls, data: dict, data_manager, playback_manager, worker_manager): - pass - - -class TimeSeriesPanel(ViewPanel): - def __init__(self, data_manager, playback_manager, worker_manager, panel_id: str | None = None): - super().__init__(panel_id) - self.data_manager = data_manager - self.playback_manager = playback_manager - self.worker_manager = worker_manager - self.title = "Time Series Plot" - self.plot_tag = f"plot_{self.panel_id}" - self.x_axis_tag = f"{self.plot_tag}_x_axis" - self.y_axis_tag = f"{self.plot_tag}_y_axis" - self.timeline_indicator_tag = f"{self.plot_tag}_timeline" - self._ui_created = False - self._series_data: dict[str, tuple[np.ndarray, np.ndarray]] = {} - self._last_plot_duration = 0 - self._update_lock = threading.RLock() - self._results_deque: deque[tuple[str, list, list]] = deque() - self._new_data = False - self._last_x_limits = (0.0, 0.0) - self._queued_x_sync: tuple | None = None - self._queued_reallow_x_zoom = False - self._total_segments = self.playback_manager.num_segments - - def to_dict(self) -> dict: - return { - "type": "timeseries", - "title": self.title, - "series_paths": list(self._series_data.keys()) - } - - @classmethod - def load_from_dict(cls, data: dict, data_manager, playback_manager, worker_manager): - panel = cls(data_manager, playback_manager, worker_manager) - panel.title = data.get("title", "Time Series Plot") - panel._series_data = {path: (np.array([]), np.array([])) for path in data.get("series_paths", [])} - return panel - - def create_ui(self, parent_tag: str): - self.data_manager.add_observer(self.on_data_loaded) - self.playback_manager.add_x_axis_observer(self._on_x_axis_sync) - with dpg.plot(height=-1, width=-1, tag=self.plot_tag, parent=parent_tag, drop_callback=self._on_series_drop, payload_type="TIMESERIES_PAYLOAD"): - dpg.add_plot_legend() - dpg.add_plot_axis(dpg.mvXAxis, no_label=True, tag=self.x_axis_tag) - dpg.add_plot_axis(dpg.mvYAxis, no_label=True, tag=self.y_axis_tag) - timeline_series_tag = dpg.add_inf_line_series(x=[0], label="Timeline", parent=self.y_axis_tag, tag=self.timeline_indicator_tag) - dpg.bind_item_theme(timeline_series_tag, "timeline_theme") - - self._new_data = True - self._queued_x_sync = self.playback_manager.x_axis_bounds - self._ui_created = True - - def update(self): - with self._update_lock: - if not self._ui_created: - return - - if self._queued_x_sync: - min_time, max_time = self._queued_x_sync - self._queued_x_sync = None - dpg.set_axis_limits(self.x_axis_tag, min_time, max_time) - self._last_x_limits = (min_time, max_time) - self._fit_y_axis(min_time, max_time) - self._queued_reallow_x_zoom = True # must wait a frame before allowing user changes so that axis limits take effect - return - - if self._queued_reallow_x_zoom: - self._queued_reallow_x_zoom = False - if tuple(dpg.get_axis_limits(self.x_axis_tag)) == self._last_x_limits: - dpg.set_axis_limits_auto(self.x_axis_tag) - else: - self._queued_x_sync = self._last_x_limits # retry, likely too early - return - - if self._new_data: # handle new data in main thread - self._new_data = False - if self._total_segments > 0: - dpg.set_axis_limits_constraints(self.x_axis_tag, -10, self._total_segments * 60 + 10) - self._fit_y_axis(*dpg.get_axis_limits(self.x_axis_tag)) - for series_path in list(self._series_data.keys()): - self.add_series(series_path, update=True) - - current_limits = dpg.get_axis_limits(self.x_axis_tag) - # downsample if plot zoom changed significantly - plot_duration = current_limits[1] - current_limits[0] - if plot_duration > self._last_plot_duration * 2 or plot_duration < self._last_plot_duration * 0.5: - self._downsample_all_series(plot_duration) - # sync x-axis if changed by user - if self._last_x_limits != current_limits: - self.playback_manager.set_x_axis_bounds(current_limits[0], current_limits[1], source_panel=self) - self._last_x_limits = current_limits - self._fit_y_axis(current_limits[0], current_limits[1]) - - while self._results_deque: # handle downsampled results in main thread - results = self._results_deque.popleft() - for series_path, downsampled_time, downsampled_values in results: - series_tag = f"series_{self.panel_id}_{series_path}" - if dpg.does_item_exist(series_tag): - dpg.set_value(series_tag, (downsampled_time, downsampled_values.astype(float))) - - # update timeline - current_time_s = self.playback_manager.current_time_s - dpg.set_value(self.timeline_indicator_tag, [[current_time_s], [0]]) - - # update timeseries legend label - for series_path, (time_array, value_array) in self._series_data.items(): - position = np.searchsorted(time_array, current_time_s, side='right') - 1 - if position >= 0 and (current_time_s - time_array[position]) <= 1.0: - value = value_array[position] - formatted_value = f"{value:.5f}" if np.issubdtype(type(value), np.floating) else str(value) - series_tag = f"series_{self.panel_id}_{series_path}" - if dpg.does_item_exist(series_tag): - dpg.configure_item(series_tag, label=f"{series_path}: {formatted_value}") - - def _on_x_axis_sync(self, min_time: float, max_time: float, source_panel): - with self._update_lock: - if source_panel != self: - self._queued_x_sync = (min_time, max_time) - - def _fit_y_axis(self, x_min: float, x_max: float): - if not self._series_data: - dpg.set_axis_limits(self.y_axis_tag, -1, 1) - return - - global_min = float('inf') - global_max = float('-inf') - found_data = False - - for time_array, value_array in self._series_data.values(): - if len(time_array) == 0: - continue - start_idx, end_idx = np.searchsorted(time_array, [x_min, x_max]) - end_idx = min(end_idx, len(time_array) - 1) - if start_idx <= end_idx: - y_slice = value_array[start_idx:end_idx + 1] - series_min, series_max = np.min(y_slice), np.max(y_slice) - global_min = min(global_min, series_min) - global_max = max(global_max, series_max) - found_data = True - - if not found_data: - dpg.set_axis_limits(self.y_axis_tag, -1, 1) - return - - if global_min == global_max: - padding = max(abs(global_min) * 0.1, 1.0) - y_min, y_max = global_min - padding, global_max + padding - else: - range_size = global_max - global_min - padding = range_size * 0.1 - y_min, y_max = global_min - padding, global_max + padding - - dpg.set_axis_limits(self.y_axis_tag, y_min, y_max) - - def _downsample_all_series(self, plot_duration): - plot_width = dpg.get_item_rect_size(self.plot_tag)[0] - if plot_width <= 0 or plot_duration <= 0: - return - - self._last_plot_duration = plot_duration - target_points_per_second = plot_width / plot_duration - work_items = [] - for series_path, (time_array, value_array) in self._series_data.items(): - if len(time_array) == 0: - continue - series_duration = time_array[-1] - time_array[0] if len(time_array) > 1 else 1 - points_per_second = len(time_array) / series_duration - if points_per_second > target_points_per_second * 2: - target_points = max(int(target_points_per_second * series_duration), plot_width) - work_items.append((series_path, time_array, value_array, target_points)) - elif dpg.does_item_exist(f"series_{self.panel_id}_{series_path}"): - dpg.set_value(f"series_{self.panel_id}_{series_path}", (time_array, value_array.astype(float))) - - if work_items: - self.worker_manager.submit_task( - TimeSeriesPanel._downsample_worker, work_items, callback=lambda results: self._results_deque.append(results), task_id=f"downsample_{self.panel_id}" - ) - - def add_series(self, series_path: str, update: bool = False): - with self._update_lock: - if update or series_path not in self._series_data: - self._series_data[series_path] = self.data_manager.get_timeseries(series_path) - - time_array, value_array = self._series_data[series_path] - series_tag = f"series_{self.panel_id}_{series_path}" - if dpg.does_item_exist(series_tag): - dpg.set_value(series_tag, (time_array, value_array.astype(float))) - else: - line_series_tag = dpg.add_line_series(x=time_array, y=value_array.astype(float), label=series_path, parent=self.y_axis_tag, tag=series_tag) - dpg.bind_item_theme(line_series_tag, "line_theme") - self._fit_y_axis(*dpg.get_axis_limits(self.x_axis_tag)) - plot_duration = dpg.get_axis_limits(self.x_axis_tag)[1] - dpg.get_axis_limits(self.x_axis_tag)[0] - self._downsample_all_series(plot_duration) - - def destroy_ui(self): - with self._update_lock: - self.data_manager.remove_observer(self.on_data_loaded) - self.playback_manager.remove_x_axis_observer(self._on_x_axis_sync) - if dpg.does_item_exist(self.plot_tag): - dpg.delete_item(self.plot_tag) - self._ui_created = False - - def get_panel_type(self) -> str: - return "timeseries" - - def clear(self): - with self._update_lock: - for series_path in list(self._series_data.keys()): - self.remove_series(series_path) - - def remove_series(self, series_path: str): - with self._update_lock: - if series_path in self._series_data: - if dpg.does_item_exist(f"series_{self.panel_id}_{series_path}"): - dpg.delete_item(f"series_{self.panel_id}_{series_path}") - del self._series_data[series_path] - - def on_data_loaded(self, data: dict): - with self._update_lock: - self._new_data = True - if data.get('metadata_loaded'): - self._total_segments = data.get('total_segments', 0) - limits = (-10, self._total_segments * 60 + 10) - self._queued_x_sync = limits - - def _on_series_drop(self, sender, app_data, user_data): - self.add_series(app_data) - - @staticmethod - def _downsample_worker(series_path, time_array, value_array, target_points): - if len(time_array) <= target_points: - return series_path, time_array, value_array - - step = len(time_array) / target_points - indices = [] - - for i in range(target_points): - start_idx = int(i * step) - end_idx = int((i + 1) * step) - if start_idx == end_idx: - indices.append(start_idx) - else: - bucket_values = value_array[start_idx:end_idx] - min_idx = start_idx + np.argmin(bucket_values) - max_idx = start_idx + np.argmax(bucket_values) - if min_idx != max_idx: - indices.extend([min(min_idx, max_idx), max(min_idx, max_idx)]) - else: - indices.append(min_idx) - indices = sorted(set(indices)) - return series_path, time_array[indices], value_array[indices] diff --git a/tools/lib/file_downloader.py b/tools/lib/file_downloader.py index c9c26bb307..5b31a5894c 100755 --- a/tools/lib/file_downloader.py +++ b/tools/lib/file_downloader.py @@ -60,8 +60,16 @@ def cmd_download(args): return try: - uf = URLFile(url, cache=False) - total = uf.get_length() + # Stream the file in a single HTTP request instead of making + # a separate Range request per chunk (which was very slow). + pool = URLFile.pool_manager() + r = pool.request("GET", url, preload_content=False) + if r.status not in (200, 206): + sys.stderr.write(f"ERROR:HTTP {r.status}\n") + sys.stderr.flush() + sys.exit(1) + + total = int(r.headers.get('content-length', 0)) if total <= 0: sys.stderr.write("ERROR:File not found or empty\n") sys.stderr.flush() @@ -73,8 +81,7 @@ def cmd_download(args): downloaded = 0 chunk_size = 1024 * 1024 with os.fdopen(tmp_fd, 'wb') as f: - while downloaded < total: - data = uf.read(min(chunk_size, total - downloaded)) + for data in r.stream(chunk_size): f.write(data) downloaded += len(data) sys.stderr.write(f"PROGRESS:{downloaded}:{total}\n") @@ -91,6 +98,8 @@ def cmd_download(args): except OSError: pass raise + finally: + r.release_conn() except Exception as e: sys.stderr.write(f"ERROR:{e}\n") diff --git a/tools/op.sh b/tools/op.sh index 7c20403a27..f21a285d17 100755 --- a/tools/op.sh +++ b/tools/op.sh @@ -167,29 +167,6 @@ function op_check_os() { fi } -function op_check_python() { - echo "Checking for compatible python version..." - REQUIRED_PYTHON_VERSION=$(grep "requires-python" $OPENPILOT_ROOT/pyproject.toml) - INSTALLED_PYTHON_VERSION=$(python3 --version 2> /dev/null || true) - - if [[ -z $INSTALLED_PYTHON_VERSION ]]; then - echo -e " ↳ [${RED}✗${NC}] python3 not found on your system. You need python version satisfying $(echo $REQUIRED_PYTHON_VERSION | cut -d '=' -f2-) to continue!" - loge "ERROR_PYTHON_NOT_FOUND" - return 1 - else - LB=$(echo $REQUIRED_PYTHON_VERSION | tr -d '",' | awk '{ split($4, v, "."); printf "%d%02d%02d", v[1], v[2], v[3] }') - UB=$(echo $REQUIRED_PYTHON_VERSION | tr -d '",' | awk '{ split($6, v, "."); printf "%d%02d%02d", v[1], v[2], v[3] }') - VERSION=$(echo $INSTALLED_PYTHON_VERSION | awk '{ split($2, v, "."); printf "%d%02d%02d", v[1], v[2], v[3] }') - if [[ $VERSION -ge LB && $VERSION -lt UB ]]; then - echo -e " ↳ [${GREEN}✔${NC}] $INSTALLED_PYTHON_VERSION detected." - else - echo -e " ↳ [${RED}✗${NC}] You need a python version satisfying $(echo $REQUIRED_PYTHON_VERSION | cut -d '=' -f2-) to continue!" - loge "ERROR_PYTHON_VERSION" "$INSTALLED_PYTHON_VERSION" - return 1 - fi - fi -} - function op_check_venv() { echo "Checking for venv..." if [[ -f $OPENPILOT_ROOT/.venv/bin/activate ]]; then @@ -214,8 +191,6 @@ function op_before_cmd() { op_activate_venv - result="${result}\n$(( op_check_python ) 2>&1)" || (echo -e "$result" && return 1) - if [[ -z $VERBOSE ]]; then echo -e "${BOLD}Checking system →${NC} [${GREEN}✔${NC}]" else @@ -436,7 +411,7 @@ function op_default() { echo "" echo -e "${BOLD}${UNDERLINE}Commands [System]:${NC}" echo -e " ${BOLD}auth${NC} Authenticate yourself for API use" - echo -e " ${BOLD}check${NC} Check the development environment (git, os, python) to start using openpilot" + echo -e " ${BOLD}check${NC} Check the development environment (git, os) to start using openpilot" echo -e " ${BOLD}esim${NC} Manage eSIM profiles on your comma device" echo -e " ${BOLD}venv${NC} Activate the python virtual environment" echo -e " ${BOLD}setup${NC} Install openpilot dependencies" diff --git a/tools/plotjuggler/.gitignore b/tools/plotjuggler/.gitignore deleted file mode 100644 index 45559d0b09..0000000000 --- a/tools/plotjuggler/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -bin/ -bin -*.rlog diff --git a/tools/replay/.gitignore b/tools/replay/.gitignore index 83f0e99a8b..aa615770a2 100644 --- a/tools/replay/.gitignore +++ b/tools/replay/.gitignore @@ -1,5 +1,2 @@ -moc_* -*.moc - replay tests/test_replay diff --git a/tools/replay/SConscript b/tools/replay/SConscript index 3efa970b37..757f3fec4e 100644 --- a/tools/replay/SConscript +++ b/tools/replay/SConscript @@ -4,7 +4,7 @@ replay_env = env.Clone() replay_env['CCFLAGS'] += ['-Wno-deprecated-declarations'] base_frameworks = [] -base_libs = [common, messaging, cereal, visionipc, 'm', 'ssl', 'crypto', 'pthread'] +base_libs = [common, messaging, cereal, visionipc, 'm', 'pthread'] replay_lib_src = ["replay.cc", "consoleui.cc", "camera.cc", "filereader.cc", "logreader.cc", "framereader.cc", "route.cc", "util.cc", "seg_mgr.cc", "timeline.cc", "py_downloader.cc"] diff --git a/tools/replay/ui.py b/tools/replay/ui.py index 8707f2be99..7fe2f405a1 100755 --- a/tools/replay/ui.py +++ b/tools/replay/ui.py @@ -218,7 +218,7 @@ def ui_thread(addr): # Update camera texture from numpy array img_rgba = cv2.cvtColor(img, cv2.COLOR_RGB2RGBA) rl.update_texture(camera_texture, rl.ffi.cast("void *", img_rgba.ctypes.data)) - rl.draw_texture(camera_texture, 0, 0, rl.WHITE) + rl.draw_texture(camera_texture, 0, 0, rl.WHITE) # noqa: TID251 # display alerts rl.draw_text_ex(font, sm['selfdriveState'].alertText1, rl.Vector2(180, 150), 30, 0, rl.RED) @@ -227,15 +227,15 @@ def ui_thread(addr): # draw plots (texture is reused internally) plot_texture = draw_plots(plot_arr) if hor_mode: - rl.draw_texture(plot_texture, 640 + 384, 0, rl.WHITE) + rl.draw_texture(plot_texture, 640 + 384, 0, rl.WHITE) # noqa: TID251 else: - rl.draw_texture(plot_texture, 0, 600, rl.WHITE) + rl.draw_texture(plot_texture, 0, 600, rl.WHITE) # noqa: TID251 # Convert lid_overlay to RGBA and update top_down texture # lid_overlay is (384, 960), need to transpose to (960, 384) for row-major RGBA buffer lid_rgba = palette[lid_overlay.T] rl.update_texture(top_down_texture, rl.ffi.cast("void *", np.ascontiguousarray(lid_rgba).ctypes.data)) - rl.draw_texture(top_down_texture, 640, 0, rl.WHITE) + rl.draw_texture(top_down_texture, 640, 0, rl.WHITE) # noqa: TID251 SPACING = 25 lines = [ diff --git a/tools/replay/util.cc b/tools/replay/util.cc index 7294de8282..7b308b5c3d 100644 --- a/tools/replay/util.cc +++ b/tools/replay/util.cc @@ -1,7 +1,6 @@ #include "tools/replay/util.h" #include -#include #include #include @@ -162,15 +161,6 @@ void precise_nano_sleep(int64_t nanoseconds, std::atomic &interrupt_reques } } -std::string sha256(const std::string &str) { - unsigned char hash[SHA256_DIGEST_LENGTH]; - SHA256_CTX sha256; - SHA256_Init(&sha256); - SHA256_Update(&sha256, str.c_str(), str.size()); - SHA256_Final(hash, &sha256); - return util::hexdump(hash, SHA256_DIGEST_LENGTH); -} - std::vector split(std::string_view source, char delimiter) { std::vector fields; size_t last = 0; diff --git a/tools/replay/util.h b/tools/replay/util.h index ee92190337..a2d0f6203a 100644 --- a/tools/replay/util.h +++ b/tools/replay/util.h @@ -46,7 +46,6 @@ private: static constexpr float growth_factor = 1.5; }; -std::string sha256(const std::string &str); void precise_nano_sleep(int64_t nanoseconds, std::atomic &interrupt_requested); std::string decompressBZ2(const std::string &in, std::atomic *abort = nullptr); std::string decompressBZ2(const std::byte *in, size_t in_size, std::atomic *abort = nullptr); diff --git a/tools/scripts/adb_ssh.sh b/tools/scripts/adb_ssh.sh index 4527a0296d..b9668e7e0b 100755 --- a/tools/scripts/adb_ssh.sh +++ b/tools/scripts/adb_ssh.sh @@ -31,8 +31,12 @@ for name, port in sorted(ports): PY ) -# Forward SSH port first for interactive shell access. -adb forward tcp:2222 tcp:22 +# Forward SSH port, finding a free local port if 2222 is taken. +SSH_PORT=2222 +while ss -tln | grep -q ":${SSH_PORT} "; do + SSH_PORT=$((SSH_PORT + 1)) +done +adb forward tcp:${SSH_PORT} tcp:22 # SSH! -ssh comma@localhost -p 2222 "$@" +ssh comma@localhost -p ${SSH_PORT} "$@" diff --git a/tools/setup_dependencies.sh b/tools/setup_dependencies.sh index 0b785bf4a2..8132cd16dc 100755 --- a/tools/setup_dependencies.sh +++ b/tools/setup_dependencies.sh @@ -113,24 +113,12 @@ function install_python_deps() { source .venv/bin/activate } -function install_macos_deps() { - if ! command -v brew > /dev/null 2>&1; then - echo "homebrew not found, skipping macOS system dependency install" - return 0 - fi - - if ! command -v cmake > /dev/null 2>&1; then - brew install cmake - fi -} - # --- Main --- if [[ "$OSTYPE" == "linux-gnu"* ]]; then install_ubuntu_deps echo "[ ] installed system dependencies t=$SECONDS" elif [[ "$OSTYPE" == "darwin"* ]]; then - install_macos_deps if [[ $SHELL == "/bin/zsh" ]]; then RC_FILE="$HOME/.zshrc" elif [[ $SHELL == "/bin/bash" ]]; then diff --git a/tools/sim/bridge/metadrive/metadrive_common.py b/tools/sim/bridge/metadrive/metadrive_common.py index 42a7eb60dd..0106579b20 100644 --- a/tools/sim/bridge/metadrive/metadrive_common.py +++ b/tools/sim/bridge/metadrive/metadrive_common.py @@ -13,11 +13,9 @@ class CopyRamRGBCamera(RGBCamera): def get_rgb_array_cpu(self): origin_img = self.cpu_texture - img = np.frombuffer(origin_img.getRamImage().getData(), dtype=np.uint8) - img = img.reshape((origin_img.getYSize(), origin_img.getXSize(), -1)) - img = img[:,:,:3] # RGBA to RGB - # img = np.swapaxes(img, 1, 0) - img = img[::-1] # Flip on vertical axis + img = np.frombuffer(origin_img.getRamImageAs("RGB").getData(), dtype=np.uint8) + img = img.reshape((origin_img.getYSize(), origin_img.getXSize(), 3)) + img = img[::-1] # Flip on vertical axis return img diff --git a/uv.lock b/uv.lock index f5c128fcb7..8e597db6ef 100644 --- a/uv.lock +++ b/uv.lock @@ -116,12 +116,12 @@ wheels = [ [[package]] name = "bzip2" version = "1.0.8" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=release-bzip2#4808f76c45cb797e697ab21e5e37d68a0ab3b2d4" } [[package]] name = "capnproto" version = "1.0.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=release-capnproto#f735fd22c66029b92019019d0596da6a4445b931" } [[package]] name = "casadi" @@ -359,16 +359,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ff/fa/d3c15189f7c52aaefbaea76fb012119b04b9013f4bf446cb4eb4c26c4e6b/cython-3.2.4-py3-none-any.whl", hash = "sha256:732fc93bc33ae4b14f6afaca663b916c2fdd5dcbfad7114e17fb2434eeaea45c", size = 1257078, upload-time = "2026-01-04T14:14:12.373Z" }, ] -[[package]] -name = "dearpygui" -version = "2.2" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/17/c8/b4afdac89c7bf458513366af3143f7383d7b09721637989c95788d93e24c/dearpygui-2.2-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:34ceae1ca1b65444e49012d6851312e44f08713da1b8cc0150cf41f1c207af9c", size = 1931443, upload-time = "2026-02-17T14:21:54.394Z" }, - { url = "https://files.pythonhosted.org/packages/43/93/a2d083b2e0edb095be815662cc41e40cf9ea7b65d6323e47bb30df7eb284/dearpygui-2.2-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:e1fae9ae59fec0e41773df64c80311a6ba67696219dde5506a2a4c013e8bcdfa", size = 2592645, upload-time = "2026-02-17T14:22:02.869Z" }, - { url = "https://files.pythonhosted.org/packages/80/ba/eae13acaad479f522db853e8b1ccd695a7bc8da2b9685c1d70a3b318df89/dearpygui-2.2-cp312-cp312-win_amd64.whl", hash = "sha256:7d399543b5a26ab6426ef3bbd776e55520b491b3e169647bde5e6b2de3701b35", size = 1830531, upload-time = "2026-02-17T14:21:43.386Z" }, -] - [[package]] name = "dnspython" version = "2.8.0" @@ -381,7 +371,7 @@ wheels = [ [[package]] name = "eigen" version = "3.4.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=release-eigen#fc9915c3a81d6488eafcdbbdc428f15d8123e540" } [[package]] name = "execnet" @@ -395,7 +385,7 @@ wheels = [ [[package]] name = "ffmpeg" version = "7.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=release-ffmpeg#8d693da088e5905d4479550e07484961765df45b" } [[package]] name = "fonttools" @@ -442,7 +432,7 @@ wheels = [ [[package]] name = "gcc-arm-none-eabi" version = "13.2.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=release-gcc-arm-none-eabi#e101138b29023effc932df7a58fb76a26c4e443a" } [[package]] name = "ghp-import" @@ -459,7 +449,7 @@ wheels = [ [[package]] name = "git-lfs" version = "3.6.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=release-git-lfs#5407b1d37b7a8a9ae3747cc20cb6e7a7b01f5059" } [[package]] name = "google-crc32c" @@ -577,7 +567,12 @@ wheels = [ [[package]] name = "libjpeg" version = "3.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=release-libjpeg#83fa530843e5109c51aef14327b6fde5dcb4507b" } + +[[package]] +name = "libusb" +version = "1.0.29" +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libusb&rev=release-libusb#5f188080524c3c6d098ab6cb8d206ceef0394e8e" } [[package]] name = "libusb1" @@ -593,7 +588,7 @@ wheels = [ [[package]] name = "libyuv" version = "1922.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=release-libyuv#600cdd08cb77cbcc001daeb031abcb5c6008c7c2" } [[package]] name = "markdown" @@ -745,7 +740,7 @@ wheels = [ [[package]] name = "ncurses" version = "6.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=release-ncurses#0503ac0d54799b58c84f900dba75abcad17e780f" } [[package]] name = "numpy" @@ -800,22 +795,23 @@ dependencies = [ { name = "cython" }, { name = "eigen" }, { name = "ffmpeg" }, + { name = "gcc-arm-none-eabi" }, { name = "git-lfs" }, { name = "inputs" }, { name = "jeepney" }, { name = "json-rpc" }, { name = "libjpeg" }, + { name = "libusb" }, { name = "libusb1" }, { name = "libyuv" }, { name = "ncurses" }, { name = "numpy" }, - { name = "openssl3" }, + { name = "pillow" }, { name = "psutil" }, { name = "pycapnp" }, { name = "pycryptodome" }, { name = "pyjwt" }, { name = "pyserial" }, - { name = "python3-dev" }, { name = "pyzmq" }, { name = "qrcode" }, { name = "raylib" }, @@ -837,7 +833,6 @@ dependencies = [ [package.optional-dependencies] dev = [ - { name = "gcc-arm-none-eabi" }, { name = "matplotlib" }, { name = "opencv-python-headless" }, ] @@ -860,7 +855,6 @@ testing = [ { name = "ty" }, ] tools = [ - { name = "dearpygui", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, { name = "metadrive-simulator", marker = "platform_machine != 'aarch64'" }, ] @@ -869,34 +863,34 @@ requires-dist = [ { name = "aiohttp" }, { name = "aiortc" }, { name = "av" }, - { name = "bzip2", git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=releases" }, - { name = "capnproto", git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases" }, + { name = "bzip2", git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=release-bzip2" }, + { name = "capnproto", git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=release-capnproto" }, { name = "casadi", specifier = ">=3.6.6" }, { name = "cffi" }, { name = "codespell", marker = "extra == 'testing'" }, { name = "coverage", marker = "extra == 'testing'" }, { name = "crcmod-plus" }, { name = "cython" }, - { name = "dearpygui", marker = "(platform_machine != 'aarch64' and extra == 'tools') or (sys_platform != 'linux' and extra == 'tools')", specifier = ">=2.1.0" }, - { name = "eigen", git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=releases" }, - { name = "ffmpeg", git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases" }, - { name = "gcc-arm-none-eabi", marker = "extra == 'dev'", git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases" }, - { name = "git-lfs", git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=releases" }, + { name = "eigen", git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=release-eigen" }, + { name = "ffmpeg", git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=release-ffmpeg" }, + { name = "gcc-arm-none-eabi", git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=release-gcc-arm-none-eabi" }, + { name = "git-lfs", git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=release-git-lfs" }, { name = "hypothesis", marker = "extra == 'testing'", specifier = "==6.47.*" }, { name = "inputs" }, { name = "jeepney" }, { name = "jinja2", marker = "extra == 'docs'" }, { name = "json-rpc" }, - { name = "libjpeg", git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=releases" }, + { name = "libjpeg", git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=release-libjpeg" }, + { name = "libusb", git = "https://github.com/commaai/dependencies.git?subdirectory=libusb&rev=release-libusb" }, { name = "libusb1" }, - { name = "libyuv", git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=releases" }, + { name = "libyuv", git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=release-libyuv" }, { name = "matplotlib", marker = "extra == 'dev'" }, { name = "metadrive-simulator", marker = "platform_machine != 'aarch64' and extra == 'tools'", git = "https://github.com/commaai/metadrive.git?rev=minimal" }, { name = "mkdocs", marker = "extra == 'docs'" }, - { name = "ncurses", git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=releases" }, + { name = "ncurses", git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=release-ncurses" }, { name = "numpy", specifier = ">=2.0" }, { name = "opencv-python-headless", marker = "extra == 'dev'" }, - { name = "openssl3", git = "https://github.com/commaai/dependencies.git?subdirectory=openssl3&rev=releases" }, + { name = "pillow" }, { name = "pre-commit-hooks", marker = "extra == 'testing'" }, { name = "psutil" }, { name = "pycapnp" }, @@ -909,7 +903,6 @@ requires-dist = [ { name = "pytest-mock", marker = "extra == 'testing'" }, { name = "pytest-subtests", marker = "extra == 'testing'" }, { name = "pytest-xdist", marker = "extra == 'testing'", git = "https://github.com/sshane/pytest-xdist?rev=2b4372bd62699fb412c4fe2f95bf9f01bd2018da" }, - { name = "python3-dev", git = "https://github.com/commaai/dependencies.git?subdirectory=python3-dev&rev=releases" }, { name = "pyzmq" }, { name = "qrcode" }, { name = "raylib", specifier = ">5.5.0.3" }, @@ -926,17 +919,12 @@ requires-dist = [ { name = "ty", marker = "extra == 'testing'" }, { name = "websocket-client" }, { name = "xattr" }, - { name = "zeromq", git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=releases" }, + { name = "zeromq", git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=release-zeromq" }, { name = "zstandard" }, - { name = "zstd", git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=releases" }, + { name = "zstd", git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=release-zstd" }, ] provides-extras = ["docs", "testing", "dev", "tools"] -[[package]] -name = "openssl3" -version = "3.4.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=openssl3&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } - [[package]] name = "packaging" version = "26.0" @@ -1303,11 +1291,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] -[[package]] -name = "python3-dev" -version = "3.12.8" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=python3-dev&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } - [[package]] name = "pyyaml" version = "6.0.3" @@ -1449,15 +1432,15 @@ wheels = [ [[package]] name = "sentry-sdk" -version = "2.53.0" +version = "2.54.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/06/66c8b705179bc54087845f28fd1b72f83751b6e9a195628e2e9af9926505/sentry_sdk-2.53.0.tar.gz", hash = "sha256:6520ef2c4acd823f28efc55e43eb6ce2e6d9f954a95a3aa96b6fd14871e92b77", size = 412369, upload-time = "2026-02-16T11:11:14.743Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c8/e9/2e3a46c304e7fa21eaa70612f60354e32699c7102eb961f67448e222ad7c/sentry_sdk-2.54.0.tar.gz", hash = "sha256:2620c2575128d009b11b20f7feb81e4e4e8ae08ec1d36cbc845705060b45cc1b", size = 413813, upload-time = "2026-03-02T15:12:41.355Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/d4/2fdf854bc3b9c7f55219678f812600a20a138af2dd847d99004994eada8f/sentry_sdk-2.53.0-py2.py3-none-any.whl", hash = "sha256:46e1ed8d84355ae54406c924f6b290c3d61f4048625989a723fd622aab838899", size = 437908, upload-time = "2026-02-16T11:11:13.227Z" }, + { url = "https://files.pythonhosted.org/packages/53/39/be412cc86bc6247b8f69e9383d7950711bd86f8d0a4a4b0fe8fad685bc21/sentry_sdk-2.54.0-py2.py3-none-any.whl", hash = "sha256:fd74e0e281dcda63afff095d23ebcd6e97006102cdc8e78a29f19ecdf796a0de", size = 439198, upload-time = "2026-03-02T15:12:39.546Z" }, ] [[package]] @@ -1553,26 +1536,26 @@ wheels = [ [[package]] name = "ty" -version = "0.0.19" +version = "0.0.20" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/84/5e/da108b9eeb392e02ff0478a34e9651490b36af295881cb56575b83f0cc3a/ty-0.0.19.tar.gz", hash = "sha256:ee3d9ed4cb586e77f6efe3d0fe5a855673ca438a3d533a27598e1d3502a2948a", size = 5220026, upload-time = "2026-02-26T12:13:15.215Z" } +sdist = { url = "https://files.pythonhosted.org/packages/56/95/8de69bb98417227b01f1b1d743c819d6456c9fd140255b6124b05b17dfd6/ty-0.0.20.tar.gz", hash = "sha256:ebba6be7974c14efbb2a9adda6ac59848f880d7259f089dfa72a093039f1dcc6", size = 5262529, upload-time = "2026-03-02T15:51:36.587Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/31/fd8c6067abb275bea11523d21ecf64e1d870b1ce80cac529cf6636df1471/ty-0.0.19-py3-none-linux_armv6l.whl", hash = "sha256:29bed05d34c8a7597567b8e327c53c1aed4a07dcfbe6c81e6d60c7444936ad77", size = 10268470, upload-time = "2026-02-26T12:13:42.881Z" }, - { url = "https://files.pythonhosted.org/packages/15/de/16a11bbf7d98c75849fc41f5d008b89bb5d080a4b10dc8ea851ee2bd371b/ty-0.0.19-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:79140870c688c97ec68e723c28935ddef9d91a76d48c68e665fe7c851e628b8a", size = 10098562, upload-time = "2026-02-26T12:13:31.618Z" }, - { url = "https://files.pythonhosted.org/packages/e7/4f/086d6ff6686eadf903913c45b53ab96694b62bbfee1d8cf3e55a9b5aa4b2/ty-0.0.19-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6e9c1f9cfa6a26f7881d14d75cf963af743f6c4189e6aa3e3b4056a65f22e730", size = 9604073, upload-time = "2026-02-26T12:13:24.645Z" }, - { url = "https://files.pythonhosted.org/packages/95/13/888a6b6c7ed4a880fee91bec997f775153ce86215ee4c56b868516314734/ty-0.0.19-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbca43b050edf1db2e64ae7b79add233c2aea2855b8a876081bbd032edcd0610", size = 10106295, upload-time = "2026-02-26T12:13:40.584Z" }, - { url = "https://files.pythonhosted.org/packages/cb/e8/05a372cae8da482de73b8246fb43236bf11e24ac28c879804568108759db/ty-0.0.19-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8acaa88ab1955ca6b15a0ccc274011c4961377fe65c3948e5d2b212f2517b87c", size = 10098234, upload-time = "2026-02-26T12:13:33.725Z" }, - { url = "https://files.pythonhosted.org/packages/c5/f1/5b0958e9e9576e7662192fe689bbb3dc88e631a4e073db3047793a547d58/ty-0.0.19-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a901b6a6dd9d17d5b3b2e7bafc3057294e88da3f5de507347316687d7f191a1", size = 10607218, upload-time = "2026-02-26T12:13:17.576Z" }, - { url = "https://files.pythonhosted.org/packages/fb/ab/358c78b77844f58ff5aca368550ab16c719f1ab0ec892ceb1114d7500f4e/ty-0.0.19-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8deafdaaaee65fd121c66064da74a922d8501be4a2d50049c71eab521a23eff7", size = 11160593, upload-time = "2026-02-26T12:13:36.008Z" }, - { url = "https://files.pythonhosted.org/packages/95/59/827fc346d66a59fe48e9689a5ceb67dbbd5b4de2e8d4625371af39a2e8b7/ty-0.0.19-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e56071af280897441018f74f921b97d53aec0856f8af85f4f949df8eda07d", size = 10822392, upload-time = "2026-02-26T12:13:29.415Z" }, - { url = "https://files.pythonhosted.org/packages/81/f9/3bbfbbe35478de9bcd63848f4bc9bffda72278dd9732dbad3efc3978432e/ty-0.0.19-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abdf5885130393ce74501dba792f48ce0a515756ec81c33a4b324bdf3509df6e", size = 10707139, upload-time = "2026-02-26T12:13:20.148Z" }, - { url = "https://files.pythonhosted.org/packages/12/9e/597023b183ec4ade83a36a0cea5c103f3bffa34f70813d46386c61447fb8/ty-0.0.19-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:877e89005c8f9d1dbff5ad14cbac9f35c528406fde38926f9b44f24830de8d6a", size = 10096933, upload-time = "2026-02-26T12:13:45.266Z" }, - { url = "https://files.pythonhosted.org/packages/1e/76/d0d2f6e674db2a17c8efa5e26682b9dfa8d34774705f35902a7b45ebd3bd/ty-0.0.19-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:39bd1da051c1e4d316efaf79dbed313255633f7c6ad6e24d29f4d9c6ffaf4de6", size = 10109547, upload-time = "2026-02-26T12:13:22.17Z" }, - { url = "https://files.pythonhosted.org/packages/a4/b0/76026c06b852a3aa4fdb5bd329fdc2175aaf3c64a3fafece9cc4df167cee/ty-0.0.19-py3-none-musllinux_1_2_i686.whl", hash = "sha256:87df8415a6c9cb27b8f1382fcdc6052e59f5b9f50f78bc14663197eb5c8d3699", size = 10289110, upload-time = "2026-02-26T12:13:38.29Z" }, - { url = "https://files.pythonhosted.org/packages/14/6c/f3b3a189816b4f079b20fe5d0d7ee38e38a472f53cc6770bb6571147e3de/ty-0.0.19-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:89b6bb23c332ed5c38dd859eb5793f887abcc936f681a40d4ea68e35eac1af33", size = 10796479, upload-time = "2026-02-26T12:13:10.992Z" }, - { url = "https://files.pythonhosted.org/packages/3d/18/caee33d1ce9dd50bd94c26cde7cda4f6971e22e474e7d72a5c86d745ad58/ty-0.0.19-py3-none-win32.whl", hash = "sha256:19b33df3aa7af7b1a9eaa4e1175c3b4dec0f5f2e140243e3492c8355c37418f3", size = 9677215, upload-time = "2026-02-26T12:13:08.519Z" }, - { url = "https://files.pythonhosted.org/packages/81/41/18fc0771d0b1da7d7cc2fc9af278d3122b754fe8b521a748734f4e16ecfd/ty-0.0.19-py3-none-win_amd64.whl", hash = "sha256:b9052c61464cdd76bc8e6796f2588c08700f25d0dcbc225bb165e390ea9d96a4", size = 10651252, upload-time = "2026-02-26T12:13:13.035Z" }, - { url = "https://files.pythonhosted.org/packages/8b/8c/26f7ce8863eb54510082747b3dfb1046ba24f16fc11de18c0e5feb36ff18/ty-0.0.19-py3-none-win_arm64.whl", hash = "sha256:9329804b66dcbae8e7af916ef4963221ed53b8ec7d09b0793591c5ae8a0f3270", size = 10093195, upload-time = "2026-02-26T12:13:26.816Z" }, + { url = "https://files.pythonhosted.org/packages/0b/2c/718abe48393e521bf852cd6b0f984766869b09c258d6e38a118768a91731/ty-0.0.20-py3-none-linux_armv6l.whl", hash = "sha256:7cc12769c169c9709a829c2248ee2826b7aae82e92caeac813d856f07c021eae", size = 10333656, upload-time = "2026-03-02T15:51:56.461Z" }, + { url = "https://files.pythonhosted.org/packages/41/0e/eb1c4cc4a12862e2327b72657bcebb10b7d9f17046f1bdcd6457a0211615/ty-0.0.20-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3b777c1bf13bc0a95985ebb8a324b8668a4a9b2e514dde5ccf09e4d55d2ff232", size = 10168505, upload-time = "2026-03-02T15:51:51.895Z" }, + { url = "https://files.pythonhosted.org/packages/89/7f/10230798e673f0dd3094dfd16e43bfd90e9494e7af6e8e7db516fb431ddf/ty-0.0.20-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b2a4a7db48bf8cba30365001bc2cad7fd13c1a5aacdd704cc4b7925de8ca5eb3", size = 9678510, upload-time = "2026-03-02T15:51:48.451Z" }, + { url = "https://files.pythonhosted.org/packages/7a/3d/59d9159577494edd1728f7db77b51bb07884bd21384f517963114e3ab5f6/ty-0.0.20-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6846427b8b353a43483e9c19936dc6a25612573b44c8f7d983dfa317e7f00d4c", size = 10162926, upload-time = "2026-03-02T15:51:40.558Z" }, + { url = "https://files.pythonhosted.org/packages/9c/a8/b7273eec3e802f78eb913fbe0ce0c16ef263723173e06a5776a8359b2c66/ty-0.0.20-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:245ceef5bd88df366869385cf96411cb14696334f8daa75597cf7e41c3012eb8", size = 10171702, upload-time = "2026-03-02T15:51:44.069Z" }, + { url = "https://files.pythonhosted.org/packages/9f/32/5f1144f2f04a275109db06e3498450c4721554215b80ae73652ef412eeab/ty-0.0.20-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4d21d1cdf67a444d3c37583c17291ddba9382a9871021f3f5d5735e09e85efe", size = 10682552, upload-time = "2026-03-02T15:51:33.102Z" }, + { url = "https://files.pythonhosted.org/packages/6a/db/9f1f637310792f12bd6ed37d5fc8ab39ba1a9b0c6c55a33865e9f1cad840/ty-0.0.20-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd4ffd907d1bd70e46af9e9a2f88622f215e1bf44658ea43b32c2c0b357299e4", size = 11242605, upload-time = "2026-03-02T15:51:34.895Z" }, + { url = "https://files.pythonhosted.org/packages/1a/68/cc9cae2e732fcfd20ccdffc508407905a023fc8493b8771c392d915528dc/ty-0.0.20-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b6594b58d8b0e9d16a22b3045fc1305db4b132c8d70c17784ab8c7a7cc986807", size = 10974655, upload-time = "2026-03-02T15:51:46.011Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c1/b9e3e3f28fe63486331e653f6aeb4184af8b1fe80542fcf74d2dda40a93d/ty-0.0.20-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3662f890518ce6cf4d7568f57d03906912d2afbf948a01089a28e325b1ef198c", size = 10761325, upload-time = "2026-03-02T15:51:26.818Z" }, + { url = "https://files.pythonhosted.org/packages/39/9e/67db935bdedf219a00fb69ec5437ba24dab66e0f2e706dd54a4eca234b84/ty-0.0.20-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0e3ffbae58f9f0d17cdc4ac6d175ceae560b7ed7d54f9ddfb1c9f31054bcdc2c", size = 10145793, upload-time = "2026-03-02T15:51:38.562Z" }, + { url = "https://files.pythonhosted.org/packages/c7/de/b0eb815d4dc5a819c7e4faddc2a79058611169f7eef07ccc006531ce228c/ty-0.0.20-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:176e52bc8bb00b0e84efd34583962878a447a3a0e34ecc45fd7097a37554261b", size = 10189640, upload-time = "2026-03-02T15:51:50.202Z" }, + { url = "https://files.pythonhosted.org/packages/b8/71/63734923965cbb70df1da3e93e4b8875434e326b89e9f850611122f279bf/ty-0.0.20-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b2bc73025418e976ca4143dde71fb9025a90754a08ac03e6aa9b80d4bed1294b", size = 10370568, upload-time = "2026-03-02T15:51:42.295Z" }, + { url = "https://files.pythonhosted.org/packages/32/a0/a532c2048533347dff48e9ca98bd86d2c224356e101688a8edaf8d6973fb/ty-0.0.20-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d52f7c9ec6e363e094b3c389c344d5a140401f14a77f0625e3f28c21918552f5", size = 10853999, upload-time = "2026-03-02T15:51:58.963Z" }, + { url = "https://files.pythonhosted.org/packages/48/88/36c652c658fe96658043e4abc8ea97801de6fb6e63ab50aaa82807bff1d8/ty-0.0.20-py3-none-win32.whl", hash = "sha256:c7d32bfe93f8fcaa52b6eef3f1b930fd7da410c2c94e96f7412c30cfbabf1d17", size = 9744206, upload-time = "2026-03-02T15:51:54.183Z" }, + { url = "https://files.pythonhosted.org/packages/ff/a7/a4a13bed1d7fd9d97aaa3c5bb5e6d3e9a689e6984806cbca2ab4c9233cac/ty-0.0.20-py3-none-win_amd64.whl", hash = "sha256:a5e10f40fc4a0a1cbcb740a4aad5c7ce35d79f030836ea3183b7a28f43170248", size = 10711999, upload-time = "2026-03-02T15:51:29.212Z" }, + { url = "https://files.pythonhosted.org/packages/8d/7e/6bfd748a9f4ff9267ed3329b86a0f02cdf6ab49f87bc36c8a164852f99fc/ty-0.0.20-py3-none-win_arm64.whl", hash = "sha256:53f7a5c12c960e71f160b734f328eff9a35d578af4b67a36b0bb5990ac5cdc27", size = 10150143, upload-time = "2026-03-02T15:51:31.283Z" }, ] [[package]] @@ -1643,38 +1626,40 @@ wheels = [ [[package]] name = "yarl" -version = "1.22.0" +version = "1.23.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "multidict" }, { name = "propcache" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", size = 187169, upload-time = "2025-10-06T14:12:55.963Z" } +sdist = { url = "https://files.pythonhosted.org/packages/23/6e/beb1beec874a72f23815c1434518bfc4ed2175065173fb138c3705f658d4/yarl-1.23.0.tar.gz", hash = "sha256:53b1ea6ca88ebd4420379c330aea57e258408dd0df9af0992e5de2078dc9f5d5", size = 194676, upload-time = "2026-03-01T22:07:53.373Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/75/ff/46736024fee3429b80a165a732e38e5d5a238721e634ab41b040d49f8738/yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f", size = 142000, upload-time = "2025-10-06T14:09:44.631Z" }, - { url = "https://files.pythonhosted.org/packages/5a/9a/b312ed670df903145598914770eb12de1bac44599549b3360acc96878df8/yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2", size = 94338, upload-time = "2025-10-06T14:09:46.372Z" }, - { url = "https://files.pythonhosted.org/packages/ba/f5/0601483296f09c3c65e303d60c070a5c19fcdbc72daa061e96170785bc7d/yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74", size = 94909, upload-time = "2025-10-06T14:09:48.648Z" }, - { url = "https://files.pythonhosted.org/packages/60/41/9a1fe0b73dbcefce72e46cf149b0e0a67612d60bfc90fb59c2b2efdfbd86/yarl-1.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1651bf8e0398574646744c1885a41198eba53dc8a9312b954073f845c90a8df", size = 372940, upload-time = "2025-10-06T14:09:50.089Z" }, - { url = "https://files.pythonhosted.org/packages/17/7a/795cb6dfee561961c30b800f0ed616b923a2ec6258b5def2a00bf8231334/yarl-1.22.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b8a0588521a26bf92a57a1705b77b8b59044cdceccac7151bd8d229e66b8dedb", size = 345825, upload-time = "2025-10-06T14:09:52.142Z" }, - { url = "https://files.pythonhosted.org/packages/d7/93/a58f4d596d2be2ae7bab1a5846c4d270b894958845753b2c606d666744d3/yarl-1.22.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42188e6a615c1a75bcaa6e150c3fe8f3e8680471a6b10150c5f7e83f47cc34d2", size = 386705, upload-time = "2025-10-06T14:09:54.128Z" }, - { url = "https://files.pythonhosted.org/packages/61/92/682279d0e099d0e14d7fd2e176bd04f48de1484f56546a3e1313cd6c8e7c/yarl-1.22.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6d2cb59377d99718913ad9a151030d6f83ef420a2b8f521d94609ecc106ee82", size = 396518, upload-time = "2025-10-06T14:09:55.762Z" }, - { url = "https://files.pythonhosted.org/packages/db/0f/0d52c98b8a885aeda831224b78f3be7ec2e1aa4a62091f9f9188c3c65b56/yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a", size = 377267, upload-time = "2025-10-06T14:09:57.958Z" }, - { url = "https://files.pythonhosted.org/packages/22/42/d2685e35908cbeaa6532c1fc73e89e7f2efb5d8a7df3959ea8e37177c5a3/yarl-1.22.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e8fbaa7cec507aa24ea27a01456e8dd4b6fab829059b69844bd348f2d467124", size = 365797, upload-time = "2025-10-06T14:09:59.527Z" }, - { url = "https://files.pythonhosted.org/packages/a2/83/cf8c7bcc6355631762f7d8bdab920ad09b82efa6b722999dfb05afa6cfac/yarl-1.22.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:433885ab5431bc3d3d4f2f9bd15bfa1614c522b0f1405d62c4f926ccd69d04fa", size = 365535, upload-time = "2025-10-06T14:10:01.139Z" }, - { url = "https://files.pythonhosted.org/packages/25/e1/5302ff9b28f0c59cac913b91fe3f16c59a033887e57ce9ca5d41a3a94737/yarl-1.22.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b790b39c7e9a4192dc2e201a282109ed2985a1ddbd5ac08dc56d0e121400a8f7", size = 382324, upload-time = "2025-10-06T14:10:02.756Z" }, - { url = "https://files.pythonhosted.org/packages/bf/cd/4617eb60f032f19ae3a688dc990d8f0d89ee0ea378b61cac81ede3e52fae/yarl-1.22.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31f0b53913220599446872d757257be5898019c85e7971599065bc55065dc99d", size = 383803, upload-time = "2025-10-06T14:10:04.552Z" }, - { url = "https://files.pythonhosted.org/packages/59/65/afc6e62bb506a319ea67b694551dab4a7e6fb7bf604e9bd9f3e11d575fec/yarl-1.22.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a49370e8f711daec68d09b821a34e1167792ee2d24d405cbc2387be4f158b520", size = 374220, upload-time = "2025-10-06T14:10:06.489Z" }, - { url = "https://files.pythonhosted.org/packages/e7/3d/68bf18d50dc674b942daec86a9ba922d3113d8399b0e52b9897530442da2/yarl-1.22.0-cp312-cp312-win32.whl", hash = "sha256:70dfd4f241c04bd9239d53b17f11e6ab672b9f1420364af63e8531198e3f5fe8", size = 81589, upload-time = "2025-10-06T14:10:09.254Z" }, - { url = "https://files.pythonhosted.org/packages/c8/9a/6ad1a9b37c2f72874f93e691b2e7ecb6137fb2b899983125db4204e47575/yarl-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:8884d8b332a5e9b88e23f60bb166890009429391864c685e17bd73a9eda9105c", size = 87213, upload-time = "2025-10-06T14:10:11.369Z" }, - { url = "https://files.pythonhosted.org/packages/44/c5/c21b562d1680a77634d748e30c653c3ca918beb35555cff24986fff54598/yarl-1.22.0-cp312-cp312-win_arm64.whl", hash = "sha256:ea70f61a47f3cc93bdf8b2f368ed359ef02a01ca6393916bc8ff877427181e74", size = 81330, upload-time = "2025-10-06T14:10:13.112Z" }, - { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" }, + { url = "https://files.pythonhosted.org/packages/88/8a/94615bc31022f711add374097ad4144d569e95ff3c38d39215d07ac153a0/yarl-1.23.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1932b6b8bba8d0160a9d1078aae5838a66039e8832d41d2992daa9a3a08f7860", size = 124737, upload-time = "2026-03-01T22:05:12.897Z" }, + { url = "https://files.pythonhosted.org/packages/e3/6f/c6554045d59d64052698add01226bc867b52fe4a12373415d7991fdca95d/yarl-1.23.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:411225bae281f114067578891bc75534cfb3d92a3b4dfef7a6ca78ba354e6069", size = 87029, upload-time = "2026-03-01T22:05:14.376Z" }, + { url = "https://files.pythonhosted.org/packages/19/2a/725ecc166d53438bc88f76822ed4b1e3b10756e790bafd7b523fe97c322d/yarl-1.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13a563739ae600a631c36ce096615fe307f131344588b0bc0daec108cdb47b25", size = 86310, upload-time = "2026-03-01T22:05:15.71Z" }, + { url = "https://files.pythonhosted.org/packages/99/30/58260ed98e6ff7f90ba84442c1ddd758c9170d70327394a6227b310cd60f/yarl-1.23.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cbf44c5cb4a7633d078788e1b56387e3d3cf2b8139a3be38040b22d6c3221c8", size = 97587, upload-time = "2026-03-01T22:05:17.384Z" }, + { url = "https://files.pythonhosted.org/packages/76/0a/8b08aac08b50682e65759f7f8dde98ae8168f72487e7357a5d684c581ef9/yarl-1.23.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53ad387048f6f09a8969631e4de3f1bf70c50e93545d64af4f751b2498755072", size = 92528, upload-time = "2026-03-01T22:05:18.804Z" }, + { url = "https://files.pythonhosted.org/packages/52/07/0b7179101fe5f8385ec6c6bb5d0cb9f76bd9fb4a769591ab6fb5cdbfc69a/yarl-1.23.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4a59ba56f340334766f3a4442e0efd0af895fae9e2b204741ef885c446b3a1a8", size = 105339, upload-time = "2026-03-01T22:05:20.235Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8a/36d82869ab5ec829ca8574dfcb92b51286fcfb1e9c7a73659616362dc880/yarl-1.23.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:803a3c3ce4acc62eaf01eaca1208dcf0783025ef27572c3336502b9c232005e7", size = 105061, upload-time = "2026-03-01T22:05:22.268Z" }, + { url = "https://files.pythonhosted.org/packages/66/3e/868e5c3364b6cee19ff3e1a122194fa4ce51def02c61023970442162859e/yarl-1.23.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3d2bff8f37f8d0f96c7ec554d16945050d54462d6e95414babaa18bfafc7f51", size = 100132, upload-time = "2026-03-01T22:05:23.638Z" }, + { url = "https://files.pythonhosted.org/packages/cf/26/9c89acf82f08a52cb52d6d39454f8d18af15f9d386a23795389d1d423823/yarl-1.23.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c75eb09e8d55bceb4367e83496ff8ef2bc7ea6960efb38e978e8073ea59ecb67", size = 99289, upload-time = "2026-03-01T22:05:25.749Z" }, + { url = "https://files.pythonhosted.org/packages/6f/54/5b0db00d2cb056922356104468019c0a132e89c8d3ab67d8ede9f4483d2a/yarl-1.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877b0738624280e34c55680d6054a307aa94f7d52fa0e3034a9cc6e790871da7", size = 96950, upload-time = "2026-03-01T22:05:27.318Z" }, + { url = "https://files.pythonhosted.org/packages/f6/40/10fa93811fd439341fad7e0718a86aca0de9548023bbb403668d6555acab/yarl-1.23.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b5405bb8f0e783a988172993cfc627e4d9d00432d6bbac65a923041edacf997d", size = 93960, upload-time = "2026-03-01T22:05:28.738Z" }, + { url = "https://files.pythonhosted.org/packages/bc/d2/8ae2e6cd77d0805f4526e30ec43b6f9a3dfc542d401ac4990d178e4bf0cf/yarl-1.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c3a3598a832590c5a3ce56ab5576361b5688c12cb1d39429cf5dba30b510760", size = 104703, upload-time = "2026-03-01T22:05:30.438Z" }, + { url = "https://files.pythonhosted.org/packages/2f/0c/b3ceacf82c3fe21183ce35fa2acf5320af003d52bc1fcf5915077681142e/yarl-1.23.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8419ebd326430d1cbb7efb5292330a2cf39114e82df5cc3d83c9a0d5ebeaf2f2", size = 98325, upload-time = "2026-03-01T22:05:31.835Z" }, + { url = "https://files.pythonhosted.org/packages/9d/e0/12900edd28bdab91a69bd2554b85ad7b151f64e8b521fe16f9ad2f56477a/yarl-1.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:be61f6fff406ca40e3b1d84716fde398fc08bc63dd96d15f3a14230a0973ed86", size = 105067, upload-time = "2026-03-01T22:05:33.358Z" }, + { url = "https://files.pythonhosted.org/packages/15/61/74bb1182cf79c9bbe4eb6b1f14a57a22d7a0be5e9cedf8e2d5c2086474c3/yarl-1.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ceb13c5c858d01321b5d9bb65e4cf37a92169ea470b70fec6f236b2c9dd7e34", size = 100285, upload-time = "2026-03-01T22:05:35.4Z" }, + { url = "https://files.pythonhosted.org/packages/69/7f/cd5ef733f2550de6241bd8bd8c3febc78158b9d75f197d9c7baa113436af/yarl-1.23.0-cp312-cp312-win32.whl", hash = "sha256:fffc45637bcd6538de8b85f51e3df3223e4ad89bccbfca0481c08c7fc8b7ed7d", size = 82359, upload-time = "2026-03-01T22:05:36.811Z" }, + { url = "https://files.pythonhosted.org/packages/f5/be/25216a49daeeb7af2bec0db22d5e7df08ed1d7c9f65d78b14f3b74fd72fc/yarl-1.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:f69f57305656a4852f2a7203efc661d8c042e6cc67f7acd97d8667fb448a426e", size = 87674, upload-time = "2026-03-01T22:05:38.171Z" }, + { url = "https://files.pythonhosted.org/packages/d2/35/aeab955d6c425b227d5b7247eafb24f2653fedc32f95373a001af5dfeb9e/yarl-1.23.0-cp312-cp312-win_arm64.whl", hash = "sha256:6e87a6e8735b44816e7db0b2fbc9686932df473c826b0d9743148432e10bb9b9", size = 81879, upload-time = "2026-03-01T22:05:40.006Z" }, + { url = "https://files.pythonhosted.org/packages/69/68/c8739671f5699c7dc470580a4f821ef37c32c4cb0b047ce223a7f115757f/yarl-1.23.0-py3-none-any.whl", hash = "sha256:a2df6afe50dea8ae15fa34c9f824a3ee958d785fd5d089063d960bae1daa0a3f", size = 48288, upload-time = "2026-03-01T22:07:51.388Z" }, ] [[package]] name = "zeromq" version = "4.3.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=release-zeromq#768bd6d6d67acc7b4e919993967187532af0d410" } [[package]] name = "zstandard" @@ -1704,4 +1689,4 @@ wheels = [ [[package]] name = "zstd" version = "1.5.6" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=release-zstd#4d4dd0b74dfc52bdeec36706fd1a3a27754679ec" }