diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index 0a04b86f2b..482c7af297 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -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" diff --git a/.github/workflows/sunnypilot-build-prebuilt.yaml b/.github/workflows/sunnypilot-build-prebuilt.yaml index 8bb84cda83..cbe2b73be3 100644 --- a/.github/workflows/sunnypilot-build-prebuilt.yaml +++ b/.github/workflows/sunnypilot-build-prebuilt.yaml @@ -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: diff --git a/.github/workflows/ui_preview.yaml b/.github/workflows/ui_preview.yaml index 2ded4a2d2a..9963f2958c 100644 --- a/.github/workflows/ui_preview.yaml +++ b/.github/workflows/ui_preview.yaml @@ -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}
" + DIFF="${DIFF}${A[$i]} : \$\${\\color{cyan}\\text{NEW}}\$\$" + DIFF="${DIFF}" - 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}" + DIFF="${DIFF} " + DIFF="${DIFF} " + DIFF="${DIFF}" + + DIFF="${DIFF}
master proposed
" + DIFF="${DIFF}
" + 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 diff --git a/RELEASES.md b/RELEASES.md index 8a5638bc84..071622f89a 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -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 diff --git a/cereal/custom.capnp b/cereal/custom.capnp index 36cc2c9498..524ecdd657 100644 --- a/cereal/custom.capnp +++ b/cereal/custom.capnp @@ -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 { diff --git a/cereal/log.capnp b/cereal/log.capnp index 059d3ecbb7..0587ba96fc 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -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 { diff --git a/common/api/__init__.py b/common/api/__init__.py index 5542760802..3e238ccc7a 100644 --- a/common/api/__init__.py +++ b/common/api/__init__.py @@ -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) diff --git a/common/params_keys.h b/common/params_keys.h index 2518307daa..3bf6200ce3 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -132,7 +132,7 @@ inline static std::unordered_map 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 keys = { // sunnylink params {"EnableSunnylinkUploader", PERSISTENT | BACKUP}, {"LastSunnylinkPingTime", CLEAR_ON_MANAGER_START}, + {"SunnylinkCache_Roles", PERSISTENT}, + {"SunnylinkCache_Users", PERSISTENT}, {"SunnylinkDongleId", PERSISTENT}, {"SunnylinkdPid", PERSISTENT}, {"SunnylinkEnabled", PERSISTENT}, diff --git a/launch_chffrplus.sh b/launch_chffrplus.sh index 66f57c2cd1..d4689aae53 100755 --- a/launch_chffrplus.sh +++ b/launch_chffrplus.sh @@ -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 diff --git a/launch_env.sh b/launch_env.sh index 049b9da279..9bee47837a 100755 --- a/launch_env.sh +++ b/launch_env.sh @@ -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" diff --git a/opendbc_repo b/opendbc_repo index 8934a5fb8f..2262527044 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 8934a5fb8fac1b3b8203e5448b47be6e47cd36b1 +Subproject commit 2262527044c2438787aba778ddeb5d66ecfb9b58 diff --git a/panda b/panda index 5479e1572a..ccb9581b1c 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 5479e1572ad1d2733c54e0acdd1a8fa6a960fab9 +Subproject commit ccb9581b1ce08ff899b50d986c5e2b155ce8c339 diff --git a/pyproject.toml b/pyproject.toml index ecf2db5789..a83736a3ae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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", diff --git a/release/build_release.sh b/release/build_release.sh index a582dbab9f..5ada9c5ecc 100755 --- a/release/build_release.sh +++ b/release/build_release.sh @@ -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" diff --git a/release/ci/squash_and_merge_prs.py b/release/ci/squash_and_merge_prs.py index 28ffd5fc3a..3abe06705a 100755 --- a/release/ci/squash_and_merge_prs.py +++ b/release/ci/squash_and_merge_prs.py @@ -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()) diff --git a/release/release_files.py b/release/release_files.py index eacc7f8964..98b88293b9 100755 --- a/release/release_files.py +++ b/release/release_files.py @@ -8,6 +8,7 @@ ROOT = HERE + "/.." blacklist = [ ".git/", + ".github/workflows/", "matlab.*.md", diff --git a/selfdrive/car/card.py b/selfdrive/car/card.py index e7c450737b..33c229958d 100755 --- a/selfdrive/car/card.py +++ b/selfdrive/car/card.py @@ -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) diff --git a/selfdrive/car/tests/test_car_interfaces.py b/selfdrive/car/tests/test_car_interfaces.py index c10443c2f4..9e6b96ba59 100644 --- a/selfdrive/car/tests/test_car_interfaces.py +++ b/selfdrive/car/tests/test_car_interfaces.py @@ -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 diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index 3504bbb2b4..3193b68cd8 100644 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -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)}") diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 357f40c61f..f9ba54bfa7 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -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', diff --git a/selfdrive/controls/lib/tests/test_latcontrol.py b/selfdrive/controls/lib/tests/test_latcontrol.py index 81edb15a98..299652ff18 100644 --- a/selfdrive/controls/lib/tests/test_latcontrol.py +++ b/selfdrive/controls/lib/tests/test_latcontrol.py @@ -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) diff --git a/selfdrive/controls/plannerd.py b/selfdrive/controls/plannerd.py index 99942f20d9..60036c3082 100755 --- a/selfdrive/controls/plannerd.py +++ b/selfdrive/controls/plannerd.py @@ -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() diff --git a/selfdrive/controls/radard.py b/selfdrive/controls/radard.py index 80ce10380a..98fce1cb26 100755 --- a/selfdrive/controls/radard.py +++ b/selfdrive/controls/radard.py @@ -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): diff --git a/selfdrive/controls/tests/test_leads.py b/selfdrive/controls/tests/test_leads.py index 89582d1e64..77384fea20 100644 --- a/selfdrive/controls/tests/test_leads.py +++ b/selfdrive/controls/tests/test_leads.py @@ -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) diff --git a/selfdrive/debug/check_can_parser_performance.py b/selfdrive/debug/check_can_parser_performance.py index 61aff0244f..20987b3cf4 100755 --- a/selfdrive/debug/check_can_parser_performance.py +++ b/selfdrive/debug/check_can_parser_performance.py @@ -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') diff --git a/selfdrive/debug/run_process_on_route.py b/selfdrive/debug/run_process_on_route.py index 45665e8a48..2ccb0fb3e7 100755 --- a/selfdrive/debug/run_process_on_route.py +++ b/selfdrive/debug/run_process_on_route.py @@ -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) diff --git a/selfdrive/locationd/paramsd.py b/selfdrive/locationd/paramsd.py index 258fc97e5a..3819fba080 100755 --- a/selfdrive/locationd/paramsd.py +++ b/selfdrive/locationd/paramsd.py @@ -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, diff --git a/selfdrive/modeld/models/commonmodel.cc b/selfdrive/modeld/models/commonmodel.cc index 99155d9f1e..d3341e76ec 100644 --- a/selfdrive/modeld/models/commonmodel.cc +++ b/selfdrive/modeld/models/commonmodel.cc @@ -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)); } diff --git a/selfdrive/pandad/panda.cc b/selfdrive/pandad/panda.cc index 45d835e5a4..72b8ddcf81 100644 --- a/selfdrive/pandad/panda.cc +++ b/selfdrive/pandad/panda.cc @@ -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() { diff --git a/selfdrive/pandad/panda.h b/selfdrive/pandad/panda.h index 3e0f4b8507..8545b558a9 100644 --- a/selfdrive/pandad/panda.h +++ b/selfdrive/pandad/panda.h @@ -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); diff --git a/selfdrive/pandad/panda_safety.cc b/selfdrive/pandad/panda_safety.cc index 6d5e5ce4f3..48589c4146 100644 --- a/selfdrive/pandad/panda_safety.cc +++ b/selfdrive/pandad/panda_safety.cc @@ -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 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 ¶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(); + 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(); + 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); } } diff --git a/selfdrive/pandad/pandad.cc b/selfdrive/pandad/pandad.cc index 61da49470a..c0ff2db399 100644 --- a/selfdrive/pandad/pandad.cc +++ b/selfdrive/pandad/pandad.cc @@ -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 &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 &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(); } diff --git a/selfdrive/pandad/pandad.h b/selfdrive/pandad/pandad.h index b338b70378..7b37fbe59a 100644 --- a/selfdrive/pandad/pandad.h +++ b/selfdrive/pandad/pandad.h @@ -16,8 +16,8 @@ public: private: void updateMultiplexingMode(); - std::string fetchCarParams(); - void setSafetyMode(const std::string ¶ms_string); + std::vector fetchCarParams(); + void setSafetyMode(const std::vector ¶ms_string); bool initialized_ = false; bool log_once_ = false; diff --git a/selfdrive/selfdrived/events.py b/selfdrive/selfdrived/events.py index f93cbaa740..d336334a4e 100755 --- a/selfdrive/selfdrived/events.py +++ b/selfdrive/selfdrived/events.py @@ -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. diff --git a/selfdrive/selfdrived/selfdrived.py b/selfdrive/selfdrived/selfdrived.py index b38b411db9..9a28a36169 100755 --- a/selfdrive/selfdrived/selfdrived.py +++ b/selfdrive/selfdrived/selfdrived.py @@ -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 diff --git a/selfdrive/test/process_replay/migration.py b/selfdrive/test/process_replay/migration.py index 2d42d31619..fd13c4f239 100644 --- a/selfdrive/test/process_replay/migration.py +++ b/selfdrive/test/process_replay/migration.py @@ -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, [], [] diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index d1a0cc0827..e9b456e983 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -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: diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 54df53fd91..353b8fa7ce 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -465979047295dad5da3a552db9227ed776a26a79 \ No newline at end of file +e9d57157494480637a8ffb52257d2b660a48be67 \ No newline at end of file diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index 9756010614..1ea59c4856 100644 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -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: diff --git a/selfdrive/ui/qt/api.cc b/selfdrive/ui/qt/api.cc index 6889b40e51..85a3144bd2 100644 --- a/selfdrive/ui/qt/api.cc +++ b/selfdrive/ui/qt/api.cc @@ -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); } diff --git a/selfdrive/ui/qt/api.h b/selfdrive/ui/qt/api.h index ad64d7e722..80b85f7ca7 100644 --- a/selfdrive/ui/qt/api.h +++ b/selfdrive/ui/qt/api.h @@ -5,6 +5,7 @@ #include #include +#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(); }; diff --git a/selfdrive/ui/qt/offroad/developer_panel.cc b/selfdrive/ui/qt/offroad/developer_panel.cc index e679720a1b..be1c665762 100644 --- a/selfdrive/ui/qt/offroad/developer_panel.cc +++ b/selfdrive/ui/qt/offroad/developer_panel.cc @@ -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; } diff --git a/selfdrive/ui/qt/offroad/developer_panel.h b/selfdrive/ui/qt/offroad/developer_panel.h index beecc24dee..591e5f0fdf 100644 --- a/selfdrive/ui/qt/offroad/developer_panel.h +++ b/selfdrive/ui/qt/offroad/developer_panel.h @@ -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; diff --git a/selfdrive/ui/qt/offroad/firehose.cc b/selfdrive/ui/qt/offroad/firehose.cc index 726b71c8d7..e6a9c5c6e9 100644 --- a/selfdrive/ui/qt/offroad/firehose.cc +++ b/selfdrive/ui/qt/offroad/firehose.cc @@ -12,6 +12,10 @@ #include #include +#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) "

" "Frequently Asked Questions

" "Does it matter how or where I drive? Nope, just drive as you normally would.

" + "Do all of my segments get pulled in Firehose Mode? No, we selectively pull a subset of your segments.

" "What's a good USB-C adapter? Any fast phone or laptop charger should be fine.

" "Does it matter which software I run? 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("%1 %2 of your driving are in the training dataset so far.").arg(count).arg(count == 1 ? "segment" : "segments")); + contribution_label->setText(tr("%n segment(s) of your driving is in the training dataset so far.", "", count)); contribution_label->show(); } }); diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index c6d3520712..a5bfe61ffd 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -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; } diff --git a/selfdrive/ui/qt/setup/setup.cc b/selfdrive/ui/qt/setup/setup.cc index aff9b015b3..dfa9e12b4e 100644 --- a/selfdrive/ui/qt/setup/setup.cc +++ b/selfdrive/ui/qt/setup/setup.cc @@ -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"); diff --git a/selfdrive/ui/sunnypilot/SConscript b/selfdrive/ui/sunnypilot/SConscript index 2975450124..4ebfc10940 100644 --- a/selfdrive/ui/sunnypilot/SConscript +++ b/selfdrive/ui/sunnypilot/SConscript @@ -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 diff --git a/selfdrive/ui/sunnypilot/qt/api.cc b/selfdrive/ui/sunnypilot/qt/api.cc new file mode 100644 index 0000000000..d75d8d39f0 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/api.cc @@ -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 +#include + +#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); +} diff --git a/selfdrive/ui/sunnypilot/qt/api.h b/selfdrive/ui/sunnypilot/qt/api.h new file mode 100644 index 0000000000..592b2e668f --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/api.h @@ -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); } +}; diff --git a/selfdrive/ui/sunnypilot/qt/network/networking.h b/selfdrive/ui/sunnypilot/qt/network/networking.h index 3370751bf5..f220b8a50a 100644 --- a/selfdrive/ui/sunnypilot/qt/network/networking.h +++ b/selfdrive/ui/sunnypilot/qt/network/networking.h @@ -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. diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/role_model.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/role_model.h new file mode 100644 index 0000000000..04b4dccf14 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/role_model.h @@ -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 + +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 ::value>::type> T as() const { + return T(m_raw_json_object); + } +}; diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sponsor_role_model.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sponsor_role_model.h new file mode 100644 index 0000000000..7e1772b591 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/sponsor_role_model.h @@ -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 + +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); } +}; diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/user_model.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/user_model.h new file mode 100644 index 0000000000..4d255ac8e3 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/models/user_model.h @@ -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 + +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; + } +}; diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.cc b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.cc new file mode 100644 index 0000000000..3919a1821f --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.cc @@ -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; + } +} diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.h new file mode 100644 index 0000000000..8ef99d1013 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/base_device_service.h @@ -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(); +}; diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.cc b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.cc new file mode 100644 index 0000000000..bffe32839f --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.cc @@ -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 +#include + +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 roles; + for (const auto &value : jsonArray) { + roles.emplace_back(value.toObject()); + } + + emit rolesReady(roles); + uiStateSP()->setSunnylinkRoles(roles); +} diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.h new file mode 100644 index 0000000000..abd0e1ab83 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/role_service.h @@ -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 + +#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 &roles); + +protected: + void handleResponse(const QString&response, bool success) override; + +private: + QString url = "/roles"; +}; diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.cc b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.cc new file mode 100644 index 0000000000..cd16360c26 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.cc @@ -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 +#include + +#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 users; + for (const auto &value : jsonArray) { + users.emplace_back(value.toObject()); + } + + emit usersReady(users); + uiStateSP()->setSunnylinkDeviceUsers(users); +} diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h new file mode 100644 index 0000000000..90eb354751 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/services/user_service.h @@ -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 + +#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&users); + +protected: + void handleResponse(const QString&response, bool success) override; + +private: + QString url = "/users"; +}; diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.cc b/selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.cc new file mode 100644 index 0000000000..4428eb55c4 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.cc @@ -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); +} diff --git a/selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.h b/selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.h new file mode 100644 index 0000000000..0961449926 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/network/sunnylink/sunnylink_client.h @@ -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 + +#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; +}; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/sponsor_widget.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/sponsor_widget.cc new file mode 100644 index 0000000000..7b5ce48238 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/sponsor_widget.cc @@ -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 = "
    "; + for (const auto & instruction : instructions) { + instructionsHtml += QString("
  1. %1
  2. ").arg(instruction); + } + instructionsHtml += "
"; + 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); +} diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/sponsor_widget.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/sponsor_widget.h new file mode 100644 index 0000000000..9f15a6084f --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/sponsor_widget.h @@ -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 +#include + +#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; +}; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc index 12ae2209f2..706e89ddf0 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.cc @@ -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 = ""+ tr("🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀")+ ""; - sunnylinkEnabledBtn->showDescription(); - sunnylinkEnabledBtn->setDescription(proud_description); + description = ""+ tr("🎉Welcome back! We're excited to see you've enabled sunnylink again! 🚀")+ ""; } else { - auto shame_description = ""+ 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 🎉.")+ ""; - sunnylinkEnabledBtn->showDescription(); - sunnylinkEnabledBtn->setDescription(shame_description); - } + description = ""+ 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 🎉.")+ ""; - 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(); } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.h index 824a081af3..d231757e36 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink_panel.h @@ -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; }; diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/mads_settings.cc b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/mads_settings.cc index e6e18f262e..d178c59b07 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/mads_settings.cc +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/mads_settings.cc @@ -39,17 +39,19 @@ MadsSettings::MadsSettings(QWidget *parent) : QWidget(parent) { ""); list->addItem(madsUnifiedEngagementModeToggle); - // Pause Lateral On Brake - std::vector 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 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; } diff --git a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/mads_settings.h b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/mads_settings.h index fccb4cbb84..312c4f0db0 100644 --- a/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/mads_settings.h +++ b/selfdrive/ui/sunnypilot/qt/offroad/settings/sunnypilot/mads_settings.h @@ -32,5 +32,5 @@ private: ParamControl *madsMainCruiseToggle; ParamControl *madsUnifiedEngagementModeToggle; - ButtonParamControl *madsPauseLateralOnBrake; + ButtonParamControl *madsSteeringMode; }; diff --git a/selfdrive/ui/sunnypilot/qt/request_repeater.cc b/selfdrive/ui/sunnypilot/qt/request_repeater.cc new file mode 100644 index 0000000000..e137d56172 --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/request_repeater.cc @@ -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); + } +} diff --git a/selfdrive/ui/sunnypilot/qt/request_repeater.h b/selfdrive/ui/sunnypilot/qt/request_repeater.h new file mode 100644 index 0000000000..0cdb370f9b --- /dev/null +++ b/selfdrive/ui/sunnypilot/qt/request_repeater.h @@ -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(); + } +}; diff --git a/selfdrive/ui/sunnypilot/ui.cc b/selfdrive/ui/sunnypilot/ui.cc index 37da83949b..e27771b9bb 100644 --- a/selfdrive/ui/sunnypilot/ui.cc +++ b/selfdrive/ui/sunnypilot/ui.cc @@ -48,6 +48,16 @@ UIStateSP *uiStateSP() { return &ui_state; } +void UIStateSP::setSunnylinkRoles(const std::vector& roles) { + sunnylinkRoles = roles; + emit sunnylinkRolesChanged(roles); +} + +void UIStateSP::setSunnylinkDeviceUsers(const std::vector& users) { + sunnylinkUsers = users; + emit sunnylinkDeviceUsersChanged(users); +} + DeviceSP *deviceSP() { static DeviceSP _device; return &_device; diff --git a/selfdrive/ui/sunnypilot/ui.h b/selfdrive/ui/sunnypilot/ui.h index 65c9237969..08e1d37b0f 100644 --- a/selfdrive/ui/sunnypilot/ui.h +++ b/selfdrive/ui/sunnypilot/ui.h @@ -7,6 +7,11 @@ #pragma once +#include + +#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 &roles); + void setSunnylinkDeviceUsers(const std::vector &users); + + inline std::vector 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().roleTier != SponsorTier::Free; + }); + } + inline SponsorRoleModel sunnylinkSponsorRole() const { + std::optional sponsorRoleWithHighestTier = std::nullopt; + for (const auto &role : sunnylinkRoles) { + if(role.roleType != RoleType::Sponsor) + continue; + + if (auto sponsorRole = role.as(); !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 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 roles); + void sunnylinkDeviceUsersChanged(std::vector users); void uiUpdate(const UIStateSP &s); private slots: void update() override; + +private: + std::vector sunnylinkRoles = {}; + std::vector sunnylinkUsers = {}; }; UIStateSP *uiStateSP(); diff --git a/selfdrive/ui/tests/test_ui/run.py b/selfdrive/ui/tests/test_ui/run.py index f371c7bbae..797a5f70b7 100755 --- a/selfdrive/ui/tests/test_ui/run.py +++ b/selfdrive/ui/tests/test_ui/run.py @@ -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, diff --git a/selfdrive/ui/translations/main_ar.ts b/selfdrive/ui/translations/main_ar.ts index a23dd1290c..643d563a78 100644 --- a/selfdrive/ui/translations/main_ar.ts +++ b/selfdrive/ui/translations/main_ar.ts @@ -159,6 +159,18 @@ Enables or disables the github runner service. + + Error Log + + + + VIEW + عرض + + + View the error log for sunnypilot crashes. + + DevicePanel @@ -419,33 +431,42 @@ FirehosePanel 🔥 Firehose Mode 🔥 - + 🔥 وضع خرطوم الحريق 🔥 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. - + يتعلم تطبيق openpilot كيفية القيادة من خلال مشاهدة البشر، مثلك، أثناء القيادة. + +يتيح لك وضع خرطوم الحريق زيادة تحميلات بيانات التدريب لتحسين نماذج القيادة في OpenPilot. كلما زادت البيانات، زادت النماذج، مما يعني وضعًا تجريبيًا أفضل. Firehose Mode: ACTIVE - - - - 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. - - - - <b>%1 %2</b> of your driving are in the training dataset so far. - + وضع خرطوم الحريق: نشط ACTIVE - + نشط <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network - + <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>غير نشط</span>: اتصل بشبكة غير محسوبة + + + 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. + للحصول على أقصى فعالية، أحضر جهازك إلى الداخل واتصل بمحول 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 (وأفرع معينة) يمكن استخدامها للتدريب. + + + <b>%n segment(s)</b> of your driving is in the training dataset so far. + + حتى الآن، يوجد </b>%n مقطع<b>%n من قيادتك في مجموعة بيانات التدريب. + حتى الآن، يوجد </b>%n مقطع<b>%n من قيادتك في مجموعة بيانات التدريب. + حتى الآن، يوجد </b>%n مقطع<b>%n من قيادتك في مجموعة بيانات التدريب. + حتى الآن، يوجد </b>%n مقطع<b>%n من قيادتك في مجموعة بيانات التدريب. + حتى الآن، يوجد </b>%n مقطع<b>%n من قيادتك في مجموعة بيانات التدريب. + حتى الآن، يوجد </b>%n مقطع<b>%n من قيادتك في مجموعة بيانات التدريب. + @@ -925,7 +946,7 @@ This may take up to a minute. Firehose - + خرطوم الحريق @@ -972,7 +993,7 @@ This may take up to a minute. Firehose - + خرطوم الحريق @@ -1441,6 +1462,89 @@ This may take up to a minute. N/A غير متاح + + Sponsor Status + + + + SPONSOR + + + + Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. + + + + Pair GitHub Account + + + + PAIR + إقران + + + Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. + + + + sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. + + + + Not Sponsor + + + + Paired + + + + Not Paired + + + + THANKS ♥ + + + + + SunnylinkSponsorPopup + + Scan the QR code to login to your GitHub account + + + + Follow the prompts to complete the pairing process + + + + Re-enter the "sunnylink" panel to verify sponsorship status + + + + If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot + + + + Scan the QR code to visit sunnyhaibin's GitHub Sponsors page + + + + Choose your sponsorship tier and confirm your support + + + + Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status + + + + Pair your GitHub account + + + + Early Access: Become a sunnypilot Sponsor + + SunnypilotPanel @@ -1469,11 +1573,11 @@ This may take up to a minute. Welcome to openpilot - + مرحباً بكم في openpilot 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. - + يجب عليك قبول الشروط والأحكام لاستخدام openpilot. اقرأ أحدث الشروط على <span style='color: #465BEA;'>https://comma.ai/terms</span> قبل الاستمرار. @@ -1634,15 +1738,15 @@ This may take up to a minute. WiFiPromptWidget Open - + انفتح Maximize your training data uploads to improve openpilot's driving models. - + قم بزيادة تحميلات بيانات التدريب الخاصة بك لتحسين نماذج القيادة الخاصة بـ openpilot. <span style='font-family: "Noto Color Emoji";'>🔥</span> Firehose Mode <span style='font-family: Noto Color Emoji;'>🔥</span> - + <span style='font-family: "Noto Color Emoji";'>🔥</span> وضع خرطوم الحريق <span style='font-family: Noto Color Emoji;'>🔥</span> diff --git a/selfdrive/ui/translations/main_de.ts b/selfdrive/ui/translations/main_de.ts index 9a578556bb..622a822ba9 100644 --- a/selfdrive/ui/translations/main_de.ts +++ b/selfdrive/ui/translations/main_de.ts @@ -159,6 +159,18 @@ Enables or disables the github runner service. + + Error Log + + + + VIEW + ANSEHEN + + + View the error log for sunnypilot crashes. + + DevicePanel @@ -431,14 +443,6 @@ Firehose Mode allows you to maximize your training data uploads to improve openp Firehose Mode: ACTIVE - - 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. - - - - <b>%1 %2</b> of your driving are in the training dataset so far. - - ACTIVE @@ -447,6 +451,17 @@ Firehose Mode allows you to maximize your training data uploads to improve openp <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network + + 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. + + + + <b>%n segment(s)</b> of your driving is in the training dataset so far. + + + + + HudRenderer @@ -1425,6 +1440,89 @@ This may take up to a minute. N/A Nicht verfügbar + + Sponsor Status + + + + SPONSOR + + + + Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. + + + + Pair GitHub Account + + + + PAIR + + + + Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. + + + + sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. + + + + Not Sponsor + + + + Paired + + + + Not Paired + + + + THANKS ♥ + + + + + SunnylinkSponsorPopup + + Scan the QR code to login to your GitHub account + + + + Follow the prompts to complete the pairing process + + + + Re-enter the "sunnylink" panel to verify sponsorship status + + + + If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot + + + + Scan the QR code to visit sunnyhaibin's GitHub Sponsors page + + + + Choose your sponsorship tier and confirm your support + + + + Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status + + + + Pair your GitHub account + + + + Early Access: Become a sunnypilot Sponsor + + SunnypilotPanel diff --git a/selfdrive/ui/translations/main_en.ts b/selfdrive/ui/translations/main_en.ts index 3f9692e5fa..fbccbedb20 100644 --- a/selfdrive/ui/translations/main_en.ts +++ b/selfdrive/ui/translations/main_en.ts @@ -1,6 +1,16 @@ + + FirehosePanel + + <b>%n segment(s)</b> of your driving is in the training dataset so far. + + <b>%n segment</b> of your driving is in the training dataset so far. + <b>%n segments</b> of your driving are in the training dataset so far. + + + InputDialog diff --git a/selfdrive/ui/translations/main_es.ts b/selfdrive/ui/translations/main_es.ts index 429f5fbafc..25ed875eff 100644 --- a/selfdrive/ui/translations/main_es.ts +++ b/selfdrive/ui/translations/main_es.ts @@ -159,6 +159,18 @@ Enables or disables the github runner service. + + Error Log + + + + VIEW + VER + + + View the error log for sunnypilot crashes. + + DevicePanel @@ -431,14 +443,6 @@ Firehose Mode allows you to maximize your training data uploads to improve openp Firehose Mode: ACTIVE - - 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. - - - - <b>%1 %2</b> of your driving are in the training dataset so far. - - ACTIVE @@ -447,6 +451,17 @@ Firehose Mode allows you to maximize your training data uploads to improve openp <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network + + 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. + + + + <b>%n segment(s)</b> of your driving is in the training dataset so far. + + + + + HudRenderer @@ -1425,6 +1440,93 @@ Esto puede tardar un minuto. N/A N/A + + Sponsor Status + Estado de patrocinio + + + SPONSOR + PATROCINADOR + + + Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. + Conviértete en patrocinador de sunnypilot para obtener acceso anticipado a las funciones de sunnylink cuando estén disponibles. + + + Pair GitHub Account + Emparejar cuenta de GitHub + + + PAIR + EMPAREJAR + + + Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. + Empareja tu cuenta de GitHub para otorgar a tu dispositivo beneficios de patrocinador, incluido acceso a la API en sunnylink. + + + sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. + 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. + + + THANKS + GRACIAS + + + Not Sponsor + No patrocinador + + + Paired + Emparejado + + + Not Paired + No emparejado + + + THANKS ♥ + GRACIAS ♥ + + + + SunnylinkSponsorPopup + + Scan the QR code to login to your GitHub account + Escanea el código QR para iniciar sesión en tu cuenta de GitHub + + + Follow the prompts to complete the pairing process + Sigue las indicaciones para completar el proceso de emparejamiento + + + Re-enter the "sunnylink" panel to verify sponsorship status + Vuelve a ingresar al panel de "sunnylink" para verificar el estado de patrocinio + + + If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot + Si el estado de patrocinio no se actualizó, por favor contacta a un moderador en Discord en https://discord.gg/sunnypilot + + + Scan the QR code to visit sunnyhaibin's GitHub Sponsors page + Escanea el código QR para visitar la página de GitHub Sponsors de sunnyhaibin + + + Choose your sponsorship tier and confirm your support + Elige tu nivel de patrocinio y confirma tu apoyo + + + Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status + Únete a nuestra comunidad en Discord en https://discord.gg/sunnypilot y contacta a un moderador para confirmar tu estado de patrocinio + + + Pair your GitHub account + Empareja tu cuenta de GitHub + + + Early Access: Become a sunnypilot Sponsor + Acceso temprano: conviértete en patrocinador de sunnypilot + SunnypilotPanel diff --git a/selfdrive/ui/translations/main_fr.ts b/selfdrive/ui/translations/main_fr.ts index a6f9006e01..2da3eabafe 100644 --- a/selfdrive/ui/translations/main_fr.ts +++ b/selfdrive/ui/translations/main_fr.ts @@ -159,6 +159,18 @@ Enables or disables the github runner service. + + Error Log + + + + VIEW + VOIR + + + View the error log for sunnypilot crashes. + + DevicePanel @@ -431,14 +443,6 @@ Firehose Mode allows you to maximize your training data uploads to improve openp Firehose Mode: ACTIVE - - 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. - - - - <b>%1 %2</b> of your driving are in the training dataset so far. - - ACTIVE @@ -447,6 +451,17 @@ Firehose Mode allows you to maximize your training data uploads to improve openp <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network + + 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. + + + + <b>%n segment(s)</b> of your driving is in the training dataset so far. + + + + + HudRenderer @@ -1425,6 +1440,89 @@ Cela peut prendre jusqu'à une minute. N/A N/A + + Sponsor Status + + + + SPONSOR + + + + Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. + + + + Pair GitHub Account + + + + PAIR + ASSOCIER + + + Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. + + + + sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. + + + + Not Sponsor + + + + Paired + + + + Not Paired + + + + THANKS ♥ + + + + + SunnylinkSponsorPopup + + Scan the QR code to login to your GitHub account + + + + Follow the prompts to complete the pairing process + + + + Re-enter the "sunnylink" panel to verify sponsorship status + + + + If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot + + + + Scan the QR code to visit sunnyhaibin's GitHub Sponsors page + + + + Choose your sponsorship tier and confirm your support + + + + Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status + + + + Pair your GitHub account + + + + Early Access: Become a sunnypilot Sponsor + + SunnypilotPanel diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index b9e6c75386..9d20a1e7d2 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -159,6 +159,18 @@ Enables or disables the github runner service. + + Error Log + + + + VIEW + 確認 + + + View the error log for sunnypilot crashes. + + DevicePanel @@ -431,14 +443,6 @@ Firehose Mode allows you to maximize your training data uploads to improve openp Firehose Mode: ACTIVE - - 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. - - - - <b>%1 %2</b> of your driving are in the training dataset so far. - - ACTIVE @@ -447,6 +451,16 @@ Firehose Mode allows you to maximize your training data uploads to improve openp <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network + + 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. + + + + <b>%n segment(s)</b> of your driving is in the training dataset so far. + + + + HudRenderer @@ -952,7 +966,7 @@ This may take up to a minute. Firehose - + データ学習 @@ -1421,6 +1435,89 @@ This may take up to a minute. N/A 該当なし + + Sponsor Status + + + + SPONSOR + + + + Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. + + + + Pair GitHub Account + + + + PAIR + OK + + + Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. + + + + sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. + + + + Not Sponsor + + + + Paired + + + + Not Paired + + + + THANKS ♥ + + + + + SunnylinkSponsorPopup + + Scan the QR code to login to your GitHub account + + + + Follow the prompts to complete the pairing process + + + + Re-enter the "sunnylink" panel to verify sponsorship status + + + + If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot + + + + Scan the QR code to visit sunnyhaibin's GitHub Sponsors page + + + + Choose your sponsorship tier and confirm your support + + + + Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status + + + + Pair your GitHub account + + + + Early Access: Become a sunnypilot Sponsor + + SunnypilotPanel diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index 9969bf8efe..8a7dc29fd3 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -129,11 +129,11 @@ WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB). - 경고: openpilot 가감속 제어 알파 기능으로 차량의 자동긴급제동(AEB)을 비활성화합니다. + 경고: openpilot 가감속 제어는 알파 기능으로 차량의 자동긴급제동(AEB)기능이 작동하지 않습니다. On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. - 이 차량은 openpilot 가감속 제어 대신 기본적으로 차량의 ACC로 가감속을 제어합니다. openpilot의 가감속 제어로 전환하려면 이 기능을 활성화하세요. openpilot 가감속 제어 알파를 활성화하는 경우 실험 모드 활성화를 권장합니다. + 이 차량에서 openpilot은 openpilot 가감속 제어 대신 기본적으로 차량의 ACC로 가감속을 제어합니다. openpilot 가감속 제어로 전환하려면 이 기능을 활성화하세요. openpilot 가감속 제어 알파 기능을 활성화하는 경우 실험 모드 활성화를 권장합니다. Enable ADB @@ -159,6 +159,18 @@ Enables or disables the github runner service. + + Error Log + + + + VIEW + 보기 + + + View the error log for sunnypilot crashes. + + DevicePanel @@ -425,27 +437,31 @@ 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. - + 오픈파일럿은 여러분과 같은 사람이 운전하는 모습을 보면서 운전하는 법을 배웁니다. + +파이어호스 모드를 사용하면 훈련 데이터 업로드를 최대화하여 오픈파일럿의 주행 모델을 개선할 수 있습니다. 더 많은 데이터는 더 큰 모델을 의미하며, 이는 더 나은 실험 모드를 의미합니다. Firehose Mode: ACTIVE - - - - 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. - - - - <b>%1 %2</b> of your driving are in the training dataset so far. - + 파이어호스 모드: 활성화 ACTIVE - + 활성화 <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network - + <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>비활성</span>: 무제한 네트워크에 연결 + + + 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. + 최대한의 효과를 얻으려면 매주 장치를 실내로 가져와 좋은 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의 특정 포크만 트레이닝에 사용할 수 있습니다. + + + <b>%n segment(s)</b> of your driving is in the training dataset so far. + + + @@ -604,7 +620,7 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed. 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. - openpilot이 차량을 식별할 수 없었습니다. 지원되지 않는 차량이거나 ECU가 인식되지 않습니다. 해당 차량에 맞는 펌웨어 버전을 추가하려면 PR을 제출하세요. 도움이 필요하시면 discord.comma.ai에 가입하세요. + openpilot이 차량을 식별할 수 없습니다. 지원되지 않는 차량이거나 ECU가 인식되지 않습니다. 해당 차량에 맞는 펌웨어 버전을 추가하려면 PR을 제출하세요. 도움이 필요하시면 discord.comma.ai에 가입하세요. 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. @@ -612,7 +628,7 @@ Pause Steering: ALC will be paused after the brake pedal is manually pressed. Device temperature too high. System cooling down before starting. Current internal component temperature: %1 - 장치 온도가 너무 높습니다. 시작하기 전에 온도를 낮춰주세요. 현재 내부 부품 온도: %1 + 장치의 온도가 너무 높습니다. 시작 전에 온도를 낮춰주세요. 현재 내부 구성 요소 온도: %1 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. @@ -952,7 +968,7 @@ This may take up to a minute. Firehose - + 파이어호스 @@ -1421,6 +1437,89 @@ This may take up to a minute. N/A N/A + + Sponsor Status + + + + SPONSOR + + + + Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. + + + + Pair GitHub Account + + + + PAIR + 동기화 + + + Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. + + + + sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. + + + + Not Sponsor + + + + Paired + + + + Not Paired + + + + THANKS ♥ + + + + + SunnylinkSponsorPopup + + Scan the QR code to login to your GitHub account + + + + Follow the prompts to complete the pairing process + + + + Re-enter the "sunnylink" panel to verify sponsorship status + + + + If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot + + + + Scan the QR code to visit sunnyhaibin's GitHub Sponsors page + + + + Choose your sponsorship tier and confirm your support + + + + Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status + + + + Pair your GitHub account + + + + Early Access: Become a sunnypilot Sponsor + + SunnypilotPanel @@ -1449,11 +1548,11 @@ This may take up to a minute. Welcome to openpilot - + 오픈 파일럿에 오신 것을 환영합니다. 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. - + 오픈파일럿을 사용하려면 이용약관에 동의해야 합니다. 최신 약관은 <span style='color: #465BEA;'>https://comma.ai/terms</span> 에서 최신 약관을 읽은 후 계속하세요. @@ -1556,7 +1655,7 @@ This may take up to a minute. 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. - 운전 시각화는 일부 회전을 더 잘 보여주기 위해 저속에서 도로를 향한 광각 카메라로 전환됩니다. 실험 모드 로고도 우측 상단에 표시됩니다. + 운전 시각화는 일부 회전을 더 잘 보여주기 위해 저속에서 도로를 향한 광각 카메라로 전환됩니다. 우측 상단에 실험 모드 로고가 표시됩니다. Always-On Driver Monitoring @@ -1564,7 +1663,7 @@ This may take up to a minute. Enable driver monitoring even when openpilot is not engaged. - Openpilot이 활성화되지 않은 경우에도 드라이버 모니터링을 활성화합니다. + openpilot이 활성화되지 않은 경우에도 드라이버 모니터링을 활성화합니다. Enable Dynamic Experimental Control diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index 3168f76d20..5fc837513d 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -159,6 +159,18 @@ Enables or disables the github runner service. + + Error Log + + + + VIEW + VER + + + View the error log for sunnypilot crashes. + + DevicePanel @@ -419,33 +431,38 @@ FirehosePanel 🔥 Firehose Mode 🔥 - 🔥 Mode Firehose 🔥 + 🔥 Modo Firehose 🔥 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. - + 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. Firehose Mode: ACTIVE - - - - 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. - - - - <b>%1 %2</b> of your driving are in the training dataset so far. - + Modo Firehose: ATIVO ACTIVE - + ATIVO <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network - + <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INATIVO</span>: conecte-se a uma rede sem restrição <br> de dados + + + 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. + 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. + + + <b>%n segment(s)</b> of your driving is in the training dataset so far. + + <b>%n segmento</b> da sua direção está no conjunto de dados de treinamento até agora. + <b>%n segmentos</b> da sua direção estão no conjunto de dados de treinamento até agora. + @@ -956,7 +973,7 @@ Isso pode levar até um minuto. Firehose - + Firehose @@ -1425,6 +1442,89 @@ Isso pode levar até um minuto. N/A N/A + + Sponsor Status + + + + SPONSOR + + + + Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. + + + + Pair GitHub Account + + + + PAIR + PAREAR + + + Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. + + + + sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. + + + + Not Sponsor + + + + Paired + + + + Not Paired + + + + THANKS ♥ + + + + + SunnylinkSponsorPopup + + Scan the QR code to login to your GitHub account + + + + Follow the prompts to complete the pairing process + + + + Re-enter the "sunnylink" panel to verify sponsorship status + + + + If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot + + + + Scan the QR code to visit sunnyhaibin's GitHub Sponsors page + + + + Choose your sponsorship tier and confirm your support + + + + Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status + + + + Pair your GitHub account + + + + Early Access: Become a sunnypilot Sponsor + + SunnypilotPanel @@ -1453,11 +1553,11 @@ Isso pode levar até um minuto. Welcome to openpilot - + Bem vindo ao openpilot 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. - + 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. @@ -1626,7 +1726,7 @@ Isso pode levar até um minuto. <span style='font-family: "Noto Color Emoji";'>🔥</span> Firehose Mode <span style='font-family: Noto Color Emoji;'>🔥</span> - <span style='font-family: "Noto Color Emoji";'>🔥</span> Mode Firehose <span style='font-family: Noto Color Emoji;'>🔥</span> + <span style='font-family: "Noto Color Emoji";'>🔥</span> Modo Firehose <span style='font-family: Noto Color Emoji;'>🔥</span> diff --git a/selfdrive/ui/translations/main_th.ts b/selfdrive/ui/translations/main_th.ts index a3d3f347f1..ca3effbb72 100644 --- a/selfdrive/ui/translations/main_th.ts +++ b/selfdrive/ui/translations/main_th.ts @@ -159,6 +159,18 @@ Enables or disables the github runner service. + + Error Log + + + + VIEW + ดู + + + View the error log for sunnypilot crashes. + + DevicePanel @@ -431,14 +443,6 @@ Firehose Mode allows you to maximize your training data uploads to improve openp Firehose Mode: ACTIVE - - 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. - - - - <b>%1 %2</b> of your driving are in the training dataset so far. - - ACTIVE @@ -447,6 +451,16 @@ Firehose Mode allows you to maximize your training data uploads to improve openp <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network + + 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. + + + + <b>%n segment(s)</b> of your driving is in the training dataset so far. + + + + HudRenderer @@ -1421,6 +1435,89 @@ This may take up to a minute. N/A ไม่มี + + Sponsor Status + + + + SPONSOR + + + + Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. + + + + Pair GitHub Account + + + + PAIR + จับคู่ + + + Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. + + + + sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. + + + + Not Sponsor + + + + Paired + + + + Not Paired + + + + THANKS ♥ + + + + + SunnylinkSponsorPopup + + Scan the QR code to login to your GitHub account + + + + Follow the prompts to complete the pairing process + + + + Re-enter the "sunnylink" panel to verify sponsorship status + + + + If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot + + + + Scan the QR code to visit sunnyhaibin's GitHub Sponsors page + + + + Choose your sponsorship tier and confirm your support + + + + Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status + + + + Pair your GitHub account + + + + Early Access: Become a sunnypilot Sponsor + + SunnypilotPanel diff --git a/selfdrive/ui/translations/main_tr.ts b/selfdrive/ui/translations/main_tr.ts index 616bf71915..a03a63dfb7 100644 --- a/selfdrive/ui/translations/main_tr.ts +++ b/selfdrive/ui/translations/main_tr.ts @@ -159,6 +159,18 @@ Enables or disables the github runner service. + + Error Log + + + + VIEW + BAK + + + View the error log for sunnypilot crashes. + + DevicePanel @@ -431,14 +443,6 @@ Firehose Mode allows you to maximize your training data uploads to improve openp Firehose Mode: ACTIVE - - 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. - - - - <b>%1 %2</b> of your driving are in the training dataset so far. - - ACTIVE @@ -447,6 +451,16 @@ Firehose Mode allows you to maximize your training data uploads to improve openp <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network + + 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. + + + + <b>%n segment(s)</b> of your driving is in the training dataset so far. + + + + HudRenderer @@ -1419,6 +1433,89 @@ This may take up to a minute. N/A N/A + + Sponsor Status + + + + SPONSOR + + + + Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. + + + + Pair GitHub Account + + + + PAIR + + + + Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. + + + + sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. + + + + Not Sponsor + + + + Paired + + + + Not Paired + + + + THANKS ♥ + + + + + SunnylinkSponsorPopup + + Scan the QR code to login to your GitHub account + + + + Follow the prompts to complete the pairing process + + + + Re-enter the "sunnylink" panel to verify sponsorship status + + + + If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot + + + + Scan the QR code to visit sunnyhaibin's GitHub Sponsors page + + + + Choose your sponsorship tier and confirm your support + + + + Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status + + + + Pair your GitHub account + + + + Early Access: Become a sunnypilot Sponsor + + SunnypilotPanel diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index e0facb278f..6b1bee2d9e 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -159,6 +159,18 @@ Enables or disables the github runner service. + + Error Log + + + + VIEW + 查看 + + + View the error log for sunnypilot crashes. + + DevicePanel @@ -425,27 +437,31 @@ 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. - + openpilot 通过观察人类驾驶(包括您)来学习如何驾驶。 + +“训练数据上传模式”允许您最大化上传训练数据,以改进 openpilot 的驾驶模型。更多数据意味着更强大的模型,也就意味着更优秀的“实验模式”。 Firehose Mode: ACTIVE - - - - 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. - - - - <b>%1 %2</b> of your driving are in the training dataset so far. - + 训练数据上传模式:激活中 ACTIVE - + 激活中 <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network - + <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>未激活</span>:请连接至不限流量的网络 + + + 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. + 为了达到最佳效果,请每周将您的设备带回室内,并连接到优质的 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(以及特定的分支)可以用于训练。 + + + <b>%n segment(s)</b> of your driving is in the training dataset so far. + + <b>目前已有 %n 段</b> 您的驾驶数据被纳入训练数据集。 + @@ -952,7 +968,7 @@ This may take up to a minute. Firehose - + 训练上传 @@ -1421,6 +1437,89 @@ This may take up to a minute. N/A N/A + + Sponsor Status + + + + SPONSOR + + + + Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. + + + + Pair GitHub Account + + + + PAIR + 配对 + + + Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. + + + + sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. + + + + Not Sponsor + + + + Paired + + + + Not Paired + + + + THANKS ♥ + + + + + SunnylinkSponsorPopup + + Scan the QR code to login to your GitHub account + + + + Follow the prompts to complete the pairing process + + + + Re-enter the "sunnylink" panel to verify sponsorship status + + + + If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot + + + + Scan the QR code to visit sunnyhaibin's GitHub Sponsors page + + + + Choose your sponsorship tier and confirm your support + + + + Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status + + + + Pair your GitHub account + + + + Early Access: Become a sunnypilot Sponsor + + SunnypilotPanel @@ -1449,11 +1548,11 @@ This may take up to a minute. Welcome to openpilot - + 欢迎使用 openpilot 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. - + 您必须接受《条款与条件》才能使用 openpilot。在继续之前,请先阅读最新条款:<span style='color: #465BEA;'>https://comma.ai/terms</span>。 diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index 8fecfe8bb8..17fa912d20 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -159,6 +159,18 @@ Enables or disables the github runner service. + + Error Log + + + + VIEW + 觀看 + + + View the error log for sunnypilot crashes. + + DevicePanel @@ -425,27 +437,31 @@ 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. - + openpilot 透過觀察人類駕駛(包括您)來學習如何駕駛。 + +「訓練資料上傳模式」可讓您最大化上傳訓練數據,以改進 openpilot 的駕駛模型。更多數據代表更強大的模型,也就意味著更優秀的「實驗模式」。 Firehose Mode: ACTIVE - - - - 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. - - - - <b>%1 %2</b> of your driving are in the training dataset so far. - + 訓練資料上傳模式:啟用中 ACTIVE - + 啟用中 <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>INACTIVE</span>: connect to unmetered network - + <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>未啟用</span>:請連接至不限流量的網路 + + + 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. + 為了達到最佳效果,請每週將您的裝置帶回室內,並連接至優質的 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(以及特定的分支)可以用於訓練。 + + + <b>%n segment(s)</b> of your driving is in the training dataset so far. + + <b>目前已有 %n 段</b> 您的駕駛數據被納入訓練資料集。 + @@ -952,7 +968,7 @@ This may take up to a minute. Firehose - + 訓練上傳 @@ -1421,6 +1437,89 @@ This may take up to a minute. N/A 無法使用 + + Sponsor Status + + + + SPONSOR + + + + Become a sponsor of sunnypilot to get early access to sunnylink features when they become available. + + + + Pair GitHub Account + + + + PAIR + 配對 + + + Pair your GitHub account to grant your device sponsor benefits, including API access on sunnylink. + + + + sunnylink Dongle ID not found. This may be due to weak internet connection or sunnylink registration issue. Please reboot and try again. + + + + Not Sponsor + + + + Paired + + + + Not Paired + + + + THANKS ♥ + + + + + SunnylinkSponsorPopup + + Scan the QR code to login to your GitHub account + + + + Follow the prompts to complete the pairing process + + + + Re-enter the "sunnylink" panel to verify sponsorship status + + + + If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot + + + + Scan the QR code to visit sunnyhaibin's GitHub Sponsors page + + + + Choose your sponsorship tier and confirm your support + + + + Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status + + + + Pair your GitHub account + + + + Early Access: Become a sunnypilot Sponsor + + SunnypilotPanel @@ -1449,11 +1548,11 @@ This may take up to a minute. Welcome to openpilot - + 歡迎使用 openpilot 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. - + 您必須接受《條款與條件》才能使用 openpilot。在繼續之前,請先閱讀最新條款:<span style='color: #465BEA;'>https://comma.ai/terms</span>。 diff --git a/sunnypilot/mads/helpers.py b/sunnypilot/mads/helpers.py index 9dc47b26a8..36a7380e79 100644 --- a/sunnypilot/mads/helpers.py +++ b/sunnypilot/mads/helpers.py @@ -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") diff --git a/sunnypilot/mads/mads.py b/sunnypilot/mads/mads.py index 9e53a8eeed..384e48b3fa 100644 --- a/sunnypilot/mads/mads.py +++ b/sunnypilot/mads/mads.py @@ -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) diff --git a/sunnypilot/mads/state.py b/sunnypilot/mads/state.py index e3acf3a0e0..89ae506ac8 100644 --- a/sunnypilot/mads/state.py +++ b/sunnypilot/mads/state.py @@ -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 diff --git a/sunnypilot/mads/tests/test_mads_state_machine.py b/sunnypilot/mads/tests/test_mads_state_machine.py index 546893d285..51013b0baf 100644 --- a/sunnypilot/mads/tests/test_mads_state_machine.py +++ b/sunnypilot/mads/tests/test_mads_state_machine.py @@ -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 diff --git a/sunnypilot/modeld/runners/run_helpers.py b/sunnypilot/modeld/runners/run_helpers.py index cc4c644583..5470106f1d 100644 --- a/sunnypilot/modeld/runners/run_helpers.py +++ b/sunnypilot/modeld/runners/run_helpers.py @@ -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 diff --git a/sunnypilot/models/fetcher.py b/sunnypilot/models/fetcher.py index f543b00a79..d9aa54aa88 100644 --- a/sunnypilot/models/fetcher.py +++ b/sunnypilot/models/fetcher.py @@ -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 diff --git a/sunnypilot/models/helpers.py b/sunnypilot/models/helpers.py index 915f1a4206..dbdef55900 100644 --- a/sunnypilot/models/helpers.py +++ b/sunnypilot/models/helpers.py @@ -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 diff --git a/sunnypilot/models/manager.py b/sunnypilot/models/manager.py index 06705bd5ce..5fbad6cd8e 100644 --- a/sunnypilot/models/manager.py +++ b/sunnypilot/models/manager.py @@ -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 diff --git a/sunnypilot/models/tests/model_manager_audit.py b/sunnypilot/models/tests/model_manager_audit.py index 1a1ea38e5b..4cd2b7d78e 100644 --- a/sunnypilot/models/tests/model_manager_audit.py +++ b/sunnypilot/models/tests/model_manager_audit.py @@ -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 diff --git a/sunnypilot/sunnylink/api.py b/sunnypilot/sunnylink/api.py index 4abefa12f8..9f82fd2d20 100644 --- a/sunnypilot/sunnylink/api.py +++ b/sunnypilot/sunnylink/api.py @@ -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) diff --git a/sunnypilot/sunnylink/athena/sunnylinkd.py b/sunnypilot/sunnylink/athena/sunnylinkd.py index 0fb7ea6d9a..2c4b95fa46 100755 --- a/sunnypilot/sunnylink/athena/sunnylinkd.py +++ b/sunnypilot/sunnylink/athena/sunnylinkd.py @@ -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, diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index f050070d63..18a42f05d8 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -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); diff --git a/system/camerad/cameras/spectra.cc b/system/camerad/cameras/spectra.cc index 713d5ba1cc..d12dbb2444 100644 --- a/system/camerad/cameras/spectra.cc +++ b/system/camerad/cameras/spectra.cc @@ -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; } diff --git a/system/camerad/cameras/spectra.h b/system/camerad/cameras/spectra.h index 21f1970326..13cb13f98f 100644 --- a/system/camerad/cameras/spectra.h +++ b/system/camerad/cameras/spectra.h @@ -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(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(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; } diff --git a/system/camerad/main.cc b/system/camerad/main.cc index d55bd495ad..05fdef1a65 100644 --- a/system/camerad/main.cc +++ b/system/camerad/main.cc @@ -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(); diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json index d42c8151ab..04e5fd3365 100644 --- a/system/hardware/tici/agnos.json +++ b/system/hardware/tici/agnos.json @@ -56,28 +56,28 @@ }, { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-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 } } diff --git a/system/hardware/tici/all-partitions.json b/system/hardware/tici/all-partitions.json index e90e8c2ec4..4d15e76b0e 100644 --- a/system/hardware/tici/all-partitions.json +++ b/system/hardware/tici/all-partitions.json @@ -339,62 +339,62 @@ }, { "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 } }, { "name": "userdata_90", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-16c037fa42ee99bc6ec92909efc8a8075a0e8a0232a7d90e39e7d40a7bd0ee8e.img.xz", - "hash": "c6fb215f2b297f7ff5b8f133bc5d687772b37f2fee42a44aa730e37a84a14e52", - "hash_raw": "16c037fa42ee99bc6ec92909efc8a8075a0e8a0232a7d90e39e7d40a7bd0ee8e", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-17f46ba0d0eec10cf14804c84d9bcf4f6fa864074f1b90ff2fb4873399d64b1a.img.xz", + "hash": "1442ae218daff960783bc493a32dd489fa787e4025c64fa3132f48b1e627647f", + "hash_raw": "17f46ba0d0eec10cf14804c84d9bcf4f6fa864074f1b90ff2fb4873399d64b1a", "size": 96636764160, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "7199262f209abbb07be5eece505ac4d4c7ba8957f2d9ff7b1ac1ef2063461665" + "ondevice_hash": "a64fea9c3a7bec11b850a55d50ceb30a032742c90aadf0ce35cdbef9349bebba" }, { "name": "userdata_89", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-62c2c41470282b581ec1bbbe0375fb3b6c66df2f4bc3dc6c6fdf796f1797f136.img.xz", - "hash": "d66f894436fa11d4ff00f8a84e54d9e23a6492b0087f69bb958d2ab0bdc6dfba", - "hash_raw": "62c2c41470282b581ec1bbbe0375fb3b6c66df2f4bc3dc6c6fdf796f1797f136", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-69b2404839f87b1de5c05766550ca0217d5ed566c9f2ea41c46b5a301540a3ac.img.xz", + "hash": "332e6cf5c221690a01ebfd0582420e039c7796a8fb2d499e44a8b657dffe4af2", + "hash_raw": "69b2404839f87b1de5c05766550ca0217d5ed566c9f2ea41c46b5a301540a3ac", "size": 95563022336, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "f1d3685618f6d1bde24ce6109284c5d30ece2f4fd015be67e8b52ef7e06067a4" + "ondevice_hash": "c560c76007fd8997e01e516ab16ff445048945a13bf830bb2a4c7f6e9d2fe5f6" }, { "name": "userdata_30", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-3e71d8804c90a6dff5048edb976149f9ea177efa139dea47cb585cef76b26f6e.img.xz", - "hash": "1b201ecbd0e1573777811bf18fa90cb080bfbccb34a6dcfd39b412632e7ca699", - "hash_raw": "3e71d8804c90a6dff5048edb976149f9ea177efa139dea47cb585cef76b26f6e", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-ffc34d8753520e684448185db801c05bb81e23c54a499cc40a2e38b7009cdf36.img.xz", + "hash": "8bfb1fdec9c8f96c97bcce1efbc7d502447bdb3b481cc408b24b5feef66839fd", + "hash_raw": "ffc34d8753520e684448185db801c05bb81e23c54a499cc40a2e38b7009cdf36", "size": 32212254720, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "47c28e63209556442cd9d2fd06a6c0e7fcf35c8a4e4fbcc550b9c089429ad0e0" + "ondevice_hash": "8f840157d73fa4469aa650d668e046c9750304af84248ab83453cbd285918859" } ] \ No newline at end of file diff --git a/system/loggerd/loggerd.cc b/system/loggerd/loggerd.cc index ea0178fe80..953ae1df32 100644 --- a/system/loggerd/loggerd.cc +++ b/system/loggerd/loggerd.cc @@ -165,8 +165,15 @@ int handle_encoder_msg(LoggerdState *s, Message *msg, std::string &name, struct s->logger.segment(), offset_segment_num, s->ready_to_rotate.load(), s->max_waiting, name.c_str()); } - // queue up all the new segment messages, they go in after the rotate - re.q.push_back(msg); + + // TODO: define this behavior, but for now don't leak + if (re.q.size() > MAIN_FPS*10) { + LOGE_100("%s: dropping frame, queue is too large", name.c_str()); + delete msg; + } else { + // queue up all the new segment messages, they go in after the rotate + re.q.push_back(msg); + } } else { LOGE("%s: encoderd packet has a older segment!!! idx.getSegmentNum():%d s->logger.segment():%d re.encoderd_segment_offset:%d", name.c_str(), idx.getSegmentNum(), s->logger.segment(), re.encoderd_segment_offset); diff --git a/system/manager/manager.py b/system/manager/manager.py index 25aba187ae..708df3aabc 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -46,7 +46,7 @@ def manager_init() -> None: ("DynamicExperimentalControl", "0"), ("Mads", "1"), ("MadsMainCruiseAllowed", "1"), - ("MadsPauseLateralOnBrake", "0"), + ("MadsSteeringMode", "0"), ("MadsUnifiedEngagementMode", "1"), ("ModelManager_LastSyncTime", "0"), ("ModelManager_ModelsCache", ""), diff --git a/system/manager/process.py b/system/manager/process.py index 0e9c9b9804..6c233c21ab 100644 --- a/system/manager/process.py +++ b/system/manager/process.py @@ -221,8 +221,12 @@ class PythonProcess(ManagerProcess): if self.proc is not None: return + # TODO: this is just a workaround for this tinygrad check: + # https://github.com/tinygrad/tinygrad/blob/ac9c96dae1656dc220ee4acc39cef4dd449aa850/tinygrad/device.py#L26 + name = self.name if "modeld" not in self.name else "MainProcess" + cloudlog.info(f"starting python {self.module}") - self.proc = Process(name=self.name, target=self.launcher, args=(self.module, self.name)) + self.proc = Process(name=name, target=self.launcher, args=(self.module, self.name)) self.proc.start() self.watchdog_seen = False self.shutting_down = False diff --git a/system/manager/process_config.py b/system/manager/process_config.py index 48136bfa14..1d2ca45944 100644 --- a/system/manager/process_config.py +++ b/system/manager/process_config.py @@ -105,10 +105,8 @@ procs = [ PythonProcess("micd", "system.micd", iscar), PythonProcess("timed", "system.timed", always_run, enabled=not PC), - # TODO: Make python process once TG allows opening QCOM from child pro - # https://github.com/tinygrad/tinygrad/blob/ac9c96dae1656dc220ee4acc39cef4dd449aa850/tinygrad/device.py#L26 - NativeProcess("modeld", "selfdrive/modeld", ["./modeld.py"], and_(only_onroad, is_stock_model)), - NativeProcess("dmonitoringmodeld", "selfdrive/modeld", ["./dmonitoringmodeld.py"], driverview, enabled=(WEBCAM or not PC)), + PythonProcess("modeld", "selfdrive.modeld.modeld", and_(only_onroad, is_stock_model)), + PythonProcess("dmonitoringmodeld", "selfdrive.modeld.dmonitoringmodeld", driverview, enabled=(WEBCAM or not PC)), NativeProcess("sensord", "system/sensord", ["./sensord"], only_onroad, enabled=not PC), NativeProcess("ui", "selfdrive/ui", ["./ui"], always_run, watchdog_max_dt=(5 if not PC else None)), diff --git a/system/ui/widgets/confirm_dialog.py b/system/ui/widgets/confirm_dialog.py new file mode 100644 index 0000000000..e5ca002ecf --- /dev/null +++ b/system/ui/widgets/confirm_dialog.py @@ -0,0 +1,62 @@ +import pyray as rl +from openpilot.system.ui.lib.button import gui_button, ButtonStyle +from openpilot.system.ui.lib.label import gui_text_box + +# Constants for dialog dimensions and styling +DIALOG_WIDTH = 1520 +DIALOG_HEIGHT = 600 +BUTTON_HEIGHT = 160 +MARGIN = 50 +TEXT_AREA_HEIGHT_REDUCTION = 200 +BACKGROUND_COLOR = rl.Color(27, 27, 27, 255) + + +def confirm_dialog(rect: rl.Rectangle, message: str, confirm_text: str, cancel_text: str = "Cancel") -> int: + # Calculate dialog position and size, centered within the parent rectangle + dialog_x = rect.x + (rect.width - DIALOG_WIDTH) / 2 + dialog_y = rect.y + (rect.height - DIALOG_HEIGHT) / 2 + dialog_rect = rl.Rectangle(dialog_x, dialog_y, DIALOG_WIDTH, DIALOG_HEIGHT) + + # Calculate button positions at the bottom of the dialog + bottom = dialog_rect.y + dialog_rect.height + button_width = (dialog_rect.width - 3 * MARGIN) // 2 + no_button_x = dialog_rect.x + MARGIN + yes_button_x = dialog_rect.x + dialog_rect.width - button_width - MARGIN + button_y = bottom - BUTTON_HEIGHT - MARGIN + no_button = rl.Rectangle(no_button_x, button_y, button_width, BUTTON_HEIGHT) + yes_button = rl.Rectangle(yes_button_x, button_y, button_width, BUTTON_HEIGHT) + + # Draw the dialog background + rl.draw_rectangle( + int(dialog_rect.x), + int(dialog_rect.y), + int(dialog_rect.width), + int(dialog_rect.height), + BACKGROUND_COLOR, + ) + + # Draw the message in the dialog, centered + text_rect = rl.Rectangle(dialog_rect.x, dialog_rect.y, dialog_rect.width, dialog_rect.height - TEXT_AREA_HEIGHT_REDUCTION) + gui_text_box( + text_rect, + message, + alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, + alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE, + ) + + # Initialize result; -1 means no action taken yet + result = -1 + + # Check for keyboard input for accessibility + if rl.is_key_pressed(rl.KeyboardKey.KEY_ENTER): + result = 1 # Confirm + elif rl.is_key_pressed(rl.KeyboardKey.KEY_ESCAPE): + result = 0 # Cancel + + # Check for button clicks + if gui_button(yes_button, confirm_text, button_style=ButtonStyle.PRIMARY): + result = 1 # Confirm + if gui_button(no_button, cancel_text): + result = 0 # Cancel + + return result diff --git a/system/ui/widgets/keyboard.py b/system/ui/widgets/keyboard.py new file mode 100644 index 0000000000..4d1ad1b2cd --- /dev/null +++ b/system/ui/widgets/keyboard.py @@ -0,0 +1,104 @@ +import pyray as rl +from openpilot.system.ui.lib.button import gui_button +from openpilot.system.ui.lib.label import gui_label + +# Constants for special keys +BACKSPACE_KEY = "<-" +ENTER_KEY = "Enter" +SPACE_KEY = " " +SHIFT_KEY = "↑" +SHIFT_DOWN_KEY = "↓" +NUMERIC_KEY = "123" +SYMBOL_KEY = "#+=" +ABC_KEY = "ABC" + +# Define keyboard layouts as a dictionary for easier access +keyboard_layouts = { + "lowercase": [ + ["q", "w", "e", "r", "t", "y", "u", "i", "o", "p"], + ["a", "s", "d", "f", "g", "h", "j", "k", "l"], + [SHIFT_KEY, "z", "x", "c", "v", "b", "n", "m", BACKSPACE_KEY], + [NUMERIC_KEY, "/", "-", SPACE_KEY, ".", ENTER_KEY], + ], + "uppercase": [ + ["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"], + ["A", "S", "D", "F", "G", "H", "J", "K", "L"], + [SHIFT_DOWN_KEY, "Z", "X", "C", "V", "B", "N", "M", BACKSPACE_KEY], + [NUMERIC_KEY, "/", "-", SPACE_KEY, ".", ENTER_KEY], + ], + "numbers": [ + ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"], + ["-", "/", ":", ";", "(", ")", "$", "&", "@", "\""], + [SYMBOL_KEY, ".", ",", "?", "!", "`", BACKSPACE_KEY], + [ABC_KEY, SPACE_KEY, ".", ENTER_KEY], + ], + "specials": [ + ["[", "]", "{", "}", "#", "%", "^", "*", "+", "="], + ["_", "\\", "|", "~", "<", ">", "€", "£", "¥", "•"], + [NUMERIC_KEY, ".", ",", "?", "!", "'", BACKSPACE_KEY], + [ABC_KEY, SPACE_KEY, ".", ENTER_KEY], + ], +} + + +class Keyboard: + def __init__(self, max_text_size: int = 255): + self._layout = keyboard_layouts["lowercase"] + self._input_text = "" + self._max_text_size = max_text_size + + @property + def text(self) -> str: + return self._input_text + + def clear(self): + self._input_text = "" + + def render(self, rect, title, sub_title): + gui_label(rl.Rectangle(rect.x, rect.y, rect.width, 95), title, 90) + gui_label(rl.Rectangle(rect.x, rect.y + 95, rect.width, 60), sub_title, 55, rl.GRAY) + if gui_button(rl.Rectangle(rect.x + rect.width - 300, rect.y, 300, 100), "Cancel"): + return -1 + + # Text box for input + rl.gui_text_box(rl.Rectangle(rect.x, rect.y + 160, rect.width, 100), self._input_text, self._max_text_size, True) + + h_space, v_space = 15, 15 + row_y_start = rect.y + 300 # Starting Y position for the first row + key_height = (rect.height - 300 - 3 * v_space) / 4 + key_max_width = (rect.width - (len(self._layout[2]) - 1) * h_space) / len(self._layout[2]) + + # Iterate over the rows of keys in the current layout + for row, keys in enumerate(self._layout): + key_width = min((rect.width - (180 if row == 1 else 0) - h_space * (len(keys) - 1)) / len(keys), key_max_width) + start_x = rect.x + (90 if row == 1 else 0) + + for i, key in enumerate(keys): + if i > 0: + start_x += h_space + + new_width = (key_width * 3 + h_space * 2) if key == SPACE_KEY else (key_width * 2 + h_space if key == ENTER_KEY else key_width) + key_rect = rl.Rectangle(start_x, row_y_start + row * (key_height + v_space), new_width, key_height) + start_x += new_width + + if gui_button(key_rect, key): + if key == ENTER_KEY: + return 1 + else: + self.handle_key_press(key) + + return 0 + + def handle_key_press(self, key): + if key in (SHIFT_DOWN_KEY, ABC_KEY): + self._layout = keyboard_layouts["lowercase"] + elif key == SHIFT_KEY: + self._layout = keyboard_layouts["uppercase"] + elif key == NUMERIC_KEY: + self._layout = keyboard_layouts["numbers"] + elif key == SYMBOL_KEY: + self._layout = keyboard_layouts["specials"] + elif key == BACKSPACE_KEY and len(self._input_text) > 0: + self._input_text = self._input_text[:-1] + elif key != BACKSPACE_KEY and len(self._input_text) < self._max_text_size: + self._input_text += key diff --git a/uv.lock b/uv.lock index 8da47bf004..fe95157eb2 100644 --- a/uv.lock +++ b/uv.lock @@ -466,6 +466,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/27/6b/7c87867d255cbce8167ed99fc65635e9395d2af0f0c915428f5b17ec412d/Cython-3.0.12-py2.py3-none-any.whl", hash = "sha256:0038c9bae46c459669390e53a1ec115f8096b2e4647ae007ff1bf4e6dee92806", size = 1171640 }, ] +[[package]] +name = "dbus-next" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/45/6a40fbe886d60a8c26f480e7d12535502b5ba123814b3b9a0b002ebca198/dbus_next-0.2.3.tar.gz", hash = "sha256:f4eae26909332ada528c0a3549dda8d4f088f9b365153952a408e28023a626a5", size = 71112 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/fc/c0a3f4c4eaa5a22fbef91713474666e13d0ea2a69c84532579490a9f2cc8/dbus_next-0.2.3-py3-none-any.whl", hash = "sha256:58948f9aff9db08316734c0be2a120f6dc502124d9642f55e90ac82ffb16a18b", size = 57885 }, +] + [[package]] name = "dictdiffer" version = "0.9.0" @@ -490,7 +499,7 @@ version = "0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "python-xlib", marker = "sys_platform == 'linux'" }, - { name = "typing-extensions" }, + { name = "typing-extensions", marker = "sys_platform != 'darwin'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/2f/3a/46ca34abf0725a754bc44ef474ad34aedcc3ea23b052d97b18b76715a6a9/EWMHlib-0.2-py3-none-any.whl", hash = "sha256:f5b07d8cfd4c7734462ee744c32d490f2f3233fa7ab354240069344208d2f6f5", size = 46657 }, @@ -631,10 +640,10 @@ name = "gymnasium" version = "1.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cloudpickle" }, - { name = "farama-notifications" }, - { name = "numpy" }, - { name = "typing-extensions" }, + { name = "cloudpickle", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "farama-notifications", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "numpy", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "typing-extensions", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/90/69/70cd29e9fc4953d013b15981ee71d4c9ef4d8b2183e6ef2fe89756746dce/gymnasium-1.1.1.tar.gz", hash = "sha256:8bd9ea9bdef32c950a444ff36afc785e1d81051ec32d30435058953c20d2456d", size = 829326 } wheels = [ @@ -945,22 +954,22 @@ name = "metadrive-simulator" version = "0.4.2.4" source = { url = "https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl" } dependencies = [ - { name = "filelock" }, - { name = "gymnasium" }, - { name = "lxml" }, - { name = "matplotlib" }, - { name = "numpy" }, - { name = "opencv-python-headless" }, - { name = "panda3d" }, - { name = "panda3d-gltf" }, - { name = "pillow" }, - { name = "progressbar" }, - { name = "psutil" }, - { name = "pygments" }, - { name = "requests" }, - { name = "shapely" }, - { name = "tqdm" }, - { name = "yapf" }, + { name = "filelock", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "gymnasium", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "lxml", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "matplotlib", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "numpy", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "opencv-python-headless", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "panda3d", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "panda3d-gltf", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "pillow", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "progressbar", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "psutil", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "pygments", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "requests", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "shapely", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "tqdm", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "yapf", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] wheels = [ { url = "https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl", hash = "sha256:fbf0ea9be67e65cd45d38ff930e3d49f705dd76c9ddbd1e1482e3f87b61efcef" }, @@ -1212,7 +1221,7 @@ name = "opencv-python-headless" version = "4.11.0.86" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/36/2f/5b2b3ba52c864848885ba988f24b7f105052f68da9ab0e693cc7c25b0b30/opencv-python-headless-4.11.0.86.tar.gz", hash = "sha256:996eb282ca4b43ec6a3972414de0e2331f5d9cda2b41091a49739c19fb843798", size = 95177929 } wheels = [ @@ -1269,11 +1278,13 @@ dev = [ { name = "av" }, { name = "azure-identity" }, { name = "azure-storage-blob" }, + { name = "dbus-next" }, { name = "dictdiffer" }, { name = "lru-dict" }, { name = "matplotlib" }, { name = "parameterized" }, { name = "pyautogui" }, + { name = "pygame" }, { name = "pyopencl", marker = "platform_machine != 'aarch64'" }, { name = "pyprof2calltree" }, { name = "pytools", marker = "platform_machine != 'aarch64'" }, @@ -1324,6 +1335,7 @@ requires-dist = [ { name = "coverage", marker = "extra == 'testing'" }, { name = "crcmod" }, { name = "cython" }, + { name = "dbus-next", marker = "extra == 'dev'" }, { name = "dictdiffer", marker = "extra == 'dev'" }, { name = "future-fstrings" }, { name = "hypothesis", marker = "extra == 'testing'", specifier = "==6.47.*" }, @@ -1347,6 +1359,7 @@ requires-dist = [ { name = "pyautogui", marker = "extra == 'dev'" }, { name = "pycapnp" }, { name = "pycryptodome" }, + { name = "pygame", marker = "extra == 'dev'" }, { name = "pyjwt" }, { name = "pyopencl", marker = "platform_machine != 'aarch64' and extra == 'dev'" }, { name = "pyopenssl", specifier = "<24.3.0" }, @@ -1418,8 +1431,8 @@ name = "panda3d-gltf" version = "0.13" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "panda3d" }, - { name = "panda3d-simplepbr" }, + { name = "panda3d", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "panda3d-simplepbr", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/07/7f/9f18fc3fa843a080acb891af6bcc12262e7bdf1d194a530f7042bebfc81f/panda3d-gltf-0.13.tar.gz", hash = "sha256:d06d373bdd91cf530909b669f43080e599463bbf6d3ef00c3558bad6c6b19675", size = 25573 } wheels = [ @@ -1431,8 +1444,8 @@ name = "panda3d-simplepbr" version = "0.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "panda3d" }, - { name = "typing-extensions" }, + { name = "panda3d", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "typing-extensions", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b1/af/505608eef09d7f9b822e69dc7631cd14102650b8fe1b6f60d9562d2788d9/panda3d-simplepbr-0.12.0.tar.gz", hash = "sha256:c71d490afeeb3a90455dcfde1d30c41f321a38742a97d18834e5c31016331ed5", size = 1929980 } wheels = [ @@ -1730,6 +1743,28 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/25/68/7e150cba9eeffdeb3c5cecdb6896d70c8edd46ce41c0491e12fb2b2256ff/pyee-12.1.1-py3-none-any.whl", hash = "sha256:18a19c650556bb6b32b406d7f017c8f513aceed1ef7ca618fb65de7bd2d347ef", size = 15527 }, ] +[[package]] +name = "pygame" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/49/cc/08bba60f00541f62aaa252ce0cfbd60aebd04616c0b9574f755b583e45ae/pygame-2.6.1.tar.gz", hash = "sha256:56fb02ead529cee00d415c3e007f75e0780c655909aaa8e8bf616ee09c9feb1f", size = 14808125 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/ca/8f367cb9fe734c4f6f6400e045593beea2635cd736158f9fabf58ee14e3c/pygame-2.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:20349195326a5e82a16e351ed93465a7845a7e2a9af55b7bc1b2110ea3e344e1", size = 13113753 }, + { url = "https://files.pythonhosted.org/packages/83/47/6edf2f890139616b3219be9cfcc8f0cb8f42eb15efd59597927e390538cb/pygame-2.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f3935459109da4bb0b3901da9904f0a3e52028a3332a355d298b1673a334cf21", size = 12378146 }, + { url = "https://files.pythonhosted.org/packages/00/9e/0d8aa8cf93db2d2ee38ebaf1c7b61d0df36ded27eb726221719c150c673d/pygame-2.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c31dbdb5d0217f32764797d21c2752e258e5fb7e895326538d82b5f75a0cd856", size = 13611760 }, + { url = "https://files.pythonhosted.org/packages/d7/9e/d06adaa5cc65876bcd7a24f59f67e07f7e4194e6298130024ed3fb22c456/pygame-2.6.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:173badf82fa198e6888017bea40f511cb28e69ecdd5a72b214e81e4dcd66c3b1", size = 14298054 }, + { url = "https://files.pythonhosted.org/packages/7a/a1/9ae2852ebd3a7cc7d9ae7ff7919ab983e4a5c1b7a14e840732f23b2b48f6/pygame-2.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce8cc108b92de9b149b344ad2e25eedbe773af0dc41dfb24d1f07f679b558c60", size = 13977107 }, + { url = "https://files.pythonhosted.org/packages/31/df/6788fd2e9a864d0496a77670e44a7c012184b7a5382866ab0e60c55c0f28/pygame-2.6.1-cp311-cp311-win32.whl", hash = "sha256:811e7b925146d8149d79193652cbb83e0eca0aae66476b1cb310f0f4226b8b5c", size = 10250863 }, + { url = "https://files.pythonhosted.org/packages/d2/55/ca3eb851aeef4f6f2e98a360c201f0d00bd1ba2eb98e2c7850d80aabc526/pygame-2.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:91476902426facd4bb0dad4dc3b2573bc82c95c71b135e0daaea072ed528d299", size = 10622016 }, + { url = "https://files.pythonhosted.org/packages/92/16/2c602c332f45ff9526d61f6bd764db5096ff9035433e2172e2d2cadae8db/pygame-2.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4ee7f2771f588c966fa2fa8b829be26698c9b4836f82ede5e4edc1a68594942e", size = 13118279 }, + { url = "https://files.pythonhosted.org/packages/cd/53/77ccbc384b251c6e34bfd2e734c638233922449a7844e3c7a11ef91cee39/pygame-2.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c8040ea2ab18c6b255af706ec01355c8a6b08dc48d77fd4ee783f8fc46a843bf", size = 12384524 }, + { url = "https://files.pythonhosted.org/packages/06/be/3ed337583f010696c3b3435e89a74fb29d0c74d0931e8f33c0a4246307a9/pygame-2.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47a6938de93fa610accd4969e638c2aebcb29b2fca518a84c3a39d91ab47116", size = 13587123 }, + { url = "https://files.pythonhosted.org/packages/fd/ca/b015586a450db59313535662991b34d24c1f0c0dc149cc5f496573900f4e/pygame-2.6.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33006f784e1c7d7e466fcb61d5489da59cc5f7eb098712f792a225df1d4e229d", size = 14275532 }, + { url = "https://files.pythonhosted.org/packages/b9/f2/d31e6ad42d657af07be2ffd779190353f759a07b51232b9e1d724f2cda46/pygame-2.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1206125f14cae22c44565c9d333607f1d9f59487b1f1432945dfc809aeaa3e88", size = 13952653 }, + { url = "https://files.pythonhosted.org/packages/f3/42/8ea2a6979e6fa971702fece1747e862e2256d4a8558fe0da6364dd946c53/pygame-2.6.1-cp312-cp312-win32.whl", hash = "sha256:84fc4054e25262140d09d39e094f6880d730199710829902f0d8ceae0213379e", size = 10252421 }, + { url = "https://files.pythonhosted.org/packages/5f/90/7d766d54bb95939725e9a9361f9c06b0cfbe3fe100aa35400f0a461a278a/pygame-2.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:3a9e7396be0d9633831c3f8d5d82dd63ba373ad65599628294b7a4f8a5a01a65", size = 10624591 }, +] + [[package]] name = "pygetwindow" version = "0.0.9" @@ -1806,162 +1841,162 @@ name = "pyobjc" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-accessibility", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-accounts", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-addressbook" }, - { name = "pyobjc-framework-adservices", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-adsupport", marker = "platform_release >= '18.0'" }, - { name = "pyobjc-framework-applescriptkit" }, - { name = "pyobjc-framework-applescriptobjc", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-applicationservices" }, - { name = "pyobjc-framework-apptrackingtransparency", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-audiovideobridging", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-authenticationservices", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-automaticassessmentconfiguration", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-automator" }, - { name = "pyobjc-framework-avfoundation", marker = "platform_release >= '11.0'" }, - { name = "pyobjc-framework-avkit", marker = "platform_release >= '13.0'" }, - { name = "pyobjc-framework-avrouting", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-backgroundassets", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-browserenginekit", marker = "platform_release >= '23.4'" }, - { name = "pyobjc-framework-businesschat", marker = "platform_release >= '18.0'" }, - { name = "pyobjc-framework-calendarstore", marker = "platform_release >= '9.0'" }, - { name = "pyobjc-framework-callkit", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-carbon" }, - { name = "pyobjc-framework-cfnetwork" }, - { name = "pyobjc-framework-cinematic", marker = "platform_release >= '23.0'" }, - { name = "pyobjc-framework-classkit", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-cloudkit", marker = "platform_release >= '14.0'" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-collaboration", marker = "platform_release >= '9.0'" }, - { name = "pyobjc-framework-colorsync", marker = "platform_release >= '17.0'" }, - { name = "pyobjc-framework-contacts", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-contactsui", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-coreaudio" }, - { name = "pyobjc-framework-coreaudiokit" }, - { name = "pyobjc-framework-corebluetooth", marker = "platform_release >= '14.0'" }, - { name = "pyobjc-framework-coredata" }, - { name = "pyobjc-framework-corehaptics", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-corelocation", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-coremedia", marker = "platform_release >= '11.0'" }, - { name = "pyobjc-framework-coremediaio", marker = "platform_release >= '11.0'" }, - { name = "pyobjc-framework-coremidi" }, - { name = "pyobjc-framework-coreml", marker = "platform_release >= '17.0'" }, - { name = "pyobjc-framework-coremotion", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-coreservices" }, - { name = "pyobjc-framework-corespotlight", marker = "platform_release >= '17.0'" }, - { name = "pyobjc-framework-coretext" }, - { name = "pyobjc-framework-corewlan", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-cryptotokenkit", marker = "platform_release >= '14.0'" }, - { name = "pyobjc-framework-datadetection", marker = "platform_release >= '21.0'" }, - { name = "pyobjc-framework-devicecheck", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-devicediscoveryextension", marker = "platform_release >= '24.0'" }, - { name = "pyobjc-framework-dictionaryservices", marker = "platform_release >= '9.0'" }, - { name = "pyobjc-framework-discrecording" }, - { name = "pyobjc-framework-discrecordingui" }, - { name = "pyobjc-framework-diskarbitration" }, - { name = "pyobjc-framework-dvdplayback" }, - { name = "pyobjc-framework-eventkit", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-exceptionhandling" }, - { name = "pyobjc-framework-executionpolicy", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-extensionkit", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-externalaccessory", marker = "platform_release >= '17.0'" }, - { name = "pyobjc-framework-fileprovider", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-fileproviderui", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-findersync", marker = "platform_release >= '14.0'" }, - { name = "pyobjc-framework-fsevents", marker = "platform_release >= '9.0'" }, - { name = "pyobjc-framework-gamecenter", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-gamecontroller", marker = "platform_release >= '13.0'" }, - { name = "pyobjc-framework-gamekit", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-gameplaykit", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-healthkit", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-imagecapturecore", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-inputmethodkit", marker = "platform_release >= '9.0'" }, - { name = "pyobjc-framework-installerplugins" }, - { name = "pyobjc-framework-instantmessage", marker = "platform_release >= '9.0'" }, - { name = "pyobjc-framework-intents", marker = "platform_release >= '16.0'" }, - { name = "pyobjc-framework-intentsui", marker = "platform_release >= '21.0'" }, - { name = "pyobjc-framework-iobluetooth" }, - { name = "pyobjc-framework-iobluetoothui" }, - { name = "pyobjc-framework-iosurface", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-ituneslibrary", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-kernelmanagement", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-latentsemanticmapping" }, - { name = "pyobjc-framework-launchservices" }, - { name = "pyobjc-framework-libdispatch", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-libxpc", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-linkpresentation", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-localauthentication", marker = "platform_release >= '14.0'" }, - { name = "pyobjc-framework-localauthenticationembeddedui", marker = "platform_release >= '21.0'" }, - { name = "pyobjc-framework-mailkit", marker = "platform_release >= '21.0'" }, - { name = "pyobjc-framework-mapkit", marker = "platform_release >= '13.0'" }, - { name = "pyobjc-framework-mediaaccessibility", marker = "platform_release >= '13.0'" }, - { name = "pyobjc-framework-mediaextension", marker = "platform_release >= '24.0'" }, - { name = "pyobjc-framework-medialibrary", marker = "platform_release >= '13.0'" }, - { name = "pyobjc-framework-mediaplayer", marker = "platform_release >= '16.0'" }, - { name = "pyobjc-framework-mediatoolbox", marker = "platform_release >= '13.0'" }, - { name = "pyobjc-framework-metal", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-metalfx", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-metalkit", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-metalperformanceshaders", marker = "platform_release >= '17.0'" }, - { name = "pyobjc-framework-metalperformanceshadersgraph", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-metrickit", marker = "platform_release >= '21.0'" }, - { name = "pyobjc-framework-mlcompute", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-modelio", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-multipeerconnectivity", marker = "platform_release >= '14.0'" }, - { name = "pyobjc-framework-naturallanguage", marker = "platform_release >= '18.0'" }, - { name = "pyobjc-framework-netfs", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-network", marker = "platform_release >= '18.0'" }, - { name = "pyobjc-framework-networkextension", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-notificationcenter", marker = "platform_release >= '14.0'" }, - { name = "pyobjc-framework-opendirectory", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-osakit" }, - { name = "pyobjc-framework-oslog", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-passkit", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-pencilkit", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-phase", marker = "platform_release >= '21.0'" }, - { name = "pyobjc-framework-photos", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-photosui", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-preferencepanes" }, - { name = "pyobjc-framework-pushkit", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-quartz" }, - { name = "pyobjc-framework-quicklookthumbnailing", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-replaykit", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-safariservices", marker = "platform_release >= '16.0'" }, - { name = "pyobjc-framework-safetykit", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-scenekit", marker = "platform_release >= '11.0'" }, - { name = "pyobjc-framework-screencapturekit", marker = "platform_release >= '21.4'" }, - { name = "pyobjc-framework-screensaver" }, - { name = "pyobjc-framework-screentime", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-scriptingbridge", marker = "platform_release >= '9.0'" }, - { name = "pyobjc-framework-searchkit" }, - { name = "pyobjc-framework-security" }, - { name = "pyobjc-framework-securityfoundation" }, - { name = "pyobjc-framework-securityinterface" }, - { name = "pyobjc-framework-sensitivecontentanalysis", marker = "platform_release >= '23.0'" }, - { name = "pyobjc-framework-servicemanagement", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-sharedwithyou", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-sharedwithyoucore", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-shazamkit", marker = "platform_release >= '21.0'" }, - { name = "pyobjc-framework-social", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-soundanalysis", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-speech", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-spritekit", marker = "platform_release >= '13.0'" }, - { name = "pyobjc-framework-storekit", marker = "platform_release >= '11.0'" }, - { name = "pyobjc-framework-symbols", marker = "platform_release >= '23.0'" }, - { name = "pyobjc-framework-syncservices" }, - { name = "pyobjc-framework-systemconfiguration" }, - { name = "pyobjc-framework-systemextensions", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-threadnetwork", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-uniformtypeidentifiers", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-usernotifications", marker = "platform_release >= '18.0'" }, - { name = "pyobjc-framework-usernotificationsui", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-videosubscriberaccount", marker = "platform_release >= '18.0'" }, - { name = "pyobjc-framework-videotoolbox", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-virtualization", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-vision", marker = "platform_release >= '17.0'" }, - { name = "pyobjc-framework-webkit" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-accessibility", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-accounts", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-addressbook", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-adservices", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-adsupport", marker = "platform_release >= '18.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-applescriptkit", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-applescriptobjc", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-applicationservices", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-apptrackingtransparency", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-audiovideobridging", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-authenticationservices", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-automaticassessmentconfiguration", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-automator", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-avfoundation", marker = "platform_release >= '11.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-avkit", marker = "platform_release >= '13.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-avrouting", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-backgroundassets", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-browserenginekit", marker = "platform_release >= '23.4' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-businesschat", marker = "platform_release >= '18.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-calendarstore", marker = "platform_release >= '9.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-callkit", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-carbon", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cfnetwork", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cinematic", marker = "platform_release >= '23.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-classkit", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cloudkit", marker = "platform_release >= '14.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-collaboration", marker = "platform_release >= '9.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-colorsync", marker = "platform_release >= '17.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-contacts", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-contactsui", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coreaudio", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coreaudiokit", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-corebluetooth", marker = "platform_release >= '14.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coredata", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-corehaptics", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-corelocation", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coremedia", marker = "platform_release >= '11.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coremediaio", marker = "platform_release >= '11.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coremidi", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coreml", marker = "platform_release >= '17.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coremotion", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coreservices", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-corespotlight", marker = "platform_release >= '17.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coretext", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-corewlan", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cryptotokenkit", marker = "platform_release >= '14.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-datadetection", marker = "platform_release >= '21.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-devicecheck", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-devicediscoveryextension", marker = "platform_release >= '24.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-dictionaryservices", marker = "platform_release >= '9.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-discrecording", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-discrecordingui", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-diskarbitration", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-dvdplayback", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-eventkit", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-exceptionhandling", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-executionpolicy", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-extensionkit", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-externalaccessory", marker = "platform_release >= '17.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-fileprovider", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-fileproviderui", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-findersync", marker = "platform_release >= '14.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-fsevents", marker = "platform_release >= '9.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-gamecenter", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-gamecontroller", marker = "platform_release >= '13.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-gamekit", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-gameplaykit", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-healthkit", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-imagecapturecore", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-inputmethodkit", marker = "platform_release >= '9.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-installerplugins", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-instantmessage", marker = "platform_release >= '9.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-intents", marker = "platform_release >= '16.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-intentsui", marker = "platform_release >= '21.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-iobluetooth", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-iobluetoothui", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-iosurface", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-ituneslibrary", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-kernelmanagement", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-latentsemanticmapping", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-launchservices", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-libdispatch", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-libxpc", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-linkpresentation", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-localauthentication", marker = "platform_release >= '14.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-localauthenticationembeddedui", marker = "platform_release >= '21.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-mailkit", marker = "platform_release >= '21.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-mapkit", marker = "platform_release >= '13.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-mediaaccessibility", marker = "platform_release >= '13.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-mediaextension", marker = "platform_release >= '24.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-medialibrary", marker = "platform_release >= '13.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-mediaplayer", marker = "platform_release >= '16.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-mediatoolbox", marker = "platform_release >= '13.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-metal", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-metalfx", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-metalkit", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-metalperformanceshaders", marker = "platform_release >= '17.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-metalperformanceshadersgraph", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-metrickit", marker = "platform_release >= '21.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-mlcompute", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-modelio", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-multipeerconnectivity", marker = "platform_release >= '14.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-naturallanguage", marker = "platform_release >= '18.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-netfs", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-network", marker = "platform_release >= '18.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-networkextension", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-notificationcenter", marker = "platform_release >= '14.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-opendirectory", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-osakit", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-oslog", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-passkit", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-pencilkit", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-phase", marker = "platform_release >= '21.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-photos", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-photosui", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-preferencepanes", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-pushkit", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quicklookthumbnailing", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-replaykit", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-safariservices", marker = "platform_release >= '16.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-safetykit", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-scenekit", marker = "platform_release >= '11.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-screencapturekit", marker = "platform_release >= '21.4' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-screensaver", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-screentime", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-scriptingbridge", marker = "platform_release >= '9.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-searchkit", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-security", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-securityfoundation", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-securityinterface", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-sensitivecontentanalysis", marker = "platform_release >= '23.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-servicemanagement", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-sharedwithyou", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-sharedwithyoucore", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-shazamkit", marker = "platform_release >= '21.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-social", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-soundanalysis", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-speech", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-spritekit", marker = "platform_release >= '13.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-storekit", marker = "platform_release >= '11.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-symbols", marker = "platform_release >= '23.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-syncservices", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-systemconfiguration", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-systemextensions", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-threadnetwork", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-uniformtypeidentifiers", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-usernotifications", marker = "platform_release >= '18.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-usernotificationsui", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-videosubscriberaccount", marker = "platform_release >= '18.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-videotoolbox", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-virtualization", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-vision", marker = "platform_release >= '17.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-webkit", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e1/d6/27b1c9a02f6cb4954984ce1a0239618e52f78c329c7e7450bf1f219b0f0a/pyobjc-11.0.tar.gz", hash = "sha256:a8f7baed65797f67afd46290b02f652c23f4b158ddf960bce0441b78f6803418", size = 11044 } wheels = [ @@ -1983,9 +2018,9 @@ name = "pyobjc-framework-accessibility" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b5/61/7484cc4ad3aa7854cd4c969379a5f044261259d08f7c20b6718493b484f9/pyobjc_framework_accessibility-11.0.tar.gz", hash = "sha256:097450c641fa9ac665199762e77867f2a82775be2f749b8fa69223b828f60656", size = 44597 } wheels = [ @@ -1998,8 +2033,8 @@ name = "pyobjc-framework-accounts" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c2/fa/b64f3f02e0a8b189dc07c391546e2dbe30ef1b3515d1427cdab743545b90/pyobjc_framework_accounts-11.0.tar.gz", hash = "sha256:afc4ae277be1e3e1f90269001c2fd886093a5465e365d7f9a3a0af3e17f06210", size = 17340 } wheels = [ @@ -2012,8 +2047,8 @@ name = "pyobjc-framework-addressbook" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/68/ef/5b5f6b61907ae43509fbf1654e043115d9a64d97efdc28fbb90d06c199f6/pyobjc_framework_addressbook-11.0.tar.gz", hash = "sha256:87073c85bb342eb27faa6eceb7a0e8a4c1e32ad1f2b62bb12dafb5e7b9f15837", size = 97116 } wheels = [ @@ -2026,8 +2061,8 @@ name = "pyobjc-framework-adservices" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/51/7c/0c6e01f83b0c5c7968564a40146f4d07080df278457bdb5a982c8f26a74d/pyobjc_framework_adservices-11.0.tar.gz", hash = "sha256:d2e1a2f395e93e1bbe754ab0d76ce1d64c0d3928472634437e0382eafc6765cd", size = 12732 } wheels = [ @@ -2040,8 +2075,8 @@ name = "pyobjc-framework-adsupport" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0c/07/b8b5f741d1e2cad97100444b255e6ecaca3668e7414039981799aa330035/pyobjc_framework_adsupport-11.0.tar.gz", hash = "sha256:20eb8a683d34fb7a6efeceaf964a24b88c3434875c44f66db5e1b609e678043a", size = 12819 } wheels = [ @@ -2054,8 +2089,8 @@ name = "pyobjc-framework-applescriptkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/14/c3/d7f9a33de7ab8e3950350e0862214e66f27ed6bff1a491bc391c377ab83e/pyobjc_framework_applescriptkit-11.0.tar.gz", hash = "sha256:4bafac4a036f0fb8ba01488b8e91d3ac861ce6e61154ffbd0b26f82b99779b50", size = 12638 } wheels = [ @@ -2068,8 +2103,8 @@ name = "pyobjc-framework-applescriptobjc" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fb/9f/bb4fdbcea418f8472d7a67d4d2e4a15fca11fed04648db5208b0fce84807/pyobjc_framework_applescriptobjc-11.0.tar.gz", hash = "sha256:baff9988b6e886aed0e76441358417707de9088be5733f22055fed7904ca1001", size = 12675 } wheels = [ @@ -2082,10 +2117,10 @@ name = "pyobjc-framework-applicationservices" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coretext" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coretext", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ba/fb/4e42573b0d3baa3fa18ec53614cf979f951313f1451e8f2e17df9429da1f/pyobjc_framework_applicationservices-11.0.tar.gz", hash = "sha256:d6ea18dfc7d5626a3ecf4ac72d510405c0d3a648ca38cae8db841acdebecf4d2", size = 224334 } wheels = [ @@ -2098,8 +2133,8 @@ name = "pyobjc-framework-apptrackingtransparency" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/36/40/c1c48ed49b5e55c7a635aa1e7ca41ffa1c5547e26243f26489c4768cd730/pyobjc_framework_apptrackingtransparency-11.0.tar.gz", hash = "sha256:cd5c834b5b19c21ad6c317ba5d29f30a8d0ae5d14e7cf557da22abc0850f1e91", size = 13385 } wheels = [ @@ -2112,8 +2147,8 @@ name = "pyobjc-framework-audiovideobridging" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/89/5f/0bd5beded0415b53f443da804410eda6a53e1bc64f8779ed9a592719da8c/pyobjc_framework_audiovideobridging-11.0.tar.gz", hash = "sha256:dbc45b06418dd780c365956fdfd69d007436b5ee54c51e671196562eb8290ba6", size = 72418 } wheels = [ @@ -2126,8 +2161,8 @@ name = "pyobjc-framework-authenticationservices" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/31/0f/2de0d941e9c9b2eb1ce8b22eb31adc7227badfe1e53f615431d3a7fdcd48/pyobjc_framework_authenticationservices-11.0.tar.gz", hash = "sha256:6a060ce651df142e8923d1383449bc6f2c7f5eb0b517152dac609bde3901064e", size = 140036 } wheels = [ @@ -2140,8 +2175,8 @@ name = "pyobjc-framework-automaticassessmentconfiguration" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/09/d5/5febfee260b88e426c7e799cc95990818feeaa9f740fb9dd516559c96520/pyobjc_framework_automaticassessmentconfiguration-11.0.tar.gz", hash = "sha256:5d3691af2b94e44ca594b6791556e15a9f0a3f9432df51cb891f5f859a65e467", size = 24420 } wheels = [ @@ -2154,8 +2189,8 @@ name = "pyobjc-framework-automator" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/25/1b/1ba4eb296c3915f2e367e45470cb310a9c78b4dd65a37bd522f458f245aa/pyobjc_framework_automator-11.0.tar.gz", hash = "sha256:412d330f8c6f30066cad15e1bdecdc865510bbce469cc7d9477384c4e9f2550f", size = 200905 } wheels = [ @@ -2168,11 +2203,11 @@ name = "pyobjc-framework-avfoundation" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coreaudio" }, - { name = "pyobjc-framework-coremedia" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coreaudio", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/76/06/018ad0e2a38dbdbc5c126d7ce37488c4d581d4e2a2b9ef678162bb36d5f6/pyobjc_framework_avfoundation-11.0.tar.gz", hash = "sha256:269a592bdaf8a16948d8935f0cf7c8cb9a53e7ea609a963ada0e55f749ddb530", size = 871064 } wheels = [ @@ -2185,9 +2220,9 @@ name = "pyobjc-framework-avkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/de/79/5b2fcb94b051da32a24b54bb0d90b1d01b190e1402b6303747de47fb17ac/pyobjc_framework_avkit-11.0.tar.gz", hash = "sha256:5fa40919320277b820df3e4c6e84cba91ef7221a28f4eb5374e3dbd80d1e521a", size = 46311 } wheels = [ @@ -2200,8 +2235,8 @@ name = "pyobjc-framework-avrouting" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d5/80/63680dc7788bc3573a20fc5421dfcf606970a0cd3b2457829d9b66603ae0/pyobjc_framework_avrouting-11.0.tar.gz", hash = "sha256:54ec9ea0b5adb5149b554e23c07c6b4f4bdb2892ca2ed7b3e88a5de936313025", size = 20561 } wheels = [ @@ -2214,8 +2249,8 @@ name = "pyobjc-framework-backgroundassets" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a3/17/83b873069b0c0763365de88648ad4a2472e9e96fcac39fa534f3633552e8/pyobjc_framework_backgroundassets-11.0.tar.gz", hash = "sha256:9488c3f86bf427898a88b7100e77200c08a487a35c75c1b5735bd69c57ba38cb", size = 23658 } wheels = [ @@ -2228,11 +2263,11 @@ name = "pyobjc-framework-browserenginekit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coreaudio" }, - { name = "pyobjc-framework-coremedia" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coreaudio", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9f/2e/df3d2f7e53132d398c2922d331dd1d2aa352997a1a4a1390e59db51c1d13/pyobjc_framework_browserenginekit-11.0.tar.gz", hash = "sha256:51971527f5103c0e09a4ef438c352ebb037fcad8971f8420a781c72ee421f758", size = 31352 } wheels = [ @@ -2245,8 +2280,8 @@ name = "pyobjc-framework-businesschat" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5a/f2/4541989f2c9c5fc3cdfc94ebf31fc6619554b6c22dafdbb57f866a392bc1/pyobjc_framework_businesschat-11.0.tar.gz", hash = "sha256:20fe1c8c848ef3c2e132172d9a007a8aa65b08875a9ca5c27afbfc4396b16dbb", size = 12953 } wheels = [ @@ -2259,8 +2294,8 @@ name = "pyobjc-framework-calendarstore" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9f/d3/722c1b16c7d9bdd5c408735c15193e8396f2d22ab6410b0af4569f39c46e/pyobjc_framework_calendarstore-11.0.tar.gz", hash = "sha256:40173f729df56b70ec14f9680962a248c3ce7b4babb46e8b0d760a13975ef174", size = 68475 } wheels = [ @@ -2273,8 +2308,8 @@ name = "pyobjc-framework-callkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e4/0a/9d39ebac92006960b8059f664d8eb7b9cdb8763fe4e8102b2d24b853004f/pyobjc_framework_callkit-11.0.tar.gz", hash = "sha256:52e44a05d0357558e1479977ed2bcb325fabc8d337f641f0249178b5b491fc59", size = 39720 } wheels = [ @@ -2287,8 +2322,8 @@ name = "pyobjc-framework-carbon" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/22/15/51964f36a8ae1002b16d213d2e5ba11cc861bdd9369f1e3f116350d788c5/pyobjc_framework_carbon-11.0.tar.gz", hash = "sha256:476f690f0b34aa9e4cb3923e61481aefdcf33e38ec6087b530a94871eee2b914", size = 37538 } wheels = [ @@ -2301,8 +2336,8 @@ name = "pyobjc-framework-cfnetwork" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4f/36/7cebdfb621c7d46eeab3173256bc2e1cba1bbbbe6c0ac8aeb9a4fe2a4627/pyobjc_framework_cfnetwork-11.0.tar.gz", hash = "sha256:eb742fc6a42b248886ff09c3cf247d56e65236864bbea4264e70af8377948d96", size = 78532 } wheels = [ @@ -2315,11 +2350,11 @@ name = "pyobjc-framework-cinematic" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-avfoundation" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coremedia" }, - { name = "pyobjc-framework-metal" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-avfoundation", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-metal", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/33/ef/b5857d567cd6e0366f61c381ebea52383b98d1ac03341f39e779a085812a/pyobjc_framework_cinematic-11.0.tar.gz", hash = "sha256:94a2de8bf3f38bd190311b6bf98d1e2cea7888840b3ce3aa92e464c0216a5cdb", size = 25740 } wheels = [ @@ -2332,8 +2367,8 @@ name = "pyobjc-framework-classkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f5/81/126075eaf5ccf254ddb4cfd99d92a266c30803c5b4572ea3a920fd85e850/pyobjc_framework_classkit-11.0.tar.gz", hash = "sha256:dc5b3856612cafdc7071fbebc252b8908dbf2433e0e5ddb15a0bcd1ee282d27c", size = 39301 } wheels = [ @@ -2346,11 +2381,11 @@ name = "pyobjc-framework-cloudkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-accounts" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coredata" }, - { name = "pyobjc-framework-corelocation" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-accounts", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coredata", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-corelocation", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/89/6c/b0709fed7fc5a1e81de311b9273bb7ba3820a636f8ba880e90510bb6d460/pyobjc_framework_cloudkit-11.0.tar.gz", hash = "sha256:e3f6bf2c3358dd394174b1e69fcec6859951fcd15f6433c6fa3082e3b7e2656d", size = 123034 } wheels = [ @@ -2363,7 +2398,7 @@ name = "pyobjc-framework-cocoa" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c5/32/53809096ad5fc3e7a2c5ddea642590a5f2cb5b81d0ad6ea67fdb2263d9f9/pyobjc_framework_cocoa-11.0.tar.gz", hash = "sha256:00346a8cb81ad7b017b32ff7bf596000f9faa905807b1bd234644ebd47f692c5", size = 6173848 } wheels = [ @@ -2376,8 +2411,8 @@ name = "pyobjc-framework-collaboration" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6b/ee/1f6893eb882af5ecc6a6f4182b2ec85df777c4bc6b9a20a6b42c23abff3f/pyobjc_framework_collaboration-11.0.tar.gz", hash = "sha256:9f53929dd6d5b1a5511494432bf83807041c6f8b9ab6cf6ff184eee0b6f8226f", size = 17084 } wheels = [ @@ -2390,8 +2425,8 @@ name = "pyobjc-framework-colorsync" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9a/24/397a80cd2313cc9e1b73b9acb1de66b740bbece4fe87ed4ea158de8fcef8/pyobjc_framework_colorsync-11.0.tar.gz", hash = "sha256:4f531f6075d9cc4b9d426620a1b04d3aaeb56b5ff178d0a6b0e93d068a5db0d2", size = 39249 } wheels = [ @@ -2404,8 +2439,8 @@ name = "pyobjc-framework-contacts" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f5/a2/89053853b28c1f2f2e69092d3e81b7c26073bc8396fc87772b3b1bfb9d57/pyobjc_framework_contacts-11.0.tar.gz", hash = "sha256:fc215baa9f66dbf9ffa1cb8170d102a3546cfd708b2b42de4e9d43645aec03d9", size = 84253 } wheels = [ @@ -2418,9 +2453,9 @@ name = "pyobjc-framework-contactsui" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-contacts" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-contacts", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/3f/67/122b16fd7f2da7f0f48c1d7fcaf0f1951253ddd5489d909a1b5fb80f3925/pyobjc_framework_contactsui-11.0.tar.gz", hash = "sha256:d0f2a4afea807fbe4db1518c4f81f0dc9aa1817fe7cb16115308fc00375a70db", size = 19486 } wheels = [ @@ -2433,8 +2468,8 @@ name = "pyobjc-framework-coreaudio" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/31/e6/3b7a8af3defec012d6cacf277fd8d5c3e254ceace63a05447dc1119f3a7e/pyobjc_framework_coreaudio-11.0.tar.gz", hash = "sha256:38b6b531381119be6998cf704d04c9ea475aaa33f6dd460e0584351475acd0ae", size = 140507 } wheels = [ @@ -2447,9 +2482,9 @@ name = "pyobjc-framework-coreaudiokit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coreaudio" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coreaudio", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ef/1a/604cac8d992b6e66adbb98edb1f65820116f5d74d8decd6d43898ae2929d/pyobjc_framework_coreaudiokit-11.0.tar.gz", hash = "sha256:1a4c3de4a02b0dfa7410c012c7f0939edd2e127d439fb934aeafc68450615f1d", size = 21450 } wheels = [ @@ -2462,8 +2497,8 @@ name = "pyobjc-framework-corebluetooth" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/93/74/66a62a36da9db5924ee15de6fe1eb544930609b307b3bfbc021b5cf43781/pyobjc_framework_corebluetooth-11.0.tar.gz", hash = "sha256:1dcb7c039c2efa7c72dc14cdda80e677240b49fa38999941a77ee02ca142998d", size = 59797 } wheels = [ @@ -2476,8 +2511,8 @@ name = "pyobjc-framework-coredata" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/84/22/6787205b91cb6d526b6b472ebaa5baff275200774050a55b4b25d2bd957a/pyobjc_framework_coredata-11.0.tar.gz", hash = "sha256:b11acb51ff31cfb69a53f4e127996bf194bcac770e8fa67cb5ba3fb16a496058", size = 260029 } wheels = [ @@ -2490,8 +2525,8 @@ name = "pyobjc-framework-corehaptics" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2a/b8/66481497362171e7ad42fc8fcc0272c04b95a707c5c1e7e8f8a8bfe58917/pyobjc_framework_corehaptics-11.0.tar.gz", hash = "sha256:1949b56ac0bd4219eb04c466cdd0f7f93d6826ed92ee61f01a4b5e98139ee039", size = 42956 } wheels = [ @@ -2504,8 +2539,8 @@ name = "pyobjc-framework-corelocation" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0a/2d/b21ca49a34db49390420a9d7d05fd9eb89850dbec0a555c9ee408f52609c/pyobjc_framework_corelocation-11.0.tar.gz", hash = "sha256:05055c3b567f7f8f796845da43fb755d84d630909b927a39f25cf706ef52687d", size = 103955 } wheels = [ @@ -2518,8 +2553,8 @@ name = "pyobjc-framework-coremedia" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/02/60/7c7b9f13c94910882de6cc08f48a52cce9739e75cc3b3b6de5c857e6536a/pyobjc_framework_coremedia-11.0.tar.gz", hash = "sha256:a414db97ba30b43c9dd96213459d6efb169f9e92ce1ad7a75516a679b181ddfb", size = 249161 } wheels = [ @@ -2532,8 +2567,8 @@ name = "pyobjc-framework-coremediaio" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a1/59/904af57d302caa4c20d3bfebb9fb9300ccc3c396134460821c9f1e8ab65b/pyobjc_framework_coremediaio-11.0.tar.gz", hash = "sha256:7d652cf1a2a75c78ea6e8dbc7fc8b782bfc0f07eafc84b700598172c82f373d8", size = 107856 } wheels = [ @@ -2546,8 +2581,8 @@ name = "pyobjc-framework-coremidi" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/96/90/d004cdf4c52b8b16842e15135495de882d743b4f0217946bd8ae1a920173/pyobjc_framework_coremidi-11.0.tar.gz", hash = "sha256:acace4448b3e4802ab5dd75bbf875aae5e1f6c8cab2b2f1d58af20fc8b2a5a7f", size = 107342 } wheels = [ @@ -2560,8 +2595,8 @@ name = "pyobjc-framework-coreml" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2e/64/4f0a990ec0955fe9b88f1fa58303c8471c551996670216527b4ac559ed8f/pyobjc_framework_coreml-11.0.tar.gz", hash = "sha256:143a1f73a0ea0a0ea103f3175cb87a61bbcb98f70f85320ed4c61302b9156d58", size = 81452 } wheels = [ @@ -2574,8 +2609,8 @@ name = "pyobjc-framework-coremotion" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/be/79/5c4ff39a48f0dc0f764d1330b2360e9f31e3a32414e8690e7f20e4574e93/pyobjc_framework_coremotion-11.0.tar.gz", hash = "sha256:d1e7ca418897e35365d07c6fd5b5d625a3c44261b6ce46dcf80787f634ad6fa5", size = 66508 } wheels = [ @@ -2588,9 +2623,9 @@ name = "pyobjc-framework-coreservices" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-fsevents" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-fsevents", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ca/b5/19c096b9938d6e2fdb1b436f21ad989b77dbeb4e59b3db4bd344800fa1e8/pyobjc_framework_coreservices-11.0.tar.gz", hash = "sha256:ac96954f1945a1153bdfef685611665749eaa8016b5af6f34bd56a274952b03a", size = 1244406 } wheels = [ @@ -2603,8 +2638,8 @@ name = "pyobjc-framework-corespotlight" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fc/6a/6707d7ef339b9ad2dd0994d1df42969ee3b231f2d098f3377d40aed60b4f/pyobjc_framework_corespotlight-11.0.tar.gz", hash = "sha256:a96c9b4ba473bc3ee19afa01a9af989458e6a56e9656c2cdea1850d2b13720e6", size = 86130 } wheels = [ @@ -2617,9 +2652,9 @@ name = "pyobjc-framework-coretext" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9d/e8/9b68dc788828e38143a3e834e66346713751cb83d7f0955016323005c1a2/pyobjc_framework_coretext-11.0.tar.gz", hash = "sha256:a68437153e627847e3898754dd3f13ae0cb852246b016a91f9c9cbccb9f91a43", size = 274222 } wheels = [ @@ -2632,8 +2667,8 @@ name = "pyobjc-framework-corewlan" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2e/a9/cda522b270adb75d62bae447b2131da62912b5eda058a07e3a433689116f/pyobjc_framework_corewlan-11.0.tar.gz", hash = "sha256:8803981d64e3eb4fa0ea56657a9b98e4004de5a84d56e32e5444815d8ed6fa6f", size = 65254 } wheels = [ @@ -2646,8 +2681,8 @@ name = "pyobjc-framework-cryptotokenkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b8/72/b871fa5476479e4a22a4a0e971fb4724b0eb94c721365539ad55f4dc3135/pyobjc_framework_cryptotokenkit-11.0.tar.gz", hash = "sha256:a1bbfe9170c35cb427d39167af55aefea651c5c8a45c0de60226dae04b61a6b1", size = 58734 } wheels = [ @@ -2660,8 +2695,8 @@ name = "pyobjc-framework-datadetection" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/33/6b/b896feb16e914dc81b6ed6cdbd0b6e6390eaafc80fff5297ec17eb0bd716/pyobjc_framework_datadetection-11.0.tar.gz", hash = "sha256:9967555151892f8400cffac86e8656f2cb8d7866963fdee255e0747fa1386533", size = 13738 } wheels = [ @@ -2674,8 +2709,8 @@ name = "pyobjc-framework-devicecheck" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/de/f8/237a92dd9ba8a88b7027f78cba83e61b0011bfc2a49351ecaa177233f639/pyobjc_framework_devicecheck-11.0.tar.gz", hash = "sha256:66cff0323dc8eef1b76d60f9c9752684f11e534ebda60ecbf6858a9c73553f64", size = 14198 } wheels = [ @@ -2688,8 +2723,8 @@ name = "pyobjc-framework-devicediscoveryextension" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e1/48/178a1879109128f34334fdae2fe4463c7620f169593bea96704f347d945e/pyobjc_framework_devicediscoveryextension-11.0.tar.gz", hash = "sha256:576dac3f418cfc4f71020a45f06231d14e4b2a8e182ef0020dd9da3cf238d02f", size = 14511 } wheels = [ @@ -2702,8 +2737,8 @@ name = "pyobjc-framework-dictionaryservices" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-coreservices" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coreservices", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d8/cf/2913c7df737eb8519acb7ef6429127e40d6c334415e38cfa18d6481150eb/pyobjc_framework_dictionaryservices-11.0.tar.gz", hash = "sha256:6b5f27c75424860f169e7c7e182fabffdba22854fedb8023de180e8770661dce", size = 10823 } wheels = [ @@ -2716,8 +2751,8 @@ name = "pyobjc-framework-discrecording" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/96/cc/f36612b67ca1fff7659d7933b563dce61f8c84dad0bf79fab08bb34949ad/pyobjc_framework_discrecording-11.0.tar.gz", hash = "sha256:6bdc533f067d049ea5032f65af70b5cdab68673574ac32dacb46509a9411d256", size = 122426 } wheels = [ @@ -2730,9 +2765,9 @@ name = "pyobjc-framework-discrecordingui" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-discrecording" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-discrecording", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d4/6b/3c120c59a939854dd4b7a162fad47011375c5ba00a12940f7217aea90eeb/pyobjc_framework_discrecordingui-11.0.tar.gz", hash = "sha256:bec8a252fd2022dce6c58b1f3366a7295efb0c7c77817f11f9efcce70527d7a2", size = 19614 } wheels = [ @@ -2745,8 +2780,8 @@ name = "pyobjc-framework-diskarbitration" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/43/fb/5d3ff093144f499904b1e1bce18d010fe2171b9be62b4679d3dda8b3ad19/pyobjc_framework_diskarbitration-11.0.tar.gz", hash = "sha256:1c3e21398b366a1ce96cf68501a2e415f5ccad4b43a3e7cc901e09e896dfb545", size = 20096 } wheels = [ @@ -2759,8 +2794,8 @@ name = "pyobjc-framework-dvdplayback" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c0/89/89ebee4863fd6f173bff9373b5bda4ffa87eba6197337617ab086e23c7d5/pyobjc_framework_dvdplayback-11.0.tar.gz", hash = "sha256:9a005f441afbc34aea301857e166fd650d82762a75d024253e18d1102b21b2f8", size = 64798 } wheels = [ @@ -2773,8 +2808,8 @@ name = "pyobjc-framework-eventkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/54/13/38a98e5cee62e1655d84cfb88cad54fdec4ec272b5fd0c5ac3fc21e33e49/pyobjc_framework_eventkit-11.0.tar.gz", hash = "sha256:3d412203a510b3d62a5eb0987406e0951b13ed39c3351c0ec874afd72496627c", size = 75399 } wheels = [ @@ -2787,8 +2822,8 @@ name = "pyobjc-framework-exceptionhandling" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cc/46/6c2c4805697a0cfb8413eb7bc6901298e7a1febd49bb1ea960274fc33af3/pyobjc_framework_exceptionhandling-11.0.tar.gz", hash = "sha256:b11562c6eeaef5d8d43e9d817cf50feceb02396e5eb6a7f61df2c0cec93d912b", size = 18157 } wheels = [ @@ -2801,8 +2836,8 @@ name = "pyobjc-framework-executionpolicy" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ab/91/2e4cacbdabf01bc1207817edacc814b6bc486df12e857a8d86964d98fef4/pyobjc_framework_executionpolicy-11.0.tar.gz", hash = "sha256:de953a8acae98079015b19e75ec8154a311ac1a70fb6d885e17fab09464c98a9", size = 13753 } wheels = [ @@ -2815,8 +2850,8 @@ name = "pyobjc-framework-extensionkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/22/98/803e3cb000dac227eb0d223802a0aeb052d34a741e572d9584e7d83afca7/pyobjc_framework_extensionkit-11.0.tar.gz", hash = "sha256:82d9e79532e5a0ff0eadf1ccac236c5d3dca344e1090a0f3e88519faa24143c7", size = 19200 } wheels = [ @@ -2829,8 +2864,8 @@ name = "pyobjc-framework-externalaccessory" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/67/b0/ac0a02fe26e66c33fee751a65c1ed06bbd2934db8636e08bb491e8334bad/pyobjc_framework_externalaccessory-11.0.tar.gz", hash = "sha256:39e59331ced75cdcccf23bb5ffe0fa9d67e0c190c1da8887a0e4349b7e27584f", size = 22577 } wheels = [ @@ -2843,8 +2878,8 @@ name = "pyobjc-framework-fileprovider" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/44/fc/b8593d8645b9933e60a885f451d0c12d9c0e1b00e62121d8660d95852dff/pyobjc_framework_fileprovider-11.0.tar.gz", hash = "sha256:dcc3ac3c90117c1b8027ea5f26dad6fe5045f688ce3e60d07ece12ec56e17ab3", size = 78701 } wheels = [ @@ -2857,8 +2892,8 @@ name = "pyobjc-framework-fileproviderui" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-fileprovider" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-fileprovider", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/3d/9d/ca4aed36e6188623e9da633634af772f239bee74934322e1c19ae7b79a53/pyobjc_framework_fileproviderui-11.0.tar.gz", hash = "sha256:cf5c7d32b29d344b65217397eea7b1a2913ce52ce923c9e04135a7a298848d04", size = 13419 } wheels = [ @@ -2871,8 +2906,8 @@ name = "pyobjc-framework-findersync" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f6/e3/24df6e24b589073815be13f2943b93feb12afbf558f6e54c4033b57c29ee/pyobjc_framework_findersync-11.0.tar.gz", hash = "sha256:8dab3feff5debd6bc3746a21ded991716723d98713d1ba37cec1c5e2ad78ee63", size = 15295 } wheels = [ @@ -2885,8 +2920,8 @@ name = "pyobjc-framework-fsevents" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/82/37/4c09cc7b8678e2bb5b68ebc62e817eb88c409b1c41bdc1510d7d24a0372d/pyobjc_framework_fsevents-11.0.tar.gz", hash = "sha256:e01dab04704a518e4c3e1f7d8722819a4f228d5082978e11618aa7abba3883fe", size = 29078 } wheels = [ @@ -2899,8 +2934,8 @@ name = "pyobjc-framework-gamecenter" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7f/3b/e66caebc948d9fe3b2671659caab220aff6d5e80ac25442d83331b523d23/pyobjc_framework_gamecenter-11.0.tar.gz", hash = "sha256:18a05500dbcf2cca4a0f05839ec010c76ee08ab65b65020c9538a31feb274483", size = 31459 } wheels = [ @@ -2913,8 +2948,8 @@ name = "pyobjc-framework-gamecontroller" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fa/30/02ca5a4fb911acf3e8018abcbd29631a842aeac02958ae91fab1acb13ad1/pyobjc_framework_gamecontroller-11.0.tar.gz", hash = "sha256:6d62f4493d634eba03a43a14c4d1e4511e1e3a2ca2e9cbefa6ae9278a272c1d0", size = 115318 } wheels = [ @@ -2927,9 +2962,9 @@ name = "pyobjc-framework-gamekit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/3f/df/c161460e5736a34f9b59aa0a3f2d6ad1d1cd9a913aa63c89c41a6ba3b6ae/pyobjc_framework_gamekit-11.0.tar.gz", hash = "sha256:29b5464ca78f0de62e6b6d56e80bbeccb96dc13820b6d5b4e835ab1cc127e5b9", size = 164394 } wheels = [ @@ -2942,9 +2977,9 @@ name = "pyobjc-framework-gameplaykit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-spritekit" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-spritekit", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/41/f0/980c4fc3c594d9726b7eb6ae83f73127b22560e1541c7d272d23d17fdf0d/pyobjc_framework_gameplaykit-11.0.tar.gz", hash = "sha256:90eeec464fba992d75a406ccbddb35ed7420a4f5226f19c018982fa3ba7bf431", size = 72837 } wheels = [ @@ -2957,8 +2992,8 @@ name = "pyobjc-framework-healthkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7b/2f/d79d2ec7c23bfc94bfaa7b7c6f6487a8bffdb73263eea6900aab56135889/pyobjc_framework_healthkit-11.0.tar.gz", hash = "sha256:e78ccb05f747ae3e70b5d73522030b7ba01ef2d390155fba7d50c1c614ae241f", size = 201558 } wheels = [ @@ -2971,8 +3006,8 @@ name = "pyobjc-framework-imagecapturecore" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/38/fe/db1fc3ffd784a9010070cd87a05d7fd2542c400395589341fab5970a01e1/pyobjc_framework_imagecapturecore-11.0.tar.gz", hash = "sha256:f5d185d8c8b564f8b4a815381bcdb424b10d203ba5bdf0fc887085e007df6f7a", size = 99935 } wheels = [ @@ -2985,8 +3020,8 @@ name = "pyobjc-framework-inputmethodkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e7/e9/13d007285582e598903264a7d25cc6771a2a52d6c2a96a68fe91db0844fb/pyobjc_framework_inputmethodkit-11.0.tar.gz", hash = "sha256:86cd648bf98c4e777c884b7f69ebcafba84866740430d297645bf388eee6ce52", size = 26684 } wheels = [ @@ -2999,8 +3034,8 @@ name = "pyobjc-framework-installerplugins" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f2/f3/0379655e8ea3566002768d5e7b3ccd72ca845390632a8dabf801348af3a7/pyobjc_framework_installerplugins-11.0.tar.gz", hash = "sha256:88ec84e6999e8b2df874758b09878504a4fbfc8471cf3cd589d57e556f5b916e", size = 27687 } wheels = [ @@ -3013,9 +3048,9 @@ name = "pyobjc-framework-instantmessage" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/08/4d/6810a1f2039ff24d9498858b3ebb46357d4091aa5cec9ff4e41bbcdb25de/pyobjc_framework_instantmessage-11.0.tar.gz", hash = "sha256:ec5c4c70c9b0e61ae82888067246e4f931e700d625b3c42604e54759d4fbf65c", size = 34027 } wheels = [ @@ -3028,8 +3063,8 @@ name = "pyobjc-framework-intents" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/56/88/07e47b0c5c46fe97c23c883ae7a053c2ca6f6fd6afe851d1c2c784644f0f/pyobjc_framework_intents-11.0.tar.gz", hash = "sha256:6405c816dfed8ffa8b3f8b0fae75f61d64787dbae8db1c475bb4450cf8fdf6b5", size = 447921 } wheels = [ @@ -3042,8 +3077,8 @@ name = "pyobjc-framework-intentsui" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-intents" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-intents", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ee/96/3b3b367f70a4d0a60d2c6251e4a1f4bf470945ae939e0ba20e6d56d10c7a/pyobjc_framework_intentsui-11.0.tar.gz", hash = "sha256:4ce04f926c823fbc1fba7d9c5b33d512b514396719e6bc50ef65b82774e42bc5", size = 20774 } wheels = [ @@ -3056,8 +3091,8 @@ name = "pyobjc-framework-iobluetooth" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1e/46/62913f8e5ac307b154b3dd50a7a0b167c9d7ac2a579223e33208c141c387/pyobjc_framework_iobluetooth-11.0.tar.gz", hash = "sha256:869f01f573482da92674abbae4a154143e993b1fe4b2c3523f9e0f9c48b798d4", size = 300463 } wheels = [ @@ -3070,8 +3105,8 @@ name = "pyobjc-framework-iobluetoothui" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-iobluetooth" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-iobluetooth", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/76/55/d194de8cfa63c96970e6c90c35e80ce3fceb42934a85d3728736a0e416ff/pyobjc_framework_iobluetoothui-11.0.tar.gz", hash = "sha256:a583758d3e54149ee2dcf00374685aa99e8ae407e044f7c378acc002f9f27e63", size = 23091 } wheels = [ @@ -3084,8 +3119,8 @@ name = "pyobjc-framework-iosurface" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/fb/91/ae9ca9e1a777eb786d9d43649437d01d24386736cffe9bb2f504b57e8db6/pyobjc_framework_iosurface-11.0.tar.gz", hash = "sha256:24da8d1cf9356717b1c7e75a1c61e9a9417b62f051d13423a4a7b0978d3dcda5", size = 20555 } wheels = [ @@ -3098,8 +3133,8 @@ name = "pyobjc-framework-ituneslibrary" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/41/fe/881ab1058d795fe68ccc1e14df0d5e161601dced15d3be84105ecc44bae6/pyobjc_framework_ituneslibrary-11.0.tar.gz", hash = "sha256:2e15dcfbb9d5e95634ddff153de159a28f5879f1a13fdf95504e011773056c6e", size = 47647 } wheels = [ @@ -3112,8 +3147,8 @@ name = "pyobjc-framework-kernelmanagement" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4a/ea/8ef534fce78817fc577f18de2b34e363873f785894f2bbbfc694823f5088/pyobjc_framework_kernelmanagement-11.0.tar.gz", hash = "sha256:812479d5f85eae27aeeaa22f64c20b926b28b5b9b2bf31c8eab9496d3e038028", size = 12794 } wheels = [ @@ -3126,8 +3161,8 @@ name = "pyobjc-framework-latentsemanticmapping" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/42/29/8838eefeb82da95931134b06624364812dedf7e9cc905f36d95d497f2904/pyobjc_framework_latentsemanticmapping-11.0.tar.gz", hash = "sha256:6f578c3e0a171706bdbfcfc2c572a8059bf8039d22c1475df13583749a35cec1", size = 17704 } wheels = [ @@ -3140,8 +3175,8 @@ name = "pyobjc-framework-launchservices" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-coreservices" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coreservices", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/da/59/eb847389224c670c885ae3d008b1ffe3b996bbe094b43e49dfa84f3947a9/pyobjc_framework_launchservices-11.0.tar.gz", hash = "sha256:7c5c8a8cec013e2cb3fa82a167ca2d61505c36a79f75c718f3f913e597f9ffee", size = 20691 } wheels = [ @@ -3154,8 +3189,8 @@ name = "pyobjc-framework-libdispatch" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ab/33/4ec96a9edd37948f09e94635852c2db695141430cc1adc7b25968e1f3a95/pyobjc_framework_libdispatch-11.0.tar.gz", hash = "sha256:d22df11b07b1c3c8e7cfc4ba9e876a95c19f44acd36cf13d40c5cccc1ffda04b", size = 53496 } wheels = [ @@ -3168,8 +3203,8 @@ name = "pyobjc-framework-libxpc" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b9/7e/9fa73ce6925db9cfd8a6b45d97943af8fe59f92251e7fd201b6e4608c172/pyobjc_framework_libxpc-11.0.tar.gz", hash = "sha256:e0c336913ab6a526b036915aa9038de2a5281e696ac2d3db3347b3040519c11d", size = 48627 } wheels = [ @@ -3182,9 +3217,9 @@ name = "pyobjc-framework-linkpresentation" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/95/5c/dac9fe4ad0a4076c863b5ac9925e751fc18c637ae411e4891c4b7558a5b3/pyobjc_framework_linkpresentation-11.0.tar.gz", hash = "sha256:bc4ace4aab4da4a4e4df10517bd478b6d51ebf00b423268ee8d9f356f9e87be9", size = 15231 } wheels = [ @@ -3197,9 +3232,9 @@ name = "pyobjc-framework-localauthentication" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-security" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-security", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ec/b1/bea4b5f8adbb69c0b34eddee63e052f35271cc630db43fbef6873352e21f/pyobjc_framework_localauthentication-11.0.tar.gz", hash = "sha256:eb55a3de647894092d6ed3f8f13fdc38e5dbf4850be320ea14dd2ac83176b298", size = 40020 } wheels = [ @@ -3212,9 +3247,9 @@ name = "pyobjc-framework-localauthenticationembeddedui" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-localauthentication" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-localauthentication", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e1/ee/821f2d2e9da4cba3dc47e50c8367c6405e91551fb7d8ec842858d5b1d45d/pyobjc_framework_localauthenticationembeddedui-11.0.tar.gz", hash = "sha256:7e9bf6df77ff12a4e827988d8578c15b4431694b2fcfd5b0dad5d7738757ee6a", size = 14204 } wheels = [ @@ -3227,8 +3262,8 @@ name = "pyobjc-framework-mailkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d8/79/9c9140f726ba14898762ddc19e7142724e0ce5930f08eb20f33f78b05be8/pyobjc_framework_mailkit-11.0.tar.gz", hash = "sha256:d08a2dcc95b5e7955c7c385fe6e018325113d02c007c4178d3fb3c9ab326c163", size = 32274 } wheels = [ @@ -3241,10 +3276,10 @@ name = "pyobjc-framework-mapkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-corelocation" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-corelocation", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/96/7e/ef86c6e218a58bb9497ce9754a77f12ffe01c4b3609279727b7d7e44655a/pyobjc_framework_mapkit-11.0.tar.gz", hash = "sha256:cd8a91df4c0b442fcf1b14d735e566a06b21b3f48a2a4afe269fca45bfa49117", size = 165080 } wheels = [ @@ -3257,8 +3292,8 @@ name = "pyobjc-framework-mediaaccessibility" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/81/8e/9fe2cb251ff6107a03bafa07f63b6593df145a2579fffb096023fb21b167/pyobjc_framework_mediaaccessibility-11.0.tar.gz", hash = "sha256:1298cc0128e1c0724e8f8e63a6167ea6809a985922c67399b997f8243de59ab4", size = 18671 } wheels = [ @@ -3271,10 +3306,10 @@ name = "pyobjc-framework-mediaextension" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-avfoundation" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coremedia" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-avfoundation", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/18/1f/e31d9431bc71077b09583ea863b3c91b7de9371d0cc17a8be99be8119daa/pyobjc_framework_mediaextension-11.0.tar.gz", hash = "sha256:ecd8a64939e1c16be005690117c21fd406fc04d3036e2adea7600d2a0c53f4ea", size = 57931 } wheels = [ @@ -3287,9 +3322,9 @@ name = "pyobjc-framework-medialibrary" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a8/a4/8c7d1635994800dc412a5db2c4b43ed499184651efcec0c8da3cf8e2bcc7/pyobjc_framework_medialibrary-11.0.tar.gz", hash = "sha256:692889fab1e479a9c207f0ff23c900dad5f47caf47c05cc995d9bb7c1e56e8b9", size = 18975 } wheels = [ @@ -3302,8 +3337,8 @@ name = "pyobjc-framework-mediaplayer" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-avfoundation" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-avfoundation", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a2/ce/3d2783f2f96ddf51bebcf6537a4a0f2a8a1fe4e520de218fc1b7c5b219ed/pyobjc_framework_mediaplayer-11.0.tar.gz", hash = "sha256:c61be0ba6c648db6b1d013a52f9afb8901a8d7fbabd983df2175c1b1fbff81e5", size = 94020 } wheels = [ @@ -3316,8 +3351,8 @@ name = "pyobjc-framework-mediatoolbox" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/da/46/cf5f3bde6cad32f10095850ca44f24ba241d18b26379187c412be1260f39/pyobjc_framework_mediatoolbox-11.0.tar.gz", hash = "sha256:de949a44f10b5a15e5a7131ee53b2806b8cb753fd01a955970ec0f475952ba24", size = 23067 } wheels = [ @@ -3330,8 +3365,8 @@ name = "pyobjc-framework-metal" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/77/e0/a6d18a1183410a5d8610ca1ae6c065b8944586441f8669faee7509817246/pyobjc_framework_metal-11.0.tar.gz", hash = "sha256:cad390150aa63502d5cfe242026b55ed39ffaf816342ddf51e44a9aead6c24be", size = 446102 } wheels = [ @@ -3344,8 +3379,8 @@ name = "pyobjc-framework-metalfx" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-metal" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-metal", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/68/cf/ff9367e4737a12ebd12a17e693ec247028cf065761acc073ebefb2b2393a/pyobjc_framework_metalfx-11.0.tar.gz", hash = "sha256:2ae41991bf7a733c44fcd5b6550cedea3accaaf0f529643975d3da113c9f0caa", size = 26436 } wheels = [ @@ -3358,9 +3393,9 @@ name = "pyobjc-framework-metalkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-metal" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-metal", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/92/27/fb3c1b10914abf2ae6682837abf76bcd8cb7af2ba613fbc55fb9d055bb95/pyobjc_framework_metalkit-11.0.tar.gz", hash = "sha256:1bbbe35c7c6a481383d32f6eaae59a1cd8084319a65c1aa343d63a257d8b4ddb", size = 44628 } wheels = [ @@ -3373,8 +3408,8 @@ name = "pyobjc-framework-metalperformanceshaders" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-metal" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-metal", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/14/c2/c08996a8c6cfef09fb9e726cc99b0bf3ad0ffcef66d5c2543e6b35dd4e2e/pyobjc_framework_metalperformanceshaders-11.0.tar.gz", hash = "sha256:41179e3a11e55325153fffd84f48946d47c1dc1944677febd871a127021e056d", size = 301444 } wheels = [ @@ -3387,8 +3422,8 @@ name = "pyobjc-framework-metalperformanceshadersgraph" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-metalperformanceshaders" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-metalperformanceshaders", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b5/b8/353852c76eb437e907ca0acf8a5b5f9255e9b9ee8c0706b69b0c17498f97/pyobjc_framework_metalperformanceshadersgraph-11.0.tar.gz", hash = "sha256:33077ebbbe1aa7787de2552a83534be6c439d7f4272de17915a85fda8fd3b72d", size = 105381 } wheels = [ @@ -3401,8 +3436,8 @@ name = "pyobjc-framework-metrickit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/28/82/605ad654f40ff4480ba9366ad3726da80c98e33b73f122fb91259be1ce81/pyobjc_framework_metrickit-11.0.tar.gz", hash = "sha256:ee3da403863beec181a2d6dc7b7eeb4d07e954b88bbabac58a82523b2f83fdc7", size = 40414 } wheels = [ @@ -3415,8 +3450,8 @@ name = "pyobjc-framework-mlcompute" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c5/c9/22fe4720685724ec1444c8e5cdb41d360b1434d0971fb3e43cf3e9bf51fd/pyobjc_framework_mlcompute-11.0.tar.gz", hash = "sha256:1a1ee9ab43d1824300055ff94b042a26f38f1d18f6f0aa08be1c88278e7284d9", size = 89265 } wheels = [ @@ -3429,9 +3464,9 @@ name = "pyobjc-framework-modelio" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ca/7c/b75b84d41e7854ffe9c9a42846f8105227a5fd0b02b690b4a75018b2caa3/pyobjc_framework_modelio-11.0.tar.gz", hash = "sha256:c875eb6ff7f94d18362a00faaa3016ae0c28140326338d18aa03c0b62f1c6b9d", size = 122652 } wheels = [ @@ -3444,8 +3479,8 @@ name = "pyobjc-framework-multipeerconnectivity" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/14/80/4137cb9751aa3846c4954b3e61f948aae17afeb6851e01194aa50683caef/pyobjc_framework_multipeerconnectivity-11.0.tar.gz", hash = "sha256:8278a3483c0b6b88a8888ca76c46fd85808f9df56d45708cbc4e4182a5565cd3", size = 25534 } wheels = [ @@ -3458,8 +3493,8 @@ name = "pyobjc-framework-naturallanguage" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/62/64/63e97635fa637384bc8c980796573dc7a9e7074a6866aef073b1faf3e11d/pyobjc_framework_naturallanguage-11.0.tar.gz", hash = "sha256:4c9471fa2c48a8fd4899de4406823e66cb0292dbba7b471622017f3647d53fa4", size = 46385 } wheels = [ @@ -3472,8 +3507,8 @@ name = "pyobjc-framework-netfs" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c7/29/eb569870b52c7581104ed2806cae2d425d60b5ab304128cd58155d5b567f/pyobjc_framework_netfs-11.0.tar.gz", hash = "sha256:3de5f627a62addf4aab8a4d2d07213e9b2b6c8adbe6cc4c332ee868075785a6a", size = 16173 } wheels = [ @@ -3486,8 +3521,8 @@ name = "pyobjc-framework-network" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/78/8e/18e55aff83549e041484d2ee94dd91b29cec9de40508e7fe9c4afec110a7/pyobjc_framework_network-11.0.tar.gz", hash = "sha256:d4dcc02773d7d642a385c7f0d951aeb7361277446c912a49230cddab60a65ab8", size = 124160 } wheels = [ @@ -3500,8 +3535,8 @@ name = "pyobjc-framework-networkextension" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/59/90/97dcfac5895b07e891adf634c3a074b68992d132ccfab386c186ac1a598c/pyobjc_framework_networkextension-11.0.tar.gz", hash = "sha256:5ba2254e2c13010b6c4f1e2948047d95eff86bfddfc77716747718fa3a8cb1af", size = 188551 } wheels = [ @@ -3514,8 +3549,8 @@ name = "pyobjc-framework-notificationcenter" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d7/d0/f0a602e01173531a2b639e283a092cf1f307fd873abd2ed590b9c4122337/pyobjc_framework_notificationcenter-11.0.tar.gz", hash = "sha256:f878b318c693d63d6b8bd1c3e2ad4f8097b22872f18f40142e394d84f1ead9f6", size = 22844 } wheels = [ @@ -3528,8 +3563,8 @@ name = "pyobjc-framework-opendirectory" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/55/cf/ba0cf807758acdc6a19e4787fdcda2eb59034aa22c4203d04fd49b276981/pyobjc_framework_opendirectory-11.0.tar.gz", hash = "sha256:0c82594f4f0bcf2318c4641527f9243962d7b03e67d4f3fb111b899a299fc7eb", size = 189165 } wheels = [ @@ -3542,8 +3577,8 @@ name = "pyobjc-framework-osakit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d3/4a/e49680f7f3ab9c0632ed9be76a0a59299e7fd797335690b3da4d117f2d7b/pyobjc_framework_osakit-11.0.tar.gz", hash = "sha256:77ac18e2660133a9eeb01c76ad3df3b4b36fd29005fc36bca00f57cca121aac3", size = 22535 } wheels = [ @@ -3556,10 +3591,10 @@ name = "pyobjc-framework-oslog" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coremedia" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b0/93/0a72353d0212a815bd5e43aec528ce7b28b71d461d26e5fa3882ff96ffa3/pyobjc_framework_oslog-11.0.tar.gz", hash = "sha256:9d29eb7c89a41d7c702dffb6e2e338a2d5219387c8dae22b67754ddf9e2fcb3f", size = 24151 } wheels = [ @@ -3572,8 +3607,8 @@ name = "pyobjc-framework-passkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cb/f8/ebb2bc840f87292a4f60080463ee698ca08516cc958364741dfff2858b33/pyobjc_framework_passkit-11.0.tar.gz", hash = "sha256:2044d9d634dd98b7b624ee09487b27e5f26a7729f6689abba23a4a011febe19c", size = 120495 } wheels = [ @@ -3586,8 +3621,8 @@ name = "pyobjc-framework-pencilkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f4/8d/1e97cd72b776e5e1294cbda84325b364702617dd435d32448dcc0a80bd93/pyobjc_framework_pencilkit-11.0.tar.gz", hash = "sha256:9598c28e83f5b7f091592cc1af2b16f7ae94cf00045d8d14ed2c17cb9e4ffd50", size = 22812 } wheels = [ @@ -3600,8 +3635,8 @@ name = "pyobjc-framework-phase" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-avfoundation" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-avfoundation", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d2/a2/65182dcb44fceb2173f4134d6cd4325dfd0731225b621aa2027d2a03d043/pyobjc_framework_phase-11.0.tar.gz", hash = "sha256:e06a0f8308ae4f3731f88b3e1239b7bdfdda3eef97023e3ce972e2f386451d80", size = 59214 } wheels = [ @@ -3614,8 +3649,8 @@ name = "pyobjc-framework-photos" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f7/c3/fc755c1f8f411433d7ba2e92f3fe3e7b417e9629675ad6baf94ac8b01e64/pyobjc_framework_photos-11.0.tar.gz", hash = "sha256:cfdfdefb0d560b091425227d5c0e24a40b445b5251ff4d37bd326cd8626b80cd", size = 92122 } wheels = [ @@ -3628,8 +3663,8 @@ name = "pyobjc-framework-photosui" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e4/2c/70ac99fb2b7ba14d220c78cf6401c0c7a47992269f85f699220a6a2cff09/pyobjc_framework_photosui-11.0.tar.gz", hash = "sha256:3c65342e31f6109d8229992b2712b29cab1021475969b55f4f215dd97e2a99db", size = 47898 } wheels = [ @@ -3642,8 +3677,8 @@ name = "pyobjc-framework-preferencepanes" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/35/01/81cc46e0a92d15f2b664b2efdcc8fd310acac570c9f63a99d446e0489784/pyobjc_framework_preferencepanes-11.0.tar.gz", hash = "sha256:ee000c351befeb81f4fa678ada85695ca4af07933b6bd9b1947164e16dd0b3e5", size = 26419 } wheels = [ @@ -3656,8 +3691,8 @@ name = "pyobjc-framework-pushkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/17/ab/7fe55ce5b32c434142be026ec27b1801a2d4694b159b502f9ecd568eebf2/pyobjc_framework_pushkit-11.0.tar.gz", hash = "sha256:df9854ed4065c50022863b3c11c2a21c4279b36c2b5c8f08b834174aacb44e81", size = 20816 } wheels = [ @@ -3670,8 +3705,8 @@ name = "pyobjc-framework-quartz" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a5/ad/f00f3f53387c23bbf4e0bb1410e11978cbf87c82fa6baff0ee86f74c5fb6/pyobjc_framework_quartz-11.0.tar.gz", hash = "sha256:3205bf7795fb9ae34747f701486b3db6dfac71924894d1f372977c4d70c3c619", size = 3952463 } wheels = [ @@ -3684,9 +3719,9 @@ name = "pyobjc-framework-quicklookthumbnailing" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/50/a1/35ca40d2d4ab05acbc9766986d482482d466529003711c7b4e52a8df4935/pyobjc_framework_quicklookthumbnailing-11.0.tar.gz", hash = "sha256:40763284bd0f71e6a55803f5234ad9cd8e8dd3aaaf5e1fd204e6c952b3f3530d", size = 16784 } wheels = [ @@ -3699,8 +3734,8 @@ name = "pyobjc-framework-replaykit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/aa/43/c751c517dbb8ee599a31e59832c01080473c7964b6996ca29906f46c0967/pyobjc_framework_replaykit-11.0.tar.gz", hash = "sha256:e5693589423eb9ad99d63a7395169f97b484a58108321877b0fc27c748344593", size = 25589 } wheels = [ @@ -3713,8 +3748,8 @@ name = "pyobjc-framework-safariservices" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/40/ec/c9a97b1aa713145cc8c522c4146af06b293cfe1a959a03ee91007949533b/pyobjc_framework_safariservices-11.0.tar.gz", hash = "sha256:dba416bd0ed5f4481bc400bf56ce57e982c19feaae94bc4eb75d8bda9af15b7e", size = 34367 } wheels = [ @@ -3727,9 +3762,9 @@ name = "pyobjc-framework-safetykit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4e/30/89bfdbdca93e57b19891ddeff1742b20a2019cdeb2e44902027dce2642e1/pyobjc_framework_safetykit-11.0.tar.gz", hash = "sha256:9ec996a6a8eecada4b9fd1138244bcffea96a37722531f0ec16566049dfd4cdb", size = 20745 } wheels = [ @@ -3742,9 +3777,9 @@ name = "pyobjc-framework-scenekit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/26/3f/a2761585399e752bce8275c9d56990d4b83e57b13d06dd98335891176a89/pyobjc_framework_scenekit-11.0.tar.gz", hash = "sha256:c0f37019f8de2a583f66e6d14dfd4ae23c8d8703e93f61c1c91728a21f62cd26", size = 213647 } wheels = [ @@ -3757,9 +3792,9 @@ name = "pyobjc-framework-screencapturekit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coremedia" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/77/90/71f10db2f52ea324f82eaccc959442c43d21778cc5b1294c29e1942e635c/pyobjc_framework_screencapturekit-11.0.tar.gz", hash = "sha256:ca2c960e28216e56f33e4ca9b9b1eda12d9c17b719bae727181e8b96f0314c4b", size = 53046 } wheels = [ @@ -3772,8 +3807,8 @@ name = "pyobjc-framework-screensaver" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f6/b6/71c20259a1bfffcb5103be62564006b1bbc21f80180658101e2370683bcb/pyobjc_framework_screensaver-11.0.tar.gz", hash = "sha256:2e4c643624cc0cffeafc535c43faf5f8de8be030307fa8a5bea257845e8af474", size = 23774 } wheels = [ @@ -3786,8 +3821,8 @@ name = "pyobjc-framework-screentime" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/42/a7/ee60ee5b0471a4367eaa1c8a243418874fd48fac5dbdfdd318a653d94aaa/pyobjc_framework_screentime-11.0.tar.gz", hash = "sha256:6dd74dc64be1865346fcff63b8849253697f7ac68d83ee2708019cf3852c1cd7", size = 14398 } wheels = [ @@ -3800,8 +3835,8 @@ name = "pyobjc-framework-scriptingbridge" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4d/f0/592af19047935e44c07ddd1eba4f05aa8eb460ee842f7d5d48501231cd69/pyobjc_framework_scriptingbridge-11.0.tar.gz", hash = "sha256:65e5edd0ea608ae7f01808b963dfa25743315f563705d75c493c2fa7032f88cc", size = 22626 } wheels = [ @@ -3814,8 +3849,8 @@ name = "pyobjc-framework-searchkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-coreservices" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coreservices", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/15/27/9676327cf7d13346d546325b411a5deaa072bd0fbe733c8aae8a9a00c0e0/pyobjc_framework_searchkit-11.0.tar.gz", hash = "sha256:36f3109e74bc5e6fab60c02be804d5ed1c511ad51ea0d597a6c6a9653573ddf5", size = 31182 } wheels = [ @@ -3828,8 +3863,8 @@ name = "pyobjc-framework-security" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c5/75/4b916bff8c650e387077a35916b7a7d331d5ff03bed7275099d96dcc6cd9/pyobjc_framework_security-11.0.tar.gz", hash = "sha256:ac078bb9cc6762d6f0f25f68325dcd7fe77acdd8c364bf4378868493f06a0758", size = 347059 } wheels = [ @@ -3842,9 +3877,9 @@ name = "pyobjc-framework-securityfoundation" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-security" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-security", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/84/d6/0d817edb11d2bdb0f536059e913191e587f1984e39397bb3341209d92c21/pyobjc_framework_securityfoundation-11.0.tar.gz", hash = "sha256:5ae906ded5dd40046c013a7e0c1f59416abafb4b72bc947b6cd259749745e637", size = 13526 } wheels = [ @@ -3857,9 +3892,9 @@ name = "pyobjc-framework-securityinterface" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-security" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-security", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b1/88/d7c4942650707fe5b1d3b45b42684f58f2cab7d2772ec74ca96ecef575eb/pyobjc_framework_securityinterface-11.0.tar.gz", hash = "sha256:8843a27cf30a8e4dd6e2cb7702a6d65ad4222429f0ccc6c062537af4683b1c08", size = 37118 } wheels = [ @@ -3872,9 +3907,9 @@ name = "pyobjc-framework-sensitivecontentanalysis" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/00/e4/f1e0f150ae6c6ad7dde9b248f34f324f4f8b1c42260dbf62420f80d79ba9/pyobjc_framework_sensitivecontentanalysis-11.0.tar.gz", hash = "sha256:0f09034688f894c0f4409c16adaf857d78714d55472de4aa2ac40fbd7ba233d6", size = 13060 } wheels = [ @@ -3887,8 +3922,8 @@ name = "pyobjc-framework-servicemanagement" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1b/59/8d38b5cdbcfb57ab842e080436dbd04d5a5d2080e99a2ea1e286cfad12a8/pyobjc_framework_servicemanagement-11.0.tar.gz", hash = "sha256:10b1bbcee3de5bb2b9fc3d6763eb682b7a1d9ddd4bd2c882fece62783cb17885", size = 16882 } wheels = [ @@ -3901,8 +3936,8 @@ name = "pyobjc-framework-sharedwithyou" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-sharedwithyoucore" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-sharedwithyoucore", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/20/84/db667061f815537717a6cac891df01a45b65e6feaa2dfa0c9d2e3803a1ef/pyobjc_framework_sharedwithyou-11.0.tar.gz", hash = "sha256:a3a03daac77ad7364ed22109ca90c6cd2dcb7611a96cbdf37d30543ef1579399", size = 33696 } wheels = [ @@ -3915,8 +3950,8 @@ name = "pyobjc-framework-sharedwithyoucore" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/52/2a/86904cd9cc3bf5cdb9101481e17e67358f39f81ffa0f36768097287e34b3/pyobjc_framework_sharedwithyoucore-11.0.tar.gz", hash = "sha256:3932452677df5d67ea27845ab26ccaaa1d1779196bf16b62c5655f13d822c82d", size = 28877 } wheels = [ @@ -3929,8 +3964,8 @@ name = "pyobjc-framework-shazamkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/dd/2a/1f4ad92260860e500cb61119e8e7fe604b0788c32f5b00446b5a56705a2b/pyobjc_framework_shazamkit-11.0.tar.gz", hash = "sha256:cea736cefe90b6bb989d0a8abdc21ef4b3b431b27657abb09d6deb0b2c1bd37a", size = 25172 } wheels = [ @@ -3943,8 +3978,8 @@ name = "pyobjc-framework-social" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6f/56/ed483f85105ef929241ab1a6ed3dbfd0be558bb900e36b274f997db9c869/pyobjc_framework_social-11.0.tar.gz", hash = "sha256:ccedd6eddb6744049467bce19b4ec4f0667ec60552731c02dcbfa8938a3ac798", size = 14806 } wheels = [ @@ -3957,8 +3992,8 @@ name = "pyobjc-framework-soundanalysis" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9a/14/697ca1b76228a96bb459f3cf43234798b05fdf11691202449d98d9d887af/pyobjc_framework_soundanalysis-11.0.tar.gz", hash = "sha256:f541fcd04ec5d7528dd2ae2d873a92a3092e87fb70b8df229c79defb4d807d1a", size = 16789 } wheels = [ @@ -3971,8 +4006,8 @@ name = "pyobjc-framework-speech" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5f/39/e9f0a73243c38d85f8da6a1a2afda73503e2fcc31a72f5479770bceae0c1/pyobjc_framework_speech-11.0.tar.gz", hash = "sha256:92a191c3ecfe7032eea2140ab5dda826a59c7bb84b13a2edb0ebc471a76e6d7b", size = 40620 } wheels = [ @@ -3985,9 +4020,9 @@ name = "pyobjc-framework-spritekit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b7/6e/642e64f5b62a7777c784931c7f018788b5620e307907d416c837fd0c4315/pyobjc_framework_spritekit-11.0.tar.gz", hash = "sha256:aa43927e325d4ac253b7c0ec4df95393b0354bd278ebe9871803419d12d1ef80", size = 129851 } wheels = [ @@ -4000,8 +4035,8 @@ name = "pyobjc-framework-storekit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/69/ca/f4e5a1ff8c98bbbf208639b2bef7bf3b88936bccda1d8ed34aa7d052f589/pyobjc_framework_storekit-11.0.tar.gz", hash = "sha256:ef7e75b28f1fa8b0b6413e64b9d5d78b8ca358fc2477483d2783f688ff8d75e0", size = 75855 } wheels = [ @@ -4014,8 +4049,8 @@ name = "pyobjc-framework-symbols" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/dc/92/a20a3d7af3c99e0ea086e43715675160a04b86c1d069bdaeb3acdb015d92/pyobjc_framework_symbols-11.0.tar.gz", hash = "sha256:e3de7736dfb8107f515cfd23f03e874dd9468e88ab076d01d922a73fefb620fa", size = 13682 } wheels = [ @@ -4028,9 +4063,9 @@ name = "pyobjc-framework-syncservices" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coredata" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coredata", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5a/22/642186906f672461bab1d7773b35ef74e432b9789ca2248186b766e9fd3b/pyobjc_framework_syncservices-11.0.tar.gz", hash = "sha256:7867c23895a8289da8d56e962c144c36ed16bd101dc07d05281c55930b142471", size = 57453 } wheels = [ @@ -4043,8 +4078,8 @@ name = "pyobjc-framework-systemconfiguration" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/70/70/ebebf311523f436df2407f35d7ce62482c01e530b77aceb3ca6356dcef43/pyobjc_framework_systemconfiguration-11.0.tar.gz", hash = "sha256:06487f0fdd43c6447b5fd3d7f3f59826178d32bcf74f848c5b3ea597191d471d", size = 142949 } wheels = [ @@ -4057,8 +4092,8 @@ name = "pyobjc-framework-systemextensions" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/62/4b/904d818debf6216b7be009d492d998c819bf2f2791bfb75870a952e32cf9/pyobjc_framework_systemextensions-11.0.tar.gz", hash = "sha256:da293c99b428fb7f18a7a1d311b17177f73a20c7ffa94de3f72d760df924255e", size = 22531 } wheels = [ @@ -4071,8 +4106,8 @@ name = "pyobjc-framework-threadnetwork" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c4/17/fc8fde4eeb6697e0a5ba1a306cd62d3a95b53f3334744cd22b87037d8a14/pyobjc_framework_threadnetwork-11.0.tar.gz", hash = "sha256:f5713579380f6fb89c877796de86cb4e98428d7a9cbfebe566fb827ba23b2d8e", size = 13820 } wheels = [ @@ -4085,8 +4120,8 @@ name = "pyobjc-framework-uniformtypeidentifiers" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/56/4f/fd571c1f87d5ee3d86c4d2008806e9623d2662bbc788d9001b3fff35275f/pyobjc_framework_uniformtypeidentifiers-11.0.tar.gz", hash = "sha256:6ae6927a3ed1f0197a8c472226f11f46ccd5ed398b4449613e1d10346d9ed15d", size = 20860 } wheels = [ @@ -4099,8 +4134,8 @@ name = "pyobjc-framework-usernotifications" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/78/f5/ca3e6a7d940b3aca4323e4f5409b14b5d2eb45432158430c584e3800ce4d/pyobjc_framework_usernotifications-11.0.tar.gz", hash = "sha256:7950a1c6a8297f006c26c3d286705ffc2a07061d6e844f1106290572097b872c", size = 54857 } wheels = [ @@ -4113,9 +4148,9 @@ name = "pyobjc-framework-usernotificationsui" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-usernotifications" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-usernotifications", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e9/e8/f0d50cdc678260a628b92e55b5752155f941c2f72b96fe3f2412a28c5d79/pyobjc_framework_usernotificationsui-11.0.tar.gz", hash = "sha256:d0ec597d189b4d228b0b836474aef318652c1c287b33442a1403c49dc59fdb7f", size = 14369 } wheels = [ @@ -4128,8 +4163,8 @@ name = "pyobjc-framework-videosubscriberaccount" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7e/2e/6a7debd84911a9384b4e7a9cc3f308e3461a00a9d74f33b153bdd872f15f/pyobjc_framework_videosubscriberaccount-11.0.tar.gz", hash = "sha256:163b32f361f48b9d20f317461464abd4427b3242693ae011633fc443c7d5449c", size = 29100 } wheels = [ @@ -4142,10 +4177,10 @@ name = "pyobjc-framework-videotoolbox" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coremedia" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ba/2d/c031a132b142fcd20846cc1ac3ba92abaa58ec04164fd36ca978d9374f1c/pyobjc_framework_videotoolbox-11.0.tar.gz", hash = "sha256:a54ed8f8bcbdd2bdea2a296dc02a8a7d42f81e2b6ccbf4d1f10cec5e7a09bec0", size = 81157 } wheels = [ @@ -4158,8 +4193,8 @@ name = "pyobjc-framework-virtualization" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/65/8d/e57e1f2c5ac950dc3da6c977effde4a55b8b70424b1bdb97b5530559f5bc/pyobjc_framework_virtualization-11.0.tar.gz", hash = "sha256:03e1c1fa20950aa7c275e5f11f1257108b6d1c6a7403afb86f4e9d5fae87b73c", size = 78144 } wheels = [ @@ -4172,10 +4207,10 @@ name = "pyobjc-framework-vision" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coreml" }, - { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-coreml", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ef/53/dc2e0562a177af9306efceb84bc21f5cf7470acaa8f28f64e62bf828b7e1/pyobjc_framework_vision-11.0.tar.gz", hash = "sha256:45342e5253c306dbcd056a68bff04ffbfa00e9ac300a02aabf2e81053b771e39", size = 133175 } wheels = [ @@ -4188,8 +4223,8 @@ name = "pyobjc-framework-webkit" version = "11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/79/4f/02a6270acf225c2a34339677e796002c77506238475059ae6e855358a40c/pyobjc_framework_webkit-11.0.tar.gz", hash = "sha256:fa6bedf9873786b3376a74ce2ea9dcd311f2a80f61e33dcbd931cc956aa29644", size = 767210 } wheels = [ @@ -4202,9 +4237,9 @@ name = "pyopencl" version = "2025.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, - { name = "platformdirs" }, - { name = "pytools" }, + { name = "numpy", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "platformdirs", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "pytools", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/28/88/0ac460d3e2def08b2ad6345db6a13613815f616bbbd60c6f4bdf774f4c41/pyopencl-2025.1.tar.gz", hash = "sha256:0116736d7f7920f87b8db4b66a03f27b1d930d2e37ddd14518407cc22dd24779", size = 422510 } wheels = [ @@ -4420,7 +4455,7 @@ name = "python-xlib" version = "0.33" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "six" }, + { name = "six", marker = "sys_platform != 'darwin'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/86/f5/8c0653e5bb54e0cbdfe27bf32d41f27bc4e12faa8742778c17f2a71be2c0/python-xlib-0.33.tar.gz", hash = "sha256:55af7906a2c75ce6cb280a584776080602444f75815a7aff4d287bb2d7018b32", size = 269068 } wheels = [ @@ -4438,9 +4473,9 @@ name = "pytools" version = "2024.1.10" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "platformdirs" }, - { name = "siphash24" }, - { name = "typing-extensions" }, + { name = "platformdirs", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "siphash24", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "typing-extensions", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ee/0f/56e109c0307f831b5d598ad73976aaaa84b4d0e98da29a642e797eaa940c/pytools-2024.1.10.tar.gz", hash = "sha256:9af6f4b045212c49be32bb31fe19606c478ee4b09631886d05a32459f4ce0a12", size = 81741 } wheels = [ @@ -4455,15 +4490,15 @@ sdist = { url = "https://files.pythonhosted.org/packages/79/0c/c16bc93ac2755bac0 [[package]] name = "pywin32" -version = "308" +version = "309" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/e2/02652007469263fe1466e98439831d65d4ca80ea1a2df29abecedf7e47b7/pywin32-308-cp311-cp311-win32.whl", hash = "sha256:5d8c8015b24a7d6855b1550d8e660d8daa09983c80e5daf89a273e5c6fb5095a", size = 5928156 }, - { url = "https://files.pythonhosted.org/packages/48/ef/f4fb45e2196bc7ffe09cad0542d9aff66b0e33f6c0954b43e49c33cad7bd/pywin32-308-cp311-cp311-win_amd64.whl", hash = "sha256:575621b90f0dc2695fec346b2d6302faebd4f0f45c05ea29404cefe35d89442b", size = 6559559 }, - { url = "https://files.pythonhosted.org/packages/79/ef/68bb6aa865c5c9b11a35771329e95917b5559845bd75b65549407f9fc6b4/pywin32-308-cp311-cp311-win_arm64.whl", hash = "sha256:100a5442b7332070983c4cd03f2e906a5648a5104b8a7f50175f7906efd16bb6", size = 7972495 }, - { url = "https://files.pythonhosted.org/packages/00/7c/d00d6bdd96de4344e06c4afbf218bc86b54436a94c01c71a8701f613aa56/pywin32-308-cp312-cp312-win32.whl", hash = "sha256:587f3e19696f4bf96fde9d8a57cec74a57021ad5f204c9e627e15c33ff568897", size = 5939729 }, - { url = "https://files.pythonhosted.org/packages/21/27/0c8811fbc3ca188f93b5354e7c286eb91f80a53afa4e11007ef661afa746/pywin32-308-cp312-cp312-win_amd64.whl", hash = "sha256:00b3e11ef09ede56c6a43c71f2d31857cf7c54b0ab6e78ac659497abd2834f47", size = 6543015 }, - { url = "https://files.pythonhosted.org/packages/9d/0f/d40f8373608caed2255781a3ad9a51d03a594a1248cd632d6a298daca693/pywin32-308-cp312-cp312-win_arm64.whl", hash = "sha256:9b4de86c8d909aed15b7011182c8cab38c8850de36e6afb1f0db22b8959e3091", size = 7976033 }, + { url = "https://files.pythonhosted.org/packages/05/54/6409b1d98f2b8fed3bc2cc854859e48ae4a2dd956176664e38ee49c50a4c/pywin32-309-cp311-cp311-win32.whl", hash = "sha256:d5df6faa32b868baf9ade7c9b25337fa5eced28eb1ab89082c8dae9c48e4cd51", size = 8779225 }, + { url = "https://files.pythonhosted.org/packages/6a/f0/ae8ddb56771093dd2905baa852958fd65d42a8972aeefcf13578dfae69f4/pywin32-309-cp311-cp311-win_amd64.whl", hash = "sha256:e7ec2cef6df0926f8a89fd64959eba591a1eeaf0258082065f7bdbe2121228db", size = 9514129 }, + { url = "https://files.pythonhosted.org/packages/7a/4b/1f5e377a04448cf410e13040bc0e4c408bfa0a65705cabf96904178f18df/pywin32-309-cp311-cp311-win_arm64.whl", hash = "sha256:54ee296f6d11db1627216e9b4d4c3231856ed2d9f194c82f26c6cb5650163f4c", size = 8450450 }, + { url = "https://files.pythonhosted.org/packages/20/2c/b0240b14ff3dba7a8a7122dc9bbf7fbd21ed0e8b57c109633675b5d1761f/pywin32-309-cp312-cp312-win32.whl", hash = "sha256:de9acacced5fa82f557298b1fed5fef7bd49beee04190f68e1e4783fbdc19926", size = 8790648 }, + { url = "https://files.pythonhosted.org/packages/dd/11/c36884c732e2b3397deee808b5dac1abbb170ec37f94c6606fcb04d1e9d7/pywin32-309-cp312-cp312-win_amd64.whl", hash = "sha256:6ff9eebb77ffc3d59812c68db33c0a7817e1337e3537859499bd27586330fc9e", size = 9497399 }, + { url = "https://files.pythonhosted.org/packages/18/9f/79703972958f8ba3fd38bc9bf1165810bd75124982419b0cc433a2894d46/pywin32-309-cp312-cp312-win_arm64.whl", hash = "sha256:619f3e0a327b5418d833f44dc87859523635cf339f86071cc65a13c07be3110f", size = 8454122 }, ] [[package]] @@ -4757,11 +4792,11 @@ wheels = [ [[package]] name = "setuptools" -version = "75.8.2" +version = "76.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d1/53/43d99d7687e8cdef5ab5f9ec5eaf2c0423c2b35133a2b7e7bc276fc32b21/setuptools-75.8.2.tar.gz", hash = "sha256:4880473a969e5f23f2a2be3646b2dfd84af9028716d398e46192f84bc36900d2", size = 1344083 } +sdist = { url = "https://files.pythonhosted.org/packages/32/d2/7b171caf085ba0d40d8391f54e1c75a1cda9255f542becf84575cfd8a732/setuptools-76.0.0.tar.gz", hash = "sha256:43b4ee60e10b0d0ee98ad11918e114c70701bc6051662a9a675a0496c1a158f4", size = 1349387 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/38/7d7362e031bd6dc121e5081d8cb6aa6f6fedf2b67bf889962134c6da4705/setuptools-75.8.2-py3-none-any.whl", hash = "sha256:558e47c15f1811c1fa7adbd0096669bf76c1d3f433f58324df69f3f5ecac4e8f", size = 1229385 }, + { url = "https://files.pythonhosted.org/packages/37/66/d2d7e6ad554f3a7c7297c3f8ef6e22643ad3d35ef5c63bf488bc89f32f31/setuptools-76.0.0-py3-none-any.whl", hash = "sha256:199466a166ff664970d0ee145839f5582cb9bca7a0a3a2e795b6a9cb2308e9c6", size = 1236106 }, ] [[package]] @@ -4769,7 +4804,7 @@ name = "shapely" version = "2.0.7" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/21/c0/a911d1fd765d07a2b6769ce155219a281bfbe311584ebe97340d75c5bdb1/shapely-2.0.7.tar.gz", hash = "sha256:28fe2997aab9a9dc026dc6a355d04e85841546b2a5d232ed953e3321ab958ee5", size = 283413 } wheels = [ @@ -4994,7 +5029,7 @@ name = "yapf" version = "0.43.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "platformdirs" }, + { name = "platformdirs", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/23/97/b6f296d1e9cc1ec25c7604178b48532fa5901f721bcf1b8d8148b13e5588/yapf-0.43.0.tar.gz", hash = "sha256:00d3aa24bfedff9420b2e0d5d9f5ab6d9d4268e72afbf59bb3fa542781d5218e", size = 254907 } wheels = [