mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-08-05 17:45:40 +08:00
Merge remote-tracking branch 'origin/master-new' into HKG-long-tune
# Conflicts: # opendbc_repo
This commit is contained in:
@@ -229,7 +229,7 @@ jobs:
|
||||
- name: Build openpilot
|
||||
run: ${{ env.RUN }} "scons -j$(nproc)"
|
||||
- name: Test car models
|
||||
timeout-minutes: ${{ contains(runner.name, 'nsc') && (steps.routes-cache.outputs.cache-hit == 'true') && 1 || 20 }}
|
||||
timeout-minutes: ${{ contains(runner.name, 'nsc') && (steps.routes-cache.outputs.cache-hit == 'true') && 1 || 6 }}
|
||||
run: |
|
||||
${{ env.RUN }} "MAX_EXAMPLES=1 $PYTEST selfdrive/car/tests/test_models.py && \
|
||||
chmod -R 777 /tmp/comma_download_cache"
|
||||
|
||||
@@ -21,8 +21,6 @@ on:
|
||||
push:
|
||||
branches: [ master, master-new, master-dev-c3-new ]
|
||||
tags: [ '*' ]
|
||||
pull_request:
|
||||
branches: [ master, master-new ]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
extra_version:
|
||||
|
||||
@@ -95,8 +95,21 @@ jobs:
|
||||
|
||||
for ((i=0; i<${#A[*]}; i=i+1));
|
||||
do
|
||||
# Check if the master file exists
|
||||
if [ ! -f "${{ github.workspace }}/master_ui/${A[$i]}.png" ]; then
|
||||
# This is a new file in PR UI that doesn't exist in master
|
||||
DIFF="${DIFF}<details open>"
|
||||
DIFF="${DIFF}<summary>${A[$i]} : \$\${\\color{cyan}\\text{NEW}}\$\$</summary>"
|
||||
DIFF="${DIFF}<table>"
|
||||
|
||||
if ! compare -fuzz 2% -highlight-color DeepSkyBlue1 -lowlight-color Black -compose Src ${{ github.workspace }}/master_ui/${A[$i]}.png ${{ github.workspace }}/pr_ui/${A[$i]}.png ${{ github.workspace }}/pr_ui/${A[$i]}_diff.png; then
|
||||
DIFF="${DIFF}<tr>"
|
||||
DIFF="${DIFF} <td> master <img src=\"https://dummyimage.com/1986x993/000000/ffffff?text=++Not+in+master\"> </td>"
|
||||
DIFF="${DIFF} <td> proposed <img src=\"https://raw.githubusercontent.com/sunnypilot/ci-artifacts/${{ env.BRANCH_NAME }}/${A[$i]}.png\"> </td>"
|
||||
DIFF="${DIFF}</tr>"
|
||||
|
||||
DIFF="${DIFF}</table>"
|
||||
DIFF="${DIFF}</details>"
|
||||
elif ! compare -fuzz 2% -highlight-color DeepSkyBlue1 -lowlight-color Black -compose Src ${{ github.workspace }}/master_ui/${A[$i]}.png ${{ github.workspace }}/pr_ui/${A[$i]}.png ${{ github.workspace }}/pr_ui/${A[$i]}_diff.png; then
|
||||
convert ${{ github.workspace }}/pr_ui/${A[$i]}_diff.png -transparent black mask.png
|
||||
composite mask.png ${{ github.workspace }}/master_ui/${A[$i]}.png composite_diff.png
|
||||
convert -delay 100 ${{ github.workspace }}/master_ui/${A[$i]}.png composite_diff.png -loop 0 ${{ github.workspace }}/pr_ui/${A[$i]}_diff.gif
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ Version 0.9.8 (2025-02-28)
|
||||
========================
|
||||
* New driving model
|
||||
* Model now gates applying positive acceleration in Chill mode
|
||||
* New driving monitoring model
|
||||
* New driver monitoring model
|
||||
* Reduced false positives related to passengers
|
||||
* Image processing pipeline moved to the ISP
|
||||
* More GPU time for bigger driving models
|
||||
|
||||
+2
-1
@@ -136,7 +136,8 @@ struct OnroadEventSP @0xda96579883444c35 {
|
||||
}
|
||||
|
||||
struct CarParamsSP @0x80ae746ee2596b11 {
|
||||
flags @0 :UInt32; # flags for car specific quirks in sunnypilot
|
||||
flags @0 :UInt32; # flags for car specific quirks in sunnypilot
|
||||
safetyParam @1 : Int16; # flags for sunnypilot's custom safety flags
|
||||
}
|
||||
|
||||
struct CarControlSP @0xa5cd762cd951a455 {
|
||||
|
||||
+3
-1
@@ -59,6 +59,7 @@ struct OnroadEvent @0xc4fa6047f024e718 {
|
||||
pcmEnable @23;
|
||||
pcmDisable @24;
|
||||
radarFault @25;
|
||||
radarTempUnavailable @93;
|
||||
brakeHold @26;
|
||||
parkBrake @27;
|
||||
manualRestart @28;
|
||||
@@ -728,7 +729,7 @@ struct PeripheralState {
|
||||
struct RadarState @0x9a185389d6fdd05f {
|
||||
mdMonoTime @6 :UInt64;
|
||||
carStateMonoTime @11 :UInt64;
|
||||
radarErrors @12 :List(Car.RadarData.Error);
|
||||
radarErrors @13 :Car.RadarData.Error;
|
||||
|
||||
leadOne @3 :LeadData;
|
||||
leadTwo @4 :LeadData;
|
||||
@@ -762,6 +763,7 @@ struct RadarState @0x9a185389d6fdd05f {
|
||||
calPercDEPRECATED @9 :Int8;
|
||||
canMonoTimesDEPRECATED @10 :List(UInt64);
|
||||
cumLagMsDEPRECATED @5 :Float32;
|
||||
radarErrorsDEPRECATED @12 :List(Car.RadarData.ErrorDEPRECATED);
|
||||
}
|
||||
|
||||
struct LiveCalibrationData {
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
from openpilot.common.api.comma_connect import CommaConnectApi
|
||||
from sunnypilot.sunnylink.api import SunnylinkApi
|
||||
|
||||
|
||||
class Api:
|
||||
def __init__(self, dongle_id, use_sunnylink=False):
|
||||
if use_sunnylink:
|
||||
self.service = SunnylinkApi(dongle_id)
|
||||
else:
|
||||
self.service = CommaConnectApi(dongle_id)
|
||||
def __init__(self, dongle_id):
|
||||
self.service = CommaConnectApi(dongle_id)
|
||||
|
||||
def request(self, method, endpoint, **params):
|
||||
return self.service.request(method, endpoint, **params)
|
||||
@@ -22,6 +18,5 @@ class Api:
|
||||
return self.service.get_token(expiry_hours)
|
||||
|
||||
|
||||
def api_get(endpoint, method='GET', timeout=None, access_token=None, use_sunnylink=False, **params):
|
||||
return SunnylinkApi(None).api_get(endpoint, method, timeout, access_token, **params) if use_sunnylink \
|
||||
else CommaConnectApi(None).api_get(endpoint, method, timeout, access_token, **params)
|
||||
def api_get(endpoint, method='GET', timeout=None, access_token=None, **params):
|
||||
return CommaConnectApi(None).api_get(endpoint, method, timeout, access_token, **params)
|
||||
|
||||
@@ -132,7 +132,7 @@ inline static std::unordered_map<std::string, uint32_t> keys = {
|
||||
// MADS params
|
||||
{"Mads", PERSISTENT | BACKUP},
|
||||
{"MadsMainCruiseAllowed", PERSISTENT | BACKUP},
|
||||
{"MadsPauseLateralOnBrake", PERSISTENT | BACKUP},
|
||||
{"MadsSteeringMode", PERSISTENT | BACKUP},
|
||||
{"MadsUnifiedEngagementMode", PERSISTENT | BACKUP},
|
||||
|
||||
// Model Manager params
|
||||
@@ -144,6 +144,8 @@ inline static std::unordered_map<std::string, uint32_t> keys = {
|
||||
// sunnylink params
|
||||
{"EnableSunnylinkUploader", PERSISTENT | BACKUP},
|
||||
{"LastSunnylinkPingTime", CLEAR_ON_MANAGER_START},
|
||||
{"SunnylinkCache_Roles", PERSISTENT},
|
||||
{"SunnylinkCache_Users", PERSISTENT},
|
||||
{"SunnylinkDongleId", PERSISTENT},
|
||||
{"SunnylinkdPid", PERSISTENT},
|
||||
{"SunnylinkEnabled", PERSISTENT},
|
||||
|
||||
+8
-8
@@ -34,25 +34,25 @@ function launch {
|
||||
# Check to see if there's a valid overlay-based update available. Conditions
|
||||
# are as follows:
|
||||
#
|
||||
# 1. The BASEDIR init file has to exist, with a newer modtime than anything in
|
||||
# the BASEDIR Git repo. This checks for local development work or the user
|
||||
# 1. The DIR init file has to exist, with a newer modtime than anything in
|
||||
# the DIR Git repo. This checks for local development work or the user
|
||||
# switching branches/forks, which should not be overwritten.
|
||||
# 2. The FINALIZED consistent file has to exist, indicating there's an update
|
||||
# that completed successfully and synced to disk.
|
||||
|
||||
if [ -f "${BASEDIR}/.overlay_init" ]; then
|
||||
find ${BASEDIR}/.git -newer ${BASEDIR}/.overlay_init | grep -q '.' 2> /dev/null
|
||||
if [ -f "${DIR}/.overlay_init" ]; then
|
||||
find ${DIR}/.git -newer ${DIR}/.overlay_init | grep -q '.' 2> /dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "${BASEDIR} has been modified, skipping overlay update installation"
|
||||
echo "${DIR} has been modified, skipping overlay update installation"
|
||||
else
|
||||
if [ -f "${STAGING_ROOT}/finalized/.overlay_consistent" ]; then
|
||||
if [ ! -d /data/safe_staging/old_openpilot ]; then
|
||||
echo "Valid overlay update found, installing"
|
||||
LAUNCHER_LOCATION="${BASH_SOURCE[0]}"
|
||||
|
||||
mv $BASEDIR /data/safe_staging/old_openpilot
|
||||
mv "${STAGING_ROOT}/finalized" $BASEDIR
|
||||
cd $BASEDIR
|
||||
mv $DIR /data/safe_staging/old_openpilot
|
||||
mv "${STAGING_ROOT}/finalized" $DIR
|
||||
cd $DIR
|
||||
|
||||
echo "Restarting launch script ${LAUNCHER_LOCATION}"
|
||||
unset AGNOS_VERSION
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ export OPENBLAS_NUM_THREADS=1
|
||||
export VECLIB_MAXIMUM_THREADS=1
|
||||
|
||||
if [ -z "$AGNOS_VERSION" ]; then
|
||||
export AGNOS_VERSION="11.10"
|
||||
export AGNOS_VERSION="11.11"
|
||||
fi
|
||||
|
||||
export STAGING_ROOT="/data/safe_staging"
|
||||
|
||||
+1
-1
Submodule opendbc_repo updated: 8934a5fb8f...2262527044
+1
-1
Submodule panda updated: 5479e1572a...ccb9581b1c
@@ -99,11 +99,13 @@ dev = [
|
||||
"av",
|
||||
"azure-identity",
|
||||
"azure-storage-blob",
|
||||
"dbus-next",
|
||||
"dictdiffer",
|
||||
"lru-dict",
|
||||
"matplotlib",
|
||||
"parameterized >=0.8, <0.9",
|
||||
"pyautogui",
|
||||
"pygame",
|
||||
"pyopencl; platform_machine != 'aarch64'", # broken on arm64
|
||||
"pytools < 2024.1.11; platform_machine != 'aarch64'", # pyopencl use a broken version
|
||||
"pywinctl",
|
||||
|
||||
@@ -40,8 +40,6 @@ rm -f panda/board/obj/panda.bin.signed
|
||||
rm -f panda/board/obj/panda_h7.bin.signed
|
||||
|
||||
VERSION=$(cat common/version.h | awk -F[\"-] '{print $2}')
|
||||
echo "#define COMMA_VERSION \"$VERSION-release\"" > common/version.h
|
||||
|
||||
echo "[-] committing version $VERSION T=$SECONDS"
|
||||
git add -f .
|
||||
git commit -a -m "openpilot v$VERSION release"
|
||||
|
||||
+152
-118
@@ -9,155 +9,189 @@ from datetime import datetime
|
||||
|
||||
|
||||
def setup_argument_parser():
|
||||
parser = argparse.ArgumentParser(description='Process and squash GitHub PRs')
|
||||
parser.add_argument('--pr-data', type=str, help='PR data in JSON format')
|
||||
parser.add_argument('--source-branch', type=str, default='master-new',
|
||||
help='Source branch for merging')
|
||||
parser.add_argument('--target-branch', type=str, default='master-dev-c3-new-test',
|
||||
help='Target branch for merging')
|
||||
parser.add_argument('--squash-script-path', type=str, required=True,
|
||||
help='Path to the squash_and_merge.py script')
|
||||
return parser
|
||||
parser = argparse.ArgumentParser(description='Process and squash GitHub PRs')
|
||||
parser.add_argument('--pr-data', type=str, help='PR data in JSON format')
|
||||
parser.add_argument('--source-branch', type=str, default='master-new',
|
||||
help='Source branch for merging')
|
||||
parser.add_argument('--target-branch', type=str, default='master-dev-c3-new-test',
|
||||
help='Target branch for merging')
|
||||
parser.add_argument('--squash-script-path', type=str, required=True,
|
||||
help='Path to the squash_and_merge.py script')
|
||||
return parser
|
||||
|
||||
|
||||
def validate_squash_script(script_path):
|
||||
if not os.path.isfile(script_path):
|
||||
raise FileNotFoundError(f"Squash script not found at: {script_path}")
|
||||
if not os.access(script_path, os.X_OK):
|
||||
raise PermissionError(f"Squash script is not executable: {script_path}")
|
||||
if not os.path.isfile(script_path):
|
||||
raise FileNotFoundError(f"Squash script not found at: {script_path}")
|
||||
if not os.access(script_path, os.X_OK):
|
||||
raise PermissionError(f"Squash script is not executable: {script_path}")
|
||||
|
||||
|
||||
def sort_prs_by_creation(pr_data):
|
||||
"""Sort PRs by creation date"""
|
||||
nodes = (pr_data.get('data', {}).get('search', {}).get('nodes', []))
|
||||
"""Sort PRs by creation date"""
|
||||
nodes = (pr_data.get('data', {}).get('search', {}).get('nodes', []))
|
||||
|
||||
return sorted(
|
||||
nodes,
|
||||
key=lambda x: datetime.fromisoformat(x.get('createdAt', '').replace('Z', '+00:00'))
|
||||
)
|
||||
return sorted(
|
||||
nodes,
|
||||
key=lambda x: datetime.fromisoformat(x.get('createdAt', '').replace('Z', '+00:00'))
|
||||
)
|
||||
|
||||
|
||||
def add_pr_comment(pr_number, comment):
|
||||
"""Add a comment to a PR using gh cli"""
|
||||
try:
|
||||
subprocess.run(
|
||||
['gh', 'pr', 'comment', str(pr_number), '--body', comment],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Failed to add comment to PR #{pr_number}: {e.stderr}")
|
||||
"""Add or update a comment to a PR using gh cli"""
|
||||
title = "## Squash and Merge"
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['gh', 'pr', 'view', str(pr_number), '--json', 'comments'],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
comments_data = json.loads(result.stdout)
|
||||
has_existing_comment = False
|
||||
|
||||
for pr_comment in comments_data['comments']:
|
||||
if pr_comment['body'].startswith(title):
|
||||
has_existing_comment = True
|
||||
break
|
||||
|
||||
full_comment = f"{title}\n\n{comment}"
|
||||
|
||||
if has_existing_comment:
|
||||
subprocess.run(
|
||||
['gh', 'pr', 'comment', '--edit-last', f"#{pr_number}", '--body', full_comment],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
else:
|
||||
subprocess.run(
|
||||
['gh', 'pr', 'comment', f"#{pr_number}", '--body', full_comment],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Failed to add/update comment on PR #{pr_number}: {e.stderr}")
|
||||
except json.JSONDecodeError:
|
||||
print(f"Failed to parse comments data for PR #{pr_number}")
|
||||
|
||||
|
||||
|
||||
def validate_pr(pr):
|
||||
"""Validate a PR and return (is_valid, skip_reason)"""
|
||||
pr_number = pr.get('number', 'UNKNOWN')
|
||||
branch = pr.get('headRefName', '')
|
||||
"""Validate a PR and return (is_valid, skip_reason)"""
|
||||
pr_number = pr.get('number', 'UNKNOWN')
|
||||
branch = pr.get('headRefName', '')
|
||||
|
||||
if not branch:
|
||||
return False, f"Missing branch name for PR #{pr_number}"
|
||||
if not branch:
|
||||
return False, f"missing branch name for PR #{pr_number}"
|
||||
|
||||
# Check if checks have passed
|
||||
commits = pr.get('commits', {}).get('nodes', [])
|
||||
if not commits:
|
||||
return False, "No commit data found"
|
||||
# Check if checks have passed
|
||||
commits = pr.get('commits', {}).get('nodes', [])
|
||||
if not commits:
|
||||
return False, "no commit data found"
|
||||
|
||||
status = commits[0].get('commit', {}).get('statusCheckRollup', {})
|
||||
if not status or status.get('state') != 'SUCCESS':
|
||||
return False, "Not all checks have passed"
|
||||
status = commits[0].get('commit', {}).get('statusCheckRollup', {})
|
||||
if not status or status.get('state') != 'SUCCESS':
|
||||
return False, "not all checks have passed"
|
||||
|
||||
# Check for merge conflicts
|
||||
merge_status = subprocess.run(['gh', 'pr', 'view', str(pr_number), '--json', 'mergeable,mergeStateStatus'], capture_output=True, text=True)
|
||||
merge_data = json.loads(merge_status.stdout)
|
||||
if not merge_data.get('mergeable'):
|
||||
return False, "Merge conflicts detected"
|
||||
# Check for merge conflicts
|
||||
merge_status = subprocess.run(['gh', 'pr', 'view', str(pr_number), '--json', 'mergeable,mergeStateStatus'],
|
||||
capture_output=True, text=True)
|
||||
merge_data = json.loads(merge_status.stdout)
|
||||
if not merge_data.get('mergeable'):
|
||||
return False, "merge conflicts detected"
|
||||
|
||||
if (mergeStateStatus := merge_data.get('mergeStateStatus')) == "BEHIND":
|
||||
return False, f"Branch is `{mergeStateStatus}`"
|
||||
if (mergeStateStatus := merge_data.get('mergeStateStatus')) == "BEHIND":
|
||||
return False, f"branch is `{mergeStateStatus}`"
|
||||
|
||||
return True, None
|
||||
return True, None
|
||||
|
||||
|
||||
def process_pr(pr_data, source_branch, target_branch, squash_script_path):
|
||||
try:
|
||||
nodes = sort_prs_by_creation(pr_data)
|
||||
if not nodes:
|
||||
print("No PRs to squash")
|
||||
return 0
|
||||
try:
|
||||
nodes = sort_prs_by_creation(pr_data)
|
||||
if not nodes:
|
||||
print("No PRs to squash")
|
||||
return 0
|
||||
|
||||
print(f"Deleting target branch {target_branch}")
|
||||
subprocess.run(['git', 'branch', '-D', target_branch], check=False)
|
||||
subprocess.run(['git', 'branch', target_branch, f'origin/{source_branch}'], check=True)
|
||||
success_count = 0
|
||||
for pr in nodes:
|
||||
pr_number = pr.get('number', 'UNKNOWN')
|
||||
branch = pr.get('headRefName', '')
|
||||
title = pr.get('title', '')
|
||||
is_valid, skip_reason = validate_pr(pr)
|
||||
print(f"Deleting target branch {target_branch}")
|
||||
subprocess.run(['git', 'branch', '-D', target_branch], check=False)
|
||||
subprocess.run(['git', 'branch', target_branch, f'origin/{source_branch}'], check=True)
|
||||
success_count = 0
|
||||
for pr in nodes:
|
||||
pr_number = pr.get('number', 'UNKNOWN')
|
||||
branch = pr.get('headRefName', '')
|
||||
title = pr.get('title', '')
|
||||
is_valid, skip_reason = validate_pr(pr)
|
||||
|
||||
if not is_valid:
|
||||
print(f"Warning: {skip_reason} for PR #{pr_number}, skipping")
|
||||
add_pr_comment(pr_number, f"⚠️ This PR was skipped in the automated `{target_branch}` squash because {skip_reason}.")
|
||||
continue
|
||||
if not is_valid:
|
||||
print(f"Warning: {skip_reason} for PR #{pr_number}, skipping")
|
||||
add_pr_comment(pr_number,
|
||||
f"⚠️ This PR was skipped in the automated `{target_branch}` squash because **{skip_reason}**.")
|
||||
continue
|
||||
|
||||
try:
|
||||
# Fetch PR branch
|
||||
subprocess.run(['git', 'fetch', 'origin', branch], check=True)
|
||||
# Delete branch if it exists (ignore errors if it doesn't)
|
||||
subprocess.run(['git', 'branch', '-D', branch], check=False)
|
||||
# Create new branch pointing to origin's branch
|
||||
subprocess.run(['git', 'branch', branch, f'origin/{branch}'], check=True)
|
||||
try:
|
||||
# Fetch PR branch
|
||||
subprocess.run(['git', 'fetch', 'origin', branch], check=True)
|
||||
# Delete branch if it exists (ignore errors if it doesn't)
|
||||
subprocess.run(['git', 'branch', '-D', branch], check=False)
|
||||
# Create new branch pointing to origin's branch
|
||||
subprocess.run(['git', 'branch', branch, f'origin/{branch}'], check=True)
|
||||
|
||||
# Run squash script
|
||||
subprocess.run([
|
||||
squash_script_path,
|
||||
'--target', target_branch,
|
||||
'--source', branch,
|
||||
'--title', f"{title} (#{pr_number})",
|
||||
], check=True)
|
||||
# Run squash script
|
||||
subprocess.run([
|
||||
squash_script_path,
|
||||
'--target', target_branch,
|
||||
'--source', branch,
|
||||
'--title', f"{title} (#{pr_number})",
|
||||
], check=True)
|
||||
|
||||
print(f"Successfully processed PR #{pr_number}")
|
||||
success_count += 1
|
||||
print(f"Successfully processed PR #{pr_number}")
|
||||
success_count += 1
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error processing PR #{pr_number}:")
|
||||
print(f"Command failed with exit code {e.returncode}")
|
||||
error_output = getattr(e, 'stderr', 'No error output available')
|
||||
print(f"Error output: {error_output}")
|
||||
add_pr_comment(pr_number, f"⚠️ Error during automated {target_branch} squash:\n```\n{error_output}\n```")
|
||||
continue
|
||||
except Exception as e:
|
||||
print(f"Unexpected error processing PR #{pr_number}: {str(e)}")
|
||||
continue
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error processing PR #{pr_number}:")
|
||||
print(f"Command failed with exit code {e.returncode}")
|
||||
error_output = getattr(e, 'stderr', 'No error output available')
|
||||
print(f"Error output: {error_output}")
|
||||
add_pr_comment(pr_number,
|
||||
f"⚠️ Error during automated `{target_branch}` squash:\n```\n{error_output}\n```")
|
||||
continue
|
||||
except Exception as e:
|
||||
print(f"Unexpected error processing PR #{pr_number}: {str(e)}")
|
||||
continue
|
||||
|
||||
return success_count
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
print(f"Error in process_pr: {str(e)}")
|
||||
print("Full traceback:")
|
||||
print(traceback.format_exc())
|
||||
return 0
|
||||
|
||||
|
||||
def main():
|
||||
parser = setup_argument_parser()
|
||||
try:
|
||||
args = parser.parse_args()
|
||||
validate_squash_script(args.squash_script_path)
|
||||
pr_data_json = json.loads(args.pr_data)
|
||||
|
||||
# Process the PRs
|
||||
success_count = process_pr(pr_data_json, args.source_branch, args.target_branch, args.squash_script_path)
|
||||
print(f"Successfully processed {success_count} PRs")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Fatal error: {str(e)}", file=sys.stderr)
|
||||
return 1
|
||||
return success_count
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
print(f"Error in process_pr: {str(e)}")
|
||||
print("Full traceback:")
|
||||
print(traceback.format_exc())
|
||||
return 0
|
||||
|
||||
|
||||
def main():
|
||||
parser = setup_argument_parser()
|
||||
try:
|
||||
args = parser.parse_args()
|
||||
validate_squash_script(args.squash_script_path)
|
||||
pr_data_json = json.loads(args.pr_data)
|
||||
|
||||
# Process the PRs
|
||||
success_count = process_pr(pr_data_json, args.source_branch, args.target_branch, args.squash_script_path)
|
||||
print(f"Successfully processed {success_count} PRs")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Fatal error: {str(e)}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
sys.exit(main())
|
||||
|
||||
@@ -8,6 +8,7 @@ ROOT = HERE + "/.."
|
||||
|
||||
blacklist = [
|
||||
".git/",
|
||||
".github/workflows/",
|
||||
|
||||
"matlab.*.md",
|
||||
|
||||
|
||||
+10
-10
@@ -16,7 +16,7 @@ from opendbc.car import DT_CTRL, structs
|
||||
from opendbc.car.can_definitions import CanData, CanRecvCallable, CanSendCallable
|
||||
from opendbc.car.carlog import carlog
|
||||
from opendbc.car.fw_versions import ObdCallback
|
||||
from opendbc.car.car_helpers import get_car, get_radar_interface
|
||||
from opendbc.car.car_helpers import get_car, interfaces
|
||||
from opendbc.car.interfaces import CarInterfaceBase, RadarInterfaceBase
|
||||
from opendbc.safety import ALTERNATIVE_EXPERIENCE
|
||||
from openpilot.selfdrive.pandad import can_capnp_to_list, can_list_to_can_capnp
|
||||
@@ -24,8 +24,8 @@ from openpilot.selfdrive.car.cruise import VCruiseHelper
|
||||
from openpilot.selfdrive.car.car_specific import MockCarState
|
||||
from openpilot.selfdrive.car.helpers import convert_carControlSP, convert_to_capnp
|
||||
|
||||
from openpilot.sunnypilot.mads.mads import MadsParams
|
||||
from openpilot.sunnypilot.selfdrive.car import interfaces
|
||||
from openpilot.sunnypilot.mads.helpers import set_alternative_experience, set_car_specific_params
|
||||
from openpilot.sunnypilot.selfdrive.car import interfaces as sunnypilot_interfaces
|
||||
|
||||
REPLAY = "REPLAY" in os.environ
|
||||
|
||||
@@ -108,8 +108,8 @@ class Car:
|
||||
fixed_fingerprint = json.loads(self.params.get("CarPlatformBundle", encoding='utf-8') or "{}").get("platform", None)
|
||||
|
||||
self.CI = get_car(*self.can_callbacks, obd_callback(self.params), experimental_long_allowed, num_pandas, cached_params, fixed_fingerprint)
|
||||
interfaces.setup_car_interface_sp(self.CI.CP, self.CI.CP_SP, self.params)
|
||||
self.RI = get_radar_interface(self.CI.CP, self.CI.CP_SP)
|
||||
sunnypilot_interfaces.setup_car_interface_sp(self.CI.CP, self.CI.CP_SP, self.params)
|
||||
self.RI = interfaces[self.CI.CP.carFingerprint].RadarInterface(self.CI.CP, self.CI.CP_SP)
|
||||
self.CP = self.CI.CP
|
||||
self.CP_SP = self.CI.CP_SP
|
||||
|
||||
@@ -126,8 +126,8 @@ class Car:
|
||||
self.CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS
|
||||
|
||||
# mads
|
||||
MadsParams().set_alternative_experience(self.CP)
|
||||
MadsParams().set_car_specific_params(self.CP, self.CP_SP)
|
||||
set_alternative_experience(self.CP, self.params)
|
||||
set_car_specific_params(self.CP, self.CP_SP, self.params)
|
||||
|
||||
# Dynamic Experimental Control
|
||||
self.dynamic_experimental_control = self.params.get_bool("DynamicExperimentalControl")
|
||||
@@ -192,7 +192,7 @@ class Car:
|
||||
self.rk = Ratekeeper(100, print_delay_threshold=None)
|
||||
|
||||
# log fingerprint in sentry
|
||||
interfaces.log_fingerprint(self.CP)
|
||||
sunnypilot_interfaces.log_fingerprint(self.CP)
|
||||
|
||||
def state_update(self) -> tuple[car.CarState, structs.RadarDataT | None]:
|
||||
"""carState update loop, driven by can"""
|
||||
@@ -256,7 +256,7 @@ class Car:
|
||||
|
||||
if RD is not None:
|
||||
tracks_msg = messaging.new_message('liveTracks')
|
||||
tracks_msg.valid = len(RD.errors) == 0
|
||||
tracks_msg.valid = not any(RD.errors.to_dict().values())
|
||||
tracks_msg.liveTracks = RD
|
||||
self.pm.send('liveTracks', tracks_msg)
|
||||
|
||||
@@ -274,7 +274,7 @@ class Car:
|
||||
# Initialize CarInterface, once controls are ready
|
||||
# TODO: this can make us miss at least a few cycles when doing an ECU knockout
|
||||
self.CI.init(self.CP, self.CP_SP, *self.can_callbacks)
|
||||
interfaces.initialize_car_interface_sp(self.CP, self.CP_SP, self.params, *self.can_callbacks)
|
||||
sunnypilot_interfaces.initialize_car_interface_sp(self.CP, self.CP_SP, self.params, *self.can_callbacks)
|
||||
# signal pandad to switch to car safety mode
|
||||
self.params.put_bool_nonblocking("ControlsReady", True)
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ class TestCarInterfaces:
|
||||
phases=(Phase.reuse, Phase.generate, Phase.shrink))
|
||||
@given(data=st.data())
|
||||
def test_car_interfaces(self, car_name, data):
|
||||
CarInterface, CarController, CarState, RadarInterface = interfaces[car_name]
|
||||
CarInterface = interfaces[car_name]
|
||||
|
||||
args = get_fuzzy_car_interface_args(data.draw)
|
||||
|
||||
@@ -44,7 +44,7 @@ class TestCarInterfaces:
|
||||
car_params_sp = CarInterface.get_params_sp(car_params, car_name, args['fingerprints'], args['car_fw'],
|
||||
experimental_long=args['experimental_long'], docs=False)
|
||||
car_params = car_params.as_reader()
|
||||
car_interface = CarInterface(car_params, car_params_sp, CarController, CarState)
|
||||
car_interface = CarInterface(car_params, car_params_sp)
|
||||
assert car_params
|
||||
assert car_params_sp
|
||||
assert car_interface
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import time
|
||||
import capnp
|
||||
import copy
|
||||
import os
|
||||
import pytest
|
||||
@@ -68,7 +67,7 @@ class TestCarModelBase(unittest.TestCase):
|
||||
platform: Platform | None = None
|
||||
test_route: CarTestRoute | None = None
|
||||
|
||||
can_msgs: list[capnp.lib.capnp._DynamicStructReader]
|
||||
can_msgs: list[tuple[int, list[CanData]]]
|
||||
fingerprint: dict[int, dict[int, int]]
|
||||
elm_frame: int | None
|
||||
car_safety_mode_frame: int | None
|
||||
@@ -83,7 +82,8 @@ class TestCarModelBase(unittest.TestCase):
|
||||
experimental_long = False
|
||||
for msg in lr:
|
||||
if msg.which() == "can":
|
||||
can_msgs.append(msg)
|
||||
can = can_capnp_to_list((msg.as_builder().to_bytes(),))[0]
|
||||
can_msgs.append((can[0], [CanData(*can) for can in can[1]]))
|
||||
if len(can_msgs) <= FRAME_FINGERPRINT:
|
||||
for m in msg.can:
|
||||
if m.src < 64:
|
||||
@@ -147,15 +147,13 @@ class TestCarModelBase(unittest.TestCase):
|
||||
raise unittest.SkipTest
|
||||
raise Exception(f"missing test route for {cls.platform}")
|
||||
|
||||
car_fw, can_msgs, experimental_long = cls.get_testing_data()
|
||||
car_fw, cls.can_msgs, experimental_long = cls.get_testing_data()
|
||||
|
||||
# if relay is expected to be open in the route
|
||||
cls.openpilot_enabled = cls.car_safety_mode_frame is not None
|
||||
|
||||
cls.can_msgs = sorted(can_msgs, key=lambda msg: msg.logMonoTime)
|
||||
|
||||
cls.CarInterface, cls.CarController, cls.CarState, cls.RadarInterface = interfaces[cls.platform]
|
||||
cls.CP = cls.CarInterface.get_params(cls.platform, cls.fingerprint, car_fw, experimental_long, docs=False)
|
||||
cls.CarInterface = interfaces[cls.platform]
|
||||
cls.CP = cls.CarInterface.get_params(cls.platform, cls.fingerprint, car_fw, experimental_long, docs=False)
|
||||
cls.CP_SP = cls.CarInterface.get_params_sp(cls.CP, cls.platform, cls.fingerprint, car_fw, experimental_long, docs=False)
|
||||
assert cls.CP
|
||||
assert cls.CP_SP
|
||||
@@ -168,12 +166,15 @@ class TestCarModelBase(unittest.TestCase):
|
||||
del cls.can_msgs
|
||||
|
||||
def setUp(self):
|
||||
self.CI = self.CarInterface(self.CP.copy(), copy.deepcopy(self.CP_SP), self.CarController, self.CarState)
|
||||
self.CI = self.CarInterface(self.CP.copy(), copy.deepcopy(self.CP_SP))
|
||||
assert self.CI
|
||||
|
||||
# TODO: check safetyModel is in release panda build
|
||||
self.safety = libsafety_py.libsafety
|
||||
|
||||
safety_param_sp = self.CP_SP.safetyParam
|
||||
self.safety.set_current_safety_param_sp(safety_param_sp)
|
||||
|
||||
cfg = self.CP.safetyConfigs[-1]
|
||||
set_status = self.safety.set_safety_hooks(cfg.safetyModel.raw, cfg.safetyParam)
|
||||
self.assertEqual(0, set_status, f"failed to set safetyModel {cfg}")
|
||||
@@ -203,8 +204,8 @@ class TestCarModelBase(unittest.TestCase):
|
||||
CC_SP = structs.CarControlSP()
|
||||
|
||||
for i, msg in enumerate(self.can_msgs):
|
||||
CS = self.CI.update(can_capnp_to_list((msg.as_builder().to_bytes(),)))
|
||||
self.CI.apply(CC, CC_SP, msg.logMonoTime)
|
||||
CS = self.CI.update(msg)
|
||||
self.CI.apply(CC, CC_SP, msg[0])
|
||||
|
||||
if CS.canValid:
|
||||
can_valid = True
|
||||
@@ -216,32 +217,32 @@ class TestCarModelBase(unittest.TestCase):
|
||||
self.assertEqual(can_invalid_cnt, 0)
|
||||
|
||||
def test_radar_interface(self):
|
||||
RI = self.RadarInterface(self.CP, self.CP_SP)
|
||||
RI = self.CarInterface.RadarInterface(self.CP, self.CP_SP)
|
||||
assert RI
|
||||
|
||||
# Since OBD port is multiplexed to bus 1 (commonly radar bus) while fingerprinting,
|
||||
# start parsing CAN messages after we've left ELM mode and can expect CAN traffic
|
||||
error_cnt = 0
|
||||
for i, msg in enumerate(self.can_msgs[self.elm_frame:]):
|
||||
rr: structs.RadarData | None = RI.update(can_capnp_to_list((msg.as_builder().to_bytes(),)))
|
||||
rr: structs.RadarData | None = RI.update(msg)
|
||||
if rr is not None and i > 50:
|
||||
error_cnt += structs.RadarData.Error.canError in rr.errors
|
||||
error_cnt += rr.errors.canError
|
||||
self.assertEqual(error_cnt, 0)
|
||||
|
||||
def test_panda_safety_rx_checks(self):
|
||||
if self.CP.dashcamOnly:
|
||||
self.skipTest("no need to check panda safety for dashcamOnly")
|
||||
|
||||
start_ts = self.can_msgs[0].logMonoTime
|
||||
start_ts = self.can_msgs[0][0]
|
||||
|
||||
failed_addrs = Counter()
|
||||
for can in self.can_msgs:
|
||||
# update panda timer
|
||||
t = (can.logMonoTime - start_ts) / 1e3
|
||||
t = (can[0] - start_ts) / 1e3
|
||||
self.safety.set_timer(int(t))
|
||||
|
||||
# run all msgs through the safety RX hook
|
||||
for msg in can.can:
|
||||
for msg in can[1]:
|
||||
if msg.src >= 64:
|
||||
continue
|
||||
|
||||
@@ -279,7 +280,7 @@ class TestCarModelBase(unittest.TestCase):
|
||||
def test_car_controller(car_control, car_control_sp):
|
||||
now_nanos = 0
|
||||
msgs_sent = 0
|
||||
CI = self.CarInterface(self.CP, self.CP_SP, self.CarController, self.CarState)
|
||||
CI = self.CarInterface(self.CP, self.CP_SP)
|
||||
for _ in range(round(10.0 / DT_CTRL)): # make sure we hit the slowest messages
|
||||
CI.update([])
|
||||
_, sendcan = CI.apply(car_control, car_control_sp, now_nanos)
|
||||
@@ -394,8 +395,8 @@ class TestCarModelBase(unittest.TestCase):
|
||||
|
||||
# warm up pass, as initial states may be different
|
||||
for can in self.can_msgs[:300]:
|
||||
self.CI.update(can_capnp_to_list((can.as_builder().to_bytes(), )))
|
||||
for msg in filter(lambda m: m.src in range(64), can.can):
|
||||
self.CI.update(can)
|
||||
for msg in filter(lambda m: m.src < 64, can[1]):
|
||||
to_send = libsafety_py.make_CANPacket(msg.address, msg.src % 4, msg.dat)
|
||||
self.safety.safety_rx_hook(to_send)
|
||||
|
||||
@@ -404,8 +405,8 @@ class TestCarModelBase(unittest.TestCase):
|
||||
checks = defaultdict(int)
|
||||
vehicle_speed_seen = self.CP.steerControlType == SteerControlType.angle and not self.CP.notCar
|
||||
for idx, can in enumerate(self.can_msgs):
|
||||
CS = self.CI.update(can_capnp_to_list((can.as_builder().to_bytes(), ))).as_reader()
|
||||
for msg in filter(lambda m: m.src in range(64), can.can):
|
||||
CS = self.CI.update(can).as_reader()
|
||||
for msg in filter(lambda m: m.src < 64, can[1]):
|
||||
to_send = libsafety_py.make_CANPacket(msg.address, msg.src % 4, msg.dat)
|
||||
ret = self.safety.safety_rx_hook(to_send)
|
||||
self.assertEqual(1, ret, f"safety rx failed ({ret=}): {(msg.address, msg.src % 4)}")
|
||||
|
||||
@@ -9,7 +9,7 @@ from openpilot.common.params import Params
|
||||
from openpilot.common.realtime import config_realtime_process, Priority, Ratekeeper
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
|
||||
from opendbc.car.car_helpers import get_car_interface
|
||||
from opendbc.car.car_helpers import interfaces
|
||||
from opendbc.car.vehicle_model import VehicleModel
|
||||
from openpilot.selfdrive.controls.lib.drive_helpers import clip_curvature
|
||||
from openpilot.selfdrive.controls.lib.latcontrol import LatControl, MIN_LATERAL_CONTROL_SPEED
|
||||
@@ -37,7 +37,7 @@ class Controls:
|
||||
self.CP_SP = messaging.log_from_bytes(self.params.get("CarParamsSP", block=True), custom.CarParamsSP)
|
||||
cloudlog.info("controlsd got CarParamsSP")
|
||||
|
||||
self.CI = get_car_interface(self.CP, self.CP_SP)
|
||||
self.CI = interfaces[self.CP.carFingerprint](self.CP, self.CP_SP)
|
||||
|
||||
self.sm = messaging.SubMaster(['liveParameters', 'liveTorqueParameters', 'modelV2', 'selfdriveState',
|
||||
'liveCalibration', 'livePose', 'longitudinalPlan', 'carState', 'carOutput',
|
||||
|
||||
@@ -17,10 +17,10 @@ class TestLatControl:
|
||||
|
||||
@parameterized.expand([(HONDA.HONDA_CIVIC, LatControlPID), (TOYOTA.TOYOTA_RAV4, LatControlTorque), (NISSAN.NISSAN_LEAF, LatControlAngle)])
|
||||
def test_saturation(self, car_name, controller):
|
||||
CarInterface, CarController, CarState, RadarInterface = interfaces[car_name]
|
||||
CarInterface = interfaces[car_name]
|
||||
CP = CarInterface.get_non_essential_params(car_name)
|
||||
CP_SP = CarInterface.get_non_essential_params_sp(CP, car_name)
|
||||
CI = CarInterface(CP, CP_SP, CarController, CarState)
|
||||
CI = CarInterface(CP, CP_SP)
|
||||
VM = VehicleModel(CP)
|
||||
|
||||
controller = controller(CP.as_reader(), CI)
|
||||
|
||||
@@ -20,7 +20,7 @@ def main():
|
||||
longitudinal_planner = LongitudinalPlanner(CP)
|
||||
pm = messaging.PubMaster(['longitudinalPlan', 'driverAssistance', 'longitudinalPlanSP'])
|
||||
sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'liveParameters', 'radarState', 'modelV2', 'selfdriveState'],
|
||||
poll='modelV2', ignore_avg_freq=['radarState'])
|
||||
poll='modelV2')
|
||||
|
||||
while True:
|
||||
sm.update()
|
||||
|
||||
@@ -6,6 +6,7 @@ from typing import Any
|
||||
|
||||
import capnp
|
||||
from cereal import messaging, log, car
|
||||
from openpilot.common.filter_simple import FirstOrderFilter
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.realtime import DT_MDL, Priority, config_realtime_process
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
@@ -51,7 +52,7 @@ class Track:
|
||||
def __init__(self, identifier: int, v_lead: float, kalman_params: KalmanParams):
|
||||
self.identifier = identifier
|
||||
self.cnt = 0
|
||||
self.aLeadTau = _LEAD_ACCEL_TAU
|
||||
self.aLeadTau = FirstOrderFilter(_LEAD_ACCEL_TAU, 0.45, DT_MDL)
|
||||
self.K_A = kalman_params.A
|
||||
self.K_C = kalman_params.C
|
||||
self.K_K = kalman_params.K
|
||||
@@ -74,17 +75,12 @@ class Track:
|
||||
|
||||
# Learn if constant acceleration
|
||||
if abs(self.aLeadK) < 0.5:
|
||||
self.aLeadTau = _LEAD_ACCEL_TAU
|
||||
self.aLeadTau.x = _LEAD_ACCEL_TAU
|
||||
else:
|
||||
self.aLeadTau *= 0.9
|
||||
self.aLeadTau.update(0.0)
|
||||
|
||||
self.cnt += 1
|
||||
|
||||
def reset_a_lead(self, aLeadK: float, aLeadTau: float):
|
||||
self.kf = KF1D([[self.vLead], [aLeadK]], self.K_A, self.K_C, self.K_K)
|
||||
self.aLeadK = aLeadK
|
||||
self.aLeadTau = aLeadTau
|
||||
|
||||
def get_RadarState(self, model_prob: float = 0.0):
|
||||
return {
|
||||
"dRel": float(self.dRel),
|
||||
@@ -93,7 +89,7 @@ class Track:
|
||||
"vLead": float(self.vLead),
|
||||
"vLeadK": float(self.vLeadK),
|
||||
"aLeadK": float(self.aLeadK),
|
||||
"aLeadTau": float(self.aLeadTau),
|
||||
"aLeadTau": float(self.aLeadTau.x),
|
||||
"status": True,
|
||||
"fcw": self.is_potential_fcw(model_prob),
|
||||
"modelProb": model_prob,
|
||||
@@ -211,9 +207,7 @@ class RadarD:
|
||||
self.v_ego_hist.append(self.v_ego)
|
||||
self.last_v_ego_frame = sm.recv_frame['carState']
|
||||
|
||||
ar_pts = {}
|
||||
for pt in rr.points:
|
||||
ar_pts[pt.trackId] = [pt.dRel, pt.yRel, pt.vRel, pt.measured]
|
||||
ar_pts = {pt.trackId: [pt.dRel, pt.yRel, pt.vRel, pt.measured] for pt in rr.points}
|
||||
|
||||
# *** remove missing points from meta data ***
|
||||
for ids in list(self.tracks.keys()):
|
||||
@@ -233,10 +227,10 @@ class RadarD:
|
||||
self.tracks[ids].update(rpt[0], rpt[1], rpt[2], v_lead, rpt[3])
|
||||
|
||||
# *** publish radarState ***
|
||||
self.radar_state_valid = sm.all_checks() and len(rr.errors) == 0
|
||||
self.radar_state_valid = sm.all_checks()
|
||||
self.radar_state = log.RadarState.new_message()
|
||||
self.radar_state.mdMonoTime = sm.logMonoTime['modelV2']
|
||||
self.radar_state.radarErrors = list(rr.errors)
|
||||
self.radar_state.radarErrors = rr.errors
|
||||
self.radar_state.carStateMonoTime = sm.logMonoTime['carState']
|
||||
|
||||
if len(sm['modelV2'].velocity.x):
|
||||
|
||||
@@ -26,6 +26,6 @@ class TestLeads:
|
||||
msgs = [m for _ in range(3) for m in single_iter_pkg()]
|
||||
out = replay_process_with_name("card", msgs, fingerprint=TOYOTA.TOYOTA_COROLLA_TSS2)
|
||||
states = [m for m in out if m.which() == "liveTracks"]
|
||||
failures = [not state.valid and len(state.liveTracks.errors) for state in states]
|
||||
failures = [not state.valid for state in states]
|
||||
|
||||
assert len(states) == 0 or all(failures)
|
||||
|
||||
@@ -6,7 +6,6 @@ from tqdm import tqdm
|
||||
from cereal import car
|
||||
from opendbc.car.tests.routes import CarTestRoute
|
||||
from openpilot.selfdrive.car.tests.test_models import TestCarModelBase
|
||||
from openpilot.selfdrive.pandad import can_capnp_to_list
|
||||
from openpilot.tools.plotjuggler.juggle import DEMO_ROUTE
|
||||
|
||||
N_RUNS = 10
|
||||
@@ -25,13 +24,11 @@ if __name__ == '__main__':
|
||||
CC = car.CarControl.new_message()
|
||||
ets = []
|
||||
for _ in tqdm(range(N_RUNS)):
|
||||
msgs = [m.as_builder().to_bytes() for m in tm.can_msgs]
|
||||
start_t = time.process_time_ns()
|
||||
for msg in msgs:
|
||||
can_list = can_capnp_to_list([msg])
|
||||
for msg in tm.can_msgs:
|
||||
for cp in tm.CI.can_parsers.values():
|
||||
if cp is not None:
|
||||
cp.update_strings(can_list)
|
||||
cp.update_strings(msg)
|
||||
ets.append((time.process_time_ns() - start_t) * 1e-6)
|
||||
|
||||
print(f'{len(tm.can_msgs)} CAN packets, {N_RUNS} runs')
|
||||
|
||||
@@ -10,21 +10,21 @@ if __name__ == "__main__":
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument("--fingerprint", help="The fingerprint to use")
|
||||
parser.add_argument("route", help="The route name to use")
|
||||
parser.add_argument("process", help="The process to run")
|
||||
parser.add_argument("process", nargs='+', help="The process(s) to run")
|
||||
args = parser.parse_args()
|
||||
|
||||
cfg = [c for c in CONFIGS if c.proc_name == args.process][0]
|
||||
cfgs = [c for c in CONFIGS if c.proc_name in args.process]
|
||||
|
||||
lr = LogReader(args.route)
|
||||
inputs = list(lr)
|
||||
|
||||
outputs = replay_process(cfg, inputs, fingerprint=args.fingerprint)
|
||||
outputs = replay_process(cfgs, inputs, fingerprint=args.fingerprint)
|
||||
|
||||
# Remove message generated by the process under test and merge in the new messages
|
||||
produces = {o.which() for o in outputs}
|
||||
inputs = [i for i in inputs if i.which() not in produces]
|
||||
outputs = sorted(inputs + outputs, key=lambda x: x.logMonoTime)
|
||||
|
||||
fn = f"{args.route.replace('/', '_')}_{args.process}.zst"
|
||||
fn = f"{args.route.replace('/', '_')}_{'_'.join(args.process)}.zst"
|
||||
print(f"Saving log to {fn}")
|
||||
save_log(fn, outputs)
|
||||
|
||||
@@ -52,14 +52,14 @@ class ParamsLearner:
|
||||
device_pose = Pose.from_live_pose(msg)
|
||||
calibrated_pose = self.calibrator.build_calibrated_pose(device_pose)
|
||||
|
||||
yaw_rate_valid = msg.angularVelocityDevice.valid and self.calibrator.calib_valid
|
||||
yaw_rate_valid = msg.angularVelocityDevice.valid
|
||||
yaw_rate_valid = yaw_rate_valid and 0 < self.yaw_rate_std < 10 # rad/s
|
||||
yaw_rate_valid = yaw_rate_valid and abs(self.yaw_rate) < 1 # rad/s
|
||||
if yaw_rate_valid:
|
||||
self.yaw_rate, self.yaw_rate_std = calibrated_pose.angular_velocity.z, calibrated_pose.angular_velocity.z_std
|
||||
else:
|
||||
# This is done to bound the yaw rate estimate when localizer values are invalid or calibrating
|
||||
self.yaw_rate, self.yaw_rate_std = 0.0, np.radians(1)
|
||||
self.yaw_rate, self.yaw_rate_std = 0.0, np.radians(10.0)
|
||||
|
||||
localizer_roll, localizer_roll_std = device_pose.orientation.x, device_pose.orientation.x_std
|
||||
localizer_roll_std = np.radians(1) if np.isnan(localizer_roll_std) else localizer_roll_std
|
||||
@@ -225,13 +225,13 @@ def main():
|
||||
liveParameters.posenetValid = True
|
||||
liveParameters.sensorValid = sensors_valid
|
||||
liveParameters.steerRatio = float(x[States.STEER_RATIO].item())
|
||||
liveParameters.steerRatioValid = min_sr <= liveParameters.steerRatio <= max_sr
|
||||
liveParameters.stiffnessFactor = float(x[States.STIFFNESS].item())
|
||||
liveParameters.stiffnessFactorValid = 0.2 <= liveParameters.stiffnessFactor <= 5.0
|
||||
liveParameters.roll = float(roll)
|
||||
liveParameters.angleOffsetAverageDeg = float(angle_offset_average)
|
||||
liveParameters.angleOffsetAverageValid = bool(avg_offset_valid)
|
||||
liveParameters.angleOffsetDeg = float(angle_offset)
|
||||
liveParameters.steerRatioValid = min_sr <= liveParameters.steerRatio <= max_sr
|
||||
liveParameters.stiffnessFactorValid = 0.2 <= liveParameters.stiffnessFactor <= 5.0
|
||||
liveParameters.angleOffsetAverageValid = bool(avg_offset_valid)
|
||||
liveParameters.angleOffsetValid = bool(total_offset_valid)
|
||||
liveParameters.valid = all((
|
||||
liveParameters.angleOffsetAverageValid,
|
||||
|
||||
@@ -37,6 +37,7 @@ cl_mem* DrivingModelFrame::prepare(cl_mem yuv_cl, int frame_width, int frame_hei
|
||||
DrivingModelFrame::~DrivingModelFrame() {
|
||||
deinit_transform();
|
||||
loadyuv_destroy(&loadyuv);
|
||||
CL_CHECK(clReleaseMemObject(input_frames_cl));
|
||||
CL_CHECK(clReleaseMemObject(img_buffer_20hz_cl));
|
||||
CL_CHECK(clReleaseMemObject(last_img_cl));
|
||||
CL_CHECK(clReleaseCommandQueue(q));
|
||||
@@ -58,5 +59,6 @@ cl_mem* MonitoringModelFrame::prepare(cl_mem yuv_cl, int frame_width, int frame_
|
||||
|
||||
MonitoringModelFrame::~MonitoringModelFrame() {
|
||||
deinit_transform();
|
||||
CL_CHECK(clReleaseMemObject(input_frame_cl));
|
||||
CL_CHECK(clReleaseCommandQueue(q));
|
||||
}
|
||||
|
||||
@@ -62,8 +62,27 @@ void Panda::set_safety_model(cereal::CarParams::SafetyModel safety_model, uint16
|
||||
handle->control_write(0xdc, (uint16_t)safety_model, safety_param);
|
||||
}
|
||||
|
||||
void Panda::set_alternative_experience(uint16_t alternative_experience) {
|
||||
handle->control_write(0xdf, alternative_experience, 0);
|
||||
void Panda::set_alternative_experience(uint16_t alternative_experience, uint16_t safety_param_sp) {
|
||||
handle->control_write(0xdf, alternative_experience, safety_param_sp);
|
||||
}
|
||||
|
||||
std::string Panda::serial_read(int port_number) {
|
||||
std::string ret;
|
||||
char buffer[USBPACKET_MAX_SIZE] = {};
|
||||
|
||||
while (true) {
|
||||
int bytes_read = handle->control_read(0xe0, port_number, 0, (unsigned char *)buffer, USBPACKET_MAX_SIZE);
|
||||
if (bytes_read <= 0) {
|
||||
break;
|
||||
}
|
||||
ret.append(buffer, bytes_read);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Panda::set_uart_baud(int uart, int rate) {
|
||||
handle->control_write(0xe4, uart, int(rate / 300));
|
||||
}
|
||||
|
||||
cereal::PandaState::PandaType Panda::get_hw_type() {
|
||||
|
||||
@@ -63,7 +63,9 @@ public:
|
||||
// Panda functionality
|
||||
cereal::PandaState::PandaType get_hw_type();
|
||||
void set_safety_model(cereal::CarParams::SafetyModel safety_model, uint16_t safety_param=0U);
|
||||
void set_alternative_experience(uint16_t alternative_experience);
|
||||
void set_alternative_experience(uint16_t alternative_experience, uint16_t safety_param_sp=0U);
|
||||
std::string serial_read(int port_number = 0);
|
||||
void set_uart_baud(int uart, int rate);
|
||||
void set_fan_speed(uint16_t fan_speed);
|
||||
uint16_t get_fan_speed();
|
||||
void set_ir_pwr(uint16_t ir_pwr);
|
||||
|
||||
@@ -10,7 +10,8 @@ void PandaSafety::configureSafetyMode() {
|
||||
|
||||
auto car_params = fetchCarParams();
|
||||
if (!car_params.empty()) {
|
||||
LOGW("got %lu bytes CarParams", car_params.size());
|
||||
LOGW("got %lu bytes CarParams", car_params[0].size());
|
||||
LOGW("got %lu bytes CarParamsSP", car_params[1].size());
|
||||
setSafetyMode(car_params);
|
||||
safety_configured_ = true;
|
||||
}
|
||||
@@ -43,7 +44,8 @@ void PandaSafety::updateMultiplexingMode() {
|
||||
}
|
||||
}
|
||||
|
||||
std::string PandaSafety::fetchCarParams() {
|
||||
// TODO-SP: Use structs instead of vector
|
||||
std::vector<std::string> PandaSafety::fetchCarParams() {
|
||||
if (!params_.getBool("FirmwareQueryDone")) {
|
||||
return {};
|
||||
}
|
||||
@@ -56,16 +58,23 @@ std::string PandaSafety::fetchCarParams() {
|
||||
if (!params_.getBool("ControlsReady")) {
|
||||
return {};
|
||||
}
|
||||
return params_.get("CarParams");
|
||||
return {params_.get("CarParams"), params_.get("CarParamsSP")};
|
||||
}
|
||||
|
||||
void PandaSafety::setSafetyMode(const std::string ¶ms_string) {
|
||||
// TODO-SP: Use structs instead of vector
|
||||
void PandaSafety::setSafetyMode(const std::vector<std::string> ¶ms_string) {
|
||||
AlignedBuffer aligned_buf;
|
||||
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(params_string.data(), params_string.size()));
|
||||
AlignedBuffer aligned_buf_sp;
|
||||
|
||||
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(params_string[0].data(), params_string[0].size()));
|
||||
cereal::CarParams::Reader car_params = cmsg.getRoot<cereal::CarParams>();
|
||||
|
||||
capnp::FlatArrayMessageReader cmsg_sp(aligned_buf_sp.align(params_string[1].data(), params_string[1].size()));
|
||||
cereal::CarParamsSP::Reader car_params_sp = cmsg_sp.getRoot<cereal::CarParamsSP>();
|
||||
|
||||
auto safety_configs = car_params.getSafetyConfigs();
|
||||
uint16_t alternative_experience = car_params.getAlternativeExperience();
|
||||
uint16_t safety_param_sp = car_params_sp.getSafetyParam();
|
||||
|
||||
for (int i = 0; i < pandas_.size(); ++i) {
|
||||
// Default to SILENT safety model if not specified
|
||||
@@ -76,8 +85,8 @@ void PandaSafety::setSafetyMode(const std::string ¶ms_string) {
|
||||
safety_param = safety_configs[i].getSafetyParam();
|
||||
}
|
||||
|
||||
LOGW("Panda %d: setting safety model: %d, param: %d, alternative experience: %d", i, (int)safety_model, safety_param, alternative_experience);
|
||||
pandas_[i]->set_alternative_experience(alternative_experience);
|
||||
LOGW("Panda %d: setting safety model: %d, param: %d, alternative experience: %d, param_sp: %d", i, (int)safety_model, safety_param, alternative_experience, safety_param_sp);
|
||||
pandas_[i]->set_alternative_experience(alternative_experience, safety_param_sp);
|
||||
pandas_[i]->set_safety_model(safety_model, safety_param);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
#define CUTOFF_IL 400
|
||||
#define SATURATE_IL 1000
|
||||
|
||||
#define ALT_EXP_DISENGAGE_LATERAL_ON_BRAKE 2048
|
||||
#define ALT_EXP_MADS_DISENGAGE_LATERAL_ON_BRAKE 2048
|
||||
|
||||
ExitHandler do_exit;
|
||||
|
||||
@@ -57,7 +57,7 @@ bool check_all_connected(const std::vector<Panda *> &pandas) {
|
||||
|
||||
bool process_mads_heartbeat(SubMaster *sm) {
|
||||
const int &alt_exp = (*sm)["carParams"].getCarParams().getAlternativeExperience();
|
||||
const bool disengage_lateral_on_brake = (alt_exp & ALT_EXP_DISENGAGE_LATERAL_ON_BRAKE) != 0;
|
||||
const bool disengage_lateral_on_brake = (alt_exp & ALT_EXP_MADS_DISENGAGE_LATERAL_ON_BRAKE) != 0;
|
||||
|
||||
const auto &mads = (*sm)["selfdriveStateSP"].getSelfdriveStateSP().getMads();
|
||||
const bool heartbeat_type = disengage_lateral_on_brake ? mads.getActive() : mads.getEnabled();
|
||||
@@ -470,6 +470,14 @@ void pandad_run(std::vector<Panda *> &pandas) {
|
||||
send_peripheral_state(peripheral_panda, &pm);
|
||||
}
|
||||
|
||||
// Forward logs from pandas to cloudlog if available
|
||||
for (auto *panda : pandas) {
|
||||
std::string log = panda->serial_read();
|
||||
if (!log.empty()) {
|
||||
LOGD("%s", log.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
rk.keepTime();
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@ public:
|
||||
|
||||
private:
|
||||
void updateMultiplexingMode();
|
||||
std::string fetchCarParams();
|
||||
void setSafetyMode(const std::string ¶ms_string);
|
||||
std::vector<std::string> fetchCarParams();
|
||||
void setSafetyMode(const std::vector<std::string> ¶ms_string);
|
||||
|
||||
bool initialized_ = false;
|
||||
bool log_once_ = false;
|
||||
|
||||
@@ -672,6 +672,11 @@ EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = {
|
||||
ET.NO_ENTRY: NoEntryAlert("Radar Error: Restart the Car"),
|
||||
},
|
||||
|
||||
EventName.radarTempUnavailable: {
|
||||
ET.SOFT_DISABLE: soft_disable_alert("Radar Temporarily Unavailable"),
|
||||
ET.NO_ENTRY: NoEntryAlert("Radar Temporarily Unavailable"),
|
||||
},
|
||||
|
||||
# Every frame from the camera should be processed by the model. If modeld
|
||||
# is not processing frames fast enough they have to be dropped. This alert is
|
||||
# thrown when over 20% of frames are dropped.
|
||||
|
||||
@@ -92,7 +92,7 @@ class SelfdriveD(CruiseHelper):
|
||||
'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters',
|
||||
'controlsState', 'carControl', 'driverAssistance', 'alertDebug'] + \
|
||||
self.camera_packets + self.sensor_packets + self.gps_packets,
|
||||
ignore_alive=ignore, ignore_avg_freq=ignore+['radarState',],
|
||||
ignore_alive=ignore, ignore_avg_freq=ignore,
|
||||
ignore_valid=ignore, frequency=int(1/DT_CTRL))
|
||||
|
||||
# read params
|
||||
@@ -294,7 +294,13 @@ class SelfdriveD(CruiseHelper):
|
||||
self.events.add(EventName.cameraFrameRate)
|
||||
if not REPLAY and self.rk.lagging:
|
||||
self.events.add(EventName.selfdrivedLagging)
|
||||
if len(self.sm['radarState'].radarErrors) or ((not self.rk.lagging or REPLAY) and not self.sm.all_checks(['radarState'])):
|
||||
if not self.sm.valid['radarState']:
|
||||
if self.sm['radarState'].radarErrors.canError:
|
||||
self.events.add(EventName.canError)
|
||||
elif self.sm['radarState'].radarErrors.radarUnavailableTemporary:
|
||||
self.events.add(EventName.radarTempUnavailable)
|
||||
else:
|
||||
self.events.add(EventName.radarFault)
|
||||
self.events.add(EventName.radarFault)
|
||||
if not self.sm.valid['pandaStates']:
|
||||
self.events.add(EventName.usbError)
|
||||
@@ -330,7 +336,7 @@ class SelfdriveD(CruiseHelper):
|
||||
self.events.add(EventName.posenetInvalid)
|
||||
if not self.sm['livePose'].inputsOK:
|
||||
self.events.add(EventName.locationdTemporaryError)
|
||||
if not self.sm['liveParameters'].valid and not TESTING_CLOSET and (not SIMULATION or REPLAY):
|
||||
if not self.sm['liveParameters'].valid and cal_status == log.LiveCalibrationData.Status.calibrated and not TESTING_CLOSET and (not SIMULATION or REPLAY):
|
||||
self.events.add(EventName.paramsdTemporaryError)
|
||||
|
||||
# conservative HW alert. if the data or frequency are off, locationd will throw an error
|
||||
|
||||
@@ -241,14 +241,16 @@ def migrate_gpsLocation(msgs):
|
||||
|
||||
@migration(inputs=["deviceState", "initData"])
|
||||
def migrate_deviceState(msgs):
|
||||
init_data = next((m.initData for _, m in msgs if m.which() == 'initData'), None)
|
||||
device_state = next((m.deviceState for _, m in msgs if m.which() == 'deviceState'), None)
|
||||
if init_data is None or device_state is None:
|
||||
return [], [], []
|
||||
|
||||
ops = []
|
||||
dt = None
|
||||
for i, msg in msgs:
|
||||
if msg.which() == 'initData':
|
||||
dt = msg.initData.deviceType
|
||||
if msg.which() == 'deviceState':
|
||||
n = msg.as_builder()
|
||||
n.deviceState.deviceType = dt
|
||||
n.deviceState.deviceType = init_data.deviceType
|
||||
ops.append((i, n.as_reader()))
|
||||
return ops, [], []
|
||||
|
||||
|
||||
@@ -342,7 +342,7 @@ def card_fingerprint_callback(rc, pm, msgs, fingerprint):
|
||||
def get_car_params_callback(rc, pm, msgs, fingerprint):
|
||||
params = Params()
|
||||
if fingerprint:
|
||||
CarInterface, _, _, _ = interfaces[fingerprint]
|
||||
CarInterface = interfaces[fingerprint]
|
||||
CP = CarInterface.get_non_essential_params(fingerprint)
|
||||
CP_SP = CarInterface.get_non_essential_params_sp(CP, fingerprint)
|
||||
else:
|
||||
|
||||
@@ -1 +1 @@
|
||||
465979047295dad5da3a552db9227ed776a26a79
|
||||
e9d57157494480637a8ffb52257d2b660a48be67
|
||||
@@ -391,7 +391,7 @@ class TestOnroad:
|
||||
result += "----------------- Model Timing -----------------\n"
|
||||
result += "------------------------------------------------\n"
|
||||
cfgs = [
|
||||
("modelV2", 0.045, 0.035),
|
||||
("modelV2", 0.045, 0.040), # TODO: this should be stricter but it's hard to measure exactly
|
||||
("driverStateV2", 0.045, 0.035),
|
||||
]
|
||||
for (s, instant_max, avg_max) in cfgs:
|
||||
|
||||
+15
-10
@@ -79,31 +79,36 @@ bool HttpRequest::timeout() const {
|
||||
return reply && reply->error() == QNetworkReply::OperationCanceledError;
|
||||
}
|
||||
|
||||
void HttpRequest::sendRequest(const QString &requestURL, const HttpRequest::Method method) {
|
||||
if (active()) {
|
||||
qDebug() << "HttpRequest is active";
|
||||
return;
|
||||
}
|
||||
QNetworkRequest HttpRequest::prepareRequest(const QString &requestURL) {
|
||||
QNetworkRequest request;
|
||||
QString token;
|
||||
if (create_jwt) {
|
||||
token = CommaApi::create_jwt();
|
||||
token = GetJwtToken();
|
||||
} else {
|
||||
QString token_json = QString::fromStdString(util::read_file(util::getenv("HOME") + "/.comma/auth.json"));
|
||||
QJsonDocument json_d = QJsonDocument::fromJson(token_json.toUtf8());
|
||||
token = json_d["access_token"].toString();
|
||||
}
|
||||
|
||||
QNetworkRequest request;
|
||||
request.setUrl(QUrl(requestURL));
|
||||
request.setRawHeader("User-Agent", getUserAgent().toUtf8());
|
||||
request.setRawHeader("User-Agent", GetUserAgent().toUtf8());
|
||||
|
||||
if (!token.isEmpty()) {
|
||||
request.setRawHeader(QByteArray("Authorization"), ("JWT " + token).toUtf8());
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
if (method == HttpRequest::Method::GET) {
|
||||
void HttpRequest::sendRequest(const QString &requestURL, const Method method) {
|
||||
if (active()) {
|
||||
qDebug() << "HttpRequest is active";
|
||||
return;
|
||||
}
|
||||
|
||||
QNetworkRequest request = prepareRequest(requestURL);
|
||||
if (method == Method::GET) {
|
||||
reply = nam()->get(request);
|
||||
} else if (method == HttpRequest::Method::DELETE) {
|
||||
} else if (method == Method::DELETE) {
|
||||
reply = nam()->deleteResource(request);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <QString>
|
||||
#include <QTimer>
|
||||
|
||||
#include "util.h"
|
||||
#include "common/util.h"
|
||||
|
||||
namespace CommaApi {
|
||||
@@ -23,10 +24,11 @@ class HttpRequest : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum class Method {GET, DELETE};
|
||||
enum class Method {GET, DELETE, POST, PUT};
|
||||
|
||||
explicit HttpRequest(QObject* parent, bool create_jwt = true, int timeout = 20000);
|
||||
void sendRequest(const QString &requestURL, const Method method = Method::GET);
|
||||
virtual void sendRequest(const QString &requestURL, Method method);
|
||||
void sendRequest(const QString &requestURL) { sendRequest(requestURL, Method::GET);}
|
||||
bool active() const;
|
||||
bool timeout() const;
|
||||
|
||||
@@ -35,13 +37,14 @@ signals:
|
||||
|
||||
protected:
|
||||
QNetworkReply *reply = nullptr;
|
||||
|
||||
private:
|
||||
static QNetworkAccessManager *nam();
|
||||
QTimer *networkTimer = nullptr;
|
||||
bool create_jwt;
|
||||
virtual QNetworkRequest prepareRequest(const QString& requestURL);
|
||||
[[nodiscard]] virtual QString GetJwtToken() const { return CommaApi::create_jwt(); }
|
||||
[[nodiscard]] virtual QString GetUserAgent() const { return getUserAgent(); }
|
||||
|
||||
private slots:
|
||||
protected slots:
|
||||
void requestTimeout();
|
||||
void requestFinished();
|
||||
};
|
||||
|
||||
@@ -57,9 +57,17 @@ DeveloperPanel::DeveloperPanel(SettingsWindow *parent) : ListWidget(parent) {
|
||||
});
|
||||
addItem(hyundaiRadarTracksToggle);
|
||||
|
||||
auto enableGithubRunner = new ParamControl("EnableGithubRunner", tr("Enable GitHub runner service"), tr("Enables or disables the github runner service."), "");
|
||||
enableGithubRunner = new ParamControl("EnableGithubRunner", tr("Enable GitHub runner service"), tr("Enables or disables the github runner service."), "");
|
||||
addItem(enableGithubRunner);
|
||||
|
||||
// error log button
|
||||
errorLogBtn = new ButtonControl(tr("Error Log"), tr("VIEW"), tr("View the error log for sunnypilot crashes."));
|
||||
connect(errorLogBtn, &ButtonControl::clicked, [=]() {
|
||||
std::string txt = util::read_file("/data/community/crashes/error.log");
|
||||
ConfirmationDialog::rich(QString::fromStdString(txt), this);
|
||||
});
|
||||
addItem(errorLogBtn);
|
||||
|
||||
// Joystick and longitudinal maneuvers should be hidden on release branches
|
||||
is_release = params.getBool("IsReleaseBranch");
|
||||
|
||||
@@ -112,6 +120,11 @@ void DeveloperPanel::updateToggles(bool _offroad) {
|
||||
}
|
||||
experimentalLongitudinalToggle->refresh();
|
||||
|
||||
// Handle specific controls visibility for release branches
|
||||
enableGithubRunner->setVisible(!is_release);
|
||||
errorLogBtn->setVisible(!is_release);
|
||||
joystickToggle->setVisible(!is_release);
|
||||
|
||||
offroad = _offroad;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,9 +16,11 @@ private:
|
||||
Params params;
|
||||
ParamControl* adbToggle;
|
||||
ParamControl* joystickToggle;
|
||||
ButtonControl* errorLogBtn;
|
||||
ParamControl* longManeuverToggle;
|
||||
ParamControl* experimentalLongitudinalToggle;
|
||||
ParamControl* hyundaiRadarTracksToggle;
|
||||
ParamControl* enableGithubRunner;
|
||||
bool is_release;
|
||||
bool offroad = false;
|
||||
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
#include <QJsonObject>
|
||||
#include <QTimer>
|
||||
|
||||
#ifdef SUNNYPILOT
|
||||
#define UIState UIStateSP
|
||||
#endif
|
||||
|
||||
FirehosePanel::FirehosePanel(SettingsWindow *parent) : QWidget((QWidget*)parent) {
|
||||
layout = new QVBoxLayout(this);
|
||||
layout->setContentsMargins(40, 40, 40, 40);
|
||||
@@ -67,6 +71,7 @@ FirehosePanel::FirehosePanel(SettingsWindow *parent) : QWidget((QWidget*)parent)
|
||||
"<br><br>"
|
||||
"<b>Frequently Asked Questions</b><br><br>"
|
||||
"<i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br>"
|
||||
"<i>Do all of my segments get pulled in Firehose Mode?</i> No, we selectively pull a subset of your segments.<br><br>"
|
||||
"<i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br>"
|
||||
"<i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training."
|
||||
));
|
||||
@@ -85,7 +90,7 @@ FirehosePanel::FirehosePanel(SettingsWindow *parent) : QWidget((QWidget*)parent)
|
||||
QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8());
|
||||
QJsonObject json = doc.object();
|
||||
int count = json["firehose"].toInt();
|
||||
contribution_label->setText(tr("<b>%1 %2</b> of your driving are in the training dataset so far.").arg(count).arg(count == 1 ? "segment" : "segments"));
|
||||
contribution_label->setText(tr("<b>%n segment(s)</b> of your driving is in the training dataset so far.", "", count));
|
||||
contribution_label->show();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -344,7 +344,11 @@ void SettingsWindow::setCurrentPanel(int index, const QString ¶m) {
|
||||
|
||||
// Find the panel by name
|
||||
for (int i = 0; i < nav_btns->buttons().size(); i++) {
|
||||
if (nav_btns->buttons()[i]->text() == tr(panelName.toStdString().c_str())) {
|
||||
bool panel_trimmed = false;
|
||||
#ifdef SUNNYPILOT
|
||||
panel_trimmed = nav_btns->buttons()[i]->text().trimmed() == tr(panelName.toStdString().c_str());
|
||||
#endif
|
||||
if ((nav_btns->buttons()[i]->text() == tr(panelName.toStdString().c_str())) || panel_trimmed) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -48,7 +48,8 @@ void Setup::download(QString url) {
|
||||
auto version = util::read_file("/VERSION");
|
||||
|
||||
struct curl_slist *list = NULL;
|
||||
list = curl_slist_append(list, ("X-openpilot-serial: " + Hardware::get_serial()).c_str());
|
||||
std::string header = "X-openpilot-serial: " + Hardware::get_serial();
|
||||
list = curl_slist_append(list, header.c_str());
|
||||
|
||||
char tmpfile[] = "/tmp/installer_XXXXXX";
|
||||
FILE *fp = fdopen(mkstemp(tmpfile), "wb");
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
widgets_src = [
|
||||
"sunnypilot/qt/request_repeater.cc",
|
||||
"sunnypilot/qt/widgets/toggle.cc",
|
||||
"sunnypilot/qt/widgets/controls.cc",
|
||||
"sunnypilot/qt/widgets/drive_stats.cc",
|
||||
@@ -13,6 +14,7 @@ qt_util = [
|
||||
|
||||
qt_src = [
|
||||
"sunnypilot/ui.cc",
|
||||
"sunnypilot/qt/api.cc",
|
||||
"sunnypilot/qt/sidebar.cc",
|
||||
"sunnypilot/qt/window.cc",
|
||||
"sunnypilot/qt/home.cc",
|
||||
@@ -21,6 +23,7 @@ qt_src = [
|
||||
"sunnypilot/qt/offroad/settings/settings.cc",
|
||||
"sunnypilot/qt/offroad/settings/software_panel.cc",
|
||||
"sunnypilot/qt/offroad/settings/sunnylink_panel.cc",
|
||||
"sunnypilot/qt/offroad/settings/sunnylink/sponsor_widget.cc",
|
||||
"sunnypilot/qt/offroad/settings/sunnypilot_panel.cc",
|
||||
"sunnypilot/qt/offroad/settings/trips_panel.cc",
|
||||
"sunnypilot/qt/offroad/settings/vehicle_panel.cc",
|
||||
@@ -35,11 +38,18 @@ sunnypilot_panel_qt_src = [
|
||||
"sunnypilot/qt/offroad/settings/sunnypilot/mads_settings.cc",
|
||||
]
|
||||
|
||||
network_src = [
|
||||
"sunnypilot/qt/network/sunnylink/sunnylink_client.cc",
|
||||
"sunnypilot/qt/network/sunnylink/services/base_device_service.cc",
|
||||
"sunnypilot/qt/network/sunnylink/services/role_service.cc",
|
||||
"sunnypilot/qt/network/sunnylink/services/user_service.cc",
|
||||
]
|
||||
|
||||
vehicle_panel_qt_src = [
|
||||
"sunnypilot/qt/offroad/settings/vehicle/platform_selector.cc",
|
||||
]
|
||||
|
||||
sp_widgets_src = widgets_src
|
||||
sp_widgets_src = widgets_src + network_src
|
||||
sp_qt_src = qt_src + sunnypilot_panel_qt_src + vehicle_panel_qt_src
|
||||
sp_qt_util = qt_util
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/api.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QJsonDocument>
|
||||
|
||||
#include "util.h"
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
|
||||
namespace SunnylinkApi {
|
||||
QString create_jwt(const QJsonObject &payloads, int expiry, bool sunnylink) {
|
||||
QJsonObject header = {{"alg", "RS256"}};
|
||||
|
||||
auto t = QDateTime::currentSecsSinceEpoch();
|
||||
auto dongle_id = sunnylink ? getSunnylinkDongleId() : getDongleId();
|
||||
QJsonObject payload = {{"identity", dongle_id.value_or("")}, {"nbf", t}, {"iat", t}, {"exp", t + expiry}};
|
||||
for (auto it = payloads.begin(); it != payloads.end(); ++it) {
|
||||
payload.insert(it.key(), it.value());
|
||||
}
|
||||
|
||||
auto b64_opts = QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals;
|
||||
QString jwt = QJsonDocument(header).toJson(QJsonDocument::Compact).toBase64(b64_opts) + '.' +
|
||||
QJsonDocument(payload).toJson(QJsonDocument::Compact).toBase64(b64_opts);
|
||||
|
||||
auto hash = QCryptographicHash::hash(jwt.toUtf8(), QCryptographicHash::Sha256);
|
||||
return jwt + "." + CommaApi::rsa_sign(hash).toBase64(b64_opts);
|
||||
}
|
||||
} // namespace SunnylinkApi
|
||||
|
||||
void HttpRequestSP::sendRequest(const QString& requestURL, Method method, const QByteArray& payload) {
|
||||
if (active()) {
|
||||
return;
|
||||
}
|
||||
QNetworkRequest request = prepareRequest(requestURL);
|
||||
|
||||
if(!payload.isEmpty() && (method == Method::POST || method == Method::PUT)) {
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
}
|
||||
|
||||
switch (method) {
|
||||
case Method::GET:
|
||||
reply = nam()->get(request);
|
||||
break;
|
||||
case Method::DELETE:
|
||||
reply = nam()->deleteResource(request);
|
||||
break;
|
||||
case Method::POST:
|
||||
reply = nam()->post(request, payload);
|
||||
break;
|
||||
case Method::PUT:
|
||||
reply = nam()->put(request, payload);
|
||||
break;
|
||||
}
|
||||
|
||||
networkTimer->start();
|
||||
connect(reply, &QNetworkReply::finished, this, &HttpRequestSP::requestFinished);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "selfdrive/ui/qt/api.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/util.h"
|
||||
#include "common/util.h"
|
||||
|
||||
namespace SunnylinkApi {
|
||||
QByteArray rsa_encrypt(const QByteArray& data);
|
||||
QByteArray rsa_decrypt(const QByteArray& data);
|
||||
QString create_jwt(const QJsonObject& payloads = {}, int expiry = 3600, bool sunnylink = false);
|
||||
}
|
||||
|
||||
class HttpRequestSP : public HttpRequest {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit HttpRequestSP(QObject* parent, bool create_jwt = true, int timeout = 20000, bool sunnylink = false) :
|
||||
HttpRequest(parent, create_jwt, timeout), sunnylink(sunnylink) {}
|
||||
|
||||
using HttpRequest::sendRequest;
|
||||
void sendRequest(const QString& requestURL, Method method, const QByteArray& payload);
|
||||
|
||||
private:
|
||||
bool sunnylink;
|
||||
|
||||
protected:
|
||||
[[nodiscard]] QString GetJwtToken() const override { return SunnylinkApi::create_jwt({}, 3600, sunnylink); }
|
||||
[[nodiscard]] QString GetUserAgent() const override { return getUserAgent(sunnylink); }
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
* 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.
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QJsonObject>
|
||||
|
||||
enum class RoleType {
|
||||
ReadOnly,
|
||||
Sponsor,
|
||||
Admin
|
||||
};
|
||||
|
||||
// haha, a role model xD
|
||||
class RoleModel {
|
||||
protected:
|
||||
QJsonObject m_raw_json_object;
|
||||
|
||||
public:
|
||||
RoleType roleType;
|
||||
|
||||
explicit RoleModel(const RoleType &roleType) : roleType(roleType) { m_raw_json_object = toJson(); }
|
||||
explicit RoleModel(const QJsonObject &json) : RoleModel(stringToRoleType(json["role_type"].toString())) { m_raw_json_object = json; }
|
||||
|
||||
[[nodiscard]] QJsonObject toJson() const {
|
||||
QJsonObject json;
|
||||
json["role_type"] = roleTypeToString(roleType);
|
||||
return json;
|
||||
}
|
||||
|
||||
static RoleType stringToRoleType(const QString &roleTypeString) {
|
||||
if (roleTypeString == "ReadOnly") return RoleType::ReadOnly;
|
||||
if (roleTypeString == "Sponsor") return RoleType::Sponsor;
|
||||
|
||||
return RoleType::Admin; // Default to Admin
|
||||
}
|
||||
|
||||
static QString roleTypeToString(const RoleType &roleType) {
|
||||
switch (roleType) {
|
||||
case RoleType::ReadOnly:
|
||||
return "ReadOnly";
|
||||
case RoleType::Sponsor:
|
||||
return "Sponsor";
|
||||
default: // RoleType::Admin
|
||||
return "Admin";
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename = typename std::enable_if<std::is_base_of<RoleModel, T>::value>::type> T as() const {
|
||||
return T(m_raw_json_object);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QJsonObject>
|
||||
|
||||
enum class SponsorTier {
|
||||
Free,
|
||||
Novice,
|
||||
Supporter,
|
||||
Contributor,
|
||||
Benefactor,
|
||||
Guardian,
|
||||
};
|
||||
|
||||
// haha, a role model xD
|
||||
class SponsorRoleModel final : RoleModel {
|
||||
public:
|
||||
SponsorTier roleTier;
|
||||
|
||||
explicit SponsorRoleModel(const RoleType &roleType, const SponsorTier &roleTier) : RoleModel(roleType), roleTier(roleTier) {}
|
||||
explicit SponsorRoleModel(const QJsonObject &json) : RoleModel(json), roleTier(stringToSponsorTier(json["role_tier"].toString())) {}
|
||||
|
||||
[[nodiscard]] QJsonObject toJson() const {
|
||||
QJsonObject json = RoleModel::toJson();
|
||||
json["role_tier"] = sponsorTierToString(roleTier);
|
||||
return json;
|
||||
}
|
||||
|
||||
static SponsorTier stringToSponsorTier(const QString &sponsorTierString) {
|
||||
const auto sponsorTierStringLower = sponsorTierString.toLower();
|
||||
if (sponsorTierStringLower == "guardian") return SponsorTier::Guardian;
|
||||
if (sponsorTierStringLower == "novice") return SponsorTier::Novice;
|
||||
if (sponsorTierStringLower == "supporter") return SponsorTier::Supporter;
|
||||
if (sponsorTierStringLower == "contributor") return SponsorTier::Contributor;
|
||||
if (sponsorTierStringLower == "benefactor") return SponsorTier::Benefactor;
|
||||
|
||||
// Default to Free
|
||||
return SponsorTier::Free;
|
||||
}
|
||||
|
||||
static QString sponsorTierToString(const SponsorTier &sponsorTier) {
|
||||
switch (sponsorTier) {
|
||||
case SponsorTier::Guardian:
|
||||
return "Guardian";
|
||||
case SponsorTier::Novice:
|
||||
return "Novice";
|
||||
case SponsorTier::Supporter:
|
||||
return "Supporter";
|
||||
case SponsorTier::Contributor:
|
||||
return "Contributor";
|
||||
case SponsorTier::Benefactor:
|
||||
return "Benefactor";
|
||||
|
||||
default: // SponsorTier::Free
|
||||
return "Free";
|
||||
}
|
||||
}
|
||||
[[nodiscard]] auto getSponsorTierString() const { return sponsorTierToString(roleTier); }
|
||||
|
||||
static QString sponsorTierToColor(const SponsorTier &sponsorTier) {
|
||||
switch (sponsorTier) {
|
||||
case SponsorTier::Guardian:
|
||||
return "gold";
|
||||
case SponsorTier::Benefactor:
|
||||
return "mediumseagreen";
|
||||
case SponsorTier::Contributor:
|
||||
return "steelblue";
|
||||
case SponsorTier::Supporter:
|
||||
return "mediumpurple";
|
||||
case SponsorTier::Novice:
|
||||
return "white";
|
||||
default: // SponsorTier::Free
|
||||
return "silver";
|
||||
}
|
||||
}
|
||||
[[nodiscard]] auto getSponsorTierColor() const { return sponsorTierToColor(roleTier); }
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QJsonObject>
|
||||
|
||||
class UserModel {
|
||||
public:
|
||||
QString device_id;
|
||||
QString user_id;
|
||||
qint64 created_at;
|
||||
qint64 updated_at;
|
||||
QString token_hash;
|
||||
|
||||
explicit UserModel(const QJsonObject &json) {
|
||||
device_id = json["device_id"].toString();
|
||||
user_id = json["user_id"].toString();
|
||||
created_at = json["created_at"].toInt();
|
||||
updated_at = json["updated_at"].toInt();
|
||||
token_hash = json["token_hash"].toString();
|
||||
}
|
||||
|
||||
[[nodiscard]] QJsonObject toJson() const {
|
||||
QJsonObject json;
|
||||
json["device_id"] = device_id;
|
||||
json["user_id"] = user_id;
|
||||
json["created_at"] = created_at;
|
||||
json["updated_at"] = updated_at;
|
||||
json["token_hash"] = token_hash;
|
||||
return json;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink//services/base_device_service.h"
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/request_repeater.h"
|
||||
|
||||
#include "common/swaglog.h"
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.h"
|
||||
|
||||
BaseDeviceService::BaseDeviceService(QObject* parent) : QObject(parent) {
|
||||
param_watcher = new ParamWatcher(this);
|
||||
connect(param_watcher, &ParamWatcher::paramChanged, [=](const QString ¶m_name, const QString ¶m_value) {
|
||||
paramsRefresh();
|
||||
});
|
||||
param_watcher->addParam("SunnylinkEnabled");
|
||||
}
|
||||
|
||||
void BaseDeviceService::paramsRefresh() {
|
||||
}
|
||||
|
||||
void BaseDeviceService::loadDeviceData(const QString &url, bool poll) {
|
||||
if (!is_sunnylink_enabled()) {
|
||||
LOGW("Sunnylink is not enabled, refusing to load data.");
|
||||
return;
|
||||
}
|
||||
|
||||
auto sl_dongle_id = getSunnylinkDongleId();
|
||||
if (!sl_dongle_id.has_value())
|
||||
return;
|
||||
|
||||
QString fullUrl = SUNNYLINK_BASE_URL + "/device/" + *sl_dongle_id + url;
|
||||
if (poll && !isCurrentyPolling()) {
|
||||
LOGD("Polling %s", qPrintable(fullUrl));
|
||||
LOGD("Cache key: SunnylinkCache_%s", qPrintable(QString(getCacheKey())));
|
||||
repeater = new RequestRepeaterSP(this, fullUrl, "SunnylinkCache_" + getCacheKey(), 60, false, true);
|
||||
connect(repeater, &RequestRepeaterSP::requestDone, this, &BaseDeviceService::handleResponse);
|
||||
} else if (isCurrentyPolling()) {
|
||||
repeater->ForceUpdate();
|
||||
} else {
|
||||
LOGD("Sending one-time %s", qPrintable(fullUrl));
|
||||
initial_request = new HttpRequestSP(this, true, 10000, true);
|
||||
connect(initial_request, &HttpRequestSP::requestDone, this, &BaseDeviceService::handleResponse);
|
||||
}
|
||||
}
|
||||
|
||||
void BaseDeviceService::stopPolling() {
|
||||
if (repeater != nullptr) {
|
||||
repeater->deleteLater();
|
||||
repeater = nullptr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/request_repeater.h"
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
|
||||
class BaseDeviceService : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
protected:
|
||||
void paramsRefresh();
|
||||
void loadDeviceData(const QString &url, bool poll = false);
|
||||
virtual void handleResponse(const QString &response, bool success) = 0;
|
||||
|
||||
static bool is_sunnylink_enabled() { return Params().getBool("SunnylinkEnabled");}
|
||||
ParamWatcher* param_watcher;
|
||||
HttpRequestSP* initial_request = nullptr;
|
||||
RequestRepeaterSP* repeater = nullptr;
|
||||
|
||||
public:
|
||||
explicit BaseDeviceService(QObject* parent = nullptr);
|
||||
virtual QString getCacheKey() const = 0;
|
||||
bool isCurrentyPolling() {return repeater != nullptr;}
|
||||
void stopPolling();
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
|
||||
RoleService::RoleService(QObject* parent) : BaseDeviceService(parent) {}
|
||||
|
||||
void RoleService::load() {
|
||||
loadDeviceData(url);
|
||||
}
|
||||
|
||||
void RoleService::startPolling() {
|
||||
loadDeviceData(url, true);
|
||||
}
|
||||
|
||||
void RoleService::handleResponse(const QString &response, bool success) {
|
||||
if (!success) return;
|
||||
|
||||
QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8());
|
||||
QJsonArray jsonArray = doc.array();
|
||||
|
||||
std::vector<RoleModel> roles;
|
||||
for (const auto &value : jsonArray) {
|
||||
roles.emplace_back(value.toObject());
|
||||
}
|
||||
|
||||
emit rolesReady(roles);
|
||||
uiStateSP()->setSunnylinkRoles(roles);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/role_model.h"
|
||||
|
||||
class RoleService : public BaseDeviceService {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit RoleService(QObject* parent = nullptr);
|
||||
void load();
|
||||
void startPolling();
|
||||
[[nodiscard]] QString getCacheKey() const final { return "Roles"; }
|
||||
|
||||
signals:
|
||||
void rolesReady(const std::vector<RoleModel> &roles);
|
||||
|
||||
protected:
|
||||
void handleResponse(const QString&response, bool success) override;
|
||||
|
||||
private:
|
||||
QString url = "/roles";
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||
|
||||
UserService::UserService(QObject* parent) : BaseDeviceService(parent) {
|
||||
url = "/users";
|
||||
}
|
||||
|
||||
void UserService::load() {
|
||||
loadDeviceData(url);
|
||||
}
|
||||
|
||||
void UserService::startPolling() {
|
||||
loadDeviceData(url, true);
|
||||
}
|
||||
|
||||
void UserService::handleResponse(const QString &response, bool success) {
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonDocument doc = QJsonDocument::fromJson(response.toUtf8());
|
||||
QJsonArray jsonArray = doc.array();
|
||||
|
||||
std::vector<UserModel> users;
|
||||
for (const auto &value : jsonArray) {
|
||||
users.emplace_back(value.toObject());
|
||||
}
|
||||
|
||||
emit usersReady(users);
|
||||
uiStateSP()->setSunnylinkDeviceUsers(users);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/user_model.h"
|
||||
|
||||
class UserService : public BaseDeviceService {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit UserService(QObject* parent = nullptr);
|
||||
void load();
|
||||
void startPolling();
|
||||
[[nodiscard]] QString getCacheKey() const final { return "Users"; };
|
||||
|
||||
signals:
|
||||
void usersReady(const std::vector<UserModel>&users);
|
||||
|
||||
protected:
|
||||
void handleResponse(const QString&response, bool success) override;
|
||||
|
||||
private:
|
||||
QString url = "/users";
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h"
|
||||
|
||||
SunnylinkClient::SunnylinkClient(QObject* parent) : QObject(parent) {
|
||||
role_service = new RoleService(parent);
|
||||
user_service = new UserService(parent);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h"
|
||||
|
||||
class SunnylinkClient : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SunnylinkClient(QObject* parent);
|
||||
RoleService* role_service;
|
||||
UserService* user_service;
|
||||
};
|
||||
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/sponsor_widget.h"
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/api.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/util.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.h"
|
||||
|
||||
// Sponsor Upsell
|
||||
using qrcodegen::QrCode;
|
||||
|
||||
SunnylinkSponsorQRWidget::SunnylinkSponsorQRWidget(bool sponsor_pair, QWidget* parent) : QWidget(parent), sponsor_pair(sponsor_pair) {
|
||||
timer = new QTimer(this);
|
||||
connect(timer, &QTimer::timeout, this, &SunnylinkSponsorQRWidget::refresh);
|
||||
}
|
||||
|
||||
void SunnylinkSponsorQRWidget::showEvent(QShowEvent *event) {
|
||||
refresh();
|
||||
timer->start(5 * 60 * 1000);
|
||||
device()->setOffroadBrightness(100);
|
||||
}
|
||||
|
||||
void SunnylinkSponsorQRWidget::hideEvent(QHideEvent *event) {
|
||||
timer->stop();
|
||||
device()->setOffroadBrightness(BACKLIGHT_OFFROAD);
|
||||
}
|
||||
|
||||
void SunnylinkSponsorQRWidget::refresh() {
|
||||
QString qrString;
|
||||
|
||||
if (sponsor_pair) {
|
||||
QString token = SunnylinkApi::create_jwt({}, 3600, true);
|
||||
auto sl_dongle_id = getSunnylinkDongleId();
|
||||
QByteArray payload = QString("1|" + sl_dongle_id.value_or("") + "|" + token).toUtf8().toBase64();
|
||||
qrString = SUNNYLINK_BASE_URL + "/sso?state=" + payload;
|
||||
} else {
|
||||
qrString = "https://github.com/sponsors/sunnyhaibin";
|
||||
}
|
||||
|
||||
this->updateQrCode(qrString);
|
||||
update();
|
||||
}
|
||||
|
||||
void SunnylinkSponsorQRWidget::updateQrCode(const QString &text) {
|
||||
QrCode qr = QrCode::encodeText(text.toUtf8().data(), QrCode::Ecc::LOW);
|
||||
qint32 sz = qr.getSize();
|
||||
QImage im(sz, sz, QImage::Format_RGB32);
|
||||
|
||||
QRgb black = qRgb(0, 0, 0);
|
||||
QRgb white = qRgb(255, 255, 255);
|
||||
for (int y = 0; y < sz; y++) {
|
||||
for (int x = 0; x < sz; x++) {
|
||||
im.setPixel(x, y, qr.getModule(x, y) ? black : white);
|
||||
}
|
||||
}
|
||||
|
||||
// Integer division to prevent anti-aliasing
|
||||
int final_sz = ((width() / sz) - 1) * sz;
|
||||
img = QPixmap::fromImage(im.scaled(final_sz, final_sz, Qt::KeepAspectRatio), Qt::MonoOnly);
|
||||
}
|
||||
|
||||
void SunnylinkSponsorQRWidget::paintEvent(QPaintEvent *e) {
|
||||
QPainter p(this);
|
||||
p.fillRect(rect(), Qt::white);
|
||||
|
||||
QSize s = (size() - img.size()) / 2;
|
||||
p.drawPixmap(s.width(), s.height(), img);
|
||||
}
|
||||
|
||||
QStringList SunnylinkSponsorPopup::getInstructions(bool sponsor_pair) {
|
||||
QStringList instructions;
|
||||
if (sponsor_pair) {
|
||||
instructions << tr("Scan the QR code to login to your GitHub account")
|
||||
<< tr("Follow the prompts to complete the pairing process")
|
||||
<< tr("Re-enter the \"sunnylink\" panel to verify sponsorship status")
|
||||
<< tr("If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot");
|
||||
} else {
|
||||
instructions << tr("Scan the QR code to visit sunnyhaibin's GitHub Sponsors page")
|
||||
<< tr("Choose your sponsorship tier and confirm your support")
|
||||
<< tr("Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status");
|
||||
}
|
||||
return instructions;
|
||||
}
|
||||
|
||||
SunnylinkSponsorPopup::SunnylinkSponsorPopup(bool sponsor_pair, QWidget *parent) : DialogBase(parent), sponsor_pair(sponsor_pair) {
|
||||
auto *hlayout = new QHBoxLayout(this);
|
||||
auto sunnylink_client = new SunnylinkClient(this);
|
||||
hlayout->setContentsMargins(0, 0, 0, 0);
|
||||
hlayout->setSpacing(0);
|
||||
|
||||
setStyleSheet("SunnylinkSponsorPopup { background-color: #E0E0E0; }");
|
||||
|
||||
// text
|
||||
auto vlayout = new QVBoxLayout();
|
||||
vlayout->setContentsMargins(85, 70, 50, 70);
|
||||
vlayout->setSpacing(50);
|
||||
hlayout->addLayout(vlayout, 1);
|
||||
{
|
||||
auto close = new QPushButton(QIcon(":/icons/close.svg"), "", this);
|
||||
close->setIconSize(QSize(80, 80));
|
||||
close->setStyleSheet("border: none;");
|
||||
vlayout->addWidget(close, 0, Qt::AlignLeft);
|
||||
connect(close, &QPushButton::clicked, this, [=] {
|
||||
sunnylink_client->role_service->load();
|
||||
sunnylink_client->user_service->load();
|
||||
QDialog::reject();
|
||||
});
|
||||
|
||||
//vlayout->addSpacing(30);
|
||||
|
||||
const QString titleText = sponsor_pair ? tr("Pair your GitHub account") : tr("Early Access: Become a sunnypilot Sponsor");
|
||||
const auto title = new QLabel(titleText, this);
|
||||
title->setStyleSheet("font-size: 75px; color: black;");
|
||||
title->setWordWrap(true);
|
||||
vlayout->addWidget(title);
|
||||
|
||||
QStringList instructions = getInstructions(sponsor_pair);
|
||||
QString instructionsHtml = "<ol type='1' style='margin-left: 15px;'>";
|
||||
for (const auto & instruction : instructions) {
|
||||
instructionsHtml += QString("<li style='margin-bottom: 50px;'>%1</li>").arg(instruction);
|
||||
}
|
||||
instructionsHtml += "</ol>";
|
||||
const auto instructionsLabel = new QLabel(instructionsHtml, this);
|
||||
|
||||
|
||||
instructionsLabel->setStyleSheet("font-size: 47px; font-weight: bold; color: black;");
|
||||
instructionsLabel->setWordWrap(true);
|
||||
vlayout->addWidget(instructionsLabel);
|
||||
|
||||
vlayout->addStretch();
|
||||
}
|
||||
|
||||
// QR code
|
||||
auto *qr = new SunnylinkSponsorQRWidget(sponsor_pair, this);
|
||||
hlayout->addWidget(qr, 1);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QrCode.hpp>
|
||||
#include <QtCore/qjsonobject.h>
|
||||
|
||||
#include "common/util.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
|
||||
|
||||
const QString SUNNYLINK_BASE_URL = util::getenv("SUNNYLINK_API_HOST", "https://stg.api.sunnypilot.ai").c_str();
|
||||
|
||||
class SunnylinkSponsorQRWidget : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SunnylinkSponsorQRWidget(bool sponsor_pair = false, QWidget* parent = 0);
|
||||
void paintEvent(QPaintEvent*) override;
|
||||
|
||||
private:
|
||||
QPixmap img;
|
||||
QTimer *timer;
|
||||
void updateQrCode(const QString &text);
|
||||
void showEvent(QShowEvent *event) override;
|
||||
void hideEvent(QHideEvent *event) override;
|
||||
|
||||
bool sponsor_pair = false;
|
||||
|
||||
private slots:
|
||||
void refresh();
|
||||
};
|
||||
|
||||
// sponsor popup widget
|
||||
class SunnylinkSponsorPopup : public DialogBase {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SunnylinkSponsorPopup(bool sponsor_pair = false, QWidget* parent = 0);
|
||||
|
||||
private:
|
||||
static QStringList getInstructions(bool sponsor_pair);
|
||||
bool sponsor_pair = false;
|
||||
};
|
||||
@@ -12,12 +12,26 @@
|
||||
|
||||
SunnylinkPanel::SunnylinkPanel(QWidget *parent) : QFrame(parent) {
|
||||
main_layout = new QStackedLayout(this);
|
||||
ListWidget *list = new ListWidget(this, false);
|
||||
sunnylink_client = new SunnylinkClient(this);
|
||||
param_watcher = new ParamWatcher(this);
|
||||
param_watcher->addParam("SunnylinkEnabled");
|
||||
connect(param_watcher, &ParamWatcher::paramChanged, [=](const QString ¶m_name, const QString ¶m_value) {
|
||||
paramsRefresh(param_name, param_value);
|
||||
});
|
||||
|
||||
is_sunnylink_enabled = Params().getBool("SunnylinkEnabled");
|
||||
connect(uiStateSP(), &UIStateSP::sunnylinkRolesChanged, this, &SunnylinkPanel::updatePanel);
|
||||
connect(uiStateSP(), &UIStateSP::sunnylinkDeviceUsersChanged, this, &SunnylinkPanel::updatePanel);
|
||||
connect(uiStateSP(), &UIStateSP::offroadTransition, [=](bool offroad) {
|
||||
is_onroad = !offroad;
|
||||
updatePanel();
|
||||
});
|
||||
|
||||
sunnylinkScreen = new QWidget(this);
|
||||
QVBoxLayout *vlayout = new QVBoxLayout(sunnylinkScreen);
|
||||
auto vlayout = new QVBoxLayout(sunnylinkScreen);
|
||||
vlayout->setContentsMargins(50, 20, 50, 20);
|
||||
|
||||
auto *list = new ListWidget(this, false);
|
||||
QString sunnylinkEnabledBtnDesc = tr("This is the master switch, it will allow you to cutoff any sunnylink requests should you want to do that.");
|
||||
sunnylinkEnabledBtn = new ParamControl(
|
||||
"SunnylinkEnabled",
|
||||
@@ -26,23 +40,47 @@ SunnylinkPanel::SunnylinkPanel(QWidget *parent) : QFrame(parent) {
|
||||
"");
|
||||
list->addItem(sunnylinkEnabledBtn);
|
||||
|
||||
status_popup = new SunnylinkSponsorPopup(false, this);
|
||||
sponsorBtn = new ButtonControlSP(
|
||||
tr("Sponsor Status"), tr("SPONSOR"),
|
||||
tr("Become a sponsor of sunnypilot to get early access to sunnylink features when they become available."));
|
||||
list->addItem(sponsorBtn);
|
||||
connect(sponsorBtn, &ButtonControlSP::clicked, [=]() {
|
||||
status_popup->exec();
|
||||
});
|
||||
list->addItem(horizontal_line());
|
||||
|
||||
pair_popup = new SunnylinkSponsorPopup(true, this);
|
||||
pairSponsorBtn = new ButtonControlSP(
|
||||
tr("Pair GitHub Account"), tr("PAIR"),
|
||||
tr("Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink.") + "🌟");
|
||||
list->addItem(pairSponsorBtn);
|
||||
connect(pairSponsorBtn, &ButtonControlSP::clicked, [=]() {
|
||||
if (getSunnylinkDongleId().value_or(tr("N/A")) == "N/A") {
|
||||
ConfirmationDialog::alert(tr("sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again."), this);
|
||||
} else {
|
||||
pair_popup->exec();
|
||||
}
|
||||
});
|
||||
list->addItem(horizontal_line());
|
||||
|
||||
connect(sunnylinkEnabledBtn, &ParamControl::showDescriptionEvent, [=]() {
|
||||
// resets the description to the default one for the Easter egg
|
||||
sunnylinkEnabledBtn->setDescription(sunnylinkEnabledBtnDesc);
|
||||
});
|
||||
|
||||
connect(sunnylinkEnabledBtn, &ParamControl::toggleFlipped, [=](bool enabled) {
|
||||
QString description;
|
||||
if (enabled) {
|
||||
auto proud_description = "<font color='SeaGreen'>"+ tr("🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀")+ "</font>";
|
||||
sunnylinkEnabledBtn->showDescription();
|
||||
sunnylinkEnabledBtn->setDescription(proud_description);
|
||||
description = "<font color='SeaGreen'>"+ tr("🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀")+ "</font>";
|
||||
} else {
|
||||
auto shame_description = "<font color='orange'>"+ tr("👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉.")+ "</font>";
|
||||
sunnylinkEnabledBtn->showDescription();
|
||||
sunnylinkEnabledBtn->setDescription(shame_description);
|
||||
}
|
||||
description = "<font color='orange'>"+ tr("👋Not going to lie, it's sad to see you disabled sunnylink 😢, but we'll be here when you're ready to come back 🎉.")+ "</font>";
|
||||
|
||||
updatePanel(offroad);
|
||||
}
|
||||
sunnylinkEnabledBtn->showDescription();
|
||||
sunnylinkEnabledBtn->setDescription(description);
|
||||
|
||||
updatePanel();
|
||||
});
|
||||
|
||||
QObject::connect(uiState(), &UIState::offroadTransition, this, &SunnylinkPanel::updatePanel);
|
||||
@@ -51,22 +89,81 @@ SunnylinkPanel::SunnylinkPanel(QWidget *parent) : QFrame(parent) {
|
||||
vlayout->addWidget(sunnylinkScroller);
|
||||
|
||||
main_layout->addWidget(sunnylinkScreen);
|
||||
|
||||
if (is_sunnylink_enabled) {
|
||||
startSunnylink();
|
||||
}
|
||||
}
|
||||
|
||||
void SunnylinkPanel::paramsRefresh(const QString ¶m_name, const QString ¶m_value) {
|
||||
// We do it on paramsRefresh because the toggleEvent happens before the value is updated
|
||||
if (param_name == "SunnylinkEnabled" && param_value == "1") {
|
||||
startSunnylink();
|
||||
} else if (param_name == "SunnylinkEnabled" && param_value == "0") {
|
||||
stopSunnylink();
|
||||
}
|
||||
|
||||
updatePanel();
|
||||
}
|
||||
|
||||
void SunnylinkPanel::startSunnylink() const {
|
||||
if (!sunnylink_client->role_service->isCurrentyPolling()) {
|
||||
sunnylink_client->role_service->startPolling();
|
||||
} else {
|
||||
sunnylink_client->role_service->load();
|
||||
}
|
||||
|
||||
if (!sunnylink_client->user_service->isCurrentyPolling()) {
|
||||
sunnylink_client->user_service->startPolling();
|
||||
} else {
|
||||
sunnylink_client->user_service->load();
|
||||
}
|
||||
}
|
||||
|
||||
void SunnylinkPanel::stopSunnylink() const {
|
||||
sunnylink_client->role_service->stopPolling();
|
||||
sunnylink_client->user_service->stopPolling();
|
||||
}
|
||||
|
||||
void SunnylinkPanel::showEvent(QShowEvent *event) {
|
||||
updatePanel(offroad);
|
||||
updatePanel();
|
||||
if (is_sunnylink_enabled) {
|
||||
startSunnylink();
|
||||
}
|
||||
}
|
||||
|
||||
void SunnylinkPanel::updatePanel(bool _offroad) {
|
||||
QString sunnylink_device_id = tr("Device ID") + " " + getSunnylinkDongleId().value_or(tr("N/A"));
|
||||
|
||||
sunnylinkEnabledBtn->setEnabled(_offroad);
|
||||
|
||||
if (sunnylinkEnabledBtn->isToggled()) {
|
||||
sunnylinkEnabledBtn->setValue(sunnylink_device_id);
|
||||
} else {
|
||||
sunnylinkEnabledBtn->setValue("");
|
||||
void SunnylinkPanel::updatePanel() {
|
||||
if (!isVisible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
offroad = _offroad;
|
||||
const auto sunnylinkDongleId = getSunnylinkDongleId().value_or(tr("N/A"));
|
||||
sunnylinkEnabledBtn->setEnabled(!is_onroad);
|
||||
|
||||
is_sunnylink_enabled = Params().getBool("SunnylinkEnabled");
|
||||
bool is_sub = uiStateSP()->isSunnylinkSponsor() && is_sunnylink_enabled;
|
||||
auto max_current_sponsor_rule = uiStateSP()->sunnylinkSponsorRole();
|
||||
auto role_name = max_current_sponsor_rule.getSponsorTierString();
|
||||
std::optional role_color = max_current_sponsor_rule.getSponsorTierColor();
|
||||
bool is_paired = uiStateSP()->isSunnylinkPaired();
|
||||
auto paired_users = uiStateSP()->sunnylinkDeviceUsers();
|
||||
|
||||
sunnylinkEnabledBtn->setEnabled(!is_onroad);
|
||||
sunnylinkEnabledBtn->setValue(tr("Device ID") + " " + sunnylinkDongleId);
|
||||
|
||||
sponsorBtn->setEnabled(!is_onroad && is_sunnylink_enabled);
|
||||
sponsorBtn->setText(is_sub ? tr("THANKS ♥")/* + " ♥️"*/ : tr("SPONSOR"));
|
||||
sponsorBtn->setValue(is_sub ? tr(role_name.toStdString().c_str()) : tr("Not Sponsor"), role_color);
|
||||
|
||||
pairSponsorBtn->setEnabled(!is_onroad && is_sunnylink_enabled);
|
||||
pairSponsorBtn->setValue(is_paired ? tr("Paired") : tr("Not Paired"));
|
||||
|
||||
|
||||
if (!is_sunnylink_enabled) {
|
||||
sunnylinkEnabledBtn->setValue("");
|
||||
sponsorBtn->setValue("");
|
||||
pairSponsorBtn->setValue("");
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
@@ -7,7 +7,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.h"
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/sponsor_widget.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
|
||||
|
||||
class SunnylinkPanel : public QFrame {
|
||||
@@ -16,16 +19,29 @@ class SunnylinkPanel : public QFrame {
|
||||
public:
|
||||
explicit SunnylinkPanel(QWidget *parent = nullptr);
|
||||
void showEvent(QShowEvent *event) override;
|
||||
void paramsRefresh(const QString¶m_name, const QString¶m_value);
|
||||
|
||||
public slots:
|
||||
void updatePanel(bool _offroad);
|
||||
void updatePanel();
|
||||
|
||||
private:
|
||||
Params params;
|
||||
QStackedLayout *main_layout = nullptr;
|
||||
QWidget *sunnylinkScreen = nullptr;
|
||||
ScrollViewSP *sunnylinkScroller = nullptr;
|
||||
bool offroad;
|
||||
SunnylinkSponsorPopup *status_popup;
|
||||
SunnylinkSponsorPopup *pair_popup;
|
||||
ButtonControlSP* sponsorBtn;
|
||||
ButtonControlSP* pairSponsorBtn;
|
||||
SunnylinkClient* sunnylink_client;
|
||||
|
||||
ParamControl *sunnylinkEnabledBtn;
|
||||
bool is_onroad = false;
|
||||
bool is_backup = false;
|
||||
bool is_restore = false;
|
||||
bool is_sunnylink_enabled = false;
|
||||
ParamWatcher *param_watcher;
|
||||
QString sunnylinkBtnDescription;
|
||||
void stopSunnylink() const;
|
||||
void startSunnylink() const;
|
||||
};
|
||||
|
||||
@@ -39,17 +39,19 @@ MadsSettings::MadsSettings(QWidget *parent) : QWidget(parent) {
|
||||
"");
|
||||
list->addItem(madsUnifiedEngagementModeToggle);
|
||||
|
||||
// Pause Lateral On Brake
|
||||
std::vector<QString> lateral_on_brake_texts{tr("Remain Active"), tr("Pause Steering")};
|
||||
madsPauseLateralOnBrake = new ButtonParamControl(
|
||||
"MadsPauseLateralOnBrake",
|
||||
tr("Steering Mode After Braking"),
|
||||
// Steering Mode On Brake
|
||||
std::vector<QString> lateral_on_brake_texts{tr("Remain Active"), tr("Pause Steering"), tr("Disengage")};
|
||||
madsSteeringMode = new ButtonParamControl(
|
||||
"MadsSteeringMode",
|
||||
tr("Steering Mode on Brake Pedal"),
|
||||
tr("Choose how Automatic Lane Centering (ALC) behaves after the brake pedal is manually pressed in sunnypilot.\n\n"
|
||||
"Remain Active: ALC will remain active even after the brake pedal is pressed.\nPause Steering: ALC will be paused after the brake pedal is manually pressed."),
|
||||
"Remain Active: ALC will remain active even after the brake pedal is pressed.\n"
|
||||
"Pause Steering: ALC will be paused when the brake pedal is manually pressed."),
|
||||
"Disengage: ALC will be disengaged after the brake pedal is pressed.\n"
|
||||
"",
|
||||
lateral_on_brake_texts,
|
||||
500);
|
||||
list->addItem(madsPauseLateralOnBrake);
|
||||
list->addItem(madsSteeringMode);
|
||||
|
||||
QObject::connect(uiState(), &UIState::offroadTransition, this, &MadsSettings::updateToggles);
|
||||
|
||||
@@ -61,7 +63,7 @@ void MadsSettings::showEvent(QShowEvent *event) {
|
||||
}
|
||||
|
||||
void MadsSettings::updateToggles(bool _offroad) {
|
||||
madsPauseLateralOnBrake->setEnabled(_offroad);
|
||||
madsSteeringMode->setEnabled(_offroad);
|
||||
|
||||
offroad = _offroad;
|
||||
}
|
||||
|
||||
@@ -32,5 +32,5 @@ private:
|
||||
|
||||
ParamControl *madsMainCruiseToggle;
|
||||
ParamControl *madsUnifiedEngagementModeToggle;
|
||||
ButtonParamControl *madsPauseLateralOnBrake;
|
||||
ButtonParamControl *madsSteeringMode;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/request_repeater.h"
|
||||
|
||||
RequestRepeaterSP::RequestRepeaterSP(QObject *parent, const QString &requestURL, const QString &cacheKey,
|
||||
int period, bool whileOnroad, bool sunnylink) : HttpRequestSP(parent, true, 20000, sunnylink) {
|
||||
request_url = requestURL;
|
||||
while_onroad = whileOnroad;
|
||||
timer = new QTimer(this);
|
||||
timer->setTimerType(Qt::VeryCoarseTimer);
|
||||
connect(timer, &QTimer::timeout, [=]() { this->timerTick(); });
|
||||
timer->start(period * 1000);
|
||||
|
||||
if (!cacheKey.isEmpty()) {
|
||||
prevResp = QString::fromStdString(params.get(cacheKey.toStdString()));
|
||||
if (!prevResp.isEmpty()) {
|
||||
QTimer::singleShot(500, [=]() { emit requestDone(prevResp, true, QNetworkReply::NoError); });
|
||||
}
|
||||
connect(this, &HttpRequest::requestDone, [=](const QString &resp, bool success) {
|
||||
if (success && resp != prevResp) {
|
||||
params.put(cacheKey.toStdString(), resp.toStdString());
|
||||
prevResp = resp;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Don't wait for the timer to fire to send the first request
|
||||
ForceUpdate();
|
||||
}
|
||||
|
||||
void RequestRepeaterSP::timerTick() {
|
||||
if ((!uiState()->scene.started || while_onroad) && device()->isAwake() && !active()) {
|
||||
LOGD("Sending request for %s", qPrintable(request_url));
|
||||
sendRequest(request_url);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "selfdrive/ui/qt/request_repeater.h"
|
||||
|
||||
#include "common/swaglog.h"
|
||||
#include "common/util.h"
|
||||
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/api.h"
|
||||
|
||||
class RequestRepeaterSP : public HttpRequestSP {
|
||||
|
||||
private:
|
||||
Params params;
|
||||
QTimer *timer;
|
||||
QString prevResp;
|
||||
QString request_url;
|
||||
bool while_onroad;
|
||||
void timerTick();
|
||||
|
||||
public:
|
||||
RequestRepeaterSP(QObject *parent, const QString &requestURL, const QString &cacheKey = "", int period = 0, bool whileOnroad=false, bool sunnylink = false);
|
||||
void ForceUpdate() {
|
||||
LOGD("Forcing update for %s", qPrintable(request_url));
|
||||
timerTick();
|
||||
}
|
||||
};
|
||||
@@ -48,6 +48,16 @@ UIStateSP *uiStateSP() {
|
||||
return &ui_state;
|
||||
}
|
||||
|
||||
void UIStateSP::setSunnylinkRoles(const std::vector<RoleModel>& roles) {
|
||||
sunnylinkRoles = roles;
|
||||
emit sunnylinkRolesChanged(roles);
|
||||
}
|
||||
|
||||
void UIStateSP::setSunnylinkDeviceUsers(const std::vector<UserModel>& users) {
|
||||
sunnylinkUsers = users;
|
||||
emit sunnylinkDeviceUsersChanged(users);
|
||||
}
|
||||
|
||||
DeviceSP *deviceSP() {
|
||||
static DeviceSP _device;
|
||||
return &_device;
|
||||
|
||||
@@ -7,6 +7,11 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/user_model.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/role_model.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sponsor_role_model.h"
|
||||
#include "selfdrive/ui/ui.h"
|
||||
|
||||
class UIStateSP : public UIState {
|
||||
@@ -15,12 +20,54 @@ class UIStateSP : public UIState {
|
||||
public:
|
||||
UIStateSP(QObject *parent = 0);
|
||||
void updateStatus() override;
|
||||
void setSunnylinkRoles(const std::vector<RoleModel> &roles);
|
||||
void setSunnylinkDeviceUsers(const std::vector<UserModel> &users);
|
||||
|
||||
inline std::vector<RoleModel> sunnylinkDeviceRoles() const { return sunnylinkRoles; }
|
||||
inline bool isSunnylinkAdmin() const {
|
||||
return std::any_of(sunnylinkRoles.begin(), sunnylinkRoles.end(), [](const RoleModel &role) {
|
||||
return role.roleType == RoleType::Admin;
|
||||
});
|
||||
}
|
||||
inline bool isSunnylinkSponsor() const {
|
||||
return std::any_of(sunnylinkRoles.begin(), sunnylinkRoles.end(), [](const RoleModel &role) {
|
||||
return role.roleType == RoleType::Sponsor && role.as<SponsorRoleModel>().roleTier != SponsorTier::Free;
|
||||
});
|
||||
}
|
||||
inline SponsorRoleModel sunnylinkSponsorRole() const {
|
||||
std::optional<SponsorRoleModel> sponsorRoleWithHighestTier = std::nullopt;
|
||||
for (const auto &role : sunnylinkRoles) {
|
||||
if(role.roleType != RoleType::Sponsor)
|
||||
continue;
|
||||
|
||||
if (auto sponsorRole = role.as<SponsorRoleModel>(); !sponsorRoleWithHighestTier.has_value() || sponsorRoleWithHighestTier->roleTier < sponsorRole.roleTier) {
|
||||
sponsorRoleWithHighestTier = sponsorRole;
|
||||
}
|
||||
}
|
||||
return sponsorRoleWithHighestTier.value_or(SponsorRoleModel(RoleType::Sponsor, SponsorTier::Free));
|
||||
}
|
||||
inline SponsorTier sunnylinkSponsorTier() const {
|
||||
return sunnylinkSponsorRole().roleTier;
|
||||
}
|
||||
inline std::vector<UserModel> sunnylinkDeviceUsers() const { return sunnylinkUsers; }
|
||||
inline bool isSunnylinkPaired() const {
|
||||
return std::any_of(sunnylinkUsers.begin(), sunnylinkUsers.end(), [](const UserModel &user) {
|
||||
return user.user_id.toLower() != "unregisteredsponsor" && user.user_id.toLower() != "temporarysponsor";
|
||||
});
|
||||
}
|
||||
|
||||
signals:
|
||||
void sunnylinkRoleChanged(bool subscriber);
|
||||
void sunnylinkRolesChanged(std::vector<RoleModel> roles);
|
||||
void sunnylinkDeviceUsersChanged(std::vector<UserModel> users);
|
||||
void uiUpdate(const UIStateSP &s);
|
||||
|
||||
private slots:
|
||||
void update() override;
|
||||
|
||||
private:
|
||||
std::vector<RoleModel> sunnylinkRoles = {};
|
||||
std::vector<UserModel> sunnylinkUsers = {};
|
||||
};
|
||||
|
||||
UIStateSP *uiStateSP();
|
||||
|
||||
@@ -207,6 +207,11 @@ def setup_settings_sunnylink(click, pm: PubMaster, scroll=None):
|
||||
click(278, 522)
|
||||
time.sleep(UI_DELAY)
|
||||
|
||||
def setup_settings_sunnylink_sponsor_button(click, pm: PubMaster, scroll=None):
|
||||
setup_settings_sunnylink(click, pm)
|
||||
click(1967, 225)
|
||||
time.sleep(UI_DELAY)
|
||||
|
||||
def setup_settings_sunnypilot(click, pm: PubMaster, scroll=None):
|
||||
setup_settings_device(click, pm)
|
||||
click(278, 852)
|
||||
@@ -268,6 +273,7 @@ CASES = {
|
||||
|
||||
CASES.update({
|
||||
"settings_sunnylink": setup_settings_sunnylink,
|
||||
"settings_sunnylink_sponsor_button": setup_settings_sunnylink_sponsor_button,
|
||||
"settings_sunnypilot": setup_settings_sunnypilot,
|
||||
"settings_sunnypilot_mads": setup_settings_sunnypilot_mads,
|
||||
"settings_trips": setup_settings_trips,
|
||||
|
||||
@@ -159,6 +159,18 @@
|
||||
<source>Enables or disables the github runner service.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error Log</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>VIEW</source>
|
||||
<translation type="unfinished">عرض</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>View the error log for sunnypilot crashes.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DevicePanel</name>
|
||||
@@ -419,33 +431,42 @@
|
||||
<name>FirehosePanel</name>
|
||||
<message>
|
||||
<source>🔥 Firehose Mode 🔥</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>🔥 وضع خرطوم الحريق 🔥</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>openpilot learns to drive by watching humans, like you, drive.
|
||||
|
||||
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.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>يتعلم تطبيق openpilot كيفية القيادة من خلال مشاهدة البشر، مثلك، أثناء القيادة.
|
||||
|
||||
يتيح لك وضع خرطوم الحريق زيادة تحميلات بيانات التدريب لتحسين نماذج القيادة في OpenPilot. كلما زادت البيانات، زادت النماذج، مما يعني وضعًا تجريبيًا أفضل.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Firehose Mode: ACTIVE</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><b>%1 %2</b> of your driving are in the training dataset so far.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>وضع خرطوم الحريق: نشط</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>ACTIVE</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>نشط</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation><span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>غير نشط</span>: اتصل بشبكة غير محسوبة</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>Do all of my segments get pulled in Firehose Mode?</i> No, we selectively pull a subset of your segments.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training.</source>
|
||||
<translation>للحصول على أقصى فعالية، أحضر جهازك إلى الداخل واتصل بمحول USB-C جيد وشبكة Wi-Fi أسبوعياً.<br><br>يمكن أن يعمل وضع خرطوم الحريق أيضاً أثناء القيادة إذا كنت متصلاً بنقطة اتصال أو ببطاقة SIM غير محدودة.<br><br><br><b>الأسئلة المتكررة</b><br><br><i>هل يهم كيف أو أين أقود؟</i> لا، فقط قد كما تفعل عادة.<br><br><i>هل يتم سحب كل مقاطع رحلاتي في وضع خرطوم الحريق؟</i> لا، نقوم بسحب مجموعة مختارة من مقاطع رحلاتك.<br><br><i>ما هو محول USB-C الجيد؟</i> أي شاحن سريع للهاتف أو اللابتوب يجب أن يكون مناسباً.<br><br><i>هل يهم أي برنامج أستخدم؟</i> نعم، فقط النسخة الأصلية من openpilot (وأفرع معينة) يمكن استخدامها للتدريب.</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<source><b>%n segment(s)</b> of your driving is in the training dataset so far.</source>
|
||||
<translation>
|
||||
<numerusform>حتى الآن، يوجد </b>%n مقطع<b>%n من قيادتك في مجموعة بيانات التدريب.</numerusform>
|
||||
<numerusform>حتى الآن، يوجد </b>%n مقطع<b>%n من قيادتك في مجموعة بيانات التدريب.</numerusform>
|
||||
<numerusform>حتى الآن، يوجد </b>%n مقطع<b>%n من قيادتك في مجموعة بيانات التدريب.</numerusform>
|
||||
<numerusform>حتى الآن، يوجد </b>%n مقطع<b>%n من قيادتك في مجموعة بيانات التدريب.</numerusform>
|
||||
<numerusform>حتى الآن، يوجد </b>%n مقطع<b>%n من قيادتك في مجموعة بيانات التدريب.</numerusform>
|
||||
<numerusform>حتى الآن، يوجد </b>%n مقطع<b>%n من قيادتك في مجموعة بيانات التدريب.</numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -925,7 +946,7 @@ This may take up to a minute.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Firehose</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>خرطوم الحريق</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -972,7 +993,7 @@ This may take up to a minute.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Firehose</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation type="unfinished">خرطوم الحريق</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -1441,6 +1462,89 @@ This may take up to a minute.</source>
|
||||
<source>N/A</source>
|
||||
<translation type="unfinished">غير متاح</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Sponsor Status</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>SPONSOR</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Become a sponsor of sunnypilot to get early access to sunnylink features when they become available.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pair GitHub Account</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>PAIR</source>
|
||||
<translation type="unfinished">إقران</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Not Sponsor</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Paired</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Not Paired</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>THANKS ♥</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SunnylinkSponsorPopup</name>
|
||||
<message>
|
||||
<source>Scan the QR code to login to your GitHub account</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Follow the prompts to complete the pairing process</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Re-enter the "sunnylink" panel to verify sponsorship status</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Scan the QR code to visit sunnyhaibin's GitHub Sponsors page</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Choose your sponsorship tier and confirm your support</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pair your GitHub account</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Early Access: Become a sunnypilot Sponsor</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SunnypilotPanel</name>
|
||||
@@ -1469,11 +1573,11 @@ This may take up to a minute.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Welcome to openpilot</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>مرحباً بكم في openpilot</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>You must accept the Terms and Conditions to use openpilot. Read the latest terms at <span style='color: #465BEA;'>https://comma.ai/terms</span> before continuing.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>يجب عليك قبول الشروط والأحكام لاستخدام openpilot. اقرأ أحدث الشروط على <span style='color: #465BEA;'>https://comma.ai/terms</span> قبل الاستمرار.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -1634,15 +1738,15 @@ This may take up to a minute.</source>
|
||||
<name>WiFiPromptWidget</name>
|
||||
<message>
|
||||
<source>Open</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>انفتح</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Maximize your training data uploads to improve openpilot's driving models.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>قم بزيادة تحميلات بيانات التدريب الخاصة بك لتحسين نماذج القيادة الخاصة بـ openpilot.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><span style='font-family: "Noto Color Emoji";'>🔥</span> Firehose Mode <span style='font-family: Noto Color Emoji;'>🔥</span></source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation><span style='font-family: "Noto Color Emoji";'>🔥</span> وضع خرطوم الحريق <span style='font-family: Noto Color Emoji;'>🔥</span></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
||||
@@ -159,6 +159,18 @@
|
||||
<source>Enables or disables the github runner service.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error Log</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>VIEW</source>
|
||||
<translation type="unfinished">ANSEHEN</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>View the error log for sunnypilot crashes.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DevicePanel</name>
|
||||
@@ -431,14 +443,6 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
|
||||
<source>Firehose Mode: ACTIVE</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><b>%1 %2</b> of your driving are in the training dataset so far.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>ACTIVE</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -447,6 +451,17 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
|
||||
<source><span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>Do all of my segments get pulled in Firehose Mode?</i> No, we selectively pull a subset of your segments.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<source><b>%n segment(s)</b> of your driving is in the training dataset so far.</source>
|
||||
<translation type="unfinished">
|
||||
<numerusform></numerusform>
|
||||
<numerusform></numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>HudRenderer</name>
|
||||
@@ -1425,6 +1440,89 @@ This may take up to a minute.</source>
|
||||
<source>N/A</source>
|
||||
<translation type="unfinished">Nicht verfügbar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Sponsor Status</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>SPONSOR</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Become a sponsor of sunnypilot to get early access to sunnylink features when they become available.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pair GitHub Account</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>PAIR</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Not Sponsor</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Paired</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Not Paired</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>THANKS ♥</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SunnylinkSponsorPopup</name>
|
||||
<message>
|
||||
<source>Scan the QR code to login to your GitHub account</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Follow the prompts to complete the pairing process</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Re-enter the "sunnylink" panel to verify sponsorship status</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Scan the QR code to visit sunnyhaibin's GitHub Sponsors page</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Choose your sponsorship tier and confirm your support</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pair your GitHub account</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Early Access: Become a sunnypilot Sponsor</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SunnypilotPanel</name>
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="en_US">
|
||||
<context>
|
||||
<name>FirehosePanel</name>
|
||||
<message numerus="yes">
|
||||
<source><b>%n segment(s)</b> of your driving is in the training dataset so far.</source>
|
||||
<translation>
|
||||
<numerusform><b>%n segment</b> of your driving is in the training dataset so far.</numerusform>
|
||||
<numerusform><b>%n segments</b> of your driving are in the training dataset so far.</numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>InputDialog</name>
|
||||
<message numerus="yes">
|
||||
|
||||
@@ -159,6 +159,18 @@
|
||||
<source>Enables or disables the github runner service.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error Log</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>VIEW</source>
|
||||
<translation type="unfinished">VER</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>View the error log for sunnypilot crashes.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DevicePanel</name>
|
||||
@@ -431,14 +443,6 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
|
||||
<source>Firehose Mode: ACTIVE</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><b>%1 %2</b> of your driving are in the training dataset so far.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>ACTIVE</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -447,6 +451,17 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
|
||||
<source><span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>Do all of my segments get pulled in Firehose Mode?</i> No, we selectively pull a subset of your segments.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<source><b>%n segment(s)</b> of your driving is in the training dataset so far.</source>
|
||||
<translation type="unfinished">
|
||||
<numerusform></numerusform>
|
||||
<numerusform></numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>HudRenderer</name>
|
||||
@@ -1425,6 +1440,93 @@ Esto puede tardar un minuto.</translation>
|
||||
<source>N/A</source>
|
||||
<translation type="unfinished">N/A</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Sponsor Status</source>
|
||||
<translation type="unfinished">Estado de patrocinio</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>SPONSOR</source>
|
||||
<translation type="unfinished">PATROCINADOR</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Become a sponsor of sunnypilot to get early access to sunnylink features when they become available.</source>
|
||||
<translation type="unfinished">Conviértete en patrocinador de sunnypilot para obtener acceso anticipado a las funciones de sunnylink cuando estén disponibles.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pair GitHub Account</source>
|
||||
<translation type="unfinished">Emparejar cuenta de GitHub</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>PAIR</source>
|
||||
<translation type="unfinished">EMPAREJAR</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink.</source>
|
||||
<translation type="unfinished">Empareja tu cuenta de GitHub para otorgar a tu dispositivo beneficios de patrocinador, incluido acceso a la API en sunnylink.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again.</source>
|
||||
<translation type="unfinished">ID del dongle de sunnylink no encontrado. Esto puede deberse a una conexión débil o a un problema de registro en sunnylink. Por favor, reinicia y vuelve a intentarlo.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>THANKS</source>
|
||||
<translation type="obsolete">GRACIAS</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Not Sponsor</source>
|
||||
<translation type="unfinished">No patrocinador</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Paired</source>
|
||||
<translation type="unfinished">Emparejado</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Not Paired</source>
|
||||
<translation type="unfinished">No emparejado</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>THANKS ♥</source>
|
||||
<translation type="unfinished">GRACIAS ♥</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SunnylinkSponsorPopup</name>
|
||||
<message>
|
||||
<source>Scan the QR code to login to your GitHub account</source>
|
||||
<translation type="unfinished">Escanea el código QR para iniciar sesión en tu cuenta de GitHub</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Follow the prompts to complete the pairing process</source>
|
||||
<translation type="unfinished">Sigue las indicaciones para completar el proceso de emparejamiento</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Re-enter the "sunnylink" panel to verify sponsorship status</source>
|
||||
<translation type="unfinished">Vuelve a ingresar al panel de "sunnylink" para verificar el estado de patrocinio</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot</source>
|
||||
<translation type="unfinished">Si el estado de patrocinio no se actualizó, por favor contacta a un moderador en Discord en https://discord.gg/sunnypilot</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Scan the QR code to visit sunnyhaibin's GitHub Sponsors page</source>
|
||||
<translation type="unfinished">Escanea el código QR para visitar la página de GitHub Sponsors de sunnyhaibin</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Choose your sponsorship tier and confirm your support</source>
|
||||
<translation type="unfinished">Elige tu nivel de patrocinio y confirma tu apoyo</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status</source>
|
||||
<translation type="unfinished">Únete a nuestra comunidad en Discord en https://discord.gg/sunnypilot y contacta a un moderador para confirmar tu estado de patrocinio</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pair your GitHub account</source>
|
||||
<translation type="unfinished">Empareja tu cuenta de GitHub</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Early Access: Become a sunnypilot Sponsor</source>
|
||||
<translation type="unfinished">Acceso temprano: conviértete en patrocinador de sunnypilot</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SunnypilotPanel</name>
|
||||
|
||||
@@ -159,6 +159,18 @@
|
||||
<source>Enables or disables the github runner service.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error Log</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>VIEW</source>
|
||||
<translation type="unfinished">VOIR</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>View the error log for sunnypilot crashes.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DevicePanel</name>
|
||||
@@ -431,14 +443,6 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
|
||||
<source>Firehose Mode: ACTIVE</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><b>%1 %2</b> of your driving are in the training dataset so far.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>ACTIVE</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -447,6 +451,17 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
|
||||
<source><span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>Do all of my segments get pulled in Firehose Mode?</i> No, we selectively pull a subset of your segments.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<source><b>%n segment(s)</b> of your driving is in the training dataset so far.</source>
|
||||
<translation type="unfinished">
|
||||
<numerusform></numerusform>
|
||||
<numerusform></numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>HudRenderer</name>
|
||||
@@ -1425,6 +1440,89 @@ Cela peut prendre jusqu'à une minute.</translation>
|
||||
<source>N/A</source>
|
||||
<translation type="unfinished">N/A</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Sponsor Status</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>SPONSOR</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Become a sponsor of sunnypilot to get early access to sunnylink features when they become available.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pair GitHub Account</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>PAIR</source>
|
||||
<translation type="unfinished">ASSOCIER</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Not Sponsor</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Paired</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Not Paired</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>THANKS ♥</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SunnylinkSponsorPopup</name>
|
||||
<message>
|
||||
<source>Scan the QR code to login to your GitHub account</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Follow the prompts to complete the pairing process</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Re-enter the "sunnylink" panel to verify sponsorship status</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Scan the QR code to visit sunnyhaibin's GitHub Sponsors page</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Choose your sponsorship tier and confirm your support</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pair your GitHub account</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Early Access: Become a sunnypilot Sponsor</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SunnypilotPanel</name>
|
||||
|
||||
@@ -159,6 +159,18 @@
|
||||
<source>Enables or disables the github runner service.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error Log</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>VIEW</source>
|
||||
<translation type="unfinished">確認</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>View the error log for sunnypilot crashes.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DevicePanel</name>
|
||||
@@ -431,14 +443,6 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
|
||||
<source>Firehose Mode: ACTIVE</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><b>%1 %2</b> of your driving are in the training dataset so far.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>ACTIVE</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -447,6 +451,16 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
|
||||
<source><span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>Do all of my segments get pulled in Firehose Mode?</i> No, we selectively pull a subset of your segments.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<source><b>%n segment(s)</b> of your driving is in the training dataset so far.</source>
|
||||
<translation type="unfinished">
|
||||
<numerusform></numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>HudRenderer</name>
|
||||
@@ -952,7 +966,7 @@ This may take up to a minute.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Firehose</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation type="unfinished">データ学習</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -1421,6 +1435,89 @@ This may take up to a minute.</source>
|
||||
<source>N/A</source>
|
||||
<translation type="unfinished">該当なし</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Sponsor Status</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>SPONSOR</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Become a sponsor of sunnypilot to get early access to sunnylink features when they become available.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pair GitHub Account</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>PAIR</source>
|
||||
<translation type="unfinished">OK</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Not Sponsor</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Paired</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Not Paired</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>THANKS ♥</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SunnylinkSponsorPopup</name>
|
||||
<message>
|
||||
<source>Scan the QR code to login to your GitHub account</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Follow the prompts to complete the pairing process</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Re-enter the "sunnylink" panel to verify sponsorship status</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Scan the QR code to visit sunnyhaibin's GitHub Sponsors page</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Choose your sponsorship tier and confirm your support</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pair your GitHub account</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Early Access: Become a sunnypilot Sponsor</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SunnypilotPanel</name>
|
||||
|
||||
@@ -129,11 +129,11 @@
|
||||
</message>
|
||||
<message>
|
||||
<source>WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB).</source>
|
||||
<translation>경고: openpilot 가감속 제어 알파 기능으로 차량의 자동긴급제동(AEB)을 비활성화합니다.</translation>
|
||||
<translation>경고: openpilot 가감속 제어는 알파 기능으로 차량의 자동긴급제동(AEB)기능이 작동하지 않습니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>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.</source>
|
||||
<translation>이 차량은 openpilot 가감속 제어 대신 기본적으로 차량의 ACC로 가감속을 제어합니다. openpilot의 가감속 제어로 전환하려면 이 기능을 활성화하세요. openpilot 가감속 제어 알파를 활성화하는 경우 실험 모드 활성화를 권장합니다.</translation>
|
||||
<translation>이 차량에서 openpilot은 openpilot 가감속 제어 대신 기본적으로 차량의 ACC로 가감속을 제어합니다. openpilot 가감속 제어로 전환하려면 이 기능을 활성화하세요. openpilot 가감속 제어 알파 기능을 활성화하는 경우 실험 모드 활성화를 권장합니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable ADB</source>
|
||||
@@ -159,6 +159,18 @@
|
||||
<source>Enables or disables the github runner service.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error Log</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>VIEW</source>
|
||||
<translation type="unfinished">보기</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>View the error log for sunnypilot crashes.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DevicePanel</name>
|
||||
@@ -425,27 +437,31 @@
|
||||
<source>openpilot learns to drive by watching humans, like you, drive.
|
||||
|
||||
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.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>오픈파일럿은 여러분과 같은 사람이 운전하는 모습을 보면서 운전하는 법을 배웁니다.
|
||||
|
||||
파이어호스 모드를 사용하면 훈련 데이터 업로드를 최대화하여 오픈파일럿의 주행 모델을 개선할 수 있습니다. 더 많은 데이터는 더 큰 모델을 의미하며, 이는 더 나은 실험 모드를 의미합니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Firehose Mode: ACTIVE</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><b>%1 %2</b> of your driving are in the training dataset so far.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>파이어호스 모드: 활성화</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>ACTIVE</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>활성화</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation><span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>비활성</span>: 무제한 네트워크에 연결</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>Do all of my segments get pulled in Firehose Mode?</i> No, we selectively pull a subset of your segments.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training.</source>
|
||||
<translation>최대한의 효과를 얻으려면 매주 장치를 실내로 가져와 좋은 USB-C 케이블에 연결하고 Wi-Fi에 연결하세요.<br><br>파이어호스 모드는 핫스팟 또는 무제한 SIM 카드에 연결된 경우에는 운전 중에도 작동할 수 있습니다.<br><br><br><b>자주 묻는 질문</b><br><br><i>운전 방법이나 장소가 중요한가요?</i> 아니요, 평소처럼 운전하시면 됩니다.<br><br><i>파이어호스 모드에서 제 모든 구간을 가져오나요?.<br><br><i> 아니요, 저희는 여러분의 구간 중 일부를 선별적으로 가져옵니다.<br><br><i>좋은 USB-C 케이블은 무엇인가요?</i> 휴대폰이나 노트북 고속 충전기가 있으면 됩니다.<br><br><i>어떤 소프트웨어를 실행하는지가 중요한가요?</i> 예, 오직 공식 openpilot의 특정 포크만 트레이닝에 사용할 수 있습니다.</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<source><b>%n segment(s)</b> of your driving is in the training dataset so far.</source>
|
||||
<translation type="unfinished">
|
||||
<numerusform></numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -604,7 +620,7 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.</s
|
||||
</message>
|
||||
<message>
|
||||
<source>openpilot was unable to identify your car. Your car is either unsupported or its ECUs are not recognized. Please submit a pull request to add the firmware versions to the proper vehicle. Need help? Join discord.comma.ai.</source>
|
||||
<translation>openpilot이 차량을 식별할 수 없었습니다. 지원되지 않는 차량이거나 ECU가 인식되지 않습니다. 해당 차량에 맞는 펌웨어 버전을 추가하려면 PR을 제출하세요. 도움이 필요하시면 discord.comma.ai에 가입하세요.</translation>
|
||||
<translation>openpilot이 차량을 식별할 수 없습니다. 지원되지 않는 차량이거나 ECU가 인식되지 않습니다. 해당 차량에 맞는 펌웨어 버전을 추가하려면 PR을 제출하세요. 도움이 필요하시면 discord.comma.ai에 가입하세요.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield.</source>
|
||||
@@ -612,7 +628,7 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed.</s
|
||||
</message>
|
||||
<message>
|
||||
<source>Device temperature too high. System cooling down before starting. Current internal component temperature: %1</source>
|
||||
<translation>장치 온도가 너무 높습니다. 시작하기 전에 온도를 낮춰주세요. 현재 내부 부품 온도: %1</translation>
|
||||
<translation>장치의 온도가 너무 높습니다. 시작 전에 온도를 낮춰주세요. 현재 내부 구성 요소 온도: %1</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>sunnypilot is now in Always Offroad mode. sunnypilot won't start until Always Offroad mode is disabled. Go to "Settings" -> "Device" to exit Always Offroad mode.</source>
|
||||
@@ -952,7 +968,7 @@ This may take up to a minute.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Firehose</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation type="unfinished">파이어호스</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -1421,6 +1437,89 @@ This may take up to a minute.</source>
|
||||
<source>N/A</source>
|
||||
<translation type="unfinished">N/A</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Sponsor Status</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>SPONSOR</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Become a sponsor of sunnypilot to get early access to sunnylink features when they become available.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pair GitHub Account</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>PAIR</source>
|
||||
<translation type="unfinished">동기화</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Not Sponsor</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Paired</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Not Paired</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>THANKS ♥</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SunnylinkSponsorPopup</name>
|
||||
<message>
|
||||
<source>Scan the QR code to login to your GitHub account</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Follow the prompts to complete the pairing process</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Re-enter the "sunnylink" panel to verify sponsorship status</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Scan the QR code to visit sunnyhaibin's GitHub Sponsors page</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Choose your sponsorship tier and confirm your support</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pair your GitHub account</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Early Access: Become a sunnypilot Sponsor</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SunnypilotPanel</name>
|
||||
@@ -1449,11 +1548,11 @@ This may take up to a minute.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Welcome to openpilot</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>오픈 파일럿에 오신 것을 환영합니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>You must accept the Terms and Conditions to use openpilot. Read the latest terms at <span style='color: #465BEA;'>https://comma.ai/terms</span> before continuing.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>오픈파일럿을 사용하려면 이용약관에 동의해야 합니다. 최신 약관은 <span style='color: #465BEA;'>https://comma.ai/terms</span> 에서 최신 약관을 읽은 후 계속하세요.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -1556,7 +1655,7 @@ This may take up to a minute.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>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.</source>
|
||||
<translation>운전 시각화는 일부 회전을 더 잘 보여주기 위해 저속에서 도로를 향한 광각 카메라로 전환됩니다. 실험 모드 로고도 우측 상단에 표시됩니다.</translation>
|
||||
<translation>운전 시각화는 일부 회전을 더 잘 보여주기 위해 저속에서 도로를 향한 광각 카메라로 전환됩니다. 우측 상단에 실험 모드 로고가 표시됩니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Always-On Driver Monitoring</source>
|
||||
@@ -1564,7 +1663,7 @@ This may take up to a minute.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable driver monitoring even when openpilot is not engaged.</source>
|
||||
<translation>Openpilot이 활성화되지 않은 경우에도 드라이버 모니터링을 활성화합니다.</translation>
|
||||
<translation>openpilot이 활성화되지 않은 경우에도 드라이버 모니터링을 활성화합니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable Dynamic Experimental Control</source>
|
||||
|
||||
@@ -159,6 +159,18 @@
|
||||
<source>Enables or disables the github runner service.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error Log</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>VIEW</source>
|
||||
<translation type="unfinished">VER</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>View the error log for sunnypilot crashes.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DevicePanel</name>
|
||||
@@ -419,33 +431,38 @@
|
||||
<name>FirehosePanel</name>
|
||||
<message>
|
||||
<source>🔥 Firehose Mode 🔥</source>
|
||||
<translation>🔥 Mode Firehose 🔥</translation>
|
||||
<translation>🔥 Modo Firehose 🔥</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>openpilot learns to drive by watching humans, like you, drive.
|
||||
|
||||
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.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>O openpilot aprende a dirigir observando humanos, como você, dirigirem.
|
||||
|
||||
O Modo Firehose permite maximizar o envio de dados de treinamento para melhorar os modelos de direção do openpilot. Mais dados significam modelos maiores, o que resulta em um Modo Experimental melhor.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Firehose Mode: ACTIVE</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><b>%1 %2</b> of your driving are in the training dataset so far.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Modo Firehose: ATIVO</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>ACTIVE</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>ATIVO</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation><span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INATIVO</span>: conecte-se a uma rede sem restrição <br> de dados</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>Do all of my segments get pulled in Firehose Mode?</i> No, we selectively pull a subset of your segments.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training.</source>
|
||||
<translation>Para maior eficácia, leve seu dispositivo para dentro de casa e conecte-o a um bom adaptador USB-C e Wi-Fi semanalmente.<br><br>O Modo Firehose também pode funcionar enquanto você dirige, se estiver conectado a um hotspot ou a um chip SIM com dados ilimitados.<br><br><br><b>Perguntas Frequentes</b><br><br><i>Importa como ou onde eu dirijo?</i> Não, basta dirigir normalmente.<br><br><i>Todos os meus segmentos são enviados no Modo Firehose?</i> Não, selecionamos apenas um subconjunto dos seus segmentos.<br><br><i>Qual é um bom adaptador USB-C?</i> Qualquer carregador rápido de telefone ou laptop deve ser suficiente.<br><br><i>Importa qual software eu uso?</i> Sim, apenas o openpilot oficial (e alguns forks específicos) podem ser usados para treinamento.</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<source><b>%n segment(s)</b> of your driving is in the training dataset so far.</source>
|
||||
<translation>
|
||||
<numerusform><b>%n segmento</b> da sua direção está no conjunto de dados de treinamento até agora.</numerusform>
|
||||
<numerusform><b>%n segmentos</b> da sua direção estão no conjunto de dados de treinamento até agora.</numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -956,7 +973,7 @@ Isso pode levar até um minuto.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Firehose</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation type="unfinished">Firehose</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -1425,6 +1442,89 @@ Isso pode levar até um minuto.</translation>
|
||||
<source>N/A</source>
|
||||
<translation type="unfinished">N/A</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Sponsor Status</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>SPONSOR</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Become a sponsor of sunnypilot to get early access to sunnylink features when they become available.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pair GitHub Account</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>PAIR</source>
|
||||
<translation type="unfinished">PAREAR</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Not Sponsor</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Paired</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Not Paired</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>THANKS ♥</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SunnylinkSponsorPopup</name>
|
||||
<message>
|
||||
<source>Scan the QR code to login to your GitHub account</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Follow the prompts to complete the pairing process</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Re-enter the "sunnylink" panel to verify sponsorship status</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Scan the QR code to visit sunnyhaibin's GitHub Sponsors page</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Choose your sponsorship tier and confirm your support</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pair your GitHub account</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Early Access: Become a sunnypilot Sponsor</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SunnypilotPanel</name>
|
||||
@@ -1453,11 +1553,11 @@ Isso pode levar até um minuto.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Welcome to openpilot</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Bem vindo ao openpilot</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>You must accept the Terms and Conditions to use openpilot. Read the latest terms at <span style='color: #465BEA;'>https://comma.ai/terms</span> before continuing.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Você deve aceitar os Termos e Condições para usar o openpilot. Leia os termos mais recentes em <span style='color: #465BEA;'>https://comma.ai/terms</span> antes de continuar.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -1626,7 +1726,7 @@ Isso pode levar até um minuto.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><span style='font-family: "Noto Color Emoji";'>🔥</span> Firehose Mode <span style='font-family: Noto Color Emoji;'>🔥</span></source>
|
||||
<translation><span style='font-family: "Noto Color Emoji";'>🔥</span> Mode Firehose <span style='font-family: Noto Color Emoji;'>🔥</span></translation>
|
||||
<translation><span style='font-family: "Noto Color Emoji";'>🔥</span> Modo Firehose <span style='font-family: Noto Color Emoji;'>🔥</span></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
||||
@@ -159,6 +159,18 @@
|
||||
<source>Enables or disables the github runner service.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error Log</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>VIEW</source>
|
||||
<translation type="unfinished">ดู</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>View the error log for sunnypilot crashes.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DevicePanel</name>
|
||||
@@ -431,14 +443,6 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
|
||||
<source>Firehose Mode: ACTIVE</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><b>%1 %2</b> of your driving are in the training dataset so far.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>ACTIVE</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -447,6 +451,16 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
|
||||
<source><span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>Do all of my segments get pulled in Firehose Mode?</i> No, we selectively pull a subset of your segments.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<source><b>%n segment(s)</b> of your driving is in the training dataset so far.</source>
|
||||
<translation type="unfinished">
|
||||
<numerusform></numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>HudRenderer</name>
|
||||
@@ -1421,6 +1435,89 @@ This may take up to a minute.</source>
|
||||
<source>N/A</source>
|
||||
<translation type="unfinished">ไม่มี</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Sponsor Status</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>SPONSOR</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Become a sponsor of sunnypilot to get early access to sunnylink features when they become available.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pair GitHub Account</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>PAIR</source>
|
||||
<translation type="unfinished">จับคู่</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Not Sponsor</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Paired</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Not Paired</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>THANKS ♥</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SunnylinkSponsorPopup</name>
|
||||
<message>
|
||||
<source>Scan the QR code to login to your GitHub account</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Follow the prompts to complete the pairing process</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Re-enter the "sunnylink" panel to verify sponsorship status</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Scan the QR code to visit sunnyhaibin's GitHub Sponsors page</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Choose your sponsorship tier and confirm your support</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pair your GitHub account</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Early Access: Become a sunnypilot Sponsor</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SunnypilotPanel</name>
|
||||
|
||||
@@ -159,6 +159,18 @@
|
||||
<source>Enables or disables the github runner service.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error Log</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>VIEW</source>
|
||||
<translation type="unfinished">BAK</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>View the error log for sunnypilot crashes.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DevicePanel</name>
|
||||
@@ -431,14 +443,6 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
|
||||
<source>Firehose Mode: ACTIVE</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><b>%1 %2</b> of your driving are in the training dataset so far.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>ACTIVE</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -447,6 +451,16 @@ Firehose Mode allows you to maximize your training data uploads to improve openp
|
||||
<source><span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>Do all of my segments get pulled in Firehose Mode?</i> No, we selectively pull a subset of your segments.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<source><b>%n segment(s)</b> of your driving is in the training dataset so far.</source>
|
||||
<translation type="unfinished">
|
||||
<numerusform></numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>HudRenderer</name>
|
||||
@@ -1419,6 +1433,89 @@ This may take up to a minute.</source>
|
||||
<source>N/A</source>
|
||||
<translation type="unfinished">N/A</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Sponsor Status</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>SPONSOR</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Become a sponsor of sunnypilot to get early access to sunnylink features when they become available.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pair GitHub Account</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>PAIR</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Not Sponsor</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Paired</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Not Paired</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>THANKS ♥</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SunnylinkSponsorPopup</name>
|
||||
<message>
|
||||
<source>Scan the QR code to login to your GitHub account</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Follow the prompts to complete the pairing process</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Re-enter the "sunnylink" panel to verify sponsorship status</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Scan the QR code to visit sunnyhaibin's GitHub Sponsors page</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Choose your sponsorship tier and confirm your support</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pair your GitHub account</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Early Access: Become a sunnypilot Sponsor</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SunnypilotPanel</name>
|
||||
|
||||
@@ -159,6 +159,18 @@
|
||||
<source>Enables or disables the github runner service.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error Log</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>VIEW</source>
|
||||
<translation type="unfinished">查看</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>View the error log for sunnypilot crashes.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DevicePanel</name>
|
||||
@@ -425,27 +437,31 @@
|
||||
<source>openpilot learns to drive by watching humans, like you, drive.
|
||||
|
||||
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.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>openpilot 通过观察人类驾驶(包括您)来学习如何驾驶。
|
||||
|
||||
“训练数据上传模式”允许您最大化上传训练数据,以改进 openpilot 的驾驶模型。更多数据意味着更强大的模型,也就意味着更优秀的“实验模式”。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Firehose Mode: ACTIVE</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><b>%1 %2</b> of your driving are in the training dataset so far.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>训练数据上传模式:激活中</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>ACTIVE</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>激活中</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation><span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>未激活</span>:请连接至不限流量的网络</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>Do all of my segments get pulled in Firehose Mode?</i> No, we selectively pull a subset of your segments.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training.</source>
|
||||
<translation>为了达到最佳效果,请每周将您的设备带回室内,并连接到优质的 USB-C 充电器和 Wi-Fi。<br><br>Firehose 模式在行驶时也能运行,但需连接到移动热点或使用不限流量的 SIM 卡。<br><br><br><b>常见问题</b><br><br><i>我开车的方式或地点有影响吗?</i>不会,请像平常一样驾驶即可。<br><br><i>Firehose 模式会上传所有的驾驶片段吗?</i>不会,我们会选择性地上传部分片段。<br><br><i>什么是好的 USB-C 充电器?</i>任何快速手机或笔记本电脑充电器都应该适用。<br><br><i>我使用的软件版本有影响吗?</i>有的,只有官方 openpilot(以及特定的分支)可以用于训练。</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<source><b>%n segment(s)</b> of your driving is in the training dataset so far.</source>
|
||||
<translation>
|
||||
<numerusform><b>目前已有 %n 段</b> 您的驾驶数据被纳入训练数据集。</numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -952,7 +968,7 @@ This may take up to a minute.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Firehose</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation type="unfinished">训练上传</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -1421,6 +1437,89 @@ This may take up to a minute.</source>
|
||||
<source>N/A</source>
|
||||
<translation type="unfinished">N/A</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Sponsor Status</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>SPONSOR</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Become a sponsor of sunnypilot to get early access to sunnylink features when they become available.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pair GitHub Account</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>PAIR</source>
|
||||
<translation type="unfinished">配对</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Not Sponsor</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Paired</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Not Paired</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>THANKS ♥</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SunnylinkSponsorPopup</name>
|
||||
<message>
|
||||
<source>Scan the QR code to login to your GitHub account</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Follow the prompts to complete the pairing process</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Re-enter the "sunnylink" panel to verify sponsorship status</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Scan the QR code to visit sunnyhaibin's GitHub Sponsors page</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Choose your sponsorship tier and confirm your support</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pair your GitHub account</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Early Access: Become a sunnypilot Sponsor</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SunnypilotPanel</name>
|
||||
@@ -1449,11 +1548,11 @@ This may take up to a minute.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Welcome to openpilot</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>欢迎使用 openpilot</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>You must accept the Terms and Conditions to use openpilot. Read the latest terms at <span style='color: #465BEA;'>https://comma.ai/terms</span> before continuing.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>您必须接受《条款与条件》才能使用 openpilot。在继续之前,请先阅读最新条款:<span style='color: #465BEA;'>https://comma.ai/terms</span>。</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
||||
@@ -159,6 +159,18 @@
|
||||
<source>Enables or disables the github runner service.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Error Log</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>VIEW</source>
|
||||
<translation type="unfinished">觀看</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>View the error log for sunnypilot crashes.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DevicePanel</name>
|
||||
@@ -425,27 +437,31 @@
|
||||
<source>openpilot learns to drive by watching humans, like you, drive.
|
||||
|
||||
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.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>openpilot 透過觀察人類駕駛(包括您)來學習如何駕駛。
|
||||
|
||||
「訓練資料上傳模式」可讓您最大化上傳訓練數據,以改進 openpilot 的駕駛模型。更多數據代表更強大的模型,也就意味著更優秀的「實驗模式」。</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Firehose Mode: ACTIVE</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><b>%1 %2</b> of your driving are in the training dataset so far.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>訓練資料上傳模式:啟用中</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>ACTIVE</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>啟用中</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation><span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>未啟用</span>:請連接至不限流量的網路</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>For maximum effectiveness, bring your device inside and connect to a good USB-C adapter and Wi-Fi weekly.<br><br>Firehose Mode can also work while you're driving if connected to a hotspot or unlimited SIM card.<br><br><br><b>Frequently Asked Questions</b><br><br><i>Does it matter how or where I drive?</i> Nope, just drive as you normally would.<br><br><i>Do all of my segments get pulled in Firehose Mode?</i> No, we selectively pull a subset of your segments.<br><br><i>What's a good USB-C adapter?</i> Any fast phone or laptop charger should be fine.<br><br><i>Does it matter which software I run?</i> Yes, only upstream openpilot (and particular forks) are able to be used for training.</source>
|
||||
<translation>為了達到最佳效果,請每週將您的裝置帶回室內,並連接至優質的 USB-C 充電器與 Wi-Fi。<br><br>訓練資料上傳模式在行駛時也能運作,但需連接至行動熱點或使用不限流量的 SIM 卡。<br><br><br><b>常見問題</b><br><br><i>我開車的方式或地點有影響嗎?</i> 不會,請像平常一樣駕駛即可。<br><br><i>訓練資料上傳模式會上傳所有的駕駛片段嗎?</i>不會,我們會選擇性地上傳部分片段。<br><br><i>什麼是好的 USB-C 充電器?</i>任何快速手機或筆電充電器都應該適用。<br><br><i>我使用的軟體版本有影響嗎?</i>有的,只有官方 openpilot(以及特定的分支)可以用於訓練。</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<source><b>%n segment(s)</b> of your driving is in the training dataset so far.</source>
|
||||
<translation>
|
||||
<numerusform><b>目前已有 %n 段</b> 您的駕駛數據被納入訓練資料集。</numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -952,7 +968,7 @@ This may take up to a minute.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Firehose</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation type="unfinished">訓練上傳</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -1421,6 +1437,89 @@ This may take up to a minute.</source>
|
||||
<source>N/A</source>
|
||||
<translation type="unfinished">無法使用</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Sponsor Status</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>SPONSOR</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Become a sponsor of sunnypilot to get early access to sunnylink features when they become available.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pair GitHub Account</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>PAIR</source>
|
||||
<translation type="unfinished">配對</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Not Sponsor</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Paired</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Not Paired</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>THANKS ♥</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SunnylinkSponsorPopup</name>
|
||||
<message>
|
||||
<source>Scan the QR code to login to your GitHub account</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Follow the prompts to complete the pairing process</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Re-enter the "sunnylink" panel to verify sponsorship status</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Scan the QR code to visit sunnyhaibin's GitHub Sponsors page</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Choose your sponsorship tier and confirm your support</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pair your GitHub account</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Early Access: Become a sunnypilot Sponsor</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SunnypilotPanel</name>
|
||||
@@ -1449,11 +1548,11 @@ This may take up to a minute.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Welcome to openpilot</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>歡迎使用 openpilot</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>You must accept the Terms and Conditions to use openpilot. Read the latest terms at <span style='color: #465BEA;'>https://comma.ai/terms</span> before continuing.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>您必須接受《條款與條件》才能使用 openpilot。在繼續之前,請先閱讀最新條款:<span style='color: #465BEA;'>https://comma.ai/terms</span>。</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
||||
+42
-48
@@ -1,63 +1,57 @@
|
||||
"""
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
Last updated: July 29, 2024
|
||||
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.
|
||||
"""
|
||||
|
||||
from openpilot.common.params import Params
|
||||
|
||||
from opendbc.car import structs
|
||||
from opendbc.safety import ALTERNATIVE_EXPERIENCE
|
||||
from opendbc.car.hyundai.values import HyundaiSafetyFlags
|
||||
from opendbc.sunnypilot.car.hyundai.values import HyundaiFlagsSP
|
||||
from opendbc.sunnypilot.car.hyundai.values import HyundaiFlagsSP, HyundaiSafetyFlagsSP
|
||||
|
||||
|
||||
class MadsParams:
|
||||
def __init__(self):
|
||||
self.params = Params()
|
||||
class MadsSteeringModeOnBrake:
|
||||
REMAIN_ACTIVE = 0
|
||||
PAUSE = 1
|
||||
DISENGAGE = 2
|
||||
|
||||
def read_param(self, key: str):
|
||||
return self.params.get_bool(key)
|
||||
|
||||
def set_alternative_experience(self, CP):
|
||||
enabled = self.read_param("Mads")
|
||||
pause_lateral_on_brake = self.read_param("MadsPauseLateralOnBrake")
|
||||
def read_steering_mode_param(params: Params):
|
||||
try:
|
||||
return int(params.get("MadsSteeringMode"))
|
||||
except (ValueError, TypeError):
|
||||
return f"{MadsSteeringModeOnBrake.REMAIN_ACTIVE}"
|
||||
|
||||
if enabled:
|
||||
CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.ENABLE_MADS
|
||||
|
||||
if pause_lateral_on_brake:
|
||||
CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.DISENGAGE_LATERAL_ON_BRAKE
|
||||
def set_alternative_experience(CP: structs.CarParams, params: Params):
|
||||
enabled = params.get_bool("Mads")
|
||||
steering_mode = read_steering_mode_param(params)
|
||||
|
||||
def set_car_specific_params(self, CP, CP_SP):
|
||||
if CP.brand == "hyundai":
|
||||
# TODO-SP: This should be separated from MADS module for future implementations
|
||||
# Use "HyundaiLongitudinalMainCruiseToggleable" param
|
||||
hyundai_cruise_main_toggleable = True
|
||||
if hyundai_cruise_main_toggleable:
|
||||
CP_SP.flags |= HyundaiFlagsSP.LONGITUDINAL_MAIN_CRUISE_TOGGLEABLE.value
|
||||
CP.safetyConfigs[-1].safetyParam |= HyundaiSafetyFlags.FLAG_HYUNDAI_LONG_MAIN_CRUISE_TOGGLEABLE.value
|
||||
if enabled:
|
||||
CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.ENABLE_MADS
|
||||
|
||||
# MADS is currently not supported in Tesla due to lack of consistent states to engage controls
|
||||
# TODO-SP: To enable MADS for Tesla, identify consistent signals for MADS toggling
|
||||
if CP.brand == "tesla":
|
||||
self.params.remove("Mads")
|
||||
if steering_mode == MadsSteeringModeOnBrake.DISENGAGE:
|
||||
CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.MADS_DISENGAGE_LATERAL_ON_BRAKE
|
||||
elif steering_mode == MadsSteeringModeOnBrake.PAUSE:
|
||||
CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.MADS_PAUSE_LATERAL_ON_BRAKE
|
||||
|
||||
|
||||
def set_car_specific_params(CP: structs.CarParams, CP_SP: structs.CarParamsSP, params: Params):
|
||||
if CP.brand == "hyundai":
|
||||
# TODO-SP: This should be separated from MADS module for future implementations
|
||||
# Use "HyundaiLongitudinalMainCruiseToggleable" param
|
||||
hyundai_cruise_main_toggleable = True
|
||||
if hyundai_cruise_main_toggleable:
|
||||
CP_SP.flags |= HyundaiFlagsSP.LONGITUDINAL_MAIN_CRUISE_TOGGLEABLE.value
|
||||
CP_SP.safetyParam |= HyundaiSafetyFlagsSP.LONG_MAIN_CRUISE_TOGGLEABLE
|
||||
|
||||
# MADS is currently not supported in Tesla due to lack of consistent states to engage controls
|
||||
# TODO-SP: To enable MADS for Tesla, identify consistent signals for MADS toggling
|
||||
if CP.brand == "tesla":
|
||||
params.remove("Mads")
|
||||
|
||||
# MADS is currently not supported in Rivian due to lack of consistent states to engage controls
|
||||
# TODO-SP: To enable MADS for Rivian, identify consistent signals for MADS toggling
|
||||
if CP.brand == "rivian":
|
||||
params.remove("Mads")
|
||||
|
||||
+18
-32
@@ -1,34 +1,14 @@
|
||||
"""
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
Last updated: July 29, 2024
|
||||
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.
|
||||
"""
|
||||
|
||||
from cereal import car, log, custom
|
||||
|
||||
from opendbc.car.hyundai.values import HyundaiFlags
|
||||
|
||||
from openpilot.sunnypilot.mads.helpers import MadsParams
|
||||
from openpilot.sunnypilot.mads.helpers import MadsSteeringModeOnBrake, read_steering_mode_param
|
||||
from openpilot.sunnypilot.mads.state import StateMachine, GEARS_ALLOW_PAUSED_SILENT
|
||||
|
||||
State = custom.ModularAssistiveDrivingSystem.ModularAssistiveDrivingSystemState
|
||||
@@ -43,7 +23,7 @@ IGNORED_SAFETY_MODES = (SafetyModel.silent, SafetyModel.noOutput)
|
||||
|
||||
class ModularAssistiveDrivingSystem:
|
||||
def __init__(self, selfdrive):
|
||||
self.mads_params = MadsParams()
|
||||
self.params = selfdrive.params
|
||||
|
||||
self.enabled = False
|
||||
self.active = False
|
||||
@@ -60,14 +40,14 @@ class ModularAssistiveDrivingSystem:
|
||||
self.allow_always = True
|
||||
|
||||
# read params on init
|
||||
self.enabled_toggle = self.mads_params.read_param("Mads")
|
||||
self.main_enabled_toggle = self.mads_params.read_param("MadsMainCruiseAllowed")
|
||||
self.pause_lateral_on_brake_toggle = self.mads_params.read_param("MadsPauseLateralOnBrake")
|
||||
self.unified_engagement_mode = self.mads_params.read_param("MadsUnifiedEngagementMode")
|
||||
self.enabled_toggle = self.params.get_bool("Mads")
|
||||
self.main_enabled_toggle = self.params.get_bool("MadsMainCruiseAllowed")
|
||||
self.steering_mode_on_brake = read_steering_mode_param(self.params)
|
||||
self.unified_engagement_mode = self.params.get_bool("MadsUnifiedEngagementMode")
|
||||
|
||||
def read_params(self):
|
||||
self.main_enabled_toggle = self.mads_params.read_param("MadsMainCruiseAllowed")
|
||||
self.unified_engagement_mode = self.mads_params.read_param("MadsUnifiedEngagementMode")
|
||||
self.main_enabled_toggle = self.params.get_bool("MadsMainCruiseAllowed")
|
||||
self.unified_engagement_mode = self.params.get_bool("MadsUnifiedEngagementMode")
|
||||
|
||||
def update_events(self, CS: car.CarState):
|
||||
def update_unified_engagement_mode():
|
||||
@@ -104,11 +84,11 @@ class ModularAssistiveDrivingSystem:
|
||||
replace_event(EventName.parkBrake, EventNameSP.silentParkBrake)
|
||||
transition_paused_state()
|
||||
|
||||
if self.pause_lateral_on_brake_toggle:
|
||||
if self.steering_mode_on_brake == MadsSteeringModeOnBrake.PAUSE:
|
||||
if CS.brakePressed:
|
||||
transition_paused_state()
|
||||
|
||||
if not (self.pause_lateral_on_brake_toggle and CS.brakePressed) and \
|
||||
if not (self.steering_mode_on_brake == MadsSteeringModeOnBrake.PAUSE and CS.brakePressed) and \
|
||||
not self.events_sp.contains_in_list(GEARS_ALLOW_PAUSED_SILENT):
|
||||
if self.state_machine.state == State.paused:
|
||||
self.events_sp.add(EventNameSP.silentLkasEnable)
|
||||
@@ -144,6 +124,12 @@ class ModularAssistiveDrivingSystem:
|
||||
if self.selfdrive.CS_prev.cruiseState.available:
|
||||
self.events_sp.add(EventNameSP.lkasDisable)
|
||||
|
||||
if self.steering_mode_on_brake == MadsSteeringModeOnBrake.DISENGAGE:
|
||||
# Disable on rising edge of accelerator or brake. Also disable on brake when speed > 0
|
||||
if (CS.brakePressed and (not self.selfdrive.CS_prev.brakePressed or not CS.standstill)) or \
|
||||
(CS.regenBraking and (not self.selfdrive.CS_prev.regenBraking or not CS.standstill)):
|
||||
self.events_sp.add(EventNameSP.lkasDisable)
|
||||
|
||||
self.events.remove(EventName.pcmDisable)
|
||||
self.events.remove(EventName.buttonCancel)
|
||||
self.events.remove(EventName.pedalPressed)
|
||||
|
||||
@@ -1,27 +1,8 @@
|
||||
"""
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
Last updated: July 29, 2024
|
||||
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.
|
||||
"""
|
||||
|
||||
from cereal import log, custom
|
||||
|
||||
@@ -1,27 +1,8 @@
|
||||
"""
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
Last updated: July 29, 2024
|
||||
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 pytest
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
# 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.
|
||||
"""
|
||||
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 os
|
||||
import pickle
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
# 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.
|
||||
"""
|
||||
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 json
|
||||
import time
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
# 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.
|
||||
"""
|
||||
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 hashlib
|
||||
import os
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
# 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.
|
||||
"""
|
||||
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 asyncio
|
||||
import os
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
# 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.
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
from cereal import messaging, custom
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ class SunnylinkApi(BaseApi):
|
||||
imei_try = 0
|
||||
while imei1 is None and imei2 is None and imei_try < MAX_RETRIES:
|
||||
try:
|
||||
imei1, imei2 = self.params.get("IMEI", encoding='utf8') or HARDWARE.get_imei(0), HARDWARE.get_imei(1)
|
||||
imei1, imei2 = HARDWARE.get_imei(0), HARDWARE.get_imei(1)
|
||||
except Exception:
|
||||
self._status_update(f"Error getting imei, trying again... [{imei_try + 1}/{MAX_RETRIES}]")
|
||||
time.sleep(1)
|
||||
|
||||
@@ -5,6 +5,7 @@ from __future__ import annotations
|
||||
import base64
|
||||
import gzip
|
||||
import os
|
||||
import ssl
|
||||
import threading
|
||||
import time
|
||||
|
||||
@@ -29,7 +30,8 @@ SUNNYLINK_RECONNECT_TIMEOUT_S = 70 # FYI changing this will also would require
|
||||
DISALLOW_LOG_UPLOAD = threading.Event()
|
||||
|
||||
params = Params()
|
||||
sunnylink_api = SunnylinkApi(params.get("SunnylinkDongleId", encoding='utf-8'))
|
||||
sunnylink_dongle_id = params.get("SunnylinkDongleId", encoding='utf-8')
|
||||
sunnylink_api = SunnylinkApi(sunnylink_dongle_id)
|
||||
|
||||
|
||||
def handle_long_poll(ws: WebSocket, exit_event: threading.Event | None) -> None:
|
||||
@@ -211,7 +213,7 @@ def main(exit_event: threading.Event = None):
|
||||
|
||||
UploadQueueCache.initialize(upload_queue)
|
||||
|
||||
ws_uri = SUNNYLINK_ATHENA_HOST
|
||||
ws_uri = f"{SUNNYLINK_ATHENA_HOST}"
|
||||
conn_start = None
|
||||
conn_retries = 0
|
||||
while (exit_event is None or not exit_event.is_set()) and sunnylink_ready(params):
|
||||
@@ -222,8 +224,9 @@ def main(exit_event: threading.Event = None):
|
||||
cloudlog.event("sunnylinkd.main.connecting_ws", ws_uri=ws_uri, retries=conn_retries)
|
||||
ws = create_connection(
|
||||
ws_uri,
|
||||
cookie=f"jwt={sunnylink_api.get_token()}",
|
||||
header={"Authorization": f"Bearer {sunnylink_api.get_token()}"},
|
||||
enable_multithread=True,
|
||||
sslopt={"cert_reqs": ssl.CERT_NONE if "localhost" in ws_uri else ssl.CERT_REQUIRED},
|
||||
timeout=SUNNYLINK_RECONNECT_TIMEOUT_S,
|
||||
)
|
||||
cloudlog.event("sunnylinkd.main.connected_ws", ws_uri=ws_uri, retries=conn_retries,
|
||||
|
||||
@@ -141,7 +141,8 @@ void CameraState::set_camera_exposure(float grey_frac) {
|
||||
// TODO: Lower latency to 2 frames, by using the histogram outputted by the sensor we can do AE before the debayering is complete
|
||||
|
||||
const auto &sensor = camera.sensor;
|
||||
const float cur_ev_ = cur_ev[camera.buf.cur_frame_data.frame_id % 3] * sensor->ev_scale;
|
||||
// Offset idx by one to not get stuck in self loop
|
||||
const float cur_ev_ = cur_ev[(camera.buf.cur_frame_data.frame_id - 1) % 3] * sensor->ev_scale;
|
||||
|
||||
// Scale target grey between min and 0.4 depending on lighting conditions
|
||||
float new_target_grey = std::clamp(0.4 - 0.3 * log2(1.0 + sensor->target_grey_factor*cur_ev_) / log2(6000.0), target_grey_minimums[camera.cc.camera_num], 0.4);
|
||||
|
||||
@@ -237,7 +237,7 @@ SpectraCamera::SpectraCamera(SpectraMaster *master, const CameraConfig &config)
|
||||
: m(master),
|
||||
enabled(config.enabled),
|
||||
cc(config) {
|
||||
ife_buf_depth = (cc.output_type == ISP_RAW_OUTPUT) ? 4 : VIPC_BUFFER_COUNT;
|
||||
ife_buf_depth = VIPC_BUFFER_COUNT;
|
||||
assert(ife_buf_depth < MAX_IFE_BUFS);
|
||||
}
|
||||
|
||||
@@ -266,7 +266,7 @@ int SpectraCamera::clear_req_queue() {
|
||||
req_mgr_flush_request.link_hdl = link_handle;
|
||||
req_mgr_flush_request.flush_type = CAM_REQ_MGR_FLUSH_TYPE_ALL;
|
||||
int ret = do_cam_control(m->video0_fd, CAM_REQ_MGR_FLUSH_REQ, &req_mgr_flush_request, sizeof(req_mgr_flush_request));
|
||||
LOGD("flushed all req: %d", ret);
|
||||
LOGD("flushed all req: %d", ret); // returns a "time until timeout" on clearing the workq
|
||||
|
||||
for (int i = 0; i < MAX_IFE_BUFS; ++i) {
|
||||
destroySyncObjectAt(i);
|
||||
@@ -1258,6 +1258,8 @@ void SpectraCamera::camera_close() {
|
||||
LOG("-- Stop devices %d", cc.camera_num);
|
||||
|
||||
if (enabled) {
|
||||
clear_req_queue();
|
||||
|
||||
// ret = device_control(sensor_fd, CAM_STOP_DEV, session_handle, sensor_dev_handle);
|
||||
// LOGD("stop sensor: %d", ret);
|
||||
int ret = device_control(m->isp_fd, CAM_STOP_DEV, session_handle, isp_dev_handle);
|
||||
@@ -1328,7 +1330,16 @@ bool SpectraCamera::handle_camera_event(const cam_req_mgr_message *event_data) {
|
||||
uint64_t timestamp = event_data->u.frame_msg.timestamp; // timestamped in the kernel's SOF IRQ callback
|
||||
//LOGD("handle cam %d ts %lu req id %lu frame id %lu", cc.camera_num, timestamp, request_id, frame_id_raw);
|
||||
|
||||
if (stress_test("skipping SOF event")) return false;
|
||||
// if there's a lag, some more frames could have already come in before
|
||||
// we cleared the queue, so we'll still get them with valid (> 0) request IDs.
|
||||
if (timestamp < last_requeue_ts) {
|
||||
LOGD("skipping frame: ts before requeue / cam %d ts %lu req id %lu frame id %lu", cc.camera_num, timestamp, request_id, frame_id_raw);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (stress_test("skipping SOF event")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!validateEvent(request_id, frame_id_raw)) {
|
||||
return false;
|
||||
@@ -1379,7 +1390,7 @@ bool SpectraCamera::validateEvent(uint64_t request_id, uint64_t frame_id_raw) {
|
||||
|
||||
if (request_id != request_id_last + 1) {
|
||||
LOGE("camera %d requests skipped %ld -> %ld", cc.camera_num, request_id_last, request_id);
|
||||
clearAndRequeue(request_id_last + 1);
|
||||
clearAndRequeue(request_id + 1);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1390,6 +1401,7 @@ void SpectraCamera::clearAndRequeue(uint64_t from_request_id) {
|
||||
// clear everything, then queue up a fresh set of frames
|
||||
LOGW("clearing and requeuing camera %d from %lu", cc.camera_num, from_request_id);
|
||||
clear_req_queue();
|
||||
last_requeue_ts = nanos_since_boot();
|
||||
for (uint64_t id = from_request_id; id < from_request_id + ife_buf_depth; ++id) {
|
||||
enqueue_frame(id);
|
||||
}
|
||||
@@ -1400,11 +1412,20 @@ bool SpectraCamera::waitForFrameReady(uint64_t request_id) {
|
||||
int buf_idx = request_id % ife_buf_depth;
|
||||
assert(sync_objs_ife[buf_idx]);
|
||||
|
||||
if (stress_test("sync sleep time")) {
|
||||
util::sleep_for(350);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto waitForSync = [&](uint32_t sync_obj, int timeout_ms, const char *sync_type) {
|
||||
double st = millis_since_boot();
|
||||
struct cam_sync_wait sync_wait = {};
|
||||
sync_wait.sync_obj = sync_obj;
|
||||
sync_wait.timeout_ms = stress_test(sync_type) ? 1 : timeout_ms;
|
||||
return do_sync_control(m->cam_sync_fd, CAM_SYNC_WAIT, &sync_wait, sizeof(sync_wait)) == 0;
|
||||
bool ret = do_sync_control(m->cam_sync_fd, CAM_SYNC_WAIT, &sync_wait, sizeof(sync_wait)) == 0;
|
||||
double et = millis_since_boot();
|
||||
if (!ret) LOGE("camera %d %s failed after %.2fms", cc.camera_num, sync_type, et-st);
|
||||
return ret;
|
||||
};
|
||||
|
||||
// wait for frame from IFE
|
||||
@@ -1415,6 +1436,7 @@ bool SpectraCamera::waitForFrameReady(uint64_t request_id) {
|
||||
// BPS is typically 7ms
|
||||
success = waitForSync(sync_objs_bps[buf_idx], 50, "BPS sync");
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
|
||||
@@ -181,6 +181,7 @@ public:
|
||||
int sync_objs_ife[MAX_IFE_BUFS] = {};
|
||||
int sync_objs_bps[MAX_IFE_BUFS] = {};
|
||||
uint64_t request_id_last = 0;
|
||||
uint64_t last_requeue_ts = 0;
|
||||
uint64_t frame_id_raw_last = 0;
|
||||
int invalid_request_count = 0;
|
||||
bool skip_expected = true;
|
||||
@@ -202,11 +203,16 @@ private:
|
||||
inline static bool first_frame_synced = false;
|
||||
|
||||
// a mode for stressing edge cases: realignment, sync failures, etc.
|
||||
inline bool stress_test(const char* log) {
|
||||
static double prob = std::stod(util::getenv("SPECTRA_ERROR_PROB", "-1"));;
|
||||
bool triggered = (prob > 0) && ((static_cast<double>(rand()) / RAND_MAX) < prob);
|
||||
inline bool stress_test(std::string log) {
|
||||
static double last_trigger = 0;
|
||||
static double prob = std::stod(util::getenv("SPECTRA_ERROR_PROB", "-1"));
|
||||
static double dt = std::stod(util::getenv("SPECTRA_ERROR_DT", "1"));
|
||||
bool triggered = (prob > 0) && \
|
||||
((static_cast<double>(rand()) / RAND_MAX) < prob) && \
|
||||
(millis_since_boot() - last_trigger) > dt;
|
||||
if (triggered) {
|
||||
LOGE("stress test (cam %d): %s", cc.camera_num, log);
|
||||
last_trigger = millis_since_boot();
|
||||
LOGE("stress test (cam %d): %s", cc.camera_num, log.c_str());
|
||||
}
|
||||
return triggered;
|
||||
}
|
||||
|
||||
@@ -6,9 +6,8 @@
|
||||
#include "common/util.h"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
int ret = util::set_realtime_priority(53);
|
||||
assert(ret == 0);
|
||||
ret = util::set_core_affinity({6});
|
||||
// doesn't need RT priority since we're using isolcpus
|
||||
int ret = util::set_core_affinity({6});
|
||||
assert(ret == 0 || Params().getBool("IsOffroad")); // failure ok while offroad due to offlining cores
|
||||
|
||||
camerad_thread();
|
||||
|
||||
@@ -56,28 +56,28 @@
|
||||
},
|
||||
{
|
||||
"name": "boot",
|
||||
"url": "https://commadist.azureedge.net/agnosupdate/boot-bca7573652def58a0afc40bbdd550d63dc08ed2e925ace69032aef84bb9dc4ba.img.xz",
|
||||
"hash": "bca7573652def58a0afc40bbdd550d63dc08ed2e925ace69032aef84bb9dc4ba",
|
||||
"hash_raw": "bca7573652def58a0afc40bbdd550d63dc08ed2e925ace69032aef84bb9dc4ba",
|
||||
"size": 18475008,
|
||||
"url": "https://commadist.azureedge.net/agnosupdate/boot-0d0d4d5a32e00b46fa36180b4a96337f2a53302d8bd0faee95f8fe1063d1e24a.img.xz",
|
||||
"hash": "0d0d4d5a32e00b46fa36180b4a96337f2a53302d8bd0faee95f8fe1063d1e24a",
|
||||
"hash_raw": "0d0d4d5a32e00b46fa36180b4a96337f2a53302d8bd0faee95f8fe1063d1e24a",
|
||||
"size": 18479104,
|
||||
"sparse": false,
|
||||
"full_check": true,
|
||||
"has_ab": true,
|
||||
"ondevice_hash": "8907415564e8a242548e871b534dcd53240fe4e4517700c6c85b5637e365f0b0"
|
||||
"ondevice_hash": "47b2096995578a5078e393c33108b42756009dbb361c43c508fc93cd8bda99cc"
|
||||
},
|
||||
{
|
||||
"name": "system",
|
||||
"url": "https://commadist.azureedge.net/agnosupdate/system-5612484e7f255659c0845de620e7c733afd2e1b939f9464f5ef039721bb7cba9.img.xz",
|
||||
"hash": "e4872f4132111b7b28586d978dd01bb48ffa031e103d029ebede7613c1bc2aa6",
|
||||
"hash_raw": "5612484e7f255659c0845de620e7c733afd2e1b939f9464f5ef039721bb7cba9",
|
||||
"url": "https://commadist.azureedge.net/agnosupdate/system-ff2da7bc34a1ad8e2831a13a49685243957a51b762d8f28e14e98ac90e84d211.img.xz",
|
||||
"hash": "fbda983c40e75d8c4784a45c687f27dd27f97f7f0e9a71b4981ec2092ff68a72",
|
||||
"hash_raw": "ff2da7bc34a1ad8e2831a13a49685243957a51b762d8f28e14e98ac90e84d211",
|
||||
"size": 4404019200,
|
||||
"sparse": true,
|
||||
"full_check": false,
|
||||
"has_ab": true,
|
||||
"ondevice_hash": "4e5e680b4ac387ddc974b32dd3d5ec1d76282511eab974866b3b72399034985e",
|
||||
"ondevice_hash": "428c744458d5f35199aab96a0928876c9deeff3e753e95044291408ba244f0af",
|
||||
"alt": {
|
||||
"hash": "5612484e7f255659c0845de620e7c733afd2e1b939f9464f5ef039721bb7cba9",
|
||||
"url": "https://commadist.azureedge.net/agnosupdate/system-5612484e7f255659c0845de620e7c733afd2e1b939f9464f5ef039721bb7cba9.img",
|
||||
"hash": "ff2da7bc34a1ad8e2831a13a49685243957a51b762d8f28e14e98ac90e84d211",
|
||||
"url": "https://commadist.azureedge.net/agnosupdate/system-ff2da7bc34a1ad8e2831a13a49685243957a51b762d8f28e14e98ac90e84d211.img",
|
||||
"size": 4404019200
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user