mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-06-19 16:52:06 +08:00
Compare commits
179 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 692a4587db | |||
| 80a6f39a79 | |||
| 997ab25057 | |||
| 05da45a1bf | |||
| 864c811ef6 | |||
| 906e9d7a80 | |||
| cfb8f3ae24 | |||
| 0cc5e56192 | |||
| 1a62ae821e | |||
| 8caa57feeb | |||
| 2858a068f0 | |||
| 7d15afe5bc | |||
| b6dd2d14db | |||
| 7d4e5bedaf | |||
| 1063114408 | |||
| 958b4df69f | |||
| f5953c5d8c | |||
| f60c2b6a83 | |||
| f833819143 | |||
| 72998034e6 | |||
| cefb344183 | |||
| c0da31abb6 | |||
| bd759a56cf | |||
| befc73c53e | |||
| 8dbfc267ac | |||
| d17e80ad94 | |||
| c2b7087723 | |||
| 81b37712f1 | |||
| 68270a13a3 | |||
| 18cd3633e5 | |||
| 9c6a4d4a57 | |||
| 1a4c48249b | |||
| 95d887a417 | |||
| e297b4c03f | |||
| 1132377837 | |||
| 707e2aedae | |||
| 35f03ae001 | |||
| 55147d8a55 | |||
| de7acc5466 | |||
| 1c0b54a447 | |||
| 8f0cdd514e | |||
| 3681caa717 | |||
| 7446c43f69 | |||
| 5f5e3668eb | |||
| 8c07958f6f | |||
| ca1ce9bcc9 | |||
| 34a0819bc5 | |||
| c68ea82a5d | |||
| e4aada10a4 | |||
| 3157054100 | |||
| 2486ef1825 | |||
| 29f15dc8ed | |||
| 31a5a3b3c0 | |||
| d8fa3cfd04 | |||
| 2a4b348497 | |||
| 3ef3aceb4b | |||
| 3d8763b3ce | |||
| b460d5804c | |||
| eecb8e5c19 | |||
| 1a4ea66987 | |||
| b2427a5f20 | |||
| c1e15e5544 | |||
| 3a45fff1b9 | |||
| ae9bd39883 | |||
| 43e7d87176 | |||
| 432c6050ed | |||
| 4e3b1f1f6b | |||
| 7ddafe62cd | |||
| ff4cc96a81 | |||
| 3b1ada64be | |||
| 6a08186434 | |||
| cf2b033c79 | |||
| 9fbef36c6b | |||
| f5a38aa613 | |||
| 25f5058430 | |||
| 7b28c2f59a | |||
| 99d954de10 | |||
| b28f33481c | |||
| 589e33f665 | |||
| 39342d7b5e | |||
| fe70650f73 | |||
| e3f9fe892a | |||
| f4373fa244 | |||
| 2376802589 | |||
| c3b51d7335 | |||
| 5d47ffdb8a | |||
| 1c89e2b885 | |||
| c552567ada | |||
| d3d8802402 | |||
| d866500c92 | |||
| 23879836d9 | |||
| 06add21971 | |||
| 66fd3d1a01 | |||
| 7097e69aa3 | |||
| 71f7754f51 | |||
| b5591cbd62 | |||
| 7430c450c2 | |||
| e54a39cf43 | |||
| 657ff0f8ec | |||
| 3afe0bcdb3 | |||
| b763f7aac1 | |||
| 450fcd4d55 | |||
| 551b4dea31 | |||
| bd269defb3 | |||
| 8998f63a28 | |||
| 399ed08926 | |||
| 90f02040fe | |||
| 8423ecedb1 | |||
| 34d1514e11 | |||
| c50d511616 | |||
| dd1479ed82 | |||
| 641af6d7e7 | |||
| f57de1c5b2 | |||
| 87ec262e39 | |||
| f82845ff42 | |||
| efcc5ccd15 | |||
| 6aac50ab56 | |||
| da0920cb60 | |||
| cb5d120136 | |||
| 091bce4a3a | |||
| 088f6aa407 | |||
| 211c8adcce | |||
| c85b6a0d1c | |||
| fe5366e5b2 | |||
| 1ecb0b0f66 | |||
| 51e455db79 | |||
| dc6672fa80 | |||
| 07b8e7783d | |||
| f17b0f200c | |||
| ad9bde8b1f | |||
| 8cf9f9fe23 | |||
| 713985d823 | |||
| 088f9d0b59 | |||
| 53bf5b0d41 | |||
| 8c33592628 | |||
| 3bbb33f6bd | |||
| 025a930ce8 | |||
| 523c92c6fe | |||
| 72282f2d2e | |||
| 2825c00fcc | |||
| 5bd9549bd1 | |||
| 3481702715 | |||
| c9781ee31d | |||
| 063aa994d2 | |||
| 50462a1d01 | |||
| 437726b348 | |||
| 9e6af5ba74 | |||
| 99bd9075d5 | |||
| c438aeb5a5 | |||
| f1ca81debf | |||
| d7e1c42c2b | |||
| 6d51d64285 | |||
| e0ccc175e4 | |||
| 734151f59b | |||
| 9a14baac4d | |||
| d3e3628a95 | |||
| fec6382b96 | |||
| 4bd020e92b | |||
| 7f5342f378 | |||
| 339bc0b8b3 | |||
| 59c64acc29 | |||
| 7229c7541e | |||
| 39e73cc46e | |||
| 285fd97606 | |||
| e5f1f86ac2 | |||
| 7e03277962 | |||
| bd9bb74d03 | |||
| 7d54b58b8d | |||
| 68d059fd5d | |||
| 728108f97f | |||
| cb6fa622ee | |||
| 5b29fd0f2c | |||
| 6f42bbab18 | |||
| b89393a5a2 | |||
| 1e5758e712 | |||
| 974a7a3f7d | |||
| fe6edda23a | |||
| 17e25f78b4 | |||
| 9b92cdd2cc |
@@ -3,3 +3,4 @@ REGIST
|
||||
PullRequest
|
||||
cancelled
|
||||
FOF
|
||||
NoO
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
name: 'Post to Discourse'
|
||||
description: 'Posts a message to a Discourse topic (existing or new)'
|
||||
|
||||
inputs:
|
||||
discourse-url:
|
||||
description: 'Discourse instance URL (e.g., https://discourse.example.com)'
|
||||
required: true
|
||||
api-key:
|
||||
description: 'Discourse API key'
|
||||
required: true
|
||||
api-username:
|
||||
description: 'Discourse API username'
|
||||
required: true
|
||||
topic-id:
|
||||
description: 'Discourse topic ID to post to (use this OR category-id + title)'
|
||||
required: false
|
||||
category-id:
|
||||
description: 'Category ID for new topic (required if topic-id not provided)'
|
||||
required: false
|
||||
title:
|
||||
description: 'Title for new topic (required if topic-id not provided)'
|
||||
required: false
|
||||
message:
|
||||
description: 'Message content (markdown supported)'
|
||||
required: true
|
||||
|
||||
outputs:
|
||||
post-number:
|
||||
description: 'The post number in the topic'
|
||||
value: ${{ steps.post.outputs.post_number }}
|
||||
post-url:
|
||||
description: 'Direct URL to the post'
|
||||
value: ${{ steps.post.outputs.post_url }}
|
||||
topic-id:
|
||||
description: 'The topic ID (useful when creating a new topic)'
|
||||
value: ${{ steps.post.outputs.topic_id }}
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Post to Discourse
|
||||
id: post
|
||||
shell: bash
|
||||
run: |
|
||||
# Validate inputs
|
||||
if [ -z "${{ inputs.topic-id }}" ] && ([ -z "${{ inputs.category-id }}" ] || [ -z "${{ inputs.title }}" ]); then
|
||||
echo "❌ Error: Must provide either topic-id OR both category-id and title"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -n "${{ inputs.topic-id }}" ] && ([ -n "${{ inputs.category-id }}" ] || [ -n "${{ inputs.title }}" ]); then
|
||||
echo "⚠️ Warning: Both topic-id and category-id/title provided. Will post to existing topic."
|
||||
fi
|
||||
|
||||
# Determine if creating new topic or posting to existing
|
||||
if [ -n "${{ inputs.topic-id }}" ]; then
|
||||
echo "📝 Posting to existing topic ID: ${{ inputs.topic-id }}"
|
||||
|
||||
# Create JSON payload for posting to existing topic
|
||||
PAYLOAD=$(jq -n \
|
||||
--arg content '${{ inputs.message }}' \
|
||||
--arg topic_id "${{ inputs.topic-id }}" \
|
||||
'{topic_id: $topic_id, raw: $content}')
|
||||
else
|
||||
echo "✨ Creating new topic: ${{ inputs.title }}"
|
||||
|
||||
# Create JSON payload for new topic
|
||||
PAYLOAD=$(jq -n \
|
||||
--arg content '${{ inputs.message }}' \
|
||||
--arg title "${{ inputs.title }}" \
|
||||
--arg category "${{ inputs.category-id }}" \
|
||||
'{title: $title, category: ($category | tonumber), raw: $content}')
|
||||
fi
|
||||
|
||||
# Post to Discourse
|
||||
RESPONSE=$(curl -s -w "\n%{http_code}" \
|
||||
-X POST "${{ inputs.discourse-url }}/posts.json" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Api-Key: ${{ inputs.api-key }}" \
|
||||
-H "Api-Username: ${{ inputs.api-username }}" \
|
||||
-d "$PAYLOAD")
|
||||
|
||||
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
||||
BODY=$(echo "$RESPONSE" | sed '$d')
|
||||
|
||||
if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
|
||||
echo "✅ Successfully posted to Discourse!"
|
||||
|
||||
POST_NUMBER=$(echo "$BODY" | jq -r '.post_number // "unknown"')
|
||||
TOPIC_ID=$(echo "$BODY" | jq -r '.topic_id // "${{ inputs.topic-id }}"')
|
||||
POST_URL="${{ inputs.discourse-url }}/t/${TOPIC_ID}/${POST_NUMBER}"
|
||||
|
||||
echo "post_number=${POST_NUMBER}" >> $GITHUB_OUTPUT
|
||||
echo "post_url=${POST_URL}" >> $GITHUB_OUTPUT
|
||||
echo "topic_id=${TOPIC_ID}" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "Topic ID: ${TOPIC_ID}"
|
||||
echo "Post number: ${POST_NUMBER}"
|
||||
echo "URL: ${POST_URL}"
|
||||
else
|
||||
echo "❌ Failed to post to Discourse"
|
||||
echo "HTTP Code: ${HTTP_CODE}"
|
||||
echo "Response: ${BODY}"
|
||||
exit 1
|
||||
fi
|
||||
@@ -21,11 +21,12 @@ env:
|
||||
PYTHONWARNINGS: error
|
||||
BASE_IMAGE: sunnypilot-base
|
||||
AZURE_TOKEN: ${{ secrets.AZURE_COMMADATACI_OPENPILOTCI_TOKEN }}
|
||||
MAPBOX_TOKEN_CI: ${{ secrets.MAPBOX_TOKEN_CI }}
|
||||
|
||||
DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
|
||||
BUILD: release/ci/docker_build_sp.sh base
|
||||
|
||||
RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c
|
||||
RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -e MAPBOX_TOKEN_CI=$MAPBOX_TOKEN_CI -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c
|
||||
|
||||
PYTEST: pytest --continue-on-collection-errors --durations=0 -n logical
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
is_stable_branch="$(echo "$CONFIG" | jq -r '.stable_branch // false')";
|
||||
echo "is_stable_branch=$is_stable_branch" >> $GITHUB_OUTPUT
|
||||
|
||||
stable_version=$(cat common/version.h | grep COMMA_VERSION | sed -e 's/[^0-9|.]//g');
|
||||
stable_version=$(cat sunnypilot/common/version.h | grep SUNNYPILOT_VERSION | sed -e 's/[^0-9|.]//g');
|
||||
echo "version=$([ "$is_stable_branch" = "true" ] && echo "$stable_version" || echo "$BUILD")" >> $GITHUB_OUTPUT
|
||||
echo "extra_version_identifier=${environment}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
@@ -302,36 +302,51 @@ jobs:
|
||||
git push -f origin ${TAG}
|
||||
|
||||
notify:
|
||||
needs: [ build, publish ]
|
||||
needs:
|
||||
- prepare_strategy
|
||||
- build
|
||||
- publish
|
||||
runs-on: ubuntu-24.04
|
||||
if: ${{ (always() && !cancelled() && !failure()) && needs.publish.result == 'success' && !failure() && (!contains(github.event_name, 'pull_request') || (github.event.action == 'labeled' && github.event.label.name == 'prebuilt')) }}
|
||||
if: ${{ (always() && !cancelled() && !failure())
|
||||
&& needs.publish.result == 'success'
|
||||
&& (!contains(github.event_name, 'pull_request') || (github.event.action == 'labeled' && github.event.label.name == 'prebuilt'))
|
||||
&& (fromJSON(vars.DEV_FEEDBACK_NOTIFICATION_BRANCHES_V2)[github.head_ref || github.ref_name] != null) }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Alpine Linux environment
|
||||
uses: jirutka/setup-alpine@v1.2.0
|
||||
with:
|
||||
packages: 'jq gettext curl'
|
||||
|
||||
- name: Send Discord Notification
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ contains(fromJSON(vars.DEV_FEEDBACK_NOTIFICATION_BRANCHES), env.SOURCE_BRANCH) && secrets.DISCORD_DEV_FEEDBACK_CHANNEL_WEBHOOK || secrets.DISCORD_DEV_PRIVATE_CHANNEL_WEBHOOK }}
|
||||
- name: Prepare notification message
|
||||
id: message
|
||||
run: |
|
||||
TEMPLATE='${{ vars.DISCORD_GENERAL_UPDATE_NOTICE }}'
|
||||
export EXTRA_VERSION_IDENTIFIER="${{ needs.build.outputs.extra_version_identifier }}"
|
||||
export VERSION="${{ needs.build.outputs.version }}"
|
||||
export branch_name=${{ env.SOURCE_BRANCH }}
|
||||
export new_branch=${{ needs.build.outputs.new_branch }}
|
||||
export extra_version_identifier=${{ needs.build.outputs.extra_version_identifier || github.run_number}}
|
||||
echo ${TEMPLATE} | envsubst | jq -c '.' | tee payload.json
|
||||
curl -X POST -H "Content-Type: application/json" -d @payload.json $DISCORD_WEBHOOK
|
||||
TEMPLATE='${{ vars.DISCOURSE_GENERAL_UPDATE_NOTICE }}'
|
||||
export VERSION="${{ needs.prepare_strategy.outputs.version }}"
|
||||
export branch_name="${{ env.SOURCE_BRANCH }}"
|
||||
export new_branch="${{ needs.prepare_strategy.outputs.new_branch }}"
|
||||
export commit_sha="${{ github.sha }}"
|
||||
export commit_short_sha="${{ github.sha }}"
|
||||
export commit_short_sha="${commit_short_sha:0:7}"
|
||||
export extra_version_identifier="${{ needs.prepare_strategy.outputs.extra_version_identifier || github.run_number }}"
|
||||
export PUBLIC_REPO_URL="${{ env.PUBLIC_REPO_URL }}"
|
||||
|
||||
echo ""
|
||||
echo "---- ℹ️ To update the list of branches that notify to dev-feedback -----"
|
||||
echo ""
|
||||
echo "1. Go to: ${{ github.server_url }}/${{ github.repository }}/settings/variables/actions/DEV_FEEDBACK_NOTIFICATION_BRANCHES"
|
||||
echo "2. Current value: ${{ vars.DEV_FEEDBACK_NOTIFICATION_BRANCHES }}"
|
||||
echo "3. Update as needed (JSON array with no spaces)"
|
||||
shell: alpine.sh {0}
|
||||
MESSAGE=$(cat << 'EOF' | envsubst
|
||||
${{ vars.DISCOURSE_GENERAL_UPDATE_NOTICE }}
|
||||
EOF
|
||||
)
|
||||
|
||||
{
|
||||
echo 'content<<EOFMARKER'
|
||||
echo "$MESSAGE"
|
||||
echo 'EOFMARKER'
|
||||
} >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Post to Discourse
|
||||
uses: ./.github/workflows/post-to-discourse
|
||||
with:
|
||||
discourse-url: ${{ vars.DISCOURSE_URL }}
|
||||
api-key: ${{ secrets.DISCOURSE_API_KEY }}
|
||||
api-username: "system"
|
||||
topic-id: ${{ fromJSON(vars.DEV_FEEDBACK_NOTIFICATION_BRANCHES_V2)[github.head_ref || github.ref_name].topic_id }}
|
||||
message: ${{ steps.message.outputs.content }}
|
||||
|
||||
manage-pr-labels:
|
||||
name: Remove prebuilt label
|
||||
|
||||
@@ -3,7 +3,6 @@ name: Build dev
|
||||
env:
|
||||
DEFAULT_SOURCE_BRANCH: "master"
|
||||
DEFAULT_TARGET_BRANCH: "master-dev"
|
||||
PR_LABEL: "dev"
|
||||
LFS_URL: 'https://gitlab.com/sunnypilot/public/sunnypilot-new-lfs.git/info/lfs'
|
||||
LFS_PUSH_URL: 'ssh://git@gitlab.com/sunnypilot/public/sunnypilot-new-lfs.git'
|
||||
|
||||
@@ -43,7 +42,7 @@ jobs:
|
||||
if: (
|
||||
(github.event_name == 'workflow_dispatch')
|
||||
|| (github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch))
|
||||
|| (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == 'dev' || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, 'dev'))))
|
||||
|| (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == vars.PREBUILT_PR_LABEL || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, vars.PREBUILT_PR_LABEL))))
|
||||
)
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -55,7 +54,7 @@ jobs:
|
||||
uses: ./.github/workflows/wait-for-action # Path to where you place the action
|
||||
if: (
|
||||
(github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch))
|
||||
|| (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == 'dev' || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, 'dev'))))
|
||||
|| (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == vars.PREBUILT_PR_LABEL || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, vars.PREBUILT_PR_LABEL))))
|
||||
)
|
||||
with:
|
||||
workflow: selfdrive_tests.yaml # The workflow file to monitor
|
||||
@@ -119,7 +118,7 @@ jobs:
|
||||
# Use GitHub API to get PRs with specific label, ordered by creation date
|
||||
PR_LIST=$(gh api graphql -f query='
|
||||
query($search_query:String!) {
|
||||
search(query: $search_query, type:ISSUE, first:100) {
|
||||
search(query: $search_query, type:ISSUE, first:40) {
|
||||
nodes {
|
||||
... on PullRequest {
|
||||
number
|
||||
@@ -149,7 +148,7 @@ jobs:
|
||||
}
|
||||
}
|
||||
}
|
||||
}' -F search_query="repo:${{ github.repository }} is:pr is:open label:${PR_LABEL},${PR_LABEL}-c3 draft:false sort:created-asc")
|
||||
}' -F search_query="repo:${{ github.repository }} is:pr is:open label:${{ vars.PREBUILT_PR_LABEL }},${{ vars.PREBUILT_PR_LABEL }}-c3 draft:false sort:created-asc")
|
||||
|
||||
PR_LIST=${PR_LIST//\'/}
|
||||
echo "PR_LIST=${PR_LIST}" >> $GITHUB_OUTPUT
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
name: Debug Discourse Posting
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
jobs:
|
||||
test-discourse-post:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Post test message to Discourse
|
||||
uses: ./.github/workflows/post-to-discourse
|
||||
with:
|
||||
discourse-url: ${{ vars.DISCOURSE_URL }}
|
||||
api-key: ${{ secrets.DISCOURSE_API_KEY }}
|
||||
api-username: ${{ secrets.DISCOURSE_API_USERNAME }}
|
||||
topic-id: ${{ vars.DISCOURSE_UPDATES_TOPIC_ID }}
|
||||
message: |
|
||||
## 🧪 Test Post from GitHub Actions
|
||||
|
||||
**This is a test post to verify Discourse integration**
|
||||
|
||||
- **Workflow**: ${{ github.workflow }}
|
||||
- **Run Number**: #${{ github.run_number }}
|
||||
- **Branch**: `${{ github.ref_name }}`
|
||||
- **Commit**: ${{ github.sha }}
|
||||
- **Actor**: @${{ github.actor }}
|
||||
- **Timestamp**: ${{ github.event.head_commit.timestamp }}
|
||||
|
||||
---
|
||||
|
||||
### Fake Build Info (for testing)
|
||||
- **Version**: 0.9.8-test
|
||||
- **Build**: #42
|
||||
- **Branch**: release-test
|
||||
|
||||
[View workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
|
||||
|
||||
*This is an automated test message. Drive safe! 🚗💨*
|
||||
|
||||
|
||||
- name: Create topic on Discourse
|
||||
uses: ./.github/workflows/post-to-discourse
|
||||
with:
|
||||
discourse-url: ${{ vars.DISCOURSE_URL }}
|
||||
api-key: ${{ secrets.DISCOURSE_API_KEY }}
|
||||
api-username: ${{ secrets.DISCOURSE_API_USERNAME }}
|
||||
#topic-id: ${{ vars.DISCOURSE_UPDATES_TOPIC_ID }}
|
||||
category-id: 4
|
||||
title: "This is a test of a new topic instead of a reply"
|
||||
message: |
|
||||
## 🧪 Test Post from GitHub Actions
|
||||
|
||||
**This is a test post to verify Discourse integration**
|
||||
|
||||
- **Workflow**: ${{ github.workflow }}
|
||||
- **Run Number**: #${{ github.run_number }}
|
||||
- **Branch**: `${{ github.ref_name }}`
|
||||
- **Commit**: ${{ github.sha }}
|
||||
- **Actor**: @${{ github.actor }}
|
||||
- **Timestamp**: ${{ github.event.head_commit.timestamp }}
|
||||
|
||||
---
|
||||
|
||||
### Fake Build Info (for testing)
|
||||
- **Version**: 0.9.8-test
|
||||
- **Build**: #42
|
||||
- **Branch**: release-test
|
||||
|
||||
[View workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
|
||||
|
||||
*This is an automated test message. Drive safe! 🚗💨*
|
||||
- name: Display results
|
||||
if: always()
|
||||
run: |
|
||||
echo "::notice::Discourse post test completed"
|
||||
echo "Check your Discourse topic to verify the post appeared correctly"
|
||||
+1080
File diff suppressed because it is too large
Load Diff
+58
-2
@@ -69,6 +69,48 @@ struct LeadData {
|
||||
struct SelfdriveStateSP @0x81c2f05a394cf4af {
|
||||
mads @0 :ModularAssistiveDrivingSystem;
|
||||
intelligentCruiseButtonManagement @1 :IntelligentCruiseButtonManagement;
|
||||
|
||||
enum AudibleAlert {
|
||||
none @0;
|
||||
|
||||
engage @1;
|
||||
disengage @2;
|
||||
refuse @3;
|
||||
|
||||
warningSoft @4;
|
||||
warningImmediate @5;
|
||||
|
||||
prompt @6;
|
||||
promptRepeat @7;
|
||||
promptDistracted @8;
|
||||
|
||||
# unused, these are reserved for upstream events so we don't collide
|
||||
reserved9 @9;
|
||||
reserved10 @10;
|
||||
reserved11 @11;
|
||||
reserved12 @12;
|
||||
reserved13 @13;
|
||||
reserved14 @14;
|
||||
reserved15 @15;
|
||||
reserved16 @16;
|
||||
reserved17 @17;
|
||||
reserved18 @18;
|
||||
reserved19 @19;
|
||||
reserved20 @20;
|
||||
reserved21 @21;
|
||||
reserved22 @22;
|
||||
reserved23 @23;
|
||||
reserved24 @24;
|
||||
reserved25 @25;
|
||||
reserved26 @26;
|
||||
reserved27 @27;
|
||||
reserved28 @28;
|
||||
reserved29 @29;
|
||||
reserved30 @30;
|
||||
|
||||
promptSingleLow @31;
|
||||
promptSingleHigh @32;
|
||||
}
|
||||
}
|
||||
|
||||
struct ModelManagerSP @0xaedffd8f31e7b55d {
|
||||
@@ -298,6 +340,7 @@ struct OnroadEventSP @0xda96579883444c35 {
|
||||
speedLimitChanged @21;
|
||||
speedLimitPending @22;
|
||||
e2eChime @23;
|
||||
navigationBanner @24;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -404,15 +447,28 @@ struct LiveMapDataSP @0xf416ec09499d9d19 {
|
||||
|
||||
struct ModelDataV2SP @0xa1680744031fdb2d {
|
||||
laneTurnDirection @0 :TurnDirection;
|
||||
}
|
||||
|
||||
enum TurnDirection {
|
||||
none @0;
|
||||
turnLeft @1;
|
||||
turnRight @2;
|
||||
}
|
||||
}
|
||||
|
||||
struct CustomReserved10 @0xcb9fd56c7057593a {
|
||||
struct Navigationd @0xcb9fd56c7057593a {
|
||||
upcomingTurn @0 :Text;
|
||||
currentSpeedLimit @1 :UInt64;
|
||||
bannerInstructions @2 :Text;
|
||||
distanceFromRoute @3 :Float64;
|
||||
allManeuvers @4 :List(Maneuver);
|
||||
valid @5 :Bool;
|
||||
|
||||
struct Maneuver {
|
||||
distance @0 :Float64;
|
||||
type @1 :Text;
|
||||
modifier @2 :Text;
|
||||
instruction @3 :Text;
|
||||
}
|
||||
}
|
||||
|
||||
struct CustomReserved11 @0xc2243c65e0340384 {
|
||||
|
||||
+1
-1
@@ -2632,7 +2632,7 @@ struct Event {
|
||||
carStateSP @114 :Custom.CarStateSP;
|
||||
liveMapDataSP @115 :Custom.LiveMapDataSP;
|
||||
modelDataV2SP @116 :Custom.ModelDataV2SP;
|
||||
customReserved10 @136 :Custom.CustomReserved10;
|
||||
navigationd @136 :Custom.Navigationd;
|
||||
customReserved11 @137 :Custom.CustomReserved11;
|
||||
customReserved12 @138 :Custom.CustomReserved12;
|
||||
customReserved13 @139 :Custom.CustomReserved13;
|
||||
|
||||
@@ -89,6 +89,7 @@ _services: dict[str, tuple] = {
|
||||
"carStateSP": (True, 100., 10),
|
||||
"liveMapDataSP": (True, 1., 1),
|
||||
"modelDataV2SP": (True, 20.),
|
||||
"navigationd": (True, 3.),
|
||||
"liveLocationKalman": (True, 20.),
|
||||
|
||||
# debug
|
||||
|
||||
+14
-2
@@ -154,15 +154,16 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"IntelligentCruiseButtonManagement", {PERSISTENT | BACKUP , BOOL}},
|
||||
{"InteractivityTimeout", {PERSISTENT | BACKUP, INT, "0"}},
|
||||
{"IsDevelopmentBranch", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||
{"IsReleaseSpBranch", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||
{"LastGPSPositionLLK", {PERSISTENT, STRING}},
|
||||
{"LeadDepartAlert", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"MaxTimeOffroad", {PERSISTENT | BACKUP, INT, "1800"}},
|
||||
{"ModelRunnerTypeCache", {CLEAR_ON_ONROAD_TRANSITION, INT}},
|
||||
{"OffroadMode", {CLEAR_ON_MANAGER_START, BOOL}},
|
||||
{"Offroad_TiciSupport", {CLEAR_ON_MANAGER_START, JSON}},
|
||||
{"OnroadScreenOffBrightness", {PERSISTENT | BACKUP, INT, "100"}},
|
||||
{"OnroadScreenOffBrightness", {PERSISTENT | BACKUP, INT, "0"}},
|
||||
{"OnroadScreenOffControl", {PERSISTENT | BACKUP, BOOL}},
|
||||
{"OnroadScreenOffTimer", {PERSISTENT | BACKUP, INT, "0"}},
|
||||
{"OnroadScreenOffTimer", {PERSISTENT | BACKUP, INT, "15"}},
|
||||
{"OnroadUploads", {PERSISTENT | BACKUP, BOOL, "1"}},
|
||||
{"QuickBootToggle", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"QuietMode", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
@@ -186,6 +187,15 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"ModelManager_LastSyncTime", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, INT, "0"}},
|
||||
{"ModelManager_ModelsCache", {PERSISTENT | BACKUP, JSON}},
|
||||
|
||||
// Navigation params
|
||||
{"AllowNavigation", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"MapboxToken", {PERSISTENT | BACKUP, STRING}},
|
||||
{"MapboxSettings", {CLEAR_ON_MANAGER_START, JSON}},
|
||||
{"MapboxRoute", {PERSISTENT, STRING}},
|
||||
{"MapboxRecompute", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"NavDesiresAllowed", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"NavEvents", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
|
||||
// Neural Network Lateral Control
|
||||
{"NeuralNetworkLateralControl", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
|
||||
@@ -205,6 +215,8 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
|
||||
// sunnypilot car specific params
|
||||
{"HyundaiLongitudinalTuning", {PERSISTENT | BACKUP, INT, "0"}},
|
||||
{"SubaruStopAndGo", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"SubaruStopAndGoManualParkingBrake", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
|
||||
{"DynamicExperimentalControl", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"BlindSpot", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
|
||||
+3
-1
@@ -15,6 +15,8 @@
|
||||
#include "common/version.h"
|
||||
#include "system/hardware/hw.h"
|
||||
|
||||
#include "sunnypilot/common/version.h"
|
||||
|
||||
class SwaglogState {
|
||||
public:
|
||||
SwaglogState() {
|
||||
@@ -56,7 +58,7 @@ public:
|
||||
if (char* daemon_name = getenv("MANAGER_DAEMON")) {
|
||||
ctx_j["daemon"] = daemon_name;
|
||||
}
|
||||
ctx_j["version"] = COMMA_VERSION;
|
||||
ctx_j["version"] = SUNNYPILOT_VERSION;
|
||||
ctx_j["dirty"] = !getenv("CLEAN");
|
||||
ctx_j["device"] = Hardware::get_name();
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ from openpilot.common.markdown import parse_markdown
|
||||
|
||||
class TestMarkdown:
|
||||
def test_all_release_notes(self):
|
||||
with open(os.path.join(BASEDIR, "RELEASES.md")) as f:
|
||||
with open(os.path.join(BASEDIR, "CHANGELOG.md")) as f:
|
||||
release_notes = f.read().split("\n\n")
|
||||
assert len(release_notes) > 10
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#include "system/hardware/hw.h"
|
||||
#include "third_party/json11/json11.hpp"
|
||||
|
||||
#include "sunnypilot/common/version.h"
|
||||
|
||||
std::string daemon_name = "testy";
|
||||
std::string dongle_id = "test_dongle_id";
|
||||
int LINE_NO = 0;
|
||||
@@ -53,7 +55,7 @@ void recv_log(int thread_cnt, int thread_msg_cnt) {
|
||||
REQUIRE(ctx["dongle_id"].string_value() == dongle_id);
|
||||
REQUIRE(ctx["dirty"].bool_value() == true);
|
||||
|
||||
REQUIRE(ctx["version"].string_value() == COMMA_VERSION);
|
||||
REQUIRE(ctx["version"].string_value() == SUNNYPILOT_VERSION);
|
||||
|
||||
std::string device = Hardware::get_name();
|
||||
REQUIRE(ctx["device"].string_value() == device);
|
||||
|
||||
+16
-14
@@ -4,7 +4,7 @@
|
||||
|
||||
A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified.
|
||||
|
||||
# 337 Supported Cars
|
||||
# 339 Supported Cars
|
||||
|
||||
|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|<a href="##"><img width=2000></a>Hardware Needed<br> |Video|Setup Video|
|
||||
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
|
||||
@@ -21,7 +21,10 @@ A supported vehicle is one that just works when you install a comma device. All
|
||||
|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Audi S3 2015-17">Buy Here</a></sub></details>|||
|
||||
|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Bolt EUV 2022-23">Buy Here</a></sub></details>|<a href="https://youtu.be/xvwzGMUA210" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Chevrolet|Bolt EV 2022-23|2LT Trim with Adaptive Cruise Control Package|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Bolt EV 2022-23">Buy Here</a></sub></details>|||
|
||||
|Chevrolet|Bolt EV Non-ACC 2017|Adaptive Cruise Control (ACC)|Stock|24 mph|7 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Bolt EV Non-ACC 2017">Buy Here</a></sub></details>|||
|
||||
|Chevrolet|Bolt EV Non-ACC 2018-21|Adaptive Cruise Control (ACC)|Stock|24 mph|7 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Bolt EV Non-ACC 2018-21">Buy Here</a></sub></details>|||
|
||||
|Chevrolet|Equinox 2019-22|Adaptive Cruise Control (ACC)|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Equinox 2019-22">Buy Here</a></sub></details>|||
|
||||
|Chevrolet|Malibu Non-ACC 2016-23|Adaptive Cruise Control (ACC)|Stock|24 mph|7 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Malibu Non-ACC 2016-23">Buy Here</a></sub></details>|||
|
||||
|Chevrolet|Silverado 1500 2020-21|Safety Package II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|6 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Silverado 1500 2020-21">Buy Here</a></sub></details>|||
|
||||
|Chevrolet|Trailblazer 2021-22|Adaptive Cruise Control (ACC)|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 GM connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Chevrolet Trailblazer 2021-22">Buy Here</a></sub></details>|||
|
||||
|Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 FCA connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Chrysler Pacifica 2017-18">Buy Here</a></sub></details>|||
|
||||
@@ -236,20 +239,20 @@ A supported vehicle is one that just works when you install a comma device. All
|
||||
|Rivian|R1T 2022-24|All|openpilot|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Rivian A connector<br>- 1 USB-C coupler<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Rivian R1T 2022-24">Buy Here</a></sub></details>||<a href="https://youtu.be/uaISd1j7Z4U" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|
||||
|SEAT|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=SEAT Ateca 2016-23">Buy Here</a></sub></details>|||
|
||||
|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=SEAT Leon 2014-20">Buy Here</a></sub></details>|||
|
||||
|Subaru|Ascent 2019-21|All[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Ascent 2019-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Crosstrek 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|<a href="https://youtu.be/Agww7oE1k-s?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Crosstrek 2020-23">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Forester 2017-18|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Forester 2017-18">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Forester 2019-21|All[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Forester 2019-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Impreza 2017-19|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Impreza 2017-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Impreza 2020-22|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Impreza 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Legacy 2015-18|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Legacy 2015-18">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Ascent 2019-21|All[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Ascent 2019-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Crosstrek 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|<a href="https://youtu.be/Agww7oE1k-s?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Crosstrek 2020-23">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Forester 2017-18|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Forester 2017-18">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Forester 2019-21|All[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Forester 2019-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Impreza 2017-19|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Impreza 2017-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Impreza 2020-22|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Impreza 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Legacy 2015-18|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Legacy 2015-18">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Legacy 2020-22|All[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Legacy 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Outback 2015-17|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Outback 2015-17">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Outback 2018-19|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Outback 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Outback 2015-17|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Outback 2015-17">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Outback 2018-19|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Outback 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|Outback 2020-22|All[<sup>8</sup>](#footnotes)|Stock|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru B connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru Outback 2020-22">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|XV 2018-19|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru XV 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|<a href="https://youtu.be/Agww7oE1k-s?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Subaru|XV 2020-21|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru XV 2020-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Subaru|XV 2018-19|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru XV 2018-19">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|<a href="https://youtu.be/Agww7oE1k-s?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Subaru|XV 2020-21|EyeSight Driver Assistance[<sup>8</sup>](#footnotes)|openpilot available[<sup>1,9</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Subaru A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Subaru XV 2020-21">Buy Here</a></sub></details><details><summary>Tools</summary><sub>- 1 Pry Tool<br>- 1 Socket Wrench 8mm or 5/16" (deep)</sub></details>|||
|
||||
|Škoda|Fabia 2022-23[<sup>15</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Fabia 2022-23">Buy Here</a></sub></details>[<sup>17</sup>](#footnotes)|||
|
||||
|Škoda|Kamiq 2021-23[<sup>13,15</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Kamiq 2021-23">Buy Here</a></sub></details>[<sup>17</sup>](#footnotes)|||
|
||||
|Škoda|Karoq 2019-23[<sup>15</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Škoda Karoq 2019-23">Buy Here</a></sub></details>|||
|
||||
@@ -308,7 +311,6 @@ A supported vehicle is one that just works when you install a comma device. All
|
||||
|Toyota|RAV4 Hybrid 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota RAV4 Hybrid 2022">Buy Here</a></sub></details>|<a href="https://youtu.be/U0nH9cnrFB0" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Toyota|RAV4 Hybrid 2023-25|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota RAV4 Hybrid 2023-25">Buy Here</a></sub></details>|<a href="https://youtu.be/4eIsEq4L4Ng" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Toyota|Sienna 2018-20|All|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Sienna 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=q1UPOo4Sh68" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Toyota|Wildlander PHEV 2021|All|openpilot|19 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v3<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Toyota Wildlander PHEV 2021">Buy Here</a></sub></details>|||
|
||||
|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon 2018-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon eHybrid 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|Volkswagen|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,16</sup>](#footnotes)|0 mph|0 mph|[](##)|[](##)|<details><summary>Parts</summary><sub>- 1 USB-C coupler<br>- 1 VW J533 connector<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable (9.5 ft)<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x?harness=Volkswagen Arteon R 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>||
|
||||
|
||||
+1
-1
Submodule opendbc_repo updated: b592ecdd3b...e0e1626820
@@ -39,7 +39,7 @@ cd $BUILD_DIR
|
||||
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}')
|
||||
VERSION=$(cat sunnypilot/common/version.h | awk -F[\"-] '{print $2}')
|
||||
echo "[-] committing version $VERSION T=$SECONDS"
|
||||
git add -f .
|
||||
git commit -a -m "openpilot v$VERSION release"
|
||||
|
||||
@@ -49,7 +49,7 @@ rm -f panda/board/obj/panda.bin.signed
|
||||
GIT_HASH=$(git --git-dir=$SOURCE_DIR/.git rev-parse HEAD)
|
||||
GIT_COMMIT_DATE=$(git --git-dir=$SOURCE_DIR/.git show --no-patch --format='%ct %ci' HEAD)
|
||||
DATETIME=$(date '+%Y-%m-%dT%H:%M:%S')
|
||||
VERSION=$(cat $SOURCE_DIR/common/version.h | awk -F\" '{print $2}')
|
||||
VERSION=$(cat $SOURCE_DIR/sunnypilot/common/version.h | awk -F\" '{print $2}')
|
||||
|
||||
echo -n "$GIT_HASH" > git_src_commit
|
||||
echo -n "$GIT_COMMIT_DATE" > git_src_commit_date
|
||||
|
||||
@@ -30,7 +30,7 @@ if [ -z "$GIT_ORIGIN" ]; then
|
||||
fi
|
||||
|
||||
# "Tagging"
|
||||
echo "#define COMMA_VERSION \"$VERSION\"" > ${OUTPUT_DIR}/common/version.h
|
||||
echo "#define SUNNYPILOT_VERSION \"$VERSION\"" > ${OUTPUT_DIR}/sunnypilot/common/version.h
|
||||
|
||||
## set git identity
|
||||
#source $DIR/identity.sh
|
||||
@@ -55,7 +55,7 @@ git add -f .
|
||||
# include source commit hash and build date in commit
|
||||
GIT_HASH=$(git --git-dir=$SOURCE_DIR/.git rev-parse HEAD)
|
||||
DATETIME=$(date '+%Y-%m-%dT%H:%M:%S')
|
||||
SP_VERSION=$(awk -F\" '{print $2}' $SOURCE_DIR/common/version.h)
|
||||
SP_VERSION=$(awk -F\" '{print $2}' $SOURCE_DIR/sunnypilot/common/version.h)
|
||||
|
||||
# Commit with detailed message
|
||||
git commit -a -m "sunnypilot v$VERSION
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:dbfa5858c0a672411ffdc691efdecb06d01ae458cc1df409bcf3fdeaa4756f72
|
||||
size 34638
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:db9671bb03e01f119bba1eb6cc0507e0f039ac4e5b7f9f839a87071c52e86e56
|
||||
size 44416
|
||||
@@ -88,6 +88,7 @@ class Car:
|
||||
self.can_callbacks = can_comm_callbacks(self.can_sock, self.pm.sock['sendcan'])
|
||||
|
||||
is_release = self.params.get_bool("IsReleaseBranch")
|
||||
is_release_sp = self.params.get_bool("IsReleaseSpBranch")
|
||||
|
||||
if CI is None:
|
||||
# wait for one pandaState and one CAN packet
|
||||
@@ -110,7 +111,7 @@ class Car:
|
||||
init_params_list_sp = sunnypilot_interfaces.initialize_params(self.params)
|
||||
|
||||
self.CI = get_car(*self.can_callbacks, obd_callback(self.params), alpha_long_allowed, is_release, num_pandas, cached_params,
|
||||
fixed_fingerprint, init_params_list_sp)
|
||||
fixed_fingerprint, init_params_list_sp, is_release_sp)
|
||||
sunnypilot_interfaces.setup_interfaces(self.CI, self.params)
|
||||
self.RI = interfaces[self.CI.CP.carFingerprint].RadarInterface(self.CI.CP, self.CI.CP_SP)
|
||||
self.CP = self.CI.CP
|
||||
|
||||
@@ -151,7 +151,7 @@ class TestCarModelBase(unittest.TestCase):
|
||||
|
||||
cls.CarInterface = interfaces[cls.platform]
|
||||
cls.CP = cls.CarInterface.get_params(cls.platform, cls.fingerprint, car_fw, alpha_long, False, docs=False)
|
||||
cls.CP_SP = cls.CarInterface.get_params_sp(cls.CP, cls.platform, cls.fingerprint, car_fw, alpha_long, docs=False)
|
||||
cls.CP_SP = cls.CarInterface.get_params_sp(cls.CP, cls.platform, cls.fingerprint, car_fw, alpha_long, False, docs=False)
|
||||
assert cls.CP
|
||||
assert cls.CP_SP
|
||||
assert cls.CP.carFingerprint == cls.platform
|
||||
|
||||
@@ -99,7 +99,6 @@ class Controls(ControlsExt, ModelStateBase):
|
||||
|
||||
self.LaC.extension.update_model_v2(self.sm['modelV2'])
|
||||
|
||||
self.lat_delay = get_lat_delay(self.params, self.sm["liveDelay"].lateralDelay)
|
||||
self.LaC.extension.update_lateral_lag(self.lat_delay)
|
||||
|
||||
long_plan = self.sm['longitudinalPlan']
|
||||
@@ -133,7 +132,7 @@ class Controls(ControlsExt, ModelStateBase):
|
||||
self.LoC.reset()
|
||||
|
||||
# accel PID loop
|
||||
pid_accel_limits = self.CI.get_pid_accel_limits(self.CP, CS.vEgo, CS.vCruise * CV.KPH_TO_MS)
|
||||
pid_accel_limits = self.CI.get_pid_accel_limits(self.CP, self.CP_SP, CS.vEgo, CS.vCruise * CV.KPH_TO_MS)
|
||||
actuators.accel = float(self.LoC.update(CC.longActive, CS, long_plan.aTarget, long_plan.shouldStop, pid_accel_limits))
|
||||
|
||||
# Steering PID loop and lateral MPC
|
||||
@@ -234,6 +233,9 @@ class Controls(ControlsExt, ModelStateBase):
|
||||
while not evt.is_set():
|
||||
self.get_params_sp()
|
||||
|
||||
if self.CP.lateralTuning.which() == 'torque':
|
||||
self.lat_delay = get_lat_delay(self.params, self.sm["liveDelay"].lateralDelay)
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
def run(self):
|
||||
|
||||
@@ -3,9 +3,11 @@ from openpilot.common.constants import CV
|
||||
from openpilot.common.realtime import DT_MDL
|
||||
from openpilot.sunnypilot.selfdrive.controls.lib.auto_lane_change import AutoLaneChangeController, AutoLaneChangeMode
|
||||
from openpilot.sunnypilot.selfdrive.controls.lib.lane_turn_desire import LaneTurnController
|
||||
from openpilot.sunnypilot.navd.navigation_desires.navigation_desires import NavigationDesires
|
||||
|
||||
LaneChangeState = log.LaneChangeState
|
||||
LaneChangeDirection = log.LaneChangeDirection
|
||||
TurnDirection = custom.ModelDataV2SP.TurnDirection
|
||||
|
||||
LANE_CHANGE_SPEED_MIN = 20 * CV.MPH_TO_MS
|
||||
LANE_CHANGE_TIME_MAX = 10.
|
||||
@@ -32,9 +34,9 @@ DESIRES = {
|
||||
}
|
||||
|
||||
TURN_DESIRES = {
|
||||
custom.TurnDirection.none: log.Desire.none,
|
||||
custom.TurnDirection.turnLeft: log.Desire.turnLeft,
|
||||
custom.TurnDirection.turnRight: log.Desire.turnRight,
|
||||
TurnDirection.none: log.Desire.none,
|
||||
TurnDirection.turnLeft: log.Desire.turnLeft,
|
||||
TurnDirection.turnRight: log.Desire.turnRight,
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +51,8 @@ class DesireHelper:
|
||||
self.desire = log.Desire.none
|
||||
self.alc = AutoLaneChangeController(self)
|
||||
self.lane_turn_controller = LaneTurnController(self)
|
||||
self.lane_turn_direction = custom.TurnDirection.none
|
||||
self.lane_turn_direction = TurnDirection.none
|
||||
self.navigation_desires = NavigationDesires()
|
||||
|
||||
@staticmethod
|
||||
def get_lane_change_direction(CS):
|
||||
@@ -126,7 +129,7 @@ class DesireHelper:
|
||||
|
||||
self.prev_one_blinker = one_blinker
|
||||
|
||||
if self.lane_turn_direction != custom.TurnDirection.none:
|
||||
if self.lane_turn_direction != TurnDirection.none:
|
||||
self.desire = TURN_DESIRES[self.lane_turn_direction]
|
||||
else:
|
||||
self.desire = DESIRES[self.lane_change_direction][self.lane_change_state]
|
||||
@@ -142,3 +145,7 @@ class DesireHelper:
|
||||
self.desire = log.Desire.none
|
||||
|
||||
self.alc.update_state()
|
||||
|
||||
nav_desire = self.navigation_desires.update(carstate, lateral_active)
|
||||
if nav_desire != log.Desire.none and (self.desire == log.Desire.none or self.desire in (log.Desire.turnLeft, log.Desire.turnRight)):
|
||||
self.desire = nav_desire
|
||||
|
||||
@@ -51,12 +51,12 @@ def limit_accel_in_turns(v_ego, angle_steers, a_target, CP):
|
||||
|
||||
|
||||
class LongitudinalPlanner(LongitudinalPlannerSP):
|
||||
def __init__(self, CP, init_v=0.0, init_a=0.0, dt=DT_MDL):
|
||||
def __init__(self, CP, CP_SP, init_v=0.0, init_a=0.0, dt=DT_MDL):
|
||||
self.CP = CP
|
||||
self.mpc = LongitudinalMpc(dt=dt)
|
||||
# TODO remove mpc modes when TR released
|
||||
self.mpc.mode = 'acc'
|
||||
LongitudinalPlannerSP.__init__(self, self.CP, self.mpc)
|
||||
LongitudinalPlannerSP.__init__(self, self.CP, CP_SP, self.mpc)
|
||||
self.fcw = False
|
||||
self.dt = dt
|
||||
self.allow_throttle = True
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
from cereal import car
|
||||
from cereal import car, custom
|
||||
from openpilot.common.gps import get_gps_location_service
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.realtime import Priority, config_realtime_process
|
||||
@@ -17,13 +17,17 @@ def main():
|
||||
CP = messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams)
|
||||
cloudlog.info("plannerd got CarParams: %s", CP.brand)
|
||||
|
||||
cloudlog.info("plannerd is waiting for CarParamsSP")
|
||||
CP_SP = messaging.log_from_bytes(params.get("CarParamsSP", block=True), custom.CarParamsSP)
|
||||
cloudlog.info("plannerd got CarParamsSP")
|
||||
|
||||
gps_location_service = get_gps_location_service(params)
|
||||
|
||||
ldw = LaneDepartureWarning()
|
||||
longitudinal_planner = LongitudinalPlanner(CP)
|
||||
longitudinal_planner = LongitudinalPlanner(CP, CP_SP)
|
||||
pm = messaging.PubMaster(['longitudinalPlan', 'driverAssistance', 'longitudinalPlanSP'])
|
||||
sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'liveParameters', 'radarState', 'modelV2', 'selfdriveState',
|
||||
'liveMapDataSP', 'carStateSP', gps_location_service],
|
||||
'liveMapDataSP', 'navigationd', 'carStateSP', gps_location_service],
|
||||
poll='carState')
|
||||
|
||||
while True:
|
||||
|
||||
@@ -24,6 +24,7 @@ from openpilot.selfdrive.selfdrived.alertmanager import AlertManager, set_offroa
|
||||
from openpilot.system.version import get_build_metadata
|
||||
|
||||
from openpilot.sunnypilot.mads.mads import ModularAssistiveDrivingSystem
|
||||
from openpilot.sunnypilot import get_sanitize_int_param
|
||||
from openpilot.sunnypilot.selfdrive.car.car_specific import CarSpecificEventsSP
|
||||
from openpilot.sunnypilot.selfdrive.car.cruise_helpers import CruiseHelper
|
||||
from openpilot.sunnypilot.selfdrive.car.intelligent_cruise_button_management.controller import IntelligentCruiseButtonManagement
|
||||
@@ -43,6 +44,7 @@ LaneChangeDirection = log.LaneChangeDirection
|
||||
EventName = log.OnroadEvent.EventName
|
||||
ButtonType = car.CarState.ButtonEvent.Type
|
||||
SafetyModel = car.CarParams.SafetyModel
|
||||
TurnDirection = custom.ModelDataV2SP.TurnDirection
|
||||
|
||||
IGNORED_SAFETY_MODES = (SafetyModel.silent, SafetyModel.noOutput)
|
||||
|
||||
@@ -86,7 +88,7 @@ class SelfdriveD(CruiseHelper):
|
||||
# TODO: de-couple selfdrived with card/conflate on carState without introducing controls mismatches
|
||||
self.car_state_sock = messaging.sub_sock('carState', timeout=20)
|
||||
|
||||
ignore = self.sensor_packets + self.gps_packets + ['alertDebug'] + ['modelDataV2SP']
|
||||
ignore = self.sensor_packets + self.gps_packets + ['alertDebug'] + ['modelDataV2SP'] + ['navigationd']
|
||||
if SIMULATION:
|
||||
ignore += ['driverCameraState', 'managerState']
|
||||
if REPLAY:
|
||||
@@ -96,7 +98,7 @@ class SelfdriveD(CruiseHelper):
|
||||
'carOutput', 'driverMonitoringState', 'longitudinalPlan', 'livePose', 'liveDelay',
|
||||
'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters',
|
||||
'controlsState', 'carControl', 'driverAssistance', 'alertDebug', 'userBookmark', 'audioFeedback',
|
||||
'modelDataV2SP', 'longitudinalPlanSP'] + \
|
||||
'modelDataV2SP', 'longitudinalPlanSP', 'navigationd'] + \
|
||||
self.camera_packets + self.sensor_packets + self.gps_packets,
|
||||
ignore_alive=ignore, ignore_avg_freq=ignore,
|
||||
ignore_valid=ignore, frequency=int(1/DT_CTRL))
|
||||
@@ -130,7 +132,12 @@ class SelfdriveD(CruiseHelper):
|
||||
self.logged_comm_issue = None
|
||||
self.not_running_prev = None
|
||||
self.experimental_mode = False
|
||||
self.personality = self.params.get("LongitudinalPersonality", return_default=True)
|
||||
self.personality = get_sanitize_int_param(
|
||||
"LongitudinalPersonality",
|
||||
min(log.LongitudinalPersonality.schema.enumerants.values()),
|
||||
max(log.LongitudinalPersonality.schema.enumerants.values()),
|
||||
self.params
|
||||
)
|
||||
self.recalibrating_seen = False
|
||||
self.state_machine = StateMachine()
|
||||
self.rk = Ratekeeper(100, print_delay_threshold=None)
|
||||
@@ -299,9 +306,9 @@ class SelfdriveD(CruiseHelper):
|
||||
|
||||
# Handle lane turn
|
||||
lane_turn_direction = self.sm['modelDataV2SP'].laneTurnDirection
|
||||
if lane_turn_direction == custom.TurnDirection.turnLeft:
|
||||
if lane_turn_direction == TurnDirection.turnLeft:
|
||||
self.events_sp.add(custom.OnroadEventSP.EventName.laneTurnLeft)
|
||||
elif lane_turn_direction == custom.TurnDirection.turnRight:
|
||||
elif lane_turn_direction == TurnDirection.turnRight:
|
||||
self.events_sp.add(custom.OnroadEventSP.EventName.laneTurnRight)
|
||||
|
||||
for i, pandaState in enumerate(self.sm['pandaStates']):
|
||||
|
||||
@@ -51,7 +51,9 @@ class Plant:
|
||||
from opendbc.car.honda.values import CAR
|
||||
from opendbc.car.honda.interface import CarInterface
|
||||
|
||||
self.planner = LongitudinalPlanner(CarInterface.get_non_essential_params(CAR.HONDA_CIVIC), init_v=self.speed)
|
||||
CP = CarInterface.get_non_essential_params(CAR.HONDA_CIVIC)
|
||||
CP_SP = CarInterface.get_non_essential_params_sp(CP, CAR.HONDA_CIVIC)
|
||||
self.planner = LongitudinalPlanner(CP, CP_SP, init_v=self.speed)
|
||||
|
||||
@property
|
||||
def current_time(self):
|
||||
@@ -69,6 +71,7 @@ class Plant:
|
||||
model = messaging.new_message('modelV2')
|
||||
car_state_sp = messaging.new_message('carStateSP')
|
||||
live_map_data_sp = messaging.new_message('liveMapDataSP')
|
||||
navigationd = messaging.new_message('navigationd')
|
||||
gps_data = messaging.new_message('gpsLocation')
|
||||
a_lead = (v_lead - self.v_lead_prev)/self.ts
|
||||
self.v_lead_prev = v_lead
|
||||
@@ -139,6 +142,7 @@ class Plant:
|
||||
'modelV2': model.modelV2,
|
||||
'carStateSP': car_state_sp.carStateSP,
|
||||
'liveMapDataSP': live_map_data_sp.liveMapDataSP,
|
||||
'navigationd': navigationd.navigationd,
|
||||
'gpsLocation': gps_data.gpsLocation}
|
||||
self.planner.update(sm)
|
||||
self.acceleration = self.planner.output_a_target
|
||||
|
||||
@@ -32,11 +32,11 @@ DeveloperPanel::DeveloperPanel(SettingsWindow *parent) : ListWidget(parent) {
|
||||
|
||||
experimentalLongitudinalToggle = new ParamControl(
|
||||
"AlphaLongitudinalEnabled",
|
||||
tr("openpilot Longitudinal Control (Alpha)"),
|
||||
tr("sunnypilot Longitudinal Control (Alpha)"),
|
||||
QString("<b>%1</b><br><br>%2")
|
||||
.arg(tr("WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB)."))
|
||||
.arg(tr("On this car, sunnypilot 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.")),
|
||||
.arg(tr("WARNING: sunnypilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB)."))
|
||||
.arg(tr("On this car, sunnypilot defaults to the car's built-in ACC instead of sunnypilot's longitudinal control. "
|
||||
"Enable this to switch to sunnypilot longitudinal control. Enabling Experimental mode is recommended when enabling sunnypilot longitudinal control alpha.")),
|
||||
""
|
||||
);
|
||||
experimentalLongitudinalToggle->setConfirmation(true, false);
|
||||
|
||||
@@ -12,7 +12,7 @@ public:
|
||||
explicit DeveloperPanel(SettingsWindow *parent);
|
||||
void showEvent(QShowEvent *event) override;
|
||||
|
||||
private:
|
||||
protected:
|
||||
Params params;
|
||||
ParamControl* adbToggle;
|
||||
ParamControl* joystickToggle;
|
||||
|
||||
@@ -33,13 +33,6 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
|
||||
"../assets/icons/experimental_white.svg",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"DynamicExperimentalControl",
|
||||
tr("Enable Dynamic Experimental Control"),
|
||||
tr("Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal."),
|
||||
"../assets/offroad/icon_blank.png",
|
||||
false,
|
||||
},
|
||||
{
|
||||
"DisengageOnAccelerator",
|
||||
tr("Disengage on Accelerator Pedal"),
|
||||
@@ -195,7 +188,7 @@ void TogglesPanel::updateToggles() {
|
||||
const QString unavailable = tr("Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control.");
|
||||
|
||||
QString long_desc = unavailable + " " + \
|
||||
tr("openpilot longitudinal control may come in a future update.");
|
||||
tr("sunnypilot longitudinal control may come in a future update.");
|
||||
if (CP.getAlphaLongitudinalAvailable()) {
|
||||
if (is_release) {
|
||||
long_desc = unavailable + " " + tr("An alpha version of sunnypilot longitudinal control can be tested, along with Experimental mode, on non-release branches.");
|
||||
|
||||
@@ -22,7 +22,7 @@ void ModelRenderer::draw(QPainter &painter, const QRect &surface_rect) {
|
||||
|
||||
update_model(model, lead_one);
|
||||
drawLaneLines(painter);
|
||||
drawPath(painter, model, surface_rect);
|
||||
drawPath(painter, model, surface_rect.height());
|
||||
|
||||
if (longitudinal_control && sm.alive("radarState")) {
|
||||
update_leads(radar_state, model.getPosition());
|
||||
@@ -34,7 +34,6 @@ void ModelRenderer::draw(QPainter &painter, const QRect &surface_rect) {
|
||||
drawLead(painter, lead_two, lead_vertices[1], surface_rect);
|
||||
}
|
||||
}
|
||||
drawLeadStatus(painter, surface_rect.height(), surface_rect.width());
|
||||
|
||||
painter.restore();
|
||||
}
|
||||
@@ -174,173 +173,6 @@ QColor ModelRenderer::blendColors(const QColor &start, const QColor &end, float
|
||||
(1 - t) * start.alphaF() + t * end.alphaF());
|
||||
}
|
||||
|
||||
|
||||
void ModelRenderer::drawLeadStatus(QPainter &painter, int height, int width) {
|
||||
auto *s = uiState();
|
||||
auto &sm = *(s->sm);
|
||||
|
||||
if (!sm.alive("radarState")) return;
|
||||
|
||||
const auto &radar_state = sm["radarState"].getRadarState();
|
||||
const auto &lead_one = radar_state.getLeadOne();
|
||||
const auto &lead_two = radar_state.getLeadTwo();
|
||||
|
||||
// Check if we have any active leads
|
||||
bool has_lead_one = lead_one.getStatus();
|
||||
bool has_lead_two = lead_two.getStatus();
|
||||
|
||||
if (!has_lead_one && !has_lead_two) {
|
||||
// Fade out status display
|
||||
lead_status_alpha = std::max(0.0f, lead_status_alpha - 0.05f);
|
||||
if (lead_status_alpha <= 0.0f) return;
|
||||
} else {
|
||||
// Fade in status display
|
||||
lead_status_alpha = std::min(1.0f, lead_status_alpha + 0.1f);
|
||||
}
|
||||
|
||||
// Draw status for each lead vehicle under its chevron
|
||||
if (true) {
|
||||
drawLeadStatusAtPosition(painter, lead_one, lead_vertices[0], height, width, "L1");
|
||||
}
|
||||
|
||||
if (has_lead_two && std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0) {
|
||||
drawLeadStatusAtPosition(painter, lead_two, lead_vertices[1], height, width, "L2");
|
||||
}
|
||||
}
|
||||
|
||||
void ModelRenderer::drawLeadStatusAtPosition(QPainter &painter,
|
||||
const cereal::RadarState::LeadData::Reader &lead_data,
|
||||
const QPointF &chevron_pos,
|
||||
int height, int width,
|
||||
const QString &label) {
|
||||
|
||||
float d_rel = lead_data.getDRel();
|
||||
float v_rel = lead_data.getVRel();
|
||||
auto *s = uiState();
|
||||
auto &sm = *(s->sm);
|
||||
float v_ego = sm["carState"].getCarState().getVEgo();
|
||||
|
||||
int chevron_data = std::atoi(Params().get("ChevronInfo").c_str());
|
||||
|
||||
// Calculate chevron size (same logic as drawLead)
|
||||
float sz = std::clamp((25 * 30) / (d_rel / 3 + 30), 15.0f, 30.0f) * 2.35;
|
||||
|
||||
QFont content_font = painter.font();
|
||||
content_font.setPixelSize(35);
|
||||
content_font.setBold(true);
|
||||
painter.setFont(content_font);
|
||||
|
||||
QFontMetrics fm(content_font);
|
||||
bool is_metric = s->scene.is_metric;
|
||||
|
||||
QStringList text_lines;
|
||||
|
||||
const int chevron_types = 3;
|
||||
const int chevron_all = chevron_types + 1; // All metrics (value 4)
|
||||
QStringList chevron_text[chevron_types];
|
||||
int position;
|
||||
float val;
|
||||
|
||||
// Distance display (chevron_data == 1 or all)
|
||||
if (chevron_data == 1 || chevron_data == chevron_all) {
|
||||
position = 0;
|
||||
val = std::max(0.0f, d_rel);
|
||||
QString distance_unit = is_metric ? "m" : "ft";
|
||||
if (!is_metric) {
|
||||
val *= 3.28084f; // Convert meters to feet
|
||||
}
|
||||
chevron_text[position].append(QString::number(val, 'f', 0) + " " + distance_unit);
|
||||
}
|
||||
|
||||
// Absolute velocity display (chevron_data == 2 or all)
|
||||
if (chevron_data == 2 || chevron_data == chevron_all) {
|
||||
position = (chevron_data == 2) ? 0 : 1;
|
||||
val = std::max(0.0f, (v_rel + v_ego) * (is_metric ? static_cast<float>(MS_TO_KPH) : static_cast<float>(MS_TO_MPH)));
|
||||
chevron_text[position].append(QString::number(val, 'f', 0) + " " + (is_metric ? "km/h" : "mph"));
|
||||
}
|
||||
|
||||
// Time-to-contact display (chevron_data == 3 or all)
|
||||
if (chevron_data == 3 || chevron_data == chevron_all) {
|
||||
position = (chevron_data == 3) ? 0 : 2;
|
||||
val = (d_rel > 0 && v_ego > 0) ? std::max(0.0f, d_rel / v_ego) : 0.0f;
|
||||
QString ttc_str = (val > 0 && val < 200) ? QString::number(val, 'f', 1) + "s" : "---";
|
||||
chevron_text[position].append(ttc_str);
|
||||
}
|
||||
|
||||
// Collect all non-empty text lines
|
||||
for (int i = 0; i < chevron_types; ++i) {
|
||||
if (!chevron_text[i].isEmpty()) {
|
||||
text_lines.append(chevron_text[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// If no text to display, return early
|
||||
if (text_lines.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Text box dimensions
|
||||
float str_w = 150; // Width of text area
|
||||
float str_h = 45; // Height per line
|
||||
|
||||
// Position text below chevron, centered horizontally
|
||||
float text_x = chevron_pos.x() - str_w / 2;
|
||||
float text_y = chevron_pos.y() + sz + 15;
|
||||
|
||||
// Clamp to screen bounds
|
||||
text_x = std::clamp(text_x, 10.0f, (float)width - str_w - 10);
|
||||
|
||||
// Shadow offset
|
||||
QPoint shadow_offset(2, 2);
|
||||
|
||||
// Draw each line of text with shadow
|
||||
for (int i = 0; i < text_lines.size(); ++i) {
|
||||
if (!text_lines[i].isEmpty()) {
|
||||
QRect textRect(text_x, text_y + (i * str_h), str_w, str_h);
|
||||
|
||||
// Draw shadow
|
||||
painter.setPen(QColor(0x0, 0x0, 0x0, (int)(200 * lead_status_alpha)));
|
||||
painter.drawText(textRect.translated(shadow_offset.x(), shadow_offset.y()),
|
||||
Qt::AlignBottom | Qt::AlignHCenter, text_lines[i]);
|
||||
|
||||
// Determine text color based on content and danger level
|
||||
QColor text_color;
|
||||
|
||||
// Check if this is a distance line (contains 'm' or 'ft')
|
||||
if (text_lines[i].contains("m") || text_lines[i].contains("ft")) {
|
||||
if (d_rel < 20.0f) {
|
||||
text_color = QColor(255, 80, 80, (int)(255 * lead_status_alpha)); // Red - danger
|
||||
} else if (d_rel < 40.0f) {
|
||||
text_color = QColor(255, 200, 80, (int)(255 * lead_status_alpha)); // Yellow - caution
|
||||
} else {
|
||||
text_color = QColor(80, 255, 120, (int)(255 * lead_status_alpha)); // Green - safe
|
||||
}
|
||||
}
|
||||
// Enhanced color coding for time-to-contact
|
||||
else if (text_lines[i].contains("s") && !text_lines[i].contains("---")) {
|
||||
float ttc_val = text_lines[i].left(text_lines[i].length() - 1).toFloat();
|
||||
if (ttc_val < 3.0f) {
|
||||
text_color = QColor(255, 80, 80, (int)(255 * lead_status_alpha)); // Red - urgent
|
||||
} else if (ttc_val < 6.0f) {
|
||||
text_color = QColor(255, 200, 80, (int)(255 * lead_status_alpha)); // Yellow - caution
|
||||
} else {
|
||||
text_color = QColor(0xff, 0xff, 0xff, (int)(255 * lead_status_alpha)); // White - safe
|
||||
}
|
||||
}
|
||||
else {
|
||||
text_color = QColor(0xff, 0xff, 0xff, (int)(255 * lead_status_alpha)); // White for other lines
|
||||
}
|
||||
|
||||
// Draw main text
|
||||
painter.setPen(text_color);
|
||||
painter.drawText(textRect, Qt::AlignBottom | Qt::AlignHCenter, text_lines[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset pen
|
||||
painter.setPen(Qt::NoPen);
|
||||
}
|
||||
|
||||
void ModelRenderer::drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data,
|
||||
const QPointF &vd, const QRect &surface_rect) {
|
||||
const float speedBuff = 10.;
|
||||
|
||||
@@ -34,20 +34,11 @@ protected:
|
||||
bool mapToScreen(float in_x, float in_y, float in_z, QPointF *out);
|
||||
void mapLineToPolygon(const cereal::XYZTData::Reader &line, float y_off, float z_off,
|
||||
QPolygonF *pvd, int max_idx, bool allow_invert = true);
|
||||
void drawLeadStatus(QPainter &painter, int height, int width);
|
||||
void drawLeadStatusAtPosition(QPainter &painter,
|
||||
const cereal::RadarState::LeadData::Reader &lead_data,
|
||||
const QPointF &chevron_pos,
|
||||
int height, int width,
|
||||
const QString &label);
|
||||
void drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd, const QRect &surface_rect);
|
||||
void update_leads(const cereal::RadarState::Reader &radar_state, const cereal::XYZTData::Reader &line);
|
||||
virtual void update_model(const cereal::ModelDataV2::Reader &model, const cereal::RadarState::LeadData::Reader &lead);
|
||||
void drawLaneLines(QPainter &painter);
|
||||
void drawPath(QPainter &painter, const cereal::ModelDataV2::Reader &model, int height);
|
||||
virtual void drawPath(QPainter &painter, const cereal::ModelDataV2::Reader &model, const QRect &surface_rect) {;
|
||||
drawPath(painter, model, surface_rect.height());
|
||||
}
|
||||
void updatePathGradient(QLinearGradient &bg);
|
||||
QColor blendColors(const QColor &start, const QColor &end, float t);
|
||||
|
||||
@@ -64,9 +55,4 @@ protected:
|
||||
QPointF lead_vertices[2] = {};
|
||||
Eigen::Matrix3f car_space_transform = Eigen::Matrix3f::Zero();
|
||||
QRectF clip_region;
|
||||
|
||||
float lead_status_alpha = 0.0f;
|
||||
QPointF lead_status_pos;
|
||||
QString lead_status_text;
|
||||
QColor lead_status_color;
|
||||
};
|
||||
|
||||
@@ -92,6 +92,16 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *event) {
|
||||
// ignore events when device is awakened by resetInteractiveTimeout
|
||||
ignore = !device()->isAwake();
|
||||
device()->resetInteractiveTimeout();
|
||||
|
||||
#ifdef SUNNYPILOT
|
||||
auto *s_sp = uiStateSP();
|
||||
bool onroadScreenControl = s_sp->scene.onroadScreenOffControl;
|
||||
bool started = s_sp->scene.started;
|
||||
bool timerExpired = (s_sp->scene.onroadScreenOffTimer == 0);
|
||||
ignore |= (onroadScreenControl and started and timerExpired);
|
||||
s_sp->reset_onroad_sleep_timer();
|
||||
#endif
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
#include "selfdrive/ui/qt/offroad/onboarding.h"
|
||||
#include "selfdrive/ui/qt/offroad/settings.h"
|
||||
|
||||
#ifdef SUNNYPILOT
|
||||
#include "selfdrive/ui/sunnypilot/ui.h"
|
||||
#endif
|
||||
|
||||
class MainWindow : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
|
||||
+10
-1
@@ -4,7 +4,7 @@ import time
|
||||
import wave
|
||||
|
||||
|
||||
from cereal import car, messaging
|
||||
from cereal import car, messaging, custom
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
from openpilot.common.filter_simple import FirstOrderFilter
|
||||
from openpilot.common.realtime import Ratekeeper
|
||||
@@ -26,8 +26,15 @@ AMBIENT_DB = 30 # DB where MIN_VOLUME is applied
|
||||
DB_SCALE = 30 # AMBIENT_DB + DB_SCALE is where MAX_VOLUME is applied
|
||||
|
||||
AudibleAlert = car.CarControl.HUDControl.AudibleAlert
|
||||
AudibleAlertSP = custom.SelfdriveStateSP.AudibleAlert
|
||||
|
||||
|
||||
sound_list_sp: dict[int, tuple[str, int | None, float]] = {
|
||||
# AudibleAlertSP, file name, play count (none for infinite)
|
||||
AudibleAlertSP.promptSingleLow: ("prompt_single_low.wav", 1, MAX_VOLUME),
|
||||
AudibleAlertSP.promptSingleHigh: ("prompt_single_high.wav", 1, MAX_VOLUME),
|
||||
}
|
||||
|
||||
sound_list: dict[int, tuple[str, int | None, float]] = {
|
||||
# AudibleAlert, file name, play count (none for infinite)
|
||||
AudibleAlert.engage: ("engage.wav", 1, MAX_VOLUME),
|
||||
@@ -40,6 +47,8 @@ sound_list: dict[int, tuple[str, int | None, float]] = {
|
||||
|
||||
AudibleAlert.warningSoft: ("warning_soft.wav", None, MAX_VOLUME),
|
||||
AudibleAlert.warningImmediate: ("warning_immediate.wav", None, MAX_VOLUME),
|
||||
|
||||
**sound_list_sp,
|
||||
}
|
||||
|
||||
def check_selfdrive_timeout_alert(sm):
|
||||
|
||||
@@ -56,14 +56,13 @@ DeveloperPanelSP::DeveloperPanelSP(SettingsWindow *parent) : DeveloperPanel(pare
|
||||
addItem(errorLogBtn);
|
||||
|
||||
QObject::connect(uiState(), &UIState::offroadTransition, this, &DeveloperPanelSP::updateToggles);
|
||||
|
||||
is_release = params.getBool("IsReleaseBranch");
|
||||
is_tested = params.getBool("IsTestedBranch");
|
||||
is_development = params.getBool("IsDevelopmentBranch");
|
||||
}
|
||||
|
||||
void DeveloperPanelSP::updateToggles(bool offroad) {
|
||||
bool disable_updates = params.getBool("DisableUpdates");
|
||||
bool is_release = params.getBool("IsReleaseBranch") || params.getBool("IsReleaseSpBranch");
|
||||
bool is_tested = params.getBool("IsTestedBranch");
|
||||
bool is_development = params.getBool("IsDevelopmentBranch");
|
||||
|
||||
prebuiltToggle->setVisible(!is_release && !is_tested && !is_development);
|
||||
prebuiltToggle->setEnabled(disable_updates);
|
||||
@@ -80,6 +79,9 @@ void DeveloperPanelSP::updateToggles(bool offroad) {
|
||||
enableGithubRunner->setVisible(!is_release);
|
||||
errorLogBtn->setVisible(!is_release);
|
||||
showAdvancedControls->setEnabled(true);
|
||||
|
||||
joystickToggle->setVisible(!is_release);
|
||||
longManeuverToggle->setVisible(!is_release);
|
||||
}
|
||||
|
||||
void DeveloperPanelSP::showEvent(QShowEvent *event) {
|
||||
|
||||
@@ -22,9 +22,6 @@ private:
|
||||
ParamControlSP *prebuiltToggle;
|
||||
Params params;
|
||||
ParamControlSP *showAdvancedControls;
|
||||
bool is_development;
|
||||
bool is_release;
|
||||
bool is_tested;
|
||||
|
||||
private slots:
|
||||
void updateToggles(bool offroad);
|
||||
|
||||
@@ -88,7 +88,7 @@ DevicePanelSP::DevicePanelSP(SettingsWindowSP *parent) : DevicePanel(parent) {
|
||||
|
||||
connect(toggleDeviceBootMode, &ButtonParamControlSP::buttonClicked, this, [=](int index) {
|
||||
params.put("DeviceBootMode", QString::number(index).toStdString());
|
||||
updateState();
|
||||
updateState(offroad);
|
||||
});
|
||||
|
||||
addItem(device_grid_layout);
|
||||
@@ -122,7 +122,7 @@ DevicePanelSP::DevicePanelSP(SettingsWindowSP *parent) : DevicePanel(parent) {
|
||||
|
||||
addItem(power_group_layout);
|
||||
|
||||
std::vector always_enabled_btns = {
|
||||
always_enabled_btns = {
|
||||
rebootBtn,
|
||||
poweroffBtn,
|
||||
offroadBtn,
|
||||
@@ -130,17 +130,7 @@ DevicePanelSP::DevicePanelSP(SettingsWindowSP *parent) : DevicePanel(parent) {
|
||||
buttons["onroadUploadsBtn"],
|
||||
};
|
||||
|
||||
QObject::connect(uiState(), &UIState::offroadTransition, [=](bool _offroad) {
|
||||
for (auto btn : findChildren<PushButtonSP*>()) {
|
||||
bool always_enabled = std::find(always_enabled_btns.begin(), always_enabled_btns.end(), btn) != always_enabled_btns.end();
|
||||
|
||||
if (!always_enabled) {
|
||||
btn->setEnabled(offroad);
|
||||
}
|
||||
}
|
||||
offroad = _offroad;
|
||||
updateState();
|
||||
});
|
||||
QObject::connect(uiState(), &UIState::offroadTransition, this, &DevicePanelSP::updateState);
|
||||
}
|
||||
|
||||
void DevicePanelSP::setOffroadMode() {
|
||||
@@ -164,7 +154,7 @@ void DevicePanelSP::setOffroadMode() {
|
||||
ConfirmationDialog::alert(tr("Disengage to Enter Always Offroad Mode"), this);
|
||||
}
|
||||
|
||||
updateState();
|
||||
updateState(offroad);
|
||||
}
|
||||
|
||||
void DevicePanelSP::resetSettings() {
|
||||
@@ -181,12 +171,16 @@ void DevicePanelSP::resetSettings() {
|
||||
}
|
||||
|
||||
void DevicePanelSP::showEvent(QShowEvent *event) {
|
||||
updateState();
|
||||
updateState(offroad);
|
||||
}
|
||||
|
||||
void DevicePanelSP::updateState() {
|
||||
if (!isVisible()) {
|
||||
return;
|
||||
void DevicePanelSP::updateState(bool _offroad) {
|
||||
for (auto btn : findChildren<PushButtonSP*>()) {
|
||||
bool always_enabled = std::find(always_enabled_btns.begin(), always_enabled_btns.end(), btn) != always_enabled_btns.end();
|
||||
|
||||
if (!always_enabled) {
|
||||
btn->setEnabled(_offroad);
|
||||
}
|
||||
}
|
||||
|
||||
bool offroad_mode_param = params.getBool("OffroadMode");
|
||||
@@ -204,4 +198,6 @@ void DevicePanelSP::updateState() {
|
||||
} else {
|
||||
AddWidgetAt(0, offroadBtn);
|
||||
}
|
||||
|
||||
offroad = _offroad;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ public:
|
||||
explicit DevicePanelSP(SettingsWindowSP *parent = 0);
|
||||
void showEvent(QShowEvent *event) override;
|
||||
void setOffroadMode();
|
||||
void updateState();
|
||||
void updateState(bool _offroad);
|
||||
void resetSettings();
|
||||
|
||||
private:
|
||||
@@ -34,6 +34,8 @@ private:
|
||||
QVBoxLayout *power_group_layout;
|
||||
bool offroad;
|
||||
|
||||
std::vector<PushButtonSP*> always_enabled_btns = {};
|
||||
|
||||
const QString alwaysOffroadStyle = R"(
|
||||
PushButtonSP {
|
||||
border-radius: 20px;
|
||||
|
||||
@@ -29,7 +29,7 @@ OnroadScreenBrightnessControl::OnroadScreenBrightnessControl(const QString ¶
|
||||
"Onroad Brightness",
|
||||
"",
|
||||
"",
|
||||
{0, 100}, 10, true);
|
||||
{0, 90}, 10, true);
|
||||
|
||||
connect(onroadScreenOffTimer, &OptionControlSP::updateLabels, this, &OnroadScreenBrightnessControl::refresh);
|
||||
connect(onroadScreenBrightness, &OptionControlSP::updateLabels, this, &OnroadScreenBrightnessControl::refresh);
|
||||
|
||||
@@ -13,9 +13,9 @@ enum class SpeedLimitOffsetType {
|
||||
};
|
||||
|
||||
inline const QString SpeedLimitOffsetTypeTexts[]{
|
||||
QObject::tr("None"),
|
||||
QObject::tr("Fixed"),
|
||||
QObject::tr("Percent"),
|
||||
QT_TRANSLATE_NOOP("SpeedLimitSettings", "None"),
|
||||
QT_TRANSLATE_NOOP("SpeedLimitSettings", "Fixed"),
|
||||
QT_TRANSLATE_NOOP("SpeedLimitSettings", "Percent"),
|
||||
};
|
||||
|
||||
enum class SpeedLimitSourcePolicy {
|
||||
@@ -27,11 +27,11 @@ enum class SpeedLimitSourcePolicy {
|
||||
};
|
||||
|
||||
inline const QString SpeedLimitSourcePolicyTexts[]{
|
||||
QObject::tr("Car\nOnly"),
|
||||
QObject::tr("Map\nOnly"),
|
||||
QObject::tr("Car\nFirst"),
|
||||
QObject::tr("Map\nFirst"),
|
||||
QObject::tr("Combined\nData")
|
||||
QT_TRANSLATE_NOOP("SpeedLimitPolicy", "Car\nOnly"),
|
||||
QT_TRANSLATE_NOOP("SpeedLimitPolicy", "Map\nOnly"),
|
||||
QT_TRANSLATE_NOOP("SpeedLimitPolicy", "Car\nFirst"),
|
||||
QT_TRANSLATE_NOOP("SpeedLimitPolicy", "Map\nFirst"),
|
||||
QT_TRANSLATE_NOOP("SpeedLimitPolicy", "Combined\nData")
|
||||
};
|
||||
|
||||
enum class SpeedLimitMode {
|
||||
@@ -42,8 +42,8 @@ enum class SpeedLimitMode {
|
||||
};
|
||||
|
||||
inline const QString SpeedLimitModeTexts[]{
|
||||
QObject::tr("Off"),
|
||||
QObject::tr("Information"),
|
||||
QObject::tr("Warning"),
|
||||
QObject::tr("Assist"),
|
||||
QT_TRANSLATE_NOOP("SpeedLimitSettings", "Off"),
|
||||
QT_TRANSLATE_NOOP("SpeedLimitSettings", "Information"),
|
||||
QT_TRANSLATE_NOOP("SpeedLimitSettings", "Warning"),
|
||||
QT_TRANSLATE_NOOP("SpeedLimitSettings", "Assist"),
|
||||
};
|
||||
|
||||
+5
-5
@@ -23,11 +23,11 @@ SpeedLimitPolicy::SpeedLimitPolicy(QWidget *parent) : QWidget(parent) {
|
||||
ListWidgetSP *list = new ListWidgetSP(this);
|
||||
|
||||
std::vector<QString> speed_limit_policy_texts{
|
||||
SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::CAR_ONLY)],
|
||||
SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::MAP_ONLY)],
|
||||
SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::CAR_FIRST)],
|
||||
SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::MAP_FIRST)],
|
||||
SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::COMBINED)]
|
||||
tr(SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::CAR_ONLY)].toStdString().c_str()),
|
||||
tr(SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::MAP_ONLY)].toStdString().c_str()),
|
||||
tr(SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::CAR_FIRST)].toStdString().c_str()),
|
||||
tr(SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::MAP_FIRST)].toStdString().c_str()),
|
||||
tr(SpeedLimitSourcePolicyTexts[static_cast<int>(SpeedLimitSourcePolicy::COMBINED)].toStdString().c_str())
|
||||
};
|
||||
speed_limit_policy = new ButtonParamControlSP(
|
||||
"SpeedLimitPolicy",
|
||||
|
||||
+35
-15
@@ -7,6 +7,8 @@
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal/speed_limit/speed_limit_settings.h"
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/util.h"
|
||||
|
||||
SpeedLimitSettings::SpeedLimitSettings(QWidget *parent) : QStackedWidget(parent) {
|
||||
subPanelFrame = new QFrame();
|
||||
QVBoxLayout *subPanelLayout = new QVBoxLayout(subPanelFrame);
|
||||
@@ -25,10 +27,10 @@ SpeedLimitSettings::SpeedLimitSettings(QWidget *parent) : QStackedWidget(parent)
|
||||
speedLimitPolicyScreen = new SpeedLimitPolicy(this);
|
||||
|
||||
std::vector<QString> speed_limit_mode_texts{
|
||||
SpeedLimitModeTexts[static_cast<int>(SpeedLimitMode::OFF)],
|
||||
SpeedLimitModeTexts[static_cast<int>(SpeedLimitMode::INFORMATION)],
|
||||
SpeedLimitModeTexts[static_cast<int>(SpeedLimitMode::WARNING)],
|
||||
SpeedLimitModeTexts[static_cast<int>(SpeedLimitMode::ASSIST)],
|
||||
tr(SpeedLimitModeTexts[static_cast<int>(SpeedLimitMode::OFF)].toStdString().c_str()),
|
||||
tr(SpeedLimitModeTexts[static_cast<int>(SpeedLimitMode::INFORMATION)].toStdString().c_str()),
|
||||
tr(SpeedLimitModeTexts[static_cast<int>(SpeedLimitMode::WARNING)].toStdString().c_str()),
|
||||
tr(SpeedLimitModeTexts[static_cast<int>(SpeedLimitMode::ASSIST)].toStdString().c_str())
|
||||
};
|
||||
speed_limit_mode_settings = new ButtonParamControlSP(
|
||||
"SpeedLimitMode",
|
||||
@@ -64,9 +66,9 @@ SpeedLimitSettings::SpeedLimitSettings(QWidget *parent) : QStackedWidget(parent)
|
||||
QVBoxLayout *offsetLayout = new QVBoxLayout(offsetFrame);
|
||||
|
||||
std::vector<QString> speed_limit_offset_texts{
|
||||
SpeedLimitOffsetTypeTexts[static_cast<int>(SpeedLimitOffsetType::NONE)],
|
||||
SpeedLimitOffsetTypeTexts[static_cast<int>(SpeedLimitOffsetType::FIXED)],
|
||||
SpeedLimitOffsetTypeTexts[static_cast<int>(SpeedLimitOffsetType::PERCENT)]
|
||||
tr(SpeedLimitOffsetTypeTexts[static_cast<int>(SpeedLimitOffsetType::NONE)].toStdString().c_str()),
|
||||
tr(SpeedLimitOffsetTypeTexts[static_cast<int>(SpeedLimitOffsetType::FIXED)].toStdString().c_str()),
|
||||
tr(SpeedLimitOffsetTypeTexts[static_cast<int>(SpeedLimitOffsetType::PERCENT)].toStdString().c_str())
|
||||
};
|
||||
speed_limit_offset_settings = new ButtonParamControlSP(
|
||||
"SpeedLimitOffsetType",
|
||||
@@ -103,13 +105,13 @@ SpeedLimitSettings::SpeedLimitSettings(QWidget *parent) : QStackedWidget(parent)
|
||||
}
|
||||
|
||||
void SpeedLimitSettings::refresh() {
|
||||
bool is_release = params.getBool("IsReleaseSpBranch");
|
||||
bool is_metric_param = params.getBool("IsMetric");
|
||||
SpeedLimitMode speed_limit_mode_param = static_cast<SpeedLimitMode>(std::atoi(params.get("SpeedLimitMode").c_str()));
|
||||
SpeedLimitOffsetType offset_type_param = static_cast<SpeedLimitOffsetType>(std::atoi(params.get("SpeedLimitOffsetType").c_str()));
|
||||
QString offsetLabel = QString::fromStdString(params.get("SpeedLimitValueOffset"));
|
||||
|
||||
bool has_longitudinal_control;
|
||||
bool intelligent_cruise_button_management_available;
|
||||
bool sla_available;
|
||||
auto cp_bytes = params.get("CarParamsPersistent");
|
||||
auto cp_sp_bytes = params.get("CarParamsSPPersistent");
|
||||
if (!cp_bytes.empty() && !cp_sp_bytes.empty()) {
|
||||
@@ -120,11 +122,24 @@ void SpeedLimitSettings::refresh() {
|
||||
cereal::CarParams::Reader CP = cmsg.getRoot<cereal::CarParams>();
|
||||
cereal::CarParamsSP::Reader CP_SP = cmsg_sp.getRoot<cereal::CarParamsSP>();
|
||||
|
||||
has_longitudinal_control = hasLongitudinalControl(CP);
|
||||
intelligent_cruise_button_management_available = CP_SP.getIntelligentCruiseButtonManagementAvailable();
|
||||
bool has_longitudinal_control = hasLongitudinalControl(CP);
|
||||
bool has_icbm = hasIntelligentCruiseButtonManagement(CP_SP);
|
||||
|
||||
/*
|
||||
* Speed Limit Assist is available when:
|
||||
* - has_longitudinal_control or has_icbm, and
|
||||
* - is not a release branch or not a disallowed brand, and
|
||||
* - is not always disallowed
|
||||
*/
|
||||
bool sla_disallow_in_release = CP.getBrand() == "tesla" && is_release;
|
||||
bool sla_always_disallow = CP.getBrand() == "rivian";
|
||||
sla_available = (has_longitudinal_control || has_icbm) && !sla_disallow_in_release && !sla_always_disallow;
|
||||
|
||||
if (!sla_available && speed_limit_mode_param == SpeedLimitMode::ASSIST) {
|
||||
params.put("SpeedLimitMode", std::to_string(static_cast<int>(SpeedLimitMode::WARNING)));
|
||||
}
|
||||
} else {
|
||||
has_longitudinal_control = false;
|
||||
intelligent_cruise_button_management_available = false;
|
||||
sla_available = false;
|
||||
}
|
||||
|
||||
speed_limit_mode_settings->setDescription(modeDescription(speed_limit_mode_param));
|
||||
@@ -144,13 +159,14 @@ void SpeedLimitSettings::refresh() {
|
||||
speed_limit_offset->showDescription();
|
||||
}
|
||||
|
||||
if (has_longitudinal_control || intelligent_cruise_button_management_available) {
|
||||
if (sla_available) {
|
||||
speed_limit_mode_settings->setEnableSelectedButtons(true, convertSpeedLimitModeValues(getSpeedLimitModeValues()));
|
||||
} else {
|
||||
speed_limit_mode_settings->setEnableSelectedButtons(true, convertSpeedLimitModeValues(
|
||||
{SpeedLimitMode::OFF,SpeedLimitMode::INFORMATION, SpeedLimitMode::WARNING}));
|
||||
{SpeedLimitMode::OFF, SpeedLimitMode::INFORMATION, SpeedLimitMode::WARNING}));
|
||||
}
|
||||
|
||||
speed_limit_mode_settings->refresh();
|
||||
speed_limit_mode_settings->showDescription();
|
||||
speed_limit_offset->showDescription();
|
||||
}
|
||||
@@ -158,3 +174,7 @@ void SpeedLimitSettings::refresh() {
|
||||
void SpeedLimitSettings::showEvent(QShowEvent *event) {
|
||||
refresh();
|
||||
}
|
||||
|
||||
void SpeedLimitSettings::hideEvent(QHideEvent *event) {
|
||||
setCurrentWidget(subPanelFrame);
|
||||
}
|
||||
|
||||
+2
@@ -21,6 +21,7 @@ public:
|
||||
SpeedLimitSettings(QWidget *parent = nullptr);
|
||||
void refresh();
|
||||
void showEvent(QShowEvent *event) override;
|
||||
void hideEvent(QHideEvent *event) override;
|
||||
|
||||
signals:
|
||||
void backPress();
|
||||
@@ -34,6 +35,7 @@ private:
|
||||
SpeedLimitPolicy *speedLimitPolicyScreen;
|
||||
ButtonParamControlSP *speed_limit_offset_settings;
|
||||
OptionControlSP *speed_limit_offset;
|
||||
bool icbm_available = false;
|
||||
|
||||
static QString offsetDescription(SpeedLimitOffsetType type = SpeedLimitOffsetType::NONE) {
|
||||
QString none_str = tr("⦿ None: No Offset");
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/longitudinal_panel.h"
|
||||
|
||||
#include "selfdrive/ui/sunnypilot/qt/util.h"
|
||||
|
||||
LongitudinalPanel::LongitudinalPanel(QWidget *parent) : QWidget(parent) {
|
||||
setStyleSheet(R"(
|
||||
#back_btn {
|
||||
@@ -36,13 +38,24 @@ LongitudinalPanel::LongitudinalPanel(QWidget *parent) : QWidget(parent) {
|
||||
intelligentCruiseButtonManagement = new ParamControlSP(
|
||||
"IntelligentCruiseButtonManagement",
|
||||
tr("Intelligent Cruise Button Management (ICBM) (Alpha)"),
|
||||
tr("When enabled, sunnypilot will attempt to manage the built-in cruise control buttons by emulating button presses for limited longitudinal control."),
|
||||
"",
|
||||
"",
|
||||
this
|
||||
);
|
||||
intelligentCruiseButtonManagement->setConfirmation(true, false);
|
||||
QObject::connect(intelligentCruiseButtonManagement, &ParamControlSP::toggleFlipped, this, [=](bool) {
|
||||
refresh(offroad);
|
||||
});
|
||||
list->addItem(intelligentCruiseButtonManagement);
|
||||
|
||||
dynamicExperimentalControl = new ParamControlSP(
|
||||
"DynamicExperimentalControl",
|
||||
tr("Dynamic Experimental Control (DEC)"),
|
||||
tr("Enable toggle to allow the model to determine when to use sunnypilot ACC or sunnypilot End to End Longitudinal."),
|
||||
"",
|
||||
this
|
||||
);
|
||||
list->addItem(dynamicExperimentalControl);
|
||||
|
||||
SmartCruiseControlVision = new ParamControl(
|
||||
"SmartCruiseControlVision",
|
||||
tr("Smart Cruise Control - Vision"),
|
||||
@@ -86,7 +99,13 @@ void LongitudinalPanel::showEvent(QShowEvent *event) {
|
||||
refresh(offroad);
|
||||
}
|
||||
|
||||
void LongitudinalPanel::hideEvent(QHideEvent *event) {
|
||||
main_layout->setCurrentWidget(cruisePanelScreen);
|
||||
}
|
||||
|
||||
void LongitudinalPanel::refresh(bool _offroad) {
|
||||
const QString icbm_description = tr("When enabled, sunnypilot will attempt to manage the built-in cruise control buttons by emulating button presses for limited longitudinal control.");
|
||||
|
||||
auto cp_bytes = params.get("CarParamsPersistent");
|
||||
auto cp_sp_bytes = params.get("CarParamsSPPersistent");
|
||||
if (!cp_bytes.empty() && !cp_sp_bytes.empty()) {
|
||||
@@ -99,15 +118,61 @@ void LongitudinalPanel::refresh(bool _offroad) {
|
||||
|
||||
has_longitudinal_control = hasLongitudinalControl(CP);
|
||||
is_pcm_cruise = CP.getPcmCruise();
|
||||
intelligent_cruise_button_management_available = CP_SP.getIntelligentCruiseButtonManagementAvailable();
|
||||
has_icbm = hasIntelligentCruiseButtonManagement(CP_SP);
|
||||
|
||||
if (CP_SP.getIntelligentCruiseButtonManagementAvailable() && !has_longitudinal_control) {
|
||||
intelligentCruiseButtonManagement->setEnabled(offroad);
|
||||
intelligentCruiseButtonManagement->setDescription(icbm_description);
|
||||
} else {
|
||||
params.remove("IntelligentCruiseButtonManagement");
|
||||
intelligentCruiseButtonManagement->setEnabled(false);
|
||||
|
||||
const QString icbm_unavaialble = tr("Intelligent Cruise Button Management is currently unavailable on this platform.");
|
||||
|
||||
QString long_desc = icbm_unavaialble;
|
||||
if (has_longitudinal_control) {
|
||||
if (CP.getAlphaLongitudinalAvailable()) {
|
||||
long_desc = icbm_unavaialble + " " + tr("Disable the sunnypilot Longitudinal Control (alpha) toggle to allow Intelligent Cruise Button Management.");
|
||||
} else {
|
||||
long_desc = icbm_unavaialble + " " + tr("sunnypilot Longitudinal Control is the default longitudinal control for this platform.");
|
||||
}
|
||||
}
|
||||
|
||||
intelligentCruiseButtonManagement->setDescription("<b>" + long_desc + "</b><br><br>" + icbm_description);
|
||||
intelligentCruiseButtonManagement->showDescription();
|
||||
}
|
||||
|
||||
if (has_longitudinal_control || has_icbm) {
|
||||
// enable Custom ACC Increments when long is available and is not PCM cruise
|
||||
customAccIncrement->setEnabled(((has_longitudinal_control && !is_pcm_cruise) || has_icbm) && offroad);
|
||||
dynamicExperimentalControl->setEnabled(has_longitudinal_control);
|
||||
SmartCruiseControlVision->setEnabled(true);
|
||||
SmartCruiseControlMap->setEnabled(true);
|
||||
} else {
|
||||
params.remove("CustomAccIncrementsEnabled");
|
||||
params.remove("DynamicExperimentalControl");
|
||||
params.remove("SmartCruiseControlVision");
|
||||
params.remove("SmartCruiseControlMap");
|
||||
customAccIncrement->setEnabled(false);
|
||||
dynamicExperimentalControl->setEnabled(false);
|
||||
SmartCruiseControlVision->setEnabled(false);
|
||||
SmartCruiseControlMap->setEnabled(false);
|
||||
}
|
||||
|
||||
intelligentCruiseButtonManagement->refresh();
|
||||
customAccIncrement->refresh();
|
||||
dynamicExperimentalControl->refresh();
|
||||
SmartCruiseControlVision->refresh();
|
||||
SmartCruiseControlMap->refresh();
|
||||
} else {
|
||||
has_longitudinal_control = false;
|
||||
is_pcm_cruise = false;
|
||||
intelligent_cruise_button_management_available = false;
|
||||
has_icbm = false;
|
||||
intelligentCruiseButtonManagement->setDescription("<b>" + tr("Start the vehicle to check vehicle compatibility.") + "</br><b><b>" + icbm_description);
|
||||
}
|
||||
|
||||
QString accEnabledDescription = tr("Enable custom Short & Long press increments for cruise speed increase/decrease.");
|
||||
QString accNoLongDescription = tr("This feature can only be used with openpilot longitudinal control enabled.");
|
||||
QString accNoLongDescription = tr("This feature can only be used with sunnypilot longitudinal control enabled.");
|
||||
QString accPcmCruiseDisabledDescription = tr("This feature is not supported on this platform due to vehicle limitations.");
|
||||
QString onroadOnlyDescription = tr("Start the vehicle to check vehicle compatibility.");
|
||||
|
||||
@@ -115,33 +180,19 @@ void LongitudinalPanel::refresh(bool _offroad) {
|
||||
customAccIncrement->setDescription(onroadOnlyDescription);
|
||||
customAccIncrement->showDescription();
|
||||
} else {
|
||||
if (has_longitudinal_control || intelligent_cruise_button_management_available) {
|
||||
if (is_pcm_cruise) {
|
||||
if (has_longitudinal_control || has_icbm) {
|
||||
if (has_longitudinal_control && is_pcm_cruise) {
|
||||
customAccIncrement->setDescription(accPcmCruiseDisabledDescription);
|
||||
customAccIncrement->showDescription();
|
||||
} else {
|
||||
customAccIncrement->setDescription(accEnabledDescription);
|
||||
}
|
||||
} else {
|
||||
params.remove("CustomAccIncrementsEnabled");
|
||||
customAccIncrement->toggleFlipped(false);
|
||||
customAccIncrement->setDescription(accNoLongDescription);
|
||||
customAccIncrement->showDescription();
|
||||
params.remove("IntelligentCruiseButtonManagement");
|
||||
intelligentCruiseButtonManagement->toggleFlipped(false);
|
||||
}
|
||||
}
|
||||
|
||||
bool icbm_allowed = intelligent_cruise_button_management_available && !has_longitudinal_control;
|
||||
intelligentCruiseButtonManagement->setEnabled(icbm_allowed && offroad);
|
||||
|
||||
// enable toggle when long is available and is not PCM cruise
|
||||
bool cai_allowed = (has_longitudinal_control && !is_pcm_cruise) || icbm_allowed;
|
||||
customAccIncrement->setEnabled(cai_allowed && !offroad);
|
||||
customAccIncrement->refresh();
|
||||
|
||||
SmartCruiseControlVision->setEnabled(has_longitudinal_control || icbm_allowed);
|
||||
SmartCruiseControlMap->setEnabled(has_longitudinal_control || icbm_allowed);
|
||||
|
||||
offroad = _offroad;
|
||||
}
|
||||
|
||||
@@ -18,13 +18,14 @@ class LongitudinalPanel : public QWidget {
|
||||
public:
|
||||
explicit LongitudinalPanel(QWidget *parent = nullptr);
|
||||
void showEvent(QShowEvent *event) override;
|
||||
void hideEvent(QHideEvent *event) override;
|
||||
void refresh(bool _offroad);
|
||||
|
||||
private:
|
||||
Params params;
|
||||
bool has_longitudinal_control = false;
|
||||
bool is_pcm_cruise = false;
|
||||
bool intelligent_cruise_button_management_available = false;;
|
||||
bool has_icbm = false;
|
||||
bool offroad = false;
|
||||
|
||||
QStackedLayout *main_layout = nullptr;
|
||||
@@ -34,6 +35,7 @@ private:
|
||||
ParamControl *SmartCruiseControlVision;
|
||||
ParamControl *SmartCruiseControlMap;
|
||||
ParamControl *intelligentCruiseButtonManagement = nullptr;
|
||||
ParamControl *dynamicExperimentalControl = nullptr;
|
||||
SpeedLimitSettings *speedLimitScreen;
|
||||
PushButtonSP *speedLimitSettings;
|
||||
};
|
||||
|
||||
@@ -310,9 +310,8 @@ void ModelsPanel::handleCurrentModelLblBtnClicked() {
|
||||
QList<TreeNode> sortedModels;
|
||||
QSet<QString> modelFolders;
|
||||
QRegularExpression re("\\(([^)]*)\\)[^(]*$");
|
||||
const auto bundles = model_manager.getAvailableBundles();
|
||||
|
||||
for (const auto &bundle : bundles) {
|
||||
for (const auto &bundle : model_manager.getAvailableBundles()) {
|
||||
auto overrides = bundle.getOverrides();
|
||||
QString folder;
|
||||
for (const auto &override : overrides) {
|
||||
@@ -392,7 +391,7 @@ void ModelsPanel::handleCurrentModelLblBtnClicked() {
|
||||
showResetParamsDialog();
|
||||
} else {
|
||||
// Find selected bundle and initiate download
|
||||
for (const auto &bundle: bundles) {
|
||||
for (const auto &bundle: model_manager.getAvailableBundles()) {
|
||||
if (QString::fromStdString(bundle.getRef()) == selectedBundleRef) {
|
||||
params.put("ModelManager_DownloadIndex", std::to_string(bundle.getIndex()));
|
||||
if (bundle.getGeneration() != model_manager.getActiveBundle().getGeneration()) {
|
||||
|
||||
@@ -21,7 +21,7 @@ SunnylinkPanel::SunnylinkPanel(QWidget *parent) : QFrame(parent) {
|
||||
paramsRefresh(param_name, param_value);
|
||||
});
|
||||
|
||||
is_sunnylink_enabled = Params().getBool("SunnylinkEnabled");
|
||||
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) {
|
||||
@@ -90,7 +90,7 @@ SunnylinkPanel::SunnylinkPanel(QWidget *parent) : QFrame(parent) {
|
||||
QString sunnylinkUploaderDesc = tr("Enable sunnylink uploader to allow sunnypilot to upload your driving data to sunnypilot servers. (only for highest tiers, and does NOT bring ANY benefit to you. We are just testing data volume.)");
|
||||
sunnylinkUploaderEnabledBtn = new ParamControlSP(
|
||||
"EnableSunnylinkUploader",
|
||||
tr("[Don't use] Enable sunnylink uploader"),
|
||||
tr("Enable sunnylink uploader (just for testing infrastructure)"),
|
||||
sunnylinkUploaderDesc,
|
||||
"", nullptr, true);
|
||||
list->addItem(sunnylinkUploaderEnabledBtn);
|
||||
@@ -272,7 +272,7 @@ void SunnylinkPanel::updatePanel() {
|
||||
const auto sunnylinkDongleId = getSunnylinkDongleId().value_or(tr("N/A"));
|
||||
sunnylinkEnabledBtn->setEnabled(!is_onroad);
|
||||
|
||||
is_sunnylink_enabled = Params().getBool("SunnylinkEnabled");
|
||||
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();
|
||||
@@ -290,7 +290,10 @@ void SunnylinkPanel::updatePanel() {
|
||||
pairSponsorBtn->setEnabled(!is_onroad && is_sunnylink_enabled);
|
||||
pairSponsorBtn->setValue(is_paired ? tr("Paired") : tr("Not Paired"));
|
||||
|
||||
sunnylinkUploaderEnabledBtn->setEnabled(max_current_sponsor_rule.roleTier == SponsorTier::Guardian && is_sunnylink_enabled);
|
||||
bool can_do_uploads = max_current_sponsor_rule.roleTier >= SponsorTier::Novice && is_sunnylink_enabled;
|
||||
sunnylinkUploaderEnabledBtn->setVisible(can_do_uploads);
|
||||
sunnylinkUploaderEnabledBtn->setEnabled(can_do_uploads);
|
||||
|
||||
|
||||
if (!is_sunnylink_enabled) {
|
||||
sunnylinkEnabledBtn->setValue("");
|
||||
|
||||
@@ -33,7 +33,7 @@ private:
|
||||
|
||||
static QString toggleDisableMsg(bool _offroad, bool _has_longitudinal_control) {
|
||||
if (!_has_longitudinal_control) {
|
||||
return tr("This feature can only be used with openpilot longitudinal control enabled.");
|
||||
return tr("This feature can only be used with sunnypilot longitudinal control enabled.");
|
||||
}
|
||||
|
||||
if (!_offroad) {
|
||||
@@ -57,7 +57,7 @@ private:
|
||||
}
|
||||
|
||||
return QString("%1<br><br>%2<br>%3<br>%4<br>")
|
||||
.arg(tr("Fine-tune your driving experience by adjusting acceleration smoothness with openpilot longitudinal control."))
|
||||
.arg(tr("Fine-tune your driving experience by adjusting acceleration smoothness with sunnypilot longitudinal control."))
|
||||
.arg(off_str)
|
||||
.arg(dynamic_str)
|
||||
.arg(predictive_str);
|
||||
|
||||
@@ -8,7 +8,52 @@
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/subaru_settings.h"
|
||||
|
||||
SubaruSettings::SubaruSettings(QWidget *parent) : BrandSettingsInterface(parent) {
|
||||
stopAndGoToggle = new ParamControl("SubaruStopAndGo", tr("Stop and Go (Beta)"), "", "");
|
||||
stopAndGoToggle->setConfirmation(true, false);
|
||||
list->addItem(stopAndGoToggle);
|
||||
|
||||
stopAndGoManualParkingBrakeToggle = new ParamControl(
|
||||
"SubaruStopAndGoManualParkingBrake",
|
||||
tr("Stop and Go for Manual Parking Brake (Beta)"),
|
||||
"",
|
||||
""
|
||||
);
|
||||
stopAndGoManualParkingBrakeToggle->setConfirmation(true, false);
|
||||
list->addItem(stopAndGoManualParkingBrakeToggle);
|
||||
}
|
||||
|
||||
void SubaruSettings::updateSettings() {
|
||||
auto cp_bytes = params.get("CarParamsPersistent");
|
||||
if (!cp_bytes.empty()) {
|
||||
AlignedBuffer aligned_buf;
|
||||
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size()));
|
||||
cereal::CarParams::Reader CP = cmsg.getRoot<cereal::CarParams>();
|
||||
|
||||
is_subaru = CP.getBrand() == "subaru";
|
||||
|
||||
if (is_subaru) {
|
||||
if (!(CP.getFlags() & (SUBARU_FLAG_GLOBAL_GEN2 | SUBARU_FLAG_HYBRID))) {
|
||||
has_stop_and_go = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
is_subaru = false;
|
||||
has_stop_and_go = false;
|
||||
}
|
||||
|
||||
bool stop_and_go_disabled = !offroad || !has_stop_and_go;
|
||||
QString stop_and_go_desc = stopAndGoDescriptionBuilder(stopAndGoDesc);
|
||||
QString stop_and_go_manual_parking_brake_desc = stopAndGoDescriptionBuilder(stopAndGoManualParkingBrakeDesc);
|
||||
if (stop_and_go_disabled) {
|
||||
stop_and_go_desc = stopAndGoDescriptionBuilder(stopAndGoDesc, stopAndGoDisabledMsg());
|
||||
stop_and_go_manual_parking_brake_desc = stopAndGoDescriptionBuilder(stopAndGoManualParkingBrakeDesc, stopAndGoDisabledMsg());
|
||||
}
|
||||
|
||||
stopAndGoToggle->setEnabled(has_stop_and_go);
|
||||
stopAndGoToggle->setDescription(stop_and_go_desc);
|
||||
stopAndGoToggle->showDescription();
|
||||
|
||||
stopAndGoManualParkingBrakeToggle->setEnabled(has_stop_and_go);
|
||||
stopAndGoManualParkingBrakeToggle->setDescription(stop_and_go_manual_parking_brake_desc);
|
||||
stopAndGoManualParkingBrakeToggle->showDescription();
|
||||
}
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/settings.h"
|
||||
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
|
||||
|
||||
const int SUBARU_FLAG_GLOBAL_GEN2 = 4;
|
||||
const int SUBARU_FLAG_HYBRID = 32;
|
||||
|
||||
class SubaruSettings : public BrandSettingsInterface {
|
||||
Q_OBJECT
|
||||
|
||||
@@ -23,4 +26,32 @@ public:
|
||||
|
||||
private:
|
||||
bool offroad = false;
|
||||
bool is_subaru;
|
||||
bool has_stop_and_go;
|
||||
|
||||
ParamControl* stopAndGoToggle;
|
||||
ParamControl* stopAndGoManualParkingBrakeToggle;
|
||||
|
||||
QString stopAndGoDesc = tr("Experimental feature to enable auto-resume during stop-and-go for certain supported Subaru platforms.");
|
||||
QString stopAndGoManualParkingBrakeDesc = tr("Experimental feature to enable stop and go for Subaru Global models with manual handbrake. Models with electric parking brake should keep this disabled. Thanks to martinl for this implementation!");
|
||||
|
||||
QString stopAndGoDisabledMsg() const {
|
||||
if (is_subaru && !has_stop_and_go) {
|
||||
return tr("This feature is currently not available on this platform.");
|
||||
}
|
||||
|
||||
if (!is_subaru) {
|
||||
return tr("Start the car to check car compatibility.");
|
||||
}
|
||||
|
||||
if (!offroad) {
|
||||
return tr("Enable \"Always Offroad\" in Device panel, or turn vehicle off to toggle.");
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
static QString stopAndGoDescriptionBuilder(const QString &base_description, const QString &custom_description = "") {
|
||||
return "<b>" + custom_description + "</b><br><br>" + base_description;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -119,7 +119,7 @@ VisualsPanel::VisualsPanel(QWidget *parent) : QWidget(parent) {
|
||||
// Visuals: Display Metrics below Chevron
|
||||
std::vector<QString> chevron_info_settings_texts{tr("Off"), tr("Distance"), tr("Speed"), tr("Time"), tr("All")};
|
||||
chevron_info_settings = new ButtonParamControlSP(
|
||||
"ChevronInfo", tr("Display Metrics Below Chevron"), tr("Display useful metrics below the chevron that tracks the lead car (only applicable to cars with openpilot longitudinal control)."),
|
||||
"ChevronInfo", tr("Display Metrics Below Chevron"), tr("Display useful metrics below the chevron that tracks the lead car (only applicable to cars with sunnypilot longitudinal control)."),
|
||||
"",
|
||||
chevron_info_settings_texts,
|
||||
200);
|
||||
@@ -140,6 +140,40 @@ VisualsPanel::VisualsPanel(QWidget *parent) : QWidget(parent) {
|
||||
vlayout->addWidget(sunnypilotScroller);
|
||||
|
||||
main_layout->addWidget(sunnypilotScreen);
|
||||
|
||||
QObject::connect(uiState(), &UIState::offroadTransition, this, &VisualsPanel::refreshLongitudinalStatus);
|
||||
|
||||
refreshLongitudinalStatus();
|
||||
}
|
||||
|
||||
void VisualsPanel::refreshLongitudinalStatus() {
|
||||
auto cp_bytes = params.get("CarParamsPersistent");
|
||||
if (!cp_bytes.empty()) {
|
||||
AlignedBuffer aligned_buf;
|
||||
capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size()));
|
||||
cereal::CarParams::Reader CP = cmsg.getRoot<cereal::CarParams>();
|
||||
|
||||
has_longitudinal_control = hasLongitudinalControl(CP);
|
||||
} else {
|
||||
has_longitudinal_control = false;
|
||||
}
|
||||
|
||||
if (chevron_info_settings) {
|
||||
QString chevronEnabledDescription = tr("Display useful metrics below the chevron that tracks the lead car (only applicable to cars with sunnypilot longitudinal control).");
|
||||
QString chevronNoLongDescription = tr("This feature requires sunnypilot longitudinal control to be available.");
|
||||
|
||||
if (has_longitudinal_control) {
|
||||
chevron_info_settings->setDescription(chevronEnabledDescription);
|
||||
} else {
|
||||
// Reset to "Off" when longitudinal not available
|
||||
params.put("ChevronInfo", "0");
|
||||
chevron_info_settings->setDescription(chevronNoLongDescription);
|
||||
}
|
||||
|
||||
// Enable only when longitudinal is available
|
||||
chevron_info_settings->setEnabled(has_longitudinal_control);
|
||||
chevron_info_settings->refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void VisualsPanel::paramsRefresh() {
|
||||
|
||||
@@ -19,6 +19,7 @@ public:
|
||||
explicit VisualsPanel(QWidget *parent = nullptr);
|
||||
|
||||
void paramsRefresh();
|
||||
void refreshLongitudinalStatus();
|
||||
|
||||
protected:
|
||||
QStackedLayout* main_layout = nullptr;
|
||||
@@ -29,4 +30,6 @@ protected:
|
||||
ParamWatcher * param_watcher;
|
||||
ButtonParamControlSP *chevron_info_settings;
|
||||
ButtonParamControlSP *dev_ui_settings;
|
||||
|
||||
bool has_longitudinal_control = false;
|
||||
};
|
||||
|
||||
@@ -18,4 +18,10 @@ void AnnotatedCameraWidgetSP::updateState(const UIState &s) {
|
||||
void AnnotatedCameraWidgetSP::showEvent(QShowEvent *event) {
|
||||
AnnotatedCameraWidget::showEvent(event);
|
||||
ui_update_params_sp(uiState());
|
||||
uiStateSP()->reset_onroad_sleep_timer(OnroadTimerStatusToggle::RESUME);
|
||||
}
|
||||
|
||||
void AnnotatedCameraWidgetSP::hideEvent(QHideEvent *event) {
|
||||
AnnotatedCameraWidget::hideEvent(event);
|
||||
uiStateSP()->reset_onroad_sleep_timer(OnroadTimerStatusToggle::PAUSE);
|
||||
}
|
||||
|
||||
@@ -18,4 +18,5 @@ public:
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent *event) override;
|
||||
void hideEvent(QHideEvent* event) override;
|
||||
};
|
||||
|
||||
@@ -12,43 +12,65 @@
|
||||
|
||||
|
||||
HudRendererSP::HudRendererSP() {
|
||||
plus_arrow_up_img = loadPixmap("../../sunnypilot/selfdrive/assets/img_plus_arrow_up", {105, 105});
|
||||
minus_arrow_down_img = loadPixmap("../../sunnypilot/selfdrive/assets/img_minus_arrow_down", {105, 105});
|
||||
plus_arrow_up_img = loadPixmap("../../sunnypilot/selfdrive/assets/img_plus_arrow_up", {90, 90});
|
||||
minus_arrow_down_img = loadPixmap("../../sunnypilot/selfdrive/assets/img_minus_arrow_down", {90, 90});
|
||||
|
||||
int small_max = e2e_alert_small * 2 - 40;
|
||||
int large_max = e2e_alert_large * 2 - 40;
|
||||
green_light_alert_small_img = loadPixmap("../../sunnypilot/selfdrive/assets/images/green_light.png", {small_max, small_max});
|
||||
green_light_alert_large_img = loadPixmap("../../sunnypilot/selfdrive/assets/images/green_light.png", {large_max, large_max});
|
||||
lead_depart_alert_small_img = loadPixmap("../../sunnypilot/selfdrive/assets/images/lead_depart.png", {small_max, small_max});
|
||||
lead_depart_alert_large_img = loadPixmap("../../sunnypilot/selfdrive/assets/images/lead_depart.png", {large_max, large_max});
|
||||
int size = e2e_alert_size * 2 - 40;
|
||||
green_light_alert_img = loadPixmap("../../sunnypilot/selfdrive/assets/images/green_light.png", {size, size});
|
||||
lead_depart_alert_img = loadPixmap("../../sunnypilot/selfdrive/assets/images/lead_depart.png", {size, size});
|
||||
}
|
||||
|
||||
void HudRendererSP::updateState(const UIState &s) {
|
||||
HudRenderer::updateState(s);
|
||||
|
||||
float speedConv = is_metric ? MS_TO_KPH : MS_TO_MPH;
|
||||
devUiInfo = s.scene.dev_ui_info;
|
||||
roadName = s.scene.road_name;
|
||||
showTurnSignals = s.scene.turn_signals;
|
||||
speedLimitMode = static_cast<SpeedLimitMode>(s.scene.speed_limit_mode);
|
||||
speedUnit = is_metric ? tr("km/h") : tr("mph");
|
||||
standstillTimer = s.scene.standstill_timer;
|
||||
|
||||
const SubMaster &sm = *(s.sm);
|
||||
const auto cs = sm["controlsState"].getControlsState();
|
||||
const auto car_state = sm["carState"].getCarState();
|
||||
const auto car_control = sm["carControl"].getCarControl();
|
||||
const auto radar_state = sm["radarState"].getRadarState();
|
||||
const auto is_gps_location_external = sm.rcv_frame("gpsLocationExternal") > 1;
|
||||
const auto gpsLocation = is_gps_location_external ? sm["gpsLocationExternal"].getGpsLocationExternal() : sm["gpsLocation"].getGpsLocation();
|
||||
const char *gps_source = is_gps_location_external ? "gpsLocationExternal" : "gpsLocation";
|
||||
const auto gpsLocation = is_gps_location_external ? sm[gps_source].getGpsLocationExternal() : sm[gps_source].getGpsLocation();
|
||||
const auto ltp = sm["liveTorqueParameters"].getLiveTorqueParameters();
|
||||
const auto car_params = sm["carParams"].getCarParams();
|
||||
const auto car_params_sp = sm["carParamsSP"].getCarParamsSP();
|
||||
const auto lp_sp = sm["longitudinalPlanSP"].getLongitudinalPlanSP();
|
||||
const auto lmd = sm["liveMapDataSP"].getLiveMapDataSP();
|
||||
|
||||
float speedConv = is_metric ? MS_TO_KPH : MS_TO_MPH;
|
||||
speedLimit = lp_sp.getSpeedLimit().getResolver().getSpeedLimit() * speedConv;
|
||||
speedLimitLast = lp_sp.getSpeedLimit().getResolver().getSpeedLimitLast() * speedConv;
|
||||
speedLimitOffset = lp_sp.getSpeedLimit().getResolver().getSpeedLimitOffset() * speedConv;
|
||||
speedLimitValid = lp_sp.getSpeedLimit().getResolver().getSpeedLimitValid();
|
||||
speedLimitLastValid = lp_sp.getSpeedLimit().getResolver().getSpeedLimitLastValid();
|
||||
speedLimitFinalLast = lp_sp.getSpeedLimit().getResolver().getSpeedLimitFinalLast() * speedConv;
|
||||
speedLimitMode = static_cast<SpeedLimitMode>(s.scene.speed_limit_mode);
|
||||
speedLimitAssistState = lp_sp.getSpeedLimit().getAssist().getState();
|
||||
speedLimitAssistActive = lp_sp.getSpeedLimit().getAssist().getActive();
|
||||
roadName = s.scene.road_name;
|
||||
if (sm.updated("carParams")) {
|
||||
steerControlType = car_params.getSteerControlType();
|
||||
}
|
||||
|
||||
if (sm.updated("carParamsSP")) {
|
||||
pcmCruiseSpeed = car_params_sp.getPcmCruiseSpeed();
|
||||
}
|
||||
|
||||
if (sm.updated("longitudinalPlanSP")) {
|
||||
speedLimit = lp_sp.getSpeedLimit().getResolver().getSpeedLimit() * speedConv;
|
||||
speedLimitLast = lp_sp.getSpeedLimit().getResolver().getSpeedLimitLast() * speedConv;
|
||||
speedLimitOffset = lp_sp.getSpeedLimit().getResolver().getSpeedLimitOffset() * speedConv;
|
||||
speedLimitValid = lp_sp.getSpeedLimit().getResolver().getSpeedLimitValid();
|
||||
speedLimitLastValid = lp_sp.getSpeedLimit().getResolver().getSpeedLimitLastValid();
|
||||
speedLimitFinalLast = lp_sp.getSpeedLimit().getResolver().getSpeedLimitFinalLast() * speedConv;
|
||||
speedLimitSource = lp_sp.getSpeedLimit().getResolver().getSource();
|
||||
speedLimitAssistState = lp_sp.getSpeedLimit().getAssist().getState();
|
||||
speedLimitAssistActive = lp_sp.getSpeedLimit().getAssist().getActive();
|
||||
smartCruiseControlVisionEnabled = lp_sp.getSmartCruiseControl().getVision().getEnabled();
|
||||
smartCruiseControlVisionActive = lp_sp.getSmartCruiseControl().getVision().getActive();
|
||||
smartCruiseControlMapEnabled = lp_sp.getSmartCruiseControl().getMap().getEnabled();
|
||||
smartCruiseControlMapActive = lp_sp.getSmartCruiseControl().getMap().getActive();
|
||||
}
|
||||
greenLightAlert = lp_sp.getE2eAlerts().getGreenLightAlert();
|
||||
leadDepartAlert = lp_sp.getE2eAlerts().getLeadDepartAlert();
|
||||
|
||||
if (sm.updated("liveMapDataSP")) {
|
||||
roadNameStr = QString::fromStdString(lmd.getRoadName());
|
||||
speedLimitAheadValid = lmd.getSpeedLimitAheadValid();
|
||||
@@ -64,7 +86,7 @@ void HudRendererSP::updateState(const UIState &s) {
|
||||
|
||||
static int reverse_delay = 0;
|
||||
bool reverse_allowed = false;
|
||||
if (int(car_state.getGearShifter()) != 4) {
|
||||
if (car_state.getGearShifter() != cereal::CarState::GearShifter::REVERSE) {
|
||||
reverse_delay = 0;
|
||||
reverse_allowed = false;
|
||||
} else {
|
||||
@@ -76,45 +98,47 @@ void HudRendererSP::updateState(const UIState &s) {
|
||||
|
||||
reversing = reverse_allowed;
|
||||
|
||||
if (sm.updated("liveParameters")) {
|
||||
roll = sm["liveParameters"].getLiveParameters().getRoll();
|
||||
}
|
||||
|
||||
if (sm.updated("deviceState")) {
|
||||
memoryUsagePercent = sm["deviceState"].getDeviceState().getMemoryUsagePercent();
|
||||
}
|
||||
|
||||
if (sm.updated(gps_source)) {
|
||||
gpsAccuracy = is_gps_location_external ? gpsLocation.getHorizontalAccuracy() : 1.0; // External reports accuracy, internal does not.
|
||||
altitude = gpsLocation.getAltitude();
|
||||
bearingAccuracyDeg = gpsLocation.getBearingAccuracyDeg();
|
||||
bearingDeg = gpsLocation.getBearingDeg();
|
||||
}
|
||||
|
||||
if (sm.updated("liveTorqueParameters")) {
|
||||
torquedUseParams = ltp.getUseParams();
|
||||
latAccelFactorFiltered = ltp.getLatAccelFactorFiltered();
|
||||
frictionCoefficientFiltered = ltp.getFrictionCoefficientFiltered();
|
||||
liveValid = ltp.getLiveValid();
|
||||
}
|
||||
|
||||
latActive = car_control.getLatActive();
|
||||
actuators = car_control.getActuators();
|
||||
longOverride = car_control.getCruiseControl().getOverride();
|
||||
carControlEnabled = car_control.getEnabled();
|
||||
|
||||
steerOverride = car_state.getSteeringPressed();
|
||||
|
||||
devUiInfo = s.scene.dev_ui_info;
|
||||
|
||||
speedUnit = is_metric ? tr("km/h") : tr("mph");
|
||||
lead_d_rel = radar_state.getLeadOne().getDRel();
|
||||
lead_v_rel = radar_state.getLeadOne().getVRel();
|
||||
lead_status = radar_state.getLeadOne().getStatus();
|
||||
steerControlType = car_params.getSteerControlType();
|
||||
actuators = car_control.getActuators();
|
||||
torqueLateral = steerControlType == cereal::CarParams::SteerControlType::TORQUE;
|
||||
angleSteers = car_state.getSteeringAngleDeg();
|
||||
desiredCurvature = cs.getDesiredCurvature();
|
||||
curvature = cs.getCurvature();
|
||||
roll = sm["liveParameters"].getLiveParameters().getRoll();
|
||||
memoryUsagePercent = sm["deviceState"].getDeviceState().getMemoryUsagePercent();
|
||||
gpsAccuracy = is_gps_location_external ? gpsLocation.getHorizontalAccuracy() : 1.0; // External reports accuracy, internal does not.
|
||||
altitude = gpsLocation.getAltitude();
|
||||
vEgo = car_state.getVEgo();
|
||||
aEgo = car_state.getAEgo();
|
||||
steeringTorqueEps = car_state.getSteeringTorqueEps();
|
||||
bearingAccuracyDeg = gpsLocation.getBearingAccuracyDeg();
|
||||
bearingDeg = gpsLocation.getBearingDeg();
|
||||
torquedUseParams = ltp.getUseParams();
|
||||
latAccelFactorFiltered = ltp.getLatAccelFactorFiltered();
|
||||
frictionCoefficientFiltered = ltp.getFrictionCoefficientFiltered();
|
||||
liveValid = ltp.getLiveValid();
|
||||
|
||||
standstillTimer = s.scene.standstill_timer;
|
||||
isStandstill = car_state.getStandstill();
|
||||
longOverride = car_control.getCruiseControl().getOverride();
|
||||
smartCruiseControlVisionEnabled = lp_sp.getSmartCruiseControl().getVision().getEnabled();
|
||||
smartCruiseControlVisionActive = lp_sp.getSmartCruiseControl().getVision().getActive();
|
||||
smartCruiseControlMapEnabled = lp_sp.getSmartCruiseControl().getMap().getEnabled();
|
||||
smartCruiseControlMapActive = lp_sp.getSmartCruiseControl().getMap().getActive();
|
||||
|
||||
greenLightAlert = lp_sp.getE2eAlerts().getGreenLightAlert();
|
||||
leadDepartAlert = lp_sp.getE2eAlerts().getLeadDepartAlert();
|
||||
if (!s.scene.started) standstillElapsedTime = 0.0;
|
||||
|
||||
// override stock current speed values
|
||||
float v_ego = (v_ego_cluster_seen && !s.scene.trueVEgoUI) ? car_state.getVEgoCluster() : car_state.getVEgo();
|
||||
@@ -125,7 +149,11 @@ void HudRendererSP::updateState(const UIState &s) {
|
||||
rightBlinkerOn = car_state.getRightBlinker();
|
||||
leftBlindspot = car_state.getLeftBlindspot();
|
||||
rightBlindspot = car_state.getRightBlindspot();
|
||||
showTurnSignals = s.scene.turn_signals;
|
||||
|
||||
speedCluster = car_state.getCruiseState().getSpeedCluster() * speedConv;
|
||||
|
||||
allow_e2e_alerts = sm["selfdriveState"].getSelfdriveState().getAlertSize() == cereal::SelfdriveState::AlertSize::NONE &&
|
||||
sm.rcv_frame("driverStateV2") > s.scene.started_frame && !reversing;
|
||||
}
|
||||
|
||||
void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) {
|
||||
@@ -218,7 +246,7 @@ void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) {
|
||||
drawRoadName(p, surface_rect);
|
||||
|
||||
// Green Light & Lead Depart Alerts
|
||||
if (greenLightAlert or leadDepartAlert) {
|
||||
if (greenLightAlert || leadDepartAlert) {
|
||||
e2eAlertDisplayTimer = 3 * UI_FREQ;
|
||||
// reset onroad sleep timer for e2e alerts
|
||||
uiStateSP()->reset_onroad_sleep_timer();
|
||||
@@ -228,11 +256,11 @@ void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) {
|
||||
e2eAlertFrame++;
|
||||
if (greenLightAlert) {
|
||||
alert_text = tr("GREEN\nLIGHT");
|
||||
alert_img = devUiInfo > 0 ? green_light_alert_small_img : green_light_alert_large_img;
|
||||
alert_img = green_light_alert_img;
|
||||
}
|
||||
else if (leadDepartAlert) {
|
||||
alert_text = tr("LEAD VEHICLE\nDEPARTING");
|
||||
alert_img = devUiInfo > 0 ? lead_depart_alert_small_img : lead_depart_alert_large_img;
|
||||
alert_img = lead_depart_alert_img;
|
||||
}
|
||||
drawE2eAlert(p, surface_rect);
|
||||
}
|
||||
@@ -250,6 +278,7 @@ void HudRendererSP::draw(QPainter &p, const QRect &surface_rect) {
|
||||
// No Alerts displayed
|
||||
else {
|
||||
e2eAlertFrame = 0;
|
||||
if (!isStandstill) standstillElapsedTime = 0.0;
|
||||
}
|
||||
|
||||
// Blinker
|
||||
@@ -540,7 +569,8 @@ void HudRendererSP::drawSpeedLimitSigns(QPainter &p, QRect &sign_rect) {
|
||||
}
|
||||
|
||||
void HudRendererSP::drawUpcomingSpeedLimit(QPainter &p) {
|
||||
bool speed_limit_ahead = speedLimitAheadValid && speedLimitAhead > 0 && speedLimitAhead != speedLimit && speedLimitAheadValidFrame > 0;
|
||||
bool speed_limit_ahead = speedLimitAheadValid && speedLimitAhead > 0 && speedLimitAhead != speedLimit && speedLimitAheadValidFrame > 0 &&
|
||||
speedLimitSource == cereal::LongitudinalPlanSP::SpeedLimit::Source::MAP;
|
||||
if (!speed_limit_ahead) {
|
||||
return;
|
||||
}
|
||||
@@ -634,7 +664,7 @@ void HudRendererSP::drawRoadName(QPainter &p, const QRect &surface_rect) {
|
||||
|
||||
void HudRendererSP::drawSpeedLimitPreActiveArrow(QPainter &p, QRect &sign_rect) {
|
||||
const int sign_margin = 12;
|
||||
const int arrow_spacing = sign_margin * 3;
|
||||
const int arrow_spacing = sign_margin * 1.4;
|
||||
int arrow_x = sign_rect.right() + arrow_spacing;
|
||||
|
||||
int _set_speed = std::nearbyint(set_speed);
|
||||
@@ -683,10 +713,23 @@ void HudRendererSP::drawSetSpeedSP(QPainter &p, const QRect &surface_rect) {
|
||||
}
|
||||
}
|
||||
|
||||
// Draw "MAX" text
|
||||
p.setFont(InterFont(40, QFont::DemiBold));
|
||||
// Draw "MAX" or carState.cruiseState.speedCluster (when ICBM is active) text
|
||||
if (!pcmCruiseSpeed && carControlEnabled) {
|
||||
if (std::nearbyint(set_speed) != std::nearbyint(speedCluster)) {
|
||||
icbm_active_counter = 3 * UI_FREQ;
|
||||
} else if (icbm_active_counter > 0) {
|
||||
icbm_active_counter--;
|
||||
}
|
||||
} else {
|
||||
icbm_active_counter = 0;
|
||||
}
|
||||
int max_str_size = (icbm_active_counter != 0) ? 60 : 40;
|
||||
int max_str_y = (icbm_active_counter != 0) ? 15 : 27;
|
||||
QString max_str = (icbm_active_counter != 0) ? QString::number(std::nearbyint(speedCluster)) : tr("MAX");
|
||||
|
||||
p.setFont(InterFont(max_str_size, QFont::DemiBold));
|
||||
p.setPen(max_color);
|
||||
p.drawText(set_speed_rect.adjusted(0, 27, 0, 0), Qt::AlignTop | Qt::AlignHCenter, tr("MAX"));
|
||||
p.drawText(set_speed_rect.adjusted(0, max_str_y, 0, 0), Qt::AlignTop | Qt::AlignHCenter, max_str);
|
||||
|
||||
// Draw set speed
|
||||
QString setSpeedStr = is_cruise_set ? QString::number(std::nearbyint(set_speed)) : "–";
|
||||
@@ -696,27 +739,26 @@ void HudRendererSP::drawSetSpeedSP(QPainter &p, const QRect &surface_rect) {
|
||||
}
|
||||
|
||||
void HudRendererSP::drawE2eAlert(QPainter &p, const QRect &surface_rect, const QString &alert_alt_text) {
|
||||
int size = devUiInfo > 0 ? e2e_alert_small : e2e_alert_large;
|
||||
int x = surface_rect.center().x() + surface_rect.width() / 4;
|
||||
int y = surface_rect.center().y() + 40;
|
||||
x += devUiInfo > 0 ? 0 : 50;
|
||||
y += devUiInfo > 0 ? 0 : 80;
|
||||
QRect alertRect(x - size, y - size, size * 2, size * 2);
|
||||
if (!allow_e2e_alerts) return;
|
||||
|
||||
int x = surface_rect.right() - e2e_alert_size - (devUiInfo > 0 ? 180 : 100) - (UI_BORDER_SIZE * 3);
|
||||
int y = surface_rect.center().y() + 20;
|
||||
QRect alertRect(x - e2e_alert_size, y - e2e_alert_size, e2e_alert_size * 2, e2e_alert_size * 2);
|
||||
|
||||
// Alert Circle
|
||||
QPoint center = alertRect.center();
|
||||
QColor frameColor;
|
||||
if (not alert_alt_text.isEmpty()) frameColor = QColor(255, 255, 255, 75);
|
||||
if (!alert_alt_text.isEmpty()) frameColor = QColor(255, 255, 255, 75);
|
||||
else frameColor = pulseElement(e2eAlertFrame) ? QColor(255, 255, 255, 75) : QColor(0, 255, 0, 75);
|
||||
p.setPen(QPen(frameColor, 15));
|
||||
p.setBrush(QColor(0, 0, 0, 190));
|
||||
p.drawEllipse(center, size, size);
|
||||
p.drawEllipse(center, e2e_alert_size, e2e_alert_size);
|
||||
|
||||
// Alert Text
|
||||
QColor txtColor;
|
||||
QFont font;
|
||||
int alert_bottom_adjustment;
|
||||
if (not alert_alt_text.isEmpty()) {
|
||||
if (!alert_alt_text.isEmpty()) {
|
||||
font = InterFont(100, QFont::Bold);
|
||||
alert_bottom_adjustment = 5;
|
||||
txtColor = QColor(255, 255, 255, 255);
|
||||
@@ -733,7 +775,7 @@ void HudRendererSP::drawE2eAlert(QPainter &p, const QRect &surface_rect, const Q
|
||||
textRect.moveBottom(alertRect.bottom() - alertRect.height() / alert_bottom_adjustment);
|
||||
p.drawText(textRect, Qt::AlignCenter, alert_text);
|
||||
|
||||
if (not alert_alt_text.isEmpty()) {
|
||||
if (!alert_alt_text.isEmpty()) {
|
||||
// Alert Alternate Text
|
||||
p.setFont(InterFont(80, QFont::Bold));
|
||||
p.setPen(QColor(255, 175, 3, 240));
|
||||
@@ -761,19 +803,33 @@ void HudRendererSP::drawCurrentSpeedSP(QPainter &p, const QRect &surface_rect) {
|
||||
}
|
||||
|
||||
void HudRendererSP::drawBlinker(QPainter &p, const QRect &surface_rect) {
|
||||
const bool hazard = leftBlinkerOn && rightBlinkerOn;
|
||||
int blinkerStatus = hazard ? 2 : (leftBlinkerOn || rightBlinkerOn) ? 1 : 0;
|
||||
|
||||
if (!leftBlinkerOn && !rightBlinkerOn) {
|
||||
blinkerFrameCounter = 0;
|
||||
lastBlinkerStatus = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (blinkerStatus != lastBlinkerStatus) {
|
||||
blinkerFrameCounter = 0;
|
||||
lastBlinkerStatus = blinkerStatus;
|
||||
}
|
||||
|
||||
++blinkerFrameCounter;
|
||||
|
||||
const int circleRadius = 44;
|
||||
const int arrowLength = 44;
|
||||
const int x_gap = 180;
|
||||
const int BLINKER_COOLDOWN_FRAMES = UI_FREQ / 10;
|
||||
if (blinkerFrameCounter < BLINKER_COOLDOWN_FRAMES) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int circleRadius = 60;
|
||||
const int arrowLength = 60;
|
||||
const int x_gap = 160;
|
||||
const int y_offset = 272;
|
||||
|
||||
const int centerX = surface_rect.center().x();
|
||||
const bool hazard = leftBlinkerOn && rightBlinkerOn;
|
||||
|
||||
const QPen bgBorder(Qt::white, 5);
|
||||
const QPen arrowPen(Qt::NoPen);
|
||||
|
||||
@@ -83,6 +83,7 @@ private:
|
||||
bool speedLimitValid;
|
||||
bool speedLimitLastValid;
|
||||
float speedLimitFinalLast;
|
||||
cereal::LongitudinalPlanSP::SpeedLimit::Source speedLimitSource;
|
||||
bool speedLimitAheadValid;
|
||||
float speedLimitAhead;
|
||||
float speedLimitAheadDistance;
|
||||
@@ -96,16 +97,14 @@ private:
|
||||
int speedLimitAssistFrame;
|
||||
QPixmap plus_arrow_up_img;
|
||||
QPixmap minus_arrow_down_img;
|
||||
int e2e_alert_small = 250;
|
||||
int e2e_alert_large = 300;
|
||||
QPixmap green_light_alert_small_img;
|
||||
QPixmap green_light_alert_large_img;
|
||||
int e2e_alert_size = 250;
|
||||
QPixmap green_light_alert_img;
|
||||
bool greenLightAlert;
|
||||
int e2eAlertFrame;
|
||||
int e2eAlertDisplayTimer = 0;
|
||||
bool allow_e2e_alerts;
|
||||
bool leadDepartAlert;
|
||||
QPixmap lead_depart_alert_small_img;
|
||||
QPixmap lead_depart_alert_large_img;
|
||||
QPixmap lead_depart_alert_img;
|
||||
QString alert_text;
|
||||
QPixmap alert_img;
|
||||
bool hideVEgoUI;
|
||||
@@ -114,5 +113,11 @@ private:
|
||||
bool leftBlindspot;
|
||||
bool rightBlindspot;
|
||||
int blinkerFrameCounter;
|
||||
int lastBlinkerStatus;
|
||||
bool showTurnSignals;
|
||||
|
||||
bool carControlEnabled;
|
||||
float speedCluster = 0;
|
||||
int icbm_active_counter = 0;
|
||||
bool pcmCruiseSpeed = true;
|
||||
};
|
||||
|
||||
@@ -21,70 +21,234 @@ void ModelRendererSP::update_model(const cereal::ModelDataV2::Reader &model, con
|
||||
mapLineToPolygon(model.getLaneLines()[2], 0.2, -0.05, &right_blindspot_vertices, max_idx_barrier);
|
||||
}
|
||||
|
||||
void ModelRendererSP::drawPath(QPainter &painter, const cereal::ModelDataV2::Reader &model, const QRect &surface_rect) {
|
||||
void ModelRendererSP::draw(QPainter &painter, const QRect &surface_rect) {
|
||||
auto *s = uiState();
|
||||
auto &sm = *(s->sm);
|
||||
bool blindspot = Params().getBool("BlindSpot");
|
||||
|
||||
if (blindspot) {
|
||||
bool left_blindspot = sm["carState"].getCarState().getLeftBlindspot();
|
||||
bool right_blindspot = sm["carState"].getCarState().getRightBlindspot();
|
||||
|
||||
//painter.setBrush(QColor::fromRgbF(1.0, 0.0, 0.0, 0.4)); // Red with alpha for blind spot
|
||||
|
||||
if (left_blindspot && !left_blindspot_vertices.isEmpty()) {
|
||||
QLinearGradient gradient(0, 0, surface_rect.width(), 0); // Horizontal gradient from left to right
|
||||
gradient.setColorAt(0.0, QColor(255, 165, 0, 102)); // Orange with alpha
|
||||
gradient.setColorAt(1.0, QColor(255, 255, 0, 102)); // Yellow with alpha
|
||||
painter.setBrush(gradient);
|
||||
painter.drawPolygon(left_blindspot_vertices);
|
||||
}
|
||||
|
||||
if (right_blindspot && !right_blindspot_vertices.isEmpty()) {
|
||||
QLinearGradient gradient(surface_rect.width(), 0, 0, 0); // Horizontal gradient from right to left
|
||||
gradient.setColorAt(0.0, QColor(255, 165, 0, 102)); // Orange with alpha
|
||||
gradient.setColorAt(1.0, QColor(255, 255, 0, 102)); // Yellow with alpha
|
||||
painter.setBrush(gradient);
|
||||
painter.drawPolygon(right_blindspot_vertices);
|
||||
}
|
||||
if (sm.rcv_frame("liveCalibration") < s->scene.started_frame ||
|
||||
sm.rcv_frame("modelV2") < s->scene.started_frame) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool rainbow = Params().getBool("RainbowMode");
|
||||
//float v_ego = sm["carState"].getCarState().getVEgo();
|
||||
clip_region = surface_rect.adjusted(-CLIP_MARGIN, -CLIP_MARGIN, CLIP_MARGIN, CLIP_MARGIN);
|
||||
experimental_mode = sm["selfdriveState"].getSelfdriveState().getExperimentalMode();
|
||||
longitudinal_control = sm["carParams"].getCarParams().getOpenpilotLongitudinalControl();
|
||||
path_offset_z = sm["liveCalibration"].getLiveCalibration().getHeight()[0];
|
||||
|
||||
if (rainbow) {
|
||||
// Simple time-based animation
|
||||
float time_offset = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch()).count() / 1000.0f;
|
||||
painter.save();
|
||||
|
||||
// simple linear gradient from bottom to top
|
||||
QLinearGradient bg(0, surface_rect.height(), 0, 0);
|
||||
const auto &model = sm["modelV2"].getModelV2();
|
||||
const auto &radar_state = sm["radarState"].getRadarState();
|
||||
const auto &lead_one = radar_state.getLeadOne();
|
||||
const auto &car_state = sm["carState"].getCarState();
|
||||
|
||||
// evenly spaced colors across the spectrum
|
||||
// The animation shifts the entire spectrum smoothly
|
||||
float animation_speed = 40.0f; // speed vroom vroom
|
||||
float hue_offset = fmod(time_offset * animation_speed, 360.0f);
|
||||
update_model(model, lead_one);
|
||||
drawLaneLines(painter);
|
||||
|
||||
// 6-8 color stops for smooth transitions more color makes it laggy
|
||||
const int num_stops = 7;
|
||||
for (int i = 0; i < num_stops; i++) {
|
||||
float position = static_cast<float>(i) / (num_stops - 1);
|
||||
|
||||
float hue = fmod(hue_offset + position * 360.0f, 360.0f);
|
||||
float saturation = 0.9f;
|
||||
float lightness = 0.6f;
|
||||
|
||||
// Alpha fades out towards the far end of the path
|
||||
float alpha = 0.8f * (1.0f - position * 0.3f);
|
||||
|
||||
QColor color = QColor::fromHslF(hue / 360.0f, saturation, lightness, alpha);
|
||||
bg.setColorAt(position, color);
|
||||
}
|
||||
|
||||
painter.setBrush(bg);
|
||||
painter.drawPolygon(track_vertices);
|
||||
if (s->scene.rainbow_mode) {
|
||||
drawRainbowPath(painter, surface_rect);
|
||||
} else {
|
||||
// Normal path rendering
|
||||
ModelRenderer::drawPath(painter, model, surface_rect.height());
|
||||
}
|
||||
|
||||
if (longitudinal_control && sm.alive("radarState")) {
|
||||
update_leads(radar_state, model.getPosition());
|
||||
const auto &lead_two = radar_state.getLeadTwo();
|
||||
if (lead_one.getStatus()) {
|
||||
drawLead(painter, lead_one, lead_vertices[0], surface_rect);
|
||||
}
|
||||
if (lead_two.getStatus() && (std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0)) {
|
||||
drawLead(painter, lead_two, lead_vertices[1], surface_rect);
|
||||
}
|
||||
}
|
||||
|
||||
if (s->scene.blindspot_ui) {
|
||||
const bool left_blindspot = car_state.getLeftBlindspot();
|
||||
const bool right_blindspot = car_state.getRightBlindspot();
|
||||
drawBlindspot(painter, surface_rect, left_blindspot, right_blindspot);
|
||||
}
|
||||
drawLeadStatus(painter, surface_rect.height(), surface_rect.width());
|
||||
|
||||
painter.restore();
|
||||
}
|
||||
|
||||
void ModelRendererSP::drawBlindspot(QPainter &painter, const QRect &surface_rect, bool left_blindspot, bool right_blindspot) {
|
||||
if (left_blindspot && !left_blindspot_vertices.isEmpty()) {
|
||||
QLinearGradient gradient(0, 0, surface_rect.width(), 0); // Horizontal gradient from left to right
|
||||
gradient.setColorAt(0.0, QColor(255, 165, 0, 102)); // Orange with alpha
|
||||
gradient.setColorAt(1.0, QColor(255, 255, 0, 102)); // Yellow with alpha
|
||||
painter.setBrush(gradient);
|
||||
painter.drawPolygon(left_blindspot_vertices);
|
||||
}
|
||||
|
||||
if (right_blindspot && !right_blindspot_vertices.isEmpty()) {
|
||||
QLinearGradient gradient(surface_rect.width(), 0, 0, 0); // Horizontal gradient from right to left
|
||||
gradient.setColorAt(0.0, QColor(255, 165, 0, 102)); // Orange with alpha
|
||||
gradient.setColorAt(1.0, QColor(255, 255, 0, 102)); // Yellow with alpha
|
||||
painter.setBrush(gradient);
|
||||
painter.drawPolygon(right_blindspot_vertices);
|
||||
}
|
||||
}
|
||||
|
||||
void ModelRendererSP::drawLeadStatus(QPainter &painter, int height, int width) {
|
||||
auto *s = uiState();
|
||||
auto &sm = *(s->sm);
|
||||
|
||||
bool longitudinal_control = sm["carParams"].getCarParams().getOpenpilotLongitudinalControl();
|
||||
if (!longitudinal_control) {
|
||||
lead_status_alpha = std::max(0.0f, lead_status_alpha - 0.05f);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sm.alive("radarState")) {
|
||||
lead_status_alpha = std::max(0.0f, lead_status_alpha - 0.05f);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &radar_state = sm["radarState"].getRadarState();
|
||||
const auto &lead_one = radar_state.getLeadOne();
|
||||
const auto &lead_two = radar_state.getLeadTwo();
|
||||
|
||||
bool has_lead_one = lead_one.getStatus();
|
||||
bool has_lead_two = lead_two.getStatus();
|
||||
|
||||
if (!has_lead_one && !has_lead_two) {
|
||||
lead_status_alpha = std::max(0.0f, lead_status_alpha - 0.05f);
|
||||
if (lead_status_alpha <= 0.0f) return;
|
||||
} else {
|
||||
lead_status_alpha = std::min(1.0f, lead_status_alpha + 0.1f);
|
||||
}
|
||||
|
||||
if (has_lead_one) {
|
||||
drawLeadStatusPosition(painter, lead_one, lead_vertices[0], height, width);
|
||||
}
|
||||
|
||||
if (has_lead_two && std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0) {
|
||||
drawLeadStatusPosition(painter, lead_two, lead_vertices[1], height, width);
|
||||
}
|
||||
}
|
||||
|
||||
void ModelRendererSP::drawLeadStatusPosition(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data,
|
||||
const QPointF &chevron_pos, int height, int width) {
|
||||
float d_rel = lead_data.getDRel();
|
||||
float v_rel = lead_data.getVRel();
|
||||
auto *s = uiState();
|
||||
auto &sm = *(s->sm);
|
||||
float v_ego = sm["carState"].getCarState().getVEgo();
|
||||
|
||||
int chevron_data = s->scene.chevron_info;
|
||||
float sz = std::clamp((25 * 30) / (d_rel / 3 + 30), 15.0f, 30.0f) * 2.35;
|
||||
|
||||
QFont content_font = painter.font();
|
||||
content_font.setPixelSize(50);
|
||||
content_font.setBold(true);
|
||||
painter.setFont(content_font);
|
||||
|
||||
bool is_metric = s->scene.is_metric;
|
||||
QStringList text_lines;
|
||||
const int chevron_all = 4;
|
||||
QStringList chevron_text[3];
|
||||
|
||||
// Distance display
|
||||
if (chevron_data == 1 || chevron_data == chevron_all) {
|
||||
int pos = 0;
|
||||
float val = std::max(0.0f, d_rel);
|
||||
QString unit = is_metric ? "m" : "ft";
|
||||
if (!is_metric) val *= 3.28084f;
|
||||
chevron_text[pos].append(QString::number(val, 'f', 0) + " " + unit);
|
||||
}
|
||||
|
||||
// Speed display
|
||||
if (chevron_data == 2 || chevron_data == chevron_all) {
|
||||
int pos = (chevron_data == 2) ? 0 : 1;
|
||||
float multiplier = is_metric ? static_cast<float>(MS_TO_KPH) : static_cast<float>(MS_TO_MPH);
|
||||
float val = std::max(0.0f, (v_rel + v_ego) * multiplier);
|
||||
QString unit = is_metric ? "km/h" : "mph";
|
||||
chevron_text[pos].append(QString::number(val, 'f', 0) + " " + unit);
|
||||
}
|
||||
|
||||
// Time to contact
|
||||
if (chevron_data == 3 || chevron_data == chevron_all) {
|
||||
int pos = (chevron_data == 3) ? 0 : 2;
|
||||
float val = (d_rel > 0 && v_ego > 0) ? std::max(0.0f, d_rel / v_ego) : 0.0f;
|
||||
QString ttc = (val > 0 && val < 200) ? QString::number(val, 'f', 1) + "s" : "---";
|
||||
chevron_text[pos].append(ttc);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
if (!chevron_text[i].isEmpty()) text_lines.append(chevron_text[i]);
|
||||
}
|
||||
|
||||
if (text_lines.isEmpty()) return;
|
||||
|
||||
QFontMetrics fm(content_font);
|
||||
float text_width = 120.0f;
|
||||
for (const QString &line : text_lines) {
|
||||
text_width = std::max(text_width, fm.horizontalAdvance(line) + 20.0f);
|
||||
}
|
||||
text_width = std::min(text_width, 250.0f);
|
||||
|
||||
float line_height = 50.0f;
|
||||
float total_height = text_lines.size() * line_height;
|
||||
float margin = 20.0f;
|
||||
|
||||
float text_y = chevron_pos.y() + sz + 15;
|
||||
if (text_y + total_height > height - margin) {
|
||||
float y_max = chevron_pos.y() > (height - margin) ? (height - margin) : chevron_pos.y();
|
||||
text_y = y_max - 15 - total_height;
|
||||
text_y = std::max(margin, text_y);
|
||||
}
|
||||
|
||||
float text_x = chevron_pos.x() - text_width / 2;
|
||||
text_x = std::clamp(text_x, margin, (float)width - text_width - margin);
|
||||
|
||||
QPoint shadow_offset(2, 2);
|
||||
QColor text_color = QColor(255, 255, 255, (int)(255 * lead_status_alpha));
|
||||
for (int i = 0; i < text_lines.size(); ++i) {
|
||||
float y = text_y + (i * line_height);
|
||||
if (y + line_height > height - margin) break;
|
||||
|
||||
QRect rect(text_x, y, text_width, line_height);
|
||||
|
||||
// Draw shadow
|
||||
painter.setPen(QColor(0, 0, 0, (int)(200 * lead_status_alpha)));
|
||||
painter.drawText(rect.translated(shadow_offset), Qt::AlignCenter, text_lines[i]);
|
||||
painter.setPen(text_color);
|
||||
painter.drawText(rect, Qt::AlignCenter, text_lines[i]);
|
||||
}
|
||||
|
||||
painter.setPen(Qt::NoPen);
|
||||
}
|
||||
|
||||
void ModelRendererSP::drawRainbowPath(QPainter &painter, const QRect &surface_rect) {
|
||||
// Simple time-based animation
|
||||
float time_offset = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now().time_since_epoch()).count() / 1000.0f;
|
||||
|
||||
// simple linear gradient from bottom to top
|
||||
QLinearGradient bg(0, surface_rect.height(), 0, 0);
|
||||
|
||||
// evenly spaced colors across the spectrum
|
||||
// The animation shifts the entire spectrum smoothly
|
||||
float animation_speed = 40.0f; // speed vroom vroom
|
||||
float hue_offset = fmod(time_offset * animation_speed, 360.0f);
|
||||
|
||||
// 6-8 color stops for smooth transitions more color makes it laggy
|
||||
const int num_stops = 7;
|
||||
for (int i = 0; i < num_stops; i++) {
|
||||
float position = static_cast<float>(i) / (num_stops - 1);
|
||||
|
||||
float hue = fmod(hue_offset + position * 360.0f, 360.0f);
|
||||
float saturation = 0.9f;
|
||||
float lightness = 0.6f;
|
||||
|
||||
// Alpha fades out towards the far end of the path
|
||||
float alpha = 0.8f * (1.0f - position * 0.3f);
|
||||
|
||||
QColor color = QColor::fromHslF(hue / 360.0f, saturation, lightness, alpha);
|
||||
bg.setColorAt(position, color);
|
||||
}
|
||||
|
||||
painter.setBrush(bg);
|
||||
painter.drawPolygon(track_vertices);
|
||||
}
|
||||
|
||||
@@ -13,10 +13,19 @@ class ModelRendererSP : public ModelRenderer {
|
||||
public:
|
||||
ModelRendererSP() = default;
|
||||
|
||||
void draw(QPainter &painter, const QRect &surface_rect);
|
||||
|
||||
private:
|
||||
void update_model(const cereal::ModelDataV2::Reader &model, const cereal::RadarState::LeadData::Reader &lead) override;
|
||||
void drawPath(QPainter &painter, const cereal::ModelDataV2::Reader &model, const QRect &rect) override;
|
||||
void drawLeadStatus(QPainter &painter, int height, int width);
|
||||
void drawLeadStatusPosition(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data,
|
||||
const QPointF &chevron_pos, int height, int width);
|
||||
void drawBlindspot(QPainter &painter, const QRect &surface_rect, bool left_blindspot, bool right_blindspot);
|
||||
void drawRainbowPath(QPainter &painter, const QRect &surface_rect);
|
||||
|
||||
QPolygonF left_blindspot_vertices;
|
||||
QPolygonF right_blindspot_vertices;
|
||||
|
||||
// Lead status animation
|
||||
float lead_status_alpha = 0.0f;
|
||||
};
|
||||
|
||||
@@ -25,10 +25,8 @@ void OnroadWindowSP::updateState(const UIStateSP &s) {
|
||||
|
||||
void OnroadWindowSP::mousePressEvent(QMouseEvent *e) {
|
||||
OnroadWindow::mousePressEvent(e);
|
||||
uiStateSP()->reset_onroad_sleep_timer();
|
||||
}
|
||||
|
||||
void OnroadWindowSP::offroadTransition(bool offroad) {
|
||||
OnroadWindow::offroadTransition(offroad);
|
||||
uiStateSP()->reset_onroad_sleep_timer();
|
||||
}
|
||||
|
||||
@@ -122,3 +122,7 @@ std::optional<cereal::Event::Reader> loadCerealEvent(Params& params, const std::
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
bool hasIntelligentCruiseButtonManagement(const cereal::CarParamsSP::Reader &car_params_sp) {
|
||||
return car_params_sp.getIntelligentCruiseButtonManagementAvailable() && Params().getBool("IntelligentCruiseButtonManagement");
|
||||
}
|
||||
|
||||
@@ -23,3 +23,4 @@ std::optional<QString> getParamIgnoringDefault(const std::string ¶m_name, co
|
||||
QMap<QString, QVariantMap> loadPlatformList();
|
||||
QStringList searchFromList(const QString &query, const QStringList &list);
|
||||
std::optional<cereal::Event::Reader> loadCerealEvent(Params& params, const std::string& _param);
|
||||
bool hasIntelligentCruiseButtonManagement(const cereal::CarParamsSP::Reader &car_params_sp);
|
||||
|
||||
@@ -390,7 +390,7 @@ class ButtonParamControlSP : public MultiButtonControlSP {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ButtonParamControlSP(const QString ¶m, const QString &title, const QString &desc, const QString &icon,
|
||||
const std::vector<QString> &button_texts, const int minimum_button_width = 225, const bool inline_layout = false, bool advancedControl = false) : MultiButtonControlSP(title, desc, icon,
|
||||
const std::vector<QString> &button_texts, const int minimum_button_width = 380, const bool inline_layout = false, bool advancedControl = false) : MultiButtonControlSP(title, desc, icon,
|
||||
button_texts, minimum_button_width, inline_layout, advancedControl) {
|
||||
key = param.toStdString();
|
||||
int value = atoi(params.get(key).c_str());
|
||||
|
||||
@@ -29,7 +29,7 @@ UIStateSP::UIStateSP(QObject *parent) : UIState(parent) {
|
||||
"wideRoadCameraState", "managerState", "selfdriveState", "longitudinalPlan",
|
||||
"modelManagerSP", "selfdriveStateSP", "longitudinalPlanSP", "backupManagerSP",
|
||||
"carControl", "gpsLocationExternal", "gpsLocation", "liveTorqueParameters",
|
||||
"carStateSP", "liveParameters", "liveMapDataSP"
|
||||
"carStateSP", "liveParameters", "liveMapDataSP", "carParamsSP"
|
||||
});
|
||||
|
||||
// update timer
|
||||
@@ -71,17 +71,22 @@ void ui_update_params_sp(UIStateSP *s) {
|
||||
s->scene.onroadScreenOffBrightness = std::atoi(params.get("OnroadScreenOffBrightness").c_str());
|
||||
s->scene.onroadScreenOffControl = params.getBool("OnroadScreenOffControl");
|
||||
s->scene.onroadScreenOffTimerParam = std::atoi(params.get("OnroadScreenOffTimer").c_str());
|
||||
s->reset_onroad_sleep_timer();
|
||||
|
||||
s->scene.turn_signals = params.getBool("ShowTurnSignals");
|
||||
s->scene.chevron_info = std::atoi(params.get("ChevronInfo").c_str());
|
||||
s->scene.blindspot_ui = params.getBool("BlindSpot");
|
||||
s->scene.rainbow_mode = params.getBool("RainbowMode");
|
||||
}
|
||||
|
||||
void UIStateSP::reset_onroad_sleep_timer() {
|
||||
if (scene.onroadScreenOffTimerParam >= 0 and scene.onroadScreenOffControl) {
|
||||
scene.onroadScreenOffTimer = scene.onroadScreenOffTimerParam * UI_FREQ;
|
||||
} else {
|
||||
void UIStateSP::reset_onroad_sleep_timer(OnroadTimerStatusToggle toggleTimerStatus) {
|
||||
// Toggling from active state to inactive
|
||||
if (toggleTimerStatus == OnroadTimerStatusToggle::PAUSE and scene.onroadScreenOffTimer != -1) {
|
||||
scene.onroadScreenOffTimer = -1;
|
||||
}
|
||||
// Toggling from a previously inactive state or resetting an active timer
|
||||
else if ((scene.onroadScreenOffTimerParam >= 0 and scene.onroadScreenOffControl and scene.onroadScreenOffTimer != -1) or toggleTimerStatus == OnroadTimerStatusToggle::RESUME) {
|
||||
scene.onroadScreenOffTimer = scene.onroadScreenOffTimerParam * UI_FREQ;
|
||||
}
|
||||
}
|
||||
|
||||
DeviceSP::DeviceSP(QObject *parent) : Device(parent) {
|
||||
|
||||
@@ -15,6 +15,12 @@
|
||||
#include "selfdrive/ui/ui.h"
|
||||
#include "selfdrive/ui/qt/util.h"
|
||||
|
||||
enum OnroadTimerStatusToggle {
|
||||
NONE,
|
||||
PAUSE,
|
||||
RESUME
|
||||
};
|
||||
|
||||
class UIStateSP : public UIState {
|
||||
Q_OBJECT
|
||||
|
||||
@@ -61,7 +67,7 @@ public:
|
||||
return user.user_id.toLower() != "unregisteredsponsor" && user.user_id.toLower() != "temporarysponsor";
|
||||
});
|
||||
}
|
||||
void reset_onroad_sleep_timer();
|
||||
void reset_onroad_sleep_timer(OnroadTimerStatusToggle toggleTimerStatus = OnroadTimerStatusToggle::NONE);
|
||||
|
||||
signals:
|
||||
void sunnylinkRoleChanged(bool subscriber);
|
||||
|
||||
@@ -18,4 +18,7 @@ typedef struct UISceneSP : UIScene {
|
||||
bool trueVEgoUI;
|
||||
bool hideVEgoUI;
|
||||
bool turn_signals = false;
|
||||
int chevron_info;
|
||||
bool blindspot_ui;
|
||||
bool rainbow_mode;
|
||||
} UISceneSP;
|
||||
|
||||
@@ -18,7 +18,7 @@ if __name__ == "__main__":
|
||||
while True:
|
||||
print("setting alert update")
|
||||
params.put_bool("UpdateAvailable", True)
|
||||
r = open(os.path.join(BASEDIR, "RELEASES.md")).read()
|
||||
r = open(os.path.join(BASEDIR, "CHANGELOG.md")).read()
|
||||
r = r[:r.find('\n\n')] # Slice latest release notes
|
||||
params.put("UpdaterNewReleaseNotes", r + "\n")
|
||||
|
||||
|
||||
@@ -188,7 +188,7 @@ def setup_offroad_alert(click, pm: PubMaster, scroll=None):
|
||||
|
||||
def setup_update_available(click, pm: PubMaster, scroll=None):
|
||||
Params().put_bool("UpdateAvailable", True)
|
||||
release_notes_path = os.path.join(BASEDIR, "RELEASES.md")
|
||||
release_notes_path = os.path.join(BASEDIR, "CHANGELOG.md")
|
||||
with open(release_notes_path) as file:
|
||||
release_notes = file.read().split('\n\n', 1)[0]
|
||||
Params().put("UpdaterNewReleaseNotes", release_notes + "\n")
|
||||
|
||||
@@ -144,11 +144,11 @@ Please use caution when using this feature. Only use the blinker when traffic an
|
||||
</message>
|
||||
<message>
|
||||
<source>Global Brightness</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>전역 밝기</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Overrides the brightness of the device. This applies to both onroad and offroad screens. </source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>기기의 밝기를 강제로 설정합니다. 이 설정은 주행 중 화면과 비주행 화면(설정 등) 모두에 적용됩니다. </translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -256,11 +256,11 @@ This only toggles the visibility of the controls; it does not toggle the actual
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable Copyparty service</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Copyparty 서비스 사용</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Copyparty is a very capable file server, you can use it to download your routes, view your logs and even make some edits on some files from your browser. Requires you to connect to your comma locally via it's IP.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Copyparty는 매우 유능한 파일 서버로, 이를 통해 주행 기록을 다운로드하고, 로그 파일을 확인하며, 심지어 브라우저에서 일부 파일을 편집할 수도 있습니다. 이 기능을 사용하려면 comma 기기의 로컬 IP를 통해 접속해야 합니다.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -538,32 +538,32 @@ Steering lag calibration is complete.</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Onroad Uploads</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>주행 중 업로드</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable Always Offroad</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>항상 오프로드 사용</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DisplayPanel</name>
|
||||
<message>
|
||||
<source>Onroad Screen: Reduced Brightness</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>주행 중 화면: 밝기 감소</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Turn off device screen or reduce brightness after driving starts. It automatically brightens again when screen is touched or a visible alert is displayed.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>운전이 시작된 후 기기 화면을 끄거나 밝기를 줄입니다. 화면을 터치하거나 시각적 알림이 표시되면 자동으로 다시 밝아집니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Interactivity Timeout</source>
|
||||
<translation type="unfinished">상호작용 타임아웃</translation>
|
||||
<translation>상호작용 타임아웃</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Apply a custom timeout for settings UI.
|
||||
This is the time after which settings UI closes automatically if user is not interacting with the screen.</source>
|
||||
<translation type="unfinished">설정 화면에 사용자 지정 타임아웃을 적용하세요.
|
||||
사용자가 화면과 상호작용하지 않으면 설정 화면이 자동으로 닫히는 시간입니다.</translation>
|
||||
<translation>설정 UI에 사용자 지정 타임아웃을 적용하세요.
|
||||
이는 사용자가 화면과 상호 작용하지 않으면 설정 UI가 자동으로 닫히는 시간입니다.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -630,55 +630,55 @@ This is the time after which settings UI closes automatically if user is not int
|
||||
<name>ExternalStorageControl</name>
|
||||
<message>
|
||||
<source>External Storage</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>외부 저장소</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Extend your comma device's storage by inserting a USB drive into the aux port.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>USB 드라이브를 AUX 포트에 삽입하여 comma 기기의 저장 공간을 확장합니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>CHECK</source>
|
||||
<translation type="unfinished">확인</translation>
|
||||
<translation>확인</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MOUNT</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>마운트</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>UNMOUNT</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>마운트 해제</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>FORMAT</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>포맷</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Are you sure you want to format this drive? This will erase all data.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>이 드라이브를 포맷하시겠습니까? 이 작업은 모든 데이터를 지웁니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Format</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>포맷</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>formatting</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>포맷 중</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>insert drive</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>드라이브 삽입</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>needs format</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>포맷 필요</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>mounting</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>마운트 중</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>unmounting</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>마운트 해제 중</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -737,57 +737,58 @@ Firehose 모드를 사용하면 훈련 데이터 업로드를 극대화하여 op
|
||||
<name>HudRendererSP</name>
|
||||
<message>
|
||||
<source>km/h</source>
|
||||
<translation type="unfinished">km/h</translation>
|
||||
<translation>km/h</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>mph</source>
|
||||
<translation type="unfinished">mph</translation>
|
||||
<translation>mph</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>GREEN
|
||||
LIGHT</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>녹색 신호</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>LEAD VEHICLE
|
||||
DEPARTING</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>전방 차량이
|
||||
출발하였습니다</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>SPEED</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>SPEED</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>LIMIT</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>LIMIT</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Near</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>근처</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>km</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>km</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>m</source>
|
||||
<translation type="unfinished">분</translation>
|
||||
<translation>m</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>mi</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>mi</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>ft</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>ft</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>AHEAD</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>전방</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>MAX</source>
|
||||
<translation type="unfinished">최대</translation>
|
||||
<translation>최대</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -905,15 +906,15 @@ DEPARTING</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enforce Torque Lateral Control</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>토크 조향 제어 강제 적용</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable this to enforce sunnypilot to steer with Torque lateral control.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>sunnypilot이 토크 조향 제어를 통해 조향하도록 강제하려면 이 기능을 활성화하세요.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Customize Params</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>매개변수 사용자 지정</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -940,31 +941,31 @@ DEPARTING</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Intelligent Cruise Button Management (ICBM) (Alpha)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>지능형 크루즈 버튼 관리 (ICBM) (알파)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>When enabled, sunnypilot will attempt to manage the built-in cruise control buttons by emulating button presses for limited longitudinal control.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>활성화하면, sunnypilot은 제한적인 가감속 제어를 위해 버튼 조작을 재현하여 내장 크루즈 컨트롤 버튼을 관리하려고 시도합니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Smart Cruise Control - Vision</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>스마트 크루즈 컨트롤 - 비전</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use vision path predictions to estimate the appropriate speed to drive through turns ahead.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>전방 커브 구간을 통과하기 위한 적절한 속도를 예측하기 위해 시각 경로 예측 기능을 사용합니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Smart Cruise Control - Map</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>스마트 크루즈 컨트롤 - 지도</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use map data to estimate the appropriate speed to drive through turns ahead.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>전방 커브 구간을 통과하기 위한 적절한 속도를 예측하기 위해 지도 데이터를 사용합니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Speed Limit</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>속도 제한</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -1195,47 +1196,47 @@ The default software delay value is 0.2</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Refresh Model List</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>모델 목록 새로고침</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>REFRESH</source>
|
||||
<translation type="unfinished">새로 고침</translation>
|
||||
<translation>새로고침</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Fetching Latest Models</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>최신 모델 불러오는 중</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable this for the car to learn and adapt its steering response time. Disable to use a fixed steering response time. Keeping this on provides the stock openpilot experience.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>이 기능을 켜면 차량이 스스로 조향 응답 속도를 학습하고 맞춥니다. 끄면 고정된 조향 응답 속도를 사용합니다. 이 기능을 켜두는 것이 기본 openpilot 경험을 제공합니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Live Steer Delay:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>실시간 조향 지연 시간:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Actuator Delay:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>액츄에이터 지연 시간:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Software Delay:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>소프트웨어 지연 시간:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Total Delay:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>전체 지연 시간:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use Lane Turn Desires</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>차로 회전 의도 사용</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Adjust Lane Turn Speed</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>차로 회전 속도 조정</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Set the maximum speed for lane turn desires. Default is 19 %1.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>차로 회전 의도의 최대 속도를 설정합니다. 기본값은 19 %1 입니다.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -1400,7 +1401,7 @@ The default software delay value is 0.2</source>
|
||||
</message>
|
||||
<message>
|
||||
<source><b>Unsupported branch detected</b> - The current version of <b><u>%1</u></b> branch is no longer supported on the comma three. Please go to <b>[Device > Software]</b> and install a supported branch with <b><u>-tici</u></b> in the branch name for the comma three.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation><b>지원되지 않는 브랜치 감지</b> - 현재 사용 중인 <b><u>%1</u></b> 브랜치는 comma three 기기에서 더 이상 지원되지 않습니다. <b>[기기 > 소프트웨어]</b>로 이동하여 브랜치 이름에 <b><u>-tici</u></b> 가 포함된 comma three용 브랜치로 설치하세요.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -1777,59 +1778,6 @@ Warning: You are on a metered connection!</source>
|
||||
<source>sunnypilot</source>
|
||||
<translation>sunnypilot</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>None</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Fixed</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Percent</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Car
|
||||
Only</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Map
|
||||
Only</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Car
|
||||
First</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Map
|
||||
First</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Combined
|
||||
Data</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Off</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Information</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Warning</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Assist</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SettingsWindow</name>
|
||||
@@ -1926,7 +1874,7 @@ Data</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Display</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>화면</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -2168,78 +2116,134 @@ Data</source>
|
||||
<name>SpeedLimitPolicy</name>
|
||||
<message>
|
||||
<source>Back</source>
|
||||
<translation type="unfinished">뒤로</translation>
|
||||
<translation>뒤로</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Speed Limit Source</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>속도 제한 출처</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>⦿ Car Only: Use Speed Limit data only from Car</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>⦿ 차량만: 차량에서 제공되는 속도 제한 데이터만 사용</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>⦿ Map Only: Use Speed Limit data only from OpenStreetMaps</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>⦿ 지도만: OpenStreetMaps에서 제공되는 속도 제한 데이터만 사용</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>⦿ Car First: Use Speed Limit data from Car if available, else use from OpenStreetMaps</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>⦿ 차량 우선: 사용 가능한 경우 차량의 속도 제한 데이터를 사용하고, 그렇지 않은 경우 OpenStreetMaps의 데이터 사용</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>⦿ Map First: Use Speed Limit data from OpenStreetMaps if available, else use from Car</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>⦿ 지도 우선: 사용 가능한 경우 OpenStreetMaps의 속도 제한 데이터를 사용하고, 그렇지 않은 경우 차량의 데이터 사용</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>⦿ Combined: Use combined Speed Limit data from Car & OpenStreetMaps</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>⦿ 결합: 차량 및 OpenStreetMaps의 속도 제한 결합 데이터 사용</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Car
|
||||
Only</source>
|
||||
<translation>차량만</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Map
|
||||
Only</source>
|
||||
<translation>지도만</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Car
|
||||
First</source>
|
||||
<translation>차량
|
||||
우선</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Map
|
||||
First</source>
|
||||
<translation>지도
|
||||
우선</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Combined
|
||||
Data</source>
|
||||
<translation>결합
|
||||
데이터</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>SpeedLimitSettings</name>
|
||||
<message>
|
||||
<source>Back</source>
|
||||
<translation type="unfinished">뒤로</translation>
|
||||
<translation>뒤로</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Speed Limit</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>속도 제한</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Customize Source</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>출처 사용자 지정</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Speed Limit Offset</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>속도 제한 오프셋</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>⦿ None: No Offset</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>⦿ 없음: 오프셋 없음</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>⦿ Fixed: Adds a fixed offset [Speed Limit + Offset]</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>⦿ 고정: 고정 오프셋을 추가합니다. [제한 속도 + 오프셋]</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>⦿ Percent: Adds a percent offset [Speed Limit + (Offset % Speed Limit)]</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>⦿ 비율: 백분율 오프셋을 추가합니다. [제한 속도 + (오프셋 % 제한 속도)]</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>⦿ Off: Disables the Speed Limit functions.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>⦿ 끄기: 속도 제한 기능을 비활성화합니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>⦿ Information: Displays the current road's speed limit.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>⦿ 정보: 현재 도로의 제한 속도를 표시합니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>⦿ Warning: Provides a warning when exceeding the current road's speed limit.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>⦿ 경고: 현재 도로의 제한 속도를 초과할 경우 경고합니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>⦿ Assist: Adjusts the vehicle's cruise speed based on the current road's speed limit when operating the +/- buttons.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>⦿ 보조: +/- 버튼을 조작할 때 현재 도로의 제한 속도를 기준으로 차량의 크루즈 속도를 조정합니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>None</source>
|
||||
<translation>없음</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Fixed</source>
|
||||
<translation>고정</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Percent</source>
|
||||
<translation>비율</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Off</source>
|
||||
<translation>끄기</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Information</source>
|
||||
<translation>정보</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Warning</source>
|
||||
<translation>경고</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Assist</source>
|
||||
<translation>보조</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -2412,27 +2416,27 @@ Data</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable sunnylink uploader to allow sunnypilot to upload your driving data to sunnypilot servers. (only for highest tiers, and does NOT bring ANY benefit to you. We are just testing data volume.)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>sunnylink 업로더를 활성화하여 sunnypilot이 귀하의 주행 데이터를 sunnypilot 서버로 업로드하도록 허용합니다. (가장 높은 등급에서만 해당하며, 사용자에게는 어떠한 이점도 제공하지 않습니다. 저희는 그냥 데이터 용량을 테스트하는 중입니다.)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>[Don't use] Enable sunnylink uploader</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>[사용하지 마세요] sunnylink 업로더 사용</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>🚀 sunnylink 🚀</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>🚀 sunnylink 🚀</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>For secure backup, restore, and remote configuration</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>안전한 백업, 복원 및 원격 설정을 위해</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Sponsorship isn't required for basic backup/restore</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>기본적인 백업/복원은 후원이 필요하지 않습니다</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Click the sponsor button for more details</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>후원 버튼을 눌러 더 자세한 정보를 확인하세요</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -2628,54 +2632,54 @@ Data</source>
|
||||
<name>TorqueLateralControlCustomParams</name>
|
||||
<message>
|
||||
<source>Manual Real-Time Tuning</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>실시간 수동 튜닝</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enforces the torque lateral controller to use the fixed values instead of the learned values from Self-Tune. Enabling this toggle overrides Self-Tune values.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>토크 조향 제어기가 셀프 튜닝에서 학습된 값 대신 고정된 값을 사용하도록 강제합니다. 이 토글을 활성화하면 셀프 튜닝 값이 무시됩니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Lateral Acceleration Factor</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>조향 가속 계수</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Friction</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>마찰</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Real-time and Offline</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>실시간 및 오프라인</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Offline Only</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>오프라인만</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>TorqueLateralControlSettings</name>
|
||||
<message>
|
||||
<source>Self-Tune</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>셀프 튜닝</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enables self-tune for Torque lateral control for platforms that do not use Torque lateral control by default.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>기본적으로 토크 조향 제어를 사용하지 않는 플랫폼에 대해 토크 조향 제어의 셀프 튜닝 기능을 활성화합니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Less Restrict Settings for Self-Tune (Beta)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>셀프 튜닝 제한 완화 설정 (베타)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Less strict settings when using Self-Tune. This allows torqued to be more forgiving when learning values.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>셀프 튜닝 사용 시 덜 엄격한 설정을 사용합니다. 이는 학습 값에 대해 torqued가 더 관대하도록 허용합니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable Custom Tuning</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>사용자 지정 튜닝 사용</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enables custom tuning for Torque lateral control. Modifying Lateral Acceleration Factor and Friction below will override the offline values indicated in the YAML files within "opendbc/car/torque_data". The values will also be used live when "Manual Real-Time Tuning" toggle is enabled.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>토크 조향 제어에 대한 사용자 지정 튜닝을 활성화합니다. 아래에서 조향 가속 계수 및 마찰을 수정하면 "opendbc/car/torque_data" 내의 YAML 파일에 명시된 오프라인 값이 무시됩니다. 이 값들은 "실시간 수동 튜닝" 토글이 활성화되면 실시간으로도 사용됩니다.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -2690,7 +2694,7 @@ Data</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Favorites</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>즐겨찾기</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@@ -2737,88 +2741,89 @@ Data</source>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable Tesla Rainbow Mode</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>테슬라 무지개 모드 사용</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>A beautiful rainbow effect on the path the model wants to take.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>모델이 가고자 하는 경로에 아름다운 무지개 효과를 추가합니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>It</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>이는 운전에</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>does not</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>어떠한</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>affect driving in any way.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>영향도 미치지 않습니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable Standstill Timer</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>정차 타이머 사용</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Show a timer on the HUD when the car is at a standstill.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>차량이 정차 상태일 때 HUD에 타이머를 표시합니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Display Road Name</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>도로 이름 표시</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Displays the name of the road the car is traveling on. The OpenStreetMap database of the location must be downloaded from the OSM panel to fetch the road name.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>차량이 주행 중인 도로의 이름을 표시합니다. 도로 이름을 가져오려면 OSM 패널에서 해당 위치의 OpenStreetMap 데이터베이스를 다운로드해야 합니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Green Traffic Light Alert (Beta)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>녹색 신호 감지 알림 (베타)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>A chime and on-screen alert will play when the traffic light you are waiting for turns green and you have no vehicle in front of you.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>앞 신호등이 초록 신호로 바뀌고 전방에 차량이 없을 때, 알림음과 화면 경고가 표시됩니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Note: This chime is only designed as a notification. It is the driver's responsibility to observe their environment and make decisions accordingly.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>참고: 이 알림음은 단순한 알림 목적으로만 설계되었습니다. 주변 환경을 살피고 그에 따라 결정하는 것은 운전자의 책임입니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Lead Departure Alert (Beta)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>전방 차량 출발 알림 (베타)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>A chime and on-screen alert will play when you are stopped, and the vehicle in front of you start moving.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>당신이 정차 중이고 전방 차량이 움직이기 시작할 때, 알림음과 화면 경고가 표시됩니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Speedometer: Always Display True Speed</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>속도계: 항상 실제 속도 표시</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Always display the true vehicle current speed from wheel speed sensors.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>항상 휠 스피드 센서에서 얻은 실제 차량 속도를 표시합니다.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Speedometer: Hide from Onroad Screen</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>속도계: 주행 화면에서 숨기기</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Right</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>오른쪽</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Right &&
|
||||
Bottom</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>오른쪽 &&
|
||||
아래</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Developer UI</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>개발자 UI</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Display real-time parameters and metrics from various sources.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>다양한 출처의 실시간 매개변수 및 측정 지표를 표시합니다.</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
||||
@@ -5,6 +5,7 @@ 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 enum import IntEnum
|
||||
import hashlib
|
||||
|
||||
PARAMS_UPDATE_PERIOD = 3 # seconds
|
||||
@@ -16,3 +17,23 @@ def get_file_hash(path: str) -> str:
|
||||
for byte_block in iter(lambda: f.read(4096), b""):
|
||||
sha256_hash.update(byte_block)
|
||||
return sha256_hash.hexdigest()
|
||||
|
||||
|
||||
class IntEnumBase(IntEnum):
|
||||
@classmethod
|
||||
def min(cls):
|
||||
return min(cls)
|
||||
|
||||
@classmethod
|
||||
def max(cls):
|
||||
return max(cls)
|
||||
|
||||
|
||||
def get_sanitize_int_param(key: str, min_val: int, max_val: int, params) -> int:
|
||||
val: int = params.get(key, return_default=True)
|
||||
clipped_val = max(min_val, min(max_val, val))
|
||||
|
||||
if clipped_val != val:
|
||||
params.put(key, clipped_val)
|
||||
|
||||
return clipped_val
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
#define SUNNYPILOT_VERSION "2025.002.000"
|
||||
@@ -8,8 +8,12 @@ from abc import abstractmethod, ABC
|
||||
|
||||
import cereal.messaging as messaging
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.constants import CV
|
||||
from openpilot.selfdrive.car.cruise import V_CRUISE_UNSET
|
||||
from openpilot.sunnypilot.navd.helpers import coordinate_from_param
|
||||
|
||||
MAX_SPEED_LIMIT = V_CRUISE_UNSET * CV.KPH_TO_MS
|
||||
|
||||
|
||||
class BaseMapData(ABC):
|
||||
def __init__(self):
|
||||
@@ -46,9 +50,9 @@ class BaseMapData(ABC):
|
||||
mapd_sp_send.valid = self.sm['liveLocationKalman'].gpsOK
|
||||
live_map_data = mapd_sp_send.liveMapDataSP
|
||||
|
||||
live_map_data.speedLimitValid = bool(speed_limit > 0)
|
||||
live_map_data.speedLimitValid = bool(MAX_SPEED_LIMIT > speed_limit > 0)
|
||||
live_map_data.speedLimit = speed_limit
|
||||
live_map_data.speedLimitAheadValid = bool(next_speed_limit > 0)
|
||||
live_map_data.speedLimitAheadValid = bool(MAX_SPEED_LIMIT > next_speed_limit > 0)
|
||||
live_map_data.speedLimitAhead = next_speed_limit
|
||||
live_map_data.speedLimitAheadDistance = next_speed_limit_distance
|
||||
live_map_data.roadName = self.get_current_road_name()
|
||||
|
||||
@@ -116,7 +116,7 @@ class ModelCache:
|
||||
|
||||
class ModelFetcher:
|
||||
"""Handles fetching and caching of model data from remote source"""
|
||||
MODEL_URL = "https://docs.sunnypilot.ai/driving_models_v7.json"
|
||||
MODEL_URL = "https://docs.sunnypilot.ai/driving_models_v8.json"
|
||||
|
||||
def __init__(self, params: Params):
|
||||
self.params = params
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# Navigation
|
||||
|
||||
Navigation daemon with Mapbox integration for semi-offline navigation. This module handles route planning, geocoding, and turn-by-turn instructions to support autonomous driving features.
|
||||
|
||||
- `navigation_helpers/`: Mapbox API integration and navigation instructions processing.
|
||||
- `navigationd`: Navigation service which uses mapbox integration to generate a route and keep it up to date. This service runs at three hz, using keep time to ensure the while loop only updates three times a second rather than every time sm updates, which in this case would be twenty hz (LLK).
|
||||
@@ -0,0 +1,16 @@
|
||||
"""
|
||||
Copyright (c) 2021-, James Vecellio, 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.
|
||||
"""
|
||||
|
||||
|
||||
class NAV_CV:
|
||||
""" These distances are expected in meters format and convert to desired format """
|
||||
SHORT_DISTANCE_METERS = 200.0
|
||||
QUARTER_MILE = 402.336
|
||||
POINT_ONE_MILE = 160.9344
|
||||
METERS_TO_KILO = 1000 # divide n by this
|
||||
METERS_TO_MILE = 1609.344 # divide n by this
|
||||
METERS_TO_FEET = 3.280839895 # multiply n by this
|
||||
@@ -0,0 +1,83 @@
|
||||
"""
|
||||
Copyright (c) 2021-, James Vecellio, 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 custom, messaging
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.realtime import DT_MDL
|
||||
|
||||
from sunnypilot.navd.constants import NAV_CV
|
||||
|
||||
|
||||
class EventBuilder:
|
||||
def __init__(self):
|
||||
self._counter: int = -1
|
||||
self._enabled: bool = False
|
||||
self._params = Params()
|
||||
|
||||
@staticmethod
|
||||
def _build_banner_message(metric: bool, nav_msg):
|
||||
m = nav_msg.allManeuvers[1] if len(nav_msg.allManeuvers) > 1 else nav_msg.allManeuvers[0]
|
||||
banner = m.instruction
|
||||
|
||||
if metric:
|
||||
dist = f'{m.distance / NAV_CV.METERS_TO_KILO:.1f} km,'
|
||||
if m.distance < NAV_CV.SHORT_DISTANCE_METERS:
|
||||
dist = f'{int(m.distance)}m,'
|
||||
else:
|
||||
dist = f'{m.distance / NAV_CV.METERS_TO_MILE:.1f} mi,'
|
||||
if m.distance < NAV_CV.QUARTER_MILE:
|
||||
dist = f'{round((m.distance * NAV_CV.METERS_TO_FEET) / 50) * 50}ft,'
|
||||
|
||||
if m.type == 'arrive' or m.type == 'depart' or 'Your destination' in banner:
|
||||
base_msg = banner
|
||||
elif banner.startswith(('Continue', 'Drive', 'Head')):
|
||||
base_msg = f'For {dist} {banner}'
|
||||
elif 'Turn' in banner or 'Take' in banner or 'Make' in banner:
|
||||
base_msg = f'In {dist} {banner}'
|
||||
else:
|
||||
base_msg = f'For {dist} Continue on {banner}'
|
||||
|
||||
return base_msg
|
||||
|
||||
@staticmethod
|
||||
def _get_turning_message(upcoming_turn):
|
||||
turn_messages = {
|
||||
'left': 'Turning Left, Make sure to nudge the wheel',
|
||||
'right': 'Turning Right, Make sure to nudge the wheel',
|
||||
'slightLeft': 'Keeping Left',
|
||||
'slightRight': 'Keeping Right',
|
||||
'sharpLeft': 'Sharp Left Turn',
|
||||
'sharpRight': 'Sharp Right Turn',
|
||||
'straight': 'Continuing Straight',
|
||||
'uturn': 'U-Turn Ahead',
|
||||
}
|
||||
return turn_messages.get(upcoming_turn, f"Upcoming {upcoming_turn.replace('_', ' ').title()}")
|
||||
|
||||
@staticmethod
|
||||
def build_navigation_events(sm: messaging.SubMaster, metric=True) -> list:
|
||||
nav_msg = sm['navigationd']
|
||||
if not nav_msg.valid:
|
||||
return []
|
||||
|
||||
banner_message = EventBuilder._build_banner_message(metric, nav_msg)
|
||||
|
||||
if nav_msg.upcomingTurn != 'none':
|
||||
banner_message = EventBuilder._get_turning_message(nav_msg.upcomingTurn)
|
||||
|
||||
return [{
|
||||
'name': custom.OnroadEventSP.EventName.navigationBanner,
|
||||
'message': banner_message,
|
||||
}]
|
||||
|
||||
def update(self, sm: messaging.SubMaster) -> list:
|
||||
self._counter += 1
|
||||
if self._counter % int(3.0 / DT_MDL) == 0:
|
||||
self._enabled = self._params.get("NavEvents", return_default=True)
|
||||
|
||||
if self._enabled:
|
||||
return self.build_navigation_events(sm)
|
||||
else:
|
||||
return []
|
||||
@@ -126,6 +126,8 @@ def string_to_direction(direction: str) -> str:
|
||||
if d in direction:
|
||||
if 'slight' in direction and d in MODIFIABLE_DIRECTIONS:
|
||||
return 'slight' + d.capitalize()
|
||||
elif 'sharp' in direction and d in MODIFIABLE_DIRECTIONS:
|
||||
return 'sharp' + d.capitalize()
|
||||
return d
|
||||
return 'none'
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
"""
|
||||
Copyright (c) 2021-, James Vecellio, 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 cereal.messaging as messaging
|
||||
from cereal import car, log
|
||||
from openpilot.common.constants import CV
|
||||
from openpilot.common.params import Params
|
||||
|
||||
|
||||
class NavigationDesires:
|
||||
def __init__(self):
|
||||
self.sm = messaging.SubMaster(['navigationd'])
|
||||
self.desire = log.Desire.none
|
||||
self._turn_speed_limit = 20 * CV.MPH_TO_MS
|
||||
self._params = Params()
|
||||
self.param_counter = -1
|
||||
self.nav_allowed: bool = False
|
||||
|
||||
def update_params(self):
|
||||
self.param_counter += 1
|
||||
if self.param_counter % 60 == 0: # every 3 seconds at 20hz
|
||||
self.nav_allowed = self._params.get("NavDesiresAllowed", return_default=True)
|
||||
|
||||
def update(self, CS: car.CarState, lateral_active: bool) -> log.Desire:
|
||||
self.update_params()
|
||||
self.sm.update(0)
|
||||
nav_msg = self.sm['navigationd']
|
||||
self.desire = log.Desire.none
|
||||
if self.nav_allowed and nav_msg.valid and lateral_active:
|
||||
upcoming = nav_msg.upcomingTurn
|
||||
if upcoming == 'slightLeft' and (not CS.leftBlindspot or CS.vEgo < self._turn_speed_limit):
|
||||
self.desire = log.Desire.keepLeft
|
||||
elif upcoming == 'slightRight' and (not CS.rightBlindspot or CS.vEgo < self._turn_speed_limit):
|
||||
self.desire = log.Desire.keepRight
|
||||
elif (upcoming == 'left' and CS.steeringPressed and CS.steeringTorque > 0 and not CS.rightBlinker
|
||||
and not CS.leftBlindspot and CS.vEgo < self._turn_speed_limit):
|
||||
self.desire = log.Desire.turnLeft
|
||||
elif (upcoming == 'right' and CS.steeringPressed and CS.steeringTorque < 0 and not CS.leftBlinker
|
||||
and not CS.rightBlindspot and CS.vEgo < self._turn_speed_limit):
|
||||
self.desire = log.Desire.turnRight
|
||||
return self.desire
|
||||
@@ -0,0 +1,96 @@
|
||||
"""
|
||||
Copyright (c) 2021-, James Vecellio, 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 pytest
|
||||
import types
|
||||
|
||||
from cereal import log
|
||||
from openpilot.common.params import Params
|
||||
|
||||
from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper
|
||||
from openpilot.sunnypilot.navd.navigation_desires.navigation_desires import NavigationDesires
|
||||
|
||||
|
||||
def make_car(vEgo=0, leftBlinker=False, rightBlinker=False, leftBlindspot=False, rightBlindspot=False, steeringPressed=False, steeringTorque=0):
|
||||
return types.SimpleNamespace(
|
||||
vEgo=vEgo, leftBlinker=leftBlinker, rightBlinker=rightBlinker,
|
||||
leftBlindspot=leftBlindspot, rightBlindspot=rightBlindspot,
|
||||
steeringPressed=steeringPressed, steeringTorque=steeringTorque
|
||||
)
|
||||
|
||||
NAVIGATION_PARAMS: list[tuple] = [
|
||||
('slightLeft', make_car(), log.Desire.keepLeft),
|
||||
('slightRight', make_car(), log.Desire.keepRight),
|
||||
('slightLeft', make_car(vEgo=9, leftBlindspot=True), log.Desire.none),
|
||||
('slightRight', make_car(vEgo=9, rightBlindspot=True), log.Desire.none),
|
||||
('left', make_car(vEgo=5, leftBlinker=True, rightBlinker=False, leftBlindspot=False, steeringPressed=True, steeringTorque=1), log.Desire.turnLeft),
|
||||
('left', make_car(vEgo=5, leftBlinker=False, rightBlinker=True), log.Desire.none),
|
||||
('right', make_car(vEgo=6, rightBlinker=True, leftBlindspot=False, steeringPressed=True, steeringTorque=-1), log.Desire.turnRight),
|
||||
('right', make_car(vEgo=6, rightBlinker=True, rightBlindspot=True), log.Desire.none),
|
||||
('left', make_car(vEgo=9, leftBlinker=True), log.Desire.none),
|
||||
]
|
||||
|
||||
INTEGRATION_PARAMS: list[tuple] = [(carstate, upcoming, log.Desire.none, expected) for upcoming, carstate, expected in NAVIGATION_PARAMS] + [
|
||||
(make_car(vEgo=9, leftBlinker=True, steeringPressed=True, steeringTorque=1), 'slightLeft', log.Desire.turnLeft, log.Desire.keepLeft),
|
||||
(make_car(vEgo=9, rightBlinker=True, steeringPressed=True, steeringTorque=-1), 'slightRight', log.Desire.turnRight, log.Desire.keepRight),
|
||||
(make_car(vEgo=9, leftBlinker=True), 'slightLeft', log.Desire.laneChangeLeft, log.Desire.laneChangeLeft),
|
||||
(make_car(vEgo=9, rightBlinker=True), 'slightRight', log.Desire.laneChangeRight, log.Desire.laneChangeRight),
|
||||
(make_car(vEgo=9), 'none', log.Desire.none, log.Desire.none),
|
||||
]
|
||||
|
||||
def make_nav_msg(valid=False, upcoming='none'):
|
||||
return types.SimpleNamespace(valid=valid, upcomingTurn=upcoming)
|
||||
|
||||
def params_setter(allowed: bool):
|
||||
params = Params()
|
||||
params.put("NavDesiresAllowed", allowed)
|
||||
|
||||
@pytest.fixture
|
||||
def mock_submaster(mocker):
|
||||
mock_sm = mocker.patch('cereal.messaging.SubMaster')
|
||||
mock_sm_instance = mocker.Mock()
|
||||
mock_sm.return_value = mock_sm_instance
|
||||
mock_sm_instance.__getitem__ = mocker.Mock(return_value=make_nav_msg(valid=False))
|
||||
params_setter(True)
|
||||
return mock_sm_instance
|
||||
|
||||
@pytest.mark.parametrize("upcoming, carstate, expected", NAVIGATION_PARAMS)
|
||||
def test_navigation_desires_update(mock_submaster, mocker, upcoming, carstate, expected):
|
||||
nav_desires = NavigationDesires()
|
||||
nav_desires.sm.__getitem__ = mocker.Mock(return_value=make_nav_msg(valid=True, upcoming=upcoming))
|
||||
nav_desires.update(carstate, True)
|
||||
assert nav_desires.desire == expected
|
||||
|
||||
@pytest.mark.parametrize("msg_valid,lateral_active", [(False, True), (True, False)])
|
||||
def test_invalid_or_inactive(mock_submaster, mocker, msg_valid, lateral_active):
|
||||
nav_desires = NavigationDesires()
|
||||
nav_desires.sm.__getitem__ = mocker.Mock(return_value=make_nav_msg(valid=msg_valid, upcoming='slightLeft'))
|
||||
nav_desires.update(make_car(), lateral_active)
|
||||
assert nav_desires.desire == log.Desire.none
|
||||
|
||||
def test_update(mock_submaster, mocker):
|
||||
mock_submaster.__getitem__ = mocker.Mock(return_value=make_nav_msg(valid=True, upcoming='left'))
|
||||
nav_desires = NavigationDesires()
|
||||
|
||||
assert nav_desires.update(make_car(leftBlinker=True, steeringPressed=True, steeringTorque=1), True) == log.Desire.turnLeft
|
||||
|
||||
params_setter(False)
|
||||
nav_desires.param_counter = 59
|
||||
nav_desires.update(make_car(leftBlinker=True), True)
|
||||
assert nav_desires.desire == log.Desire.none
|
||||
|
||||
@pytest.mark.parametrize("carstate, upcoming, current_desire, expected", INTEGRATION_PARAMS)
|
||||
def test_desire_helper(mock_submaster, mocker, carstate, upcoming, current_desire, expected):
|
||||
mock_submaster.__getitem__ = mocker.Mock(return_value=make_nav_msg(valid=True, upcoming=upcoming))
|
||||
dh = DesireHelper()
|
||||
dh.desire = current_desire
|
||||
|
||||
if current_desire in (log.Desire.laneChangeLeft, log.Desire.laneChangeRight):
|
||||
dh.lane_change_state = log.LaneChangeState.laneChangeStarting
|
||||
dh.lane_change_direction = log.LaneChangeDirection.left if current_desire == log.Desire.laneChangeLeft else log.LaneChangeDirection.right
|
||||
|
||||
dh.update(carstate, True, 1.0)
|
||||
assert dh.desire == expected
|
||||
@@ -0,0 +1,113 @@
|
||||
"""
|
||||
Copyright (c) 2021-, James Vecellio, 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 requests
|
||||
from urllib.parse import quote
|
||||
|
||||
from openpilot.common.params import Params
|
||||
|
||||
|
||||
class MapboxIntegration:
|
||||
def __init__(self):
|
||||
self.params = Params()
|
||||
|
||||
def get_public_token(self) -> str:
|
||||
token: str = self.params.get('MapboxToken', return_default=True)
|
||||
return token
|
||||
|
||||
def set_destination(self, postvars, current_lon, current_lat, bearing=None) -> tuple[dict, bool]:
|
||||
if 'latitude' in postvars and 'longitude' in postvars:
|
||||
self.nav_confirmed(postvars, current_lon, current_lat, bearing)
|
||||
return postvars, True
|
||||
|
||||
addr = postvars['place_name']
|
||||
if not addr:
|
||||
return postvars, False
|
||||
|
||||
token = self.get_public_token()
|
||||
url = f'https://api.mapbox.com/geocoding/v5/mapbox.places/{quote(addr)}.json?access_token={token}&limit=1&proximity={current_lon},{current_lat}'
|
||||
try:
|
||||
response = requests.get(url, timeout=5)
|
||||
if response.status_code == 200:
|
||||
features = response.json()['features']
|
||||
if features:
|
||||
longitude, latitude = features[0]['geometry']['coordinates']
|
||||
postvars.update({'latitude': latitude, 'longitude': longitude, 'name': addr})
|
||||
self.nav_confirmed(postvars, current_lon, current_lat, bearing)
|
||||
return postvars, True
|
||||
except requests.RequestException:
|
||||
pass # Broad exception to handle network errors like no internet without crashing navd process.
|
||||
return postvars, False
|
||||
|
||||
def nav_confirmed(self, postvars, start_lon, start_lat, bearing=None) -> None:
|
||||
if not postvars:
|
||||
return
|
||||
|
||||
latitude = float(postvars['latitude'])
|
||||
longitude = float(postvars['longitude'])
|
||||
|
||||
data: dict = {'navData': {'current': {'latitude': latitude, 'longitude': longitude}, 'route': {}}}
|
||||
|
||||
token = self.get_public_token()
|
||||
route_data = self.generate_route(start_lon, start_lat, longitude, latitude, token, bearing)
|
||||
if route_data:
|
||||
data['navData']['route'] = route_data
|
||||
self.params.put('MapboxSettings', data)
|
||||
|
||||
@staticmethod
|
||||
def generate_route(start_lon, start_lat, end_lon, end_lat, token, bearing=None) -> dict | None:
|
||||
if not token:
|
||||
return None
|
||||
|
||||
params = {
|
||||
'access_token': token,
|
||||
'geometries': 'geojson',
|
||||
'steps': 'true',
|
||||
'overview': 'full',
|
||||
'annotations': 'maxspeed',
|
||||
'alternatives': 'false',
|
||||
'banner_instructions': 'true',
|
||||
}
|
||||
if bearing is not None:
|
||||
params['bearings'] = f'{int((bearing + 360) % 360):.0f},90;'
|
||||
|
||||
try:
|
||||
response = requests.get(f'https://api.mapbox.com/directions/v5/mapbox/driving/{start_lon},{start_lat};{end_lon},{end_lat}', params=params, timeout=5)
|
||||
data = response.json() if response.status_code == 200 else {}
|
||||
except requests.RequestException:
|
||||
return None
|
||||
|
||||
routes = data['routes'] if data else None
|
||||
legs = routes[0]['legs'] if routes else None
|
||||
|
||||
if data.get('code') != 'Ok' or not routes or not legs:
|
||||
return None
|
||||
|
||||
route = routes[0]
|
||||
leg = legs[0]
|
||||
|
||||
steps = [
|
||||
{
|
||||
'maneuver': step['maneuver']['type'],
|
||||
'instruction': step['maneuver']['instruction'],
|
||||
'distance': step['distance'],
|
||||
'duration': step['duration'],
|
||||
'location': {'longitude': step['maneuver']['location'][0], 'latitude': step['maneuver']['location'][1]},
|
||||
'modifier': step['maneuver'].get('modifier', 'none'),
|
||||
'bannerInstructions': step['bannerInstructions'],
|
||||
}
|
||||
for step in leg['steps']
|
||||
]
|
||||
|
||||
maxspeed = [{'speed': item['speed'], 'unit': item['unit']} for item in leg['annotation']['maxspeed'] if 'speed' in item]
|
||||
|
||||
return {
|
||||
'steps': steps,
|
||||
'totalDistance': route['distance'],
|
||||
'totalDuration': route['duration'],
|
||||
'geometry': [{'longitude': coord[0], 'latitude': coord[1]} for coord in route['geometry']['coordinates']],
|
||||
'maxspeed': maxspeed,
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
"""
|
||||
Copyright (c) 2021-, James Vecellio, 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 openpilot.common.constants import CV
|
||||
from openpilot.common.params import Params
|
||||
|
||||
from openpilot.sunnypilot.navd.helpers import Coordinate, string_to_direction
|
||||
|
||||
|
||||
class NavigationInstructions:
|
||||
def __init__(self):
|
||||
self.coord = Coordinate(0, 0)
|
||||
self.params = Params()
|
||||
self._cached_route = None
|
||||
self._route_loaded = False
|
||||
self._no_route = False
|
||||
|
||||
def get_route_progress(self, current_lat, current_lon) -> dict | None:
|
||||
route = self.get_current_route()
|
||||
if not route or not route['geometry'] or not route['steps']:
|
||||
return None
|
||||
|
||||
self.coord.latitude = current_lat
|
||||
self.coord.longitude = current_lon
|
||||
|
||||
# Find the closest point on the route relative to self
|
||||
closest_idx, min_distance = min(((idx, self.coord.distance_to(coord)) for idx, coord in enumerate(route['geometry'])), key=lambda x: x[1])
|
||||
closest_cumulative = route['cumulative_distances'][closest_idx]
|
||||
|
||||
# Find the current step index, which is the HIGHEST idx where the step location cumulative less/equal closest cumulative
|
||||
current_step_idx = max((idx for idx, step in enumerate(route['steps']) if step['cumulative_distance'] <= closest_cumulative), default=-1)
|
||||
current_step = route['steps'][current_step_idx if current_step_idx >= 0 else 0]
|
||||
|
||||
# The next turn is the next step relative to our cumulative index
|
||||
next_turn_idx = current_step_idx + 1
|
||||
next_turn = route['steps'][next_turn_idx] if 0 <= next_turn_idx < len(route['steps']) else None
|
||||
|
||||
current_maxspeed = current_step['maxspeed']
|
||||
|
||||
distance_to_end_of_step = max(0, current_step['distance'] - (closest_cumulative - current_step['cumulative_distance']))
|
||||
|
||||
all_maneuvers: list = []
|
||||
max_maneuvers = 3
|
||||
for idx in range(current_step_idx, min(current_step_idx + max_maneuvers, len(route['steps']))):
|
||||
step = route['steps'][idx]
|
||||
if idx == current_step_idx:
|
||||
distance = distance_to_end_of_step
|
||||
else:
|
||||
distance = step['cumulative_distance'] - closest_cumulative
|
||||
all_maneuvers.append({'distance': distance, 'type': step['maneuver'], 'modifier': step['modifier'], 'instruction': step['instruction']})
|
||||
|
||||
return {
|
||||
'distance_from_route': min_distance,
|
||||
'current_step': current_step,
|
||||
'next_turn': next_turn,
|
||||
'current_maxspeed': current_maxspeed,
|
||||
'all_maneuvers': all_maneuvers,
|
||||
'current_step_idx': current_step_idx,
|
||||
'distance_to_end_of_step': distance_to_end_of_step,
|
||||
}
|
||||
|
||||
def get_current_route(self):
|
||||
if self._route_loaded and self._cached_route is not None:
|
||||
return self._cached_route
|
||||
if self._no_route:
|
||||
return None
|
||||
|
||||
param_value = self.params.get('MapboxSettings')
|
||||
route = param_value['navData']['route'] if param_value else None
|
||||
if not route:
|
||||
self._no_route = True
|
||||
return None
|
||||
|
||||
geometry = [Coordinate(coord['latitude'], coord['longitude']) for coord in route['geometry']]
|
||||
cumulative_distances = [0.0]
|
||||
cumulative_distances.extend(cumulative_distances[-1] + geometry[step - 1].distance_to(geometry[step]) for step in range(1, len(geometry)))
|
||||
maxspeed = [(speed['speed'], speed['unit']) for speed in route['maxspeed']]
|
||||
steps = []
|
||||
for step in route['steps']:
|
||||
location = Coordinate(step['location']['latitude'], step['location']['longitude'])
|
||||
closest_idx = min(range(len(geometry)), key=lambda i: location.distance_to(geometry[i]))
|
||||
steps.append({
|
||||
'bannerInstructions': step['bannerInstructions'],
|
||||
'distance': step['distance'],
|
||||
'duration': step['duration'],
|
||||
'maneuver': step['maneuver'],
|
||||
'location': location,
|
||||
'cumulative_distance': cumulative_distances[closest_idx],
|
||||
'maxspeed': maxspeed[min(closest_idx, len(maxspeed) - 1)] if len(maxspeed) > 0 else (0, 'kmh'),
|
||||
'modifier': string_to_direction(step['modifier']),
|
||||
'instruction': step['instruction'],
|
||||
})
|
||||
self._cached_route = {
|
||||
'steps': steps,
|
||||
'total_distance': route['totalDistance'],
|
||||
'total_duration': route['totalDuration'],
|
||||
'geometry': geometry,
|
||||
'cumulative_distances': cumulative_distances,
|
||||
'maxspeed': maxspeed,
|
||||
}
|
||||
self._route_loaded = True
|
||||
return self._cached_route
|
||||
|
||||
def clear_route_cache(self):
|
||||
self._cached_route = None
|
||||
self._route_loaded = False
|
||||
self._no_route = False
|
||||
|
||||
def get_upcoming_turn_from_progress(self, progress, current_lat, current_lon) -> str:
|
||||
if progress and progress['next_turn']:
|
||||
self.coord.latitude = current_lat
|
||||
self.coord.longitude = current_lon
|
||||
distance = self.coord.distance_to(progress['next_turn']['location'])
|
||||
if distance <= 100:
|
||||
modifier = progress['next_turn']['modifier']
|
||||
return str(modifier)
|
||||
return 'none'
|
||||
|
||||
@staticmethod
|
||||
def get_current_speed_limit_from_progress(progress, is_metric: bool) -> int:
|
||||
if progress and progress['current_maxspeed']:
|
||||
speed, _ = progress['current_maxspeed']
|
||||
if is_metric:
|
||||
return int(speed)
|
||||
else:
|
||||
return int(round(speed * CV.KPH_TO_MPH))
|
||||
return 0
|
||||
|
||||
@staticmethod
|
||||
def arrived_at_destination(progress) -> bool:
|
||||
if progress['all_maneuvers'][0]['type'] == 'arrive':
|
||||
return True
|
||||
elif progress['all_maneuvers'][0]['instruction'].startswith('Your destination'):
|
||||
return True
|
||||
return False
|
||||
@@ -0,0 +1,94 @@
|
||||
"""
|
||||
Copyright (c) 2021-, James Vecellio, 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
|
||||
|
||||
from openpilot.common.constants import CV
|
||||
|
||||
from openpilot.sunnypilot.navd.navigation_helpers.mapbox_integration import MapboxIntegration
|
||||
from openpilot.sunnypilot.navd.navigation_helpers.nav_instructions import NavigationInstructions
|
||||
|
||||
|
||||
class TestMapbox:
|
||||
@classmethod
|
||||
def setup_class(cls):
|
||||
cls.mapbox = MapboxIntegration()
|
||||
cls.nav = NavigationInstructions()
|
||||
|
||||
token = os.environ.get('MAPBOX_TOKEN_CI')
|
||||
if token:
|
||||
cls.mapbox.params.put('MapboxToken', token)
|
||||
|
||||
# route setup
|
||||
cls.current_lon, cls.current_lat = -119.17557, 34.23305
|
||||
cls.mapbox.params.put('MapboxRoute', '740 E Ventura Blvd. Camarillo, CA')
|
||||
cls.postvars = {"place_name": cls.mapbox.params.get('MapboxRoute')}
|
||||
cls.postvars, cls.valid_addr = cls.mapbox.set_destination(cls.postvars, cls.current_lon, cls.current_lat)
|
||||
cls.route = cls.nav.get_current_route()
|
||||
cls.progress = cls.nav.get_route_progress(cls.current_lat, cls.current_lon)
|
||||
|
||||
def test_set_destination(self):
|
||||
assert self.valid_addr
|
||||
settings = self.mapbox.params.get('MapboxSettings')
|
||||
assert settings is not None
|
||||
dest_lat = settings['navData']['current']['latitude']
|
||||
dest_lon = settings['navData']['current']['longitude']
|
||||
assert dest_lat == self.postvars["latitude"] and dest_lon == self.postvars["longitude"]
|
||||
|
||||
def test_get_route(self):
|
||||
assert self.route is not None
|
||||
assert 'steps' in self.route
|
||||
assert 'geometry' in self.route
|
||||
assert 'maxspeed' in self.route
|
||||
assert 'total_distance' in self.route
|
||||
assert 'total_duration' in self.route
|
||||
assert len(self.route['steps']) > 0
|
||||
assert len(self.route['geometry']) > 0
|
||||
assert len(self.route['maxspeed']) > 0
|
||||
|
||||
if self.route and 'steps' in self.route:
|
||||
for step in self.route['steps']:
|
||||
assert 'modifier' in step
|
||||
|
||||
def test_upcoming_turn_detection(self):
|
||||
upcoming = self.nav.get_upcoming_turn_from_progress(self.progress, self.current_lat, self.current_lon)
|
||||
assert isinstance(upcoming, str)
|
||||
assert upcoming == 'none'
|
||||
|
||||
if self.route['steps']:
|
||||
turn_lat = self.route['steps'][1]['location'].latitude
|
||||
turn_lon = self.route['steps'][1]['location'].longitude
|
||||
close_lat = turn_lat - 0.0008 # 80 ish meters before the turn
|
||||
if self.progress and self.progress.get('next_turn'):
|
||||
expected_turn = self.progress['next_turn']['modifier']
|
||||
upcoming_close = self.nav.get_upcoming_turn_from_progress(self.progress, close_lat, turn_lon)
|
||||
if expected_turn:
|
||||
assert upcoming_close == expected_turn == 'right', "Should be a right turn upcoming"
|
||||
|
||||
def test_route_progress_tracking(self):
|
||||
assert self.progress is not None
|
||||
assert 'distance_from_route' in self.progress
|
||||
assert 'next_turn' in self.progress
|
||||
assert 'current_maxspeed' in self.progress
|
||||
assert 'all_maneuvers' in self.progress
|
||||
assert 'distance_to_end_of_step' in self.progress
|
||||
assert self.progress['distance_from_route'] >= 0
|
||||
assert isinstance(self.progress['all_maneuvers'], list)
|
||||
|
||||
def test_speed_limit_handling(self):
|
||||
speed_limit_metric = self.nav.get_current_speed_limit_from_progress(self.progress, True)
|
||||
speed_limit_imperial = self.nav.get_current_speed_limit_from_progress(self.progress, False)
|
||||
assert isinstance(speed_limit_metric, int)
|
||||
assert isinstance(speed_limit_imperial, int)
|
||||
expected_metric = int(self.progress['current_maxspeed'][0])
|
||||
expected_imperial = int(round(self.progress['current_maxspeed'][0] * CV.KPH_TO_MPH))
|
||||
assert speed_limit_metric == expected_metric
|
||||
assert speed_limit_imperial == expected_imperial
|
||||
|
||||
def test_arrival_detection(self):
|
||||
is_arrived = self.nav.arrived_at_destination(self.progress)
|
||||
assert isinstance(is_arrived, bool)
|
||||
assert not is_arrived
|
||||
Executable
+160
@@ -0,0 +1,160 @@
|
||||
"""
|
||||
Copyright (c) 2021-, James Vecellio, 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 math
|
||||
|
||||
import cereal.messaging as messaging
|
||||
from cereal import custom
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.realtime import Ratekeeper
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
|
||||
from openpilot.sunnypilot.navd.constants import NAV_CV
|
||||
from openpilot.sunnypilot.navd.helpers import Coordinate, parse_banner_instructions
|
||||
from openpilot.sunnypilot.navd.navigation_helpers.mapbox_integration import MapboxIntegration
|
||||
from openpilot.sunnypilot.navd.navigation_helpers.nav_instructions import NavigationInstructions
|
||||
|
||||
|
||||
class Navigationd:
|
||||
def __init__(self):
|
||||
self.params = Params()
|
||||
self.mapbox = MapboxIntegration()
|
||||
self.nav_instructions = NavigationInstructions()
|
||||
|
||||
self.sm = messaging.SubMaster(['liveLocationKalman'])
|
||||
self.pm = messaging.PubMaster(['navigationd'])
|
||||
self.rk = Ratekeeper(3) # 3 Hz
|
||||
|
||||
self.route = None
|
||||
self.destination: str | None = None
|
||||
self.new_destination: str = ''
|
||||
|
||||
self.allow_navigation: bool = False
|
||||
self.recompute_allowed: bool = False
|
||||
self.allow_recompute: bool = False
|
||||
self.reroute_counter: int = 0
|
||||
self.cancel_route_counter: int = 0
|
||||
|
||||
self.frame: int = -1
|
||||
self.last_position: Coordinate | None = None
|
||||
self.last_bearing: float | None = None
|
||||
self.is_metric: bool = False
|
||||
self.valid: bool = False
|
||||
|
||||
def _update_params(self):
|
||||
if self.last_position is not None:
|
||||
self.frame += 1
|
||||
if self.frame % 9 == 0:
|
||||
self.allow_navigation = self.params.get('AllowNavigation', return_default=True)
|
||||
self.is_metric = self.params.get('IsMetric', return_default=True)
|
||||
self.new_destination = self.params.get('MapboxRoute')
|
||||
self.recompute_allowed = self.params.get('MapboxRecompute', return_default=True)
|
||||
|
||||
self.allow_recompute: bool = (self.new_destination != self.destination and self.new_destination != '') or (
|
||||
self.recompute_allowed and self.reroute_counter > 9 and self.route
|
||||
)
|
||||
|
||||
if self.allow_recompute:
|
||||
postvars = {'place_name': self.new_destination}
|
||||
postvars, valid_addr = self.mapbox.set_destination(postvars, self.last_position.longitude, self.last_position.latitude, self.last_bearing)
|
||||
cloudlog.debug(f'Set new destination to: {self.new_destination}, valid: {valid_addr}')
|
||||
if valid_addr:
|
||||
self.destination = self.new_destination
|
||||
self.nav_instructions.clear_route_cache()
|
||||
self.route = self.nav_instructions.get_current_route()
|
||||
self.cancel_route_counter = 0
|
||||
self.reroute_counter = 0
|
||||
|
||||
if self.cancel_route_counter == 30:
|
||||
self.cancel_route_counter = 0
|
||||
self.destination = None
|
||||
self.nav_instructions.clear_route_cache()
|
||||
self.route = None
|
||||
|
||||
self.valid = self.route is not None
|
||||
|
||||
def _update_navigation(self) -> tuple[str, dict | None, dict]:
|
||||
banner_instructions: str = ''
|
||||
nav_data: dict = {}
|
||||
if self.allow_navigation and self.last_position is not None:
|
||||
if progress := self.nav_instructions.get_route_progress(self.last_position.latitude, self.last_position.longitude):
|
||||
nav_data['upcoming_turn'] = self.nav_instructions.get_upcoming_turn_from_progress(progress, self.last_position.latitude, self.last_position.longitude)
|
||||
nav_data['current_speed_limit'] = self.nav_instructions.get_current_speed_limit_from_progress(progress, self.is_metric)
|
||||
arrived = self.nav_instructions.arrived_at_destination(progress)
|
||||
|
||||
if progress['current_step']:
|
||||
parsed = parse_banner_instructions(progress['current_step']['bannerInstructions'], progress['distance_to_end_of_step'])
|
||||
if parsed:
|
||||
banner_instructions = parsed['maneuverPrimaryText']
|
||||
|
||||
nav_data['distance_from_route'] = progress['distance_from_route']
|
||||
large_distance = progress['distance_from_route'] > 100
|
||||
|
||||
if large_distance:
|
||||
self.cancel_route_counter = self.cancel_route_counter + 1 if progress['distance_from_route'] > NAV_CV.QUARTER_MILE else 0
|
||||
if self.recompute_allowed:
|
||||
self.reroute_counter += 1
|
||||
elif arrived:
|
||||
self.cancel_route_counter += 1
|
||||
else:
|
||||
self.cancel_route_counter = 0
|
||||
self.reroute_counter = 0
|
||||
|
||||
# Don't recompute in last segment to prevent reroute loops
|
||||
if self.route:
|
||||
if progress['current_step_idx'] == len(self.route['steps']) - 1:
|
||||
self.allow_recompute = False
|
||||
else:
|
||||
banner_instructions = ''
|
||||
progress = None
|
||||
nav_data = {}
|
||||
self.valid = False
|
||||
|
||||
return banner_instructions, progress, nav_data
|
||||
|
||||
def _build_navigation_message(self, banner_instructions: str, progress: dict | None, nav_data: dict, valid: bool):
|
||||
msg = messaging.new_message('navigationd')
|
||||
msg.valid = valid
|
||||
msg.navigationd.upcomingTurn = nav_data.get('upcoming_turn', 'none')
|
||||
msg.navigationd.currentSpeedLimit = nav_data.get('current_speed_limit', 0)
|
||||
msg.navigationd.bannerInstructions = banner_instructions
|
||||
msg.navigationd.distanceFromRoute = nav_data.get('distance_from_route', 0.0)
|
||||
msg.navigationd.valid = self.valid
|
||||
|
||||
all_maneuvers = (
|
||||
[custom.Navigationd.Maneuver.new_message(distance=m['distance'], type=m['type'], modifier=m['modifier'],
|
||||
instruction=m['instruction']) for m in progress['all_maneuvers']]
|
||||
if progress
|
||||
else []
|
||||
)
|
||||
msg.navigationd.allManeuvers = all_maneuvers
|
||||
|
||||
return msg
|
||||
|
||||
def run(self):
|
||||
cloudlog.warning('navigationd init')
|
||||
|
||||
while True:
|
||||
self.sm.update()
|
||||
location = self.sm['liveLocationKalman']
|
||||
localizer_valid = location.positionGeodetic.valid if location else False
|
||||
|
||||
if localizer_valid:
|
||||
self.last_bearing = math.degrees(location.calibratedOrientationNED.value[2])
|
||||
self.last_position = Coordinate(location.positionGeodetic.value[0], location.positionGeodetic.value[1])
|
||||
|
||||
self._update_params()
|
||||
banner_instructions, progress, nav_data = self._update_navigation()
|
||||
|
||||
msg = self._build_navigation_message(banner_instructions, progress, nav_data, valid=localizer_valid)
|
||||
|
||||
self.pm.send('navigationd', msg)
|
||||
self.rk.keep_time()
|
||||
|
||||
|
||||
def main():
|
||||
nav = Navigationd()
|
||||
nav.run()
|
||||
@@ -0,0 +1,92 @@
|
||||
"""
|
||||
Copyright (c) 2021-, James Vecellio, 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 custom
|
||||
from openpilot.common.params import Params
|
||||
|
||||
from openpilot.sunnypilot.navd.event_builder import EventBuilder
|
||||
|
||||
|
||||
class MockSM(dict):
|
||||
def __init__(self, nav_msg):
|
||||
super().__init__()
|
||||
self['navigationd'] = nav_msg
|
||||
|
||||
|
||||
class TestEventBuilder:
|
||||
def setup_method(self):
|
||||
self.params = Params()
|
||||
self.event_builder = EventBuilder()
|
||||
|
||||
def create_nav_msg(self, upcoming_turn='none', valid=True):
|
||||
nav_msg = custom.Navigationd.new_message()
|
||||
nav_msg.valid = valid
|
||||
nav_msg.upcomingTurn = upcoming_turn
|
||||
nav_msg.allManeuvers = [
|
||||
custom.Navigationd.Maneuver.new_message(distance=192.84873284, type='turn', modifier='left', instruction='West Esplanade Drive'),
|
||||
custom.Navigationd.Maneuver.new_message(distance=192.84809314, type='turn', modifier='right', instruction='West Esplanade Drive'),
|
||||
]
|
||||
return nav_msg
|
||||
|
||||
def test_validity(self):
|
||||
nav_msg = self.create_nav_msg(valid=False)
|
||||
events = EventBuilder.build_navigation_events(MockSM(nav_msg))
|
||||
assert events == []
|
||||
|
||||
def test_enabled(self):
|
||||
self.params.put("NavEvents", True)
|
||||
nav_msg = self.create_nav_msg()
|
||||
events = self.event_builder.update(MockSM(nav_msg))
|
||||
expected = [{
|
||||
'name': custom.OnroadEventSP.EventName.navigationBanner,
|
||||
'message': 'For 192m, Continue on West Esplanade Drive'
|
||||
}]
|
||||
assert events == expected
|
||||
|
||||
self.params.put("NavEvents", False)
|
||||
self.event_builder._counter = 59
|
||||
events = self.event_builder.update(MockSM(nav_msg))
|
||||
assert events == []
|
||||
|
||||
|
||||
def test_build_navigation_events(self):
|
||||
nav_msg = self.create_nav_msg()
|
||||
events = EventBuilder.build_navigation_events(MockSM(nav_msg), False)
|
||||
expected = [{
|
||||
'name': custom.OnroadEventSP.EventName.navigationBanner,
|
||||
'message': 'For 650ft, Continue on West Esplanade Drive',
|
||||
}]
|
||||
assert events == expected
|
||||
|
||||
def test_distance_condition_imperial(self):
|
||||
nav_msg = self.create_nav_msg()
|
||||
nav_msg.allManeuvers[1] = custom.Navigationd.Maneuver.new_message(distance=160.0, type='continue', modifier='straight', instruction='1234 Apple Way')
|
||||
events = EventBuilder.build_navigation_events(MockSM(nav_msg), False)
|
||||
expected = [{
|
||||
'name': custom.OnroadEventSP.EventName.navigationBanner,
|
||||
'message': 'For 500ft, Continue on 1234 Apple Way',
|
||||
}]
|
||||
assert events == expected
|
||||
|
||||
def test_upcoming_turn_override(self):
|
||||
nav_msg = self.create_nav_msg(upcoming_turn='left')
|
||||
events = EventBuilder.build_navigation_events(MockSM(nav_msg))
|
||||
expected = [{
|
||||
'name': custom.OnroadEventSP.EventName.navigationBanner,
|
||||
'message': 'Turning Left, Make sure to nudge the wheel',
|
||||
}]
|
||||
assert events == expected
|
||||
|
||||
def test_straight(self):
|
||||
nav_msg = self.create_nav_msg()
|
||||
nav_msg.allManeuvers[1] = custom.Navigationd.Maneuver.new_message(distance=80.0, type='continue', modifier='straight', instruction='1234 Apple Way')
|
||||
|
||||
events = EventBuilder.build_navigation_events(MockSM(nav_msg))
|
||||
expected = [{
|
||||
'name': custom.OnroadEventSP.EventName.navigationBanner,
|
||||
'message': 'For 80m, Continue on 1234 Apple Way'
|
||||
}]
|
||||
assert events == expected
|
||||
@@ -0,0 +1,76 @@
|
||||
"""
|
||||
Copyright (c) 2021-, James Vecellio, 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 platform
|
||||
import pytest
|
||||
|
||||
import cereal.messaging as messaging
|
||||
|
||||
from openpilot.sunnypilot.navd.navigationd import Navigationd
|
||||
from openpilot.sunnypilot.navd.helpers import Coordinate
|
||||
|
||||
|
||||
class TestNavigationd:
|
||||
is_darwin = platform.system() == "Darwin"
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup_method(self, mocker):
|
||||
if self.is_darwin:
|
||||
mocker.patch('cereal.messaging.SubMaster')
|
||||
mocker.patch('cereal.messaging.PubMaster')
|
||||
|
||||
def test_update_params(self):
|
||||
nav = Navigationd()
|
||||
nav.last_position = None
|
||||
nav._update_params()
|
||||
assert nav.frame == -1
|
||||
nav.last_position = Coordinate(latitude=37.0, longitude=128.0)
|
||||
nav._update_params()
|
||||
assert nav.frame == 0 # frame only updates when last position is set
|
||||
|
||||
def test_update_navigation_no_position(self):
|
||||
nav = Navigationd()
|
||||
nav.last_position = None
|
||||
banner, progress, nav_data = nav._update_navigation()
|
||||
assert banner == ''
|
||||
assert progress is None
|
||||
assert nav_data == {}
|
||||
|
||||
def test_update_navigation(self):
|
||||
nav = Navigationd()
|
||||
nav.last_position = Coordinate(latitude=37.0, longitude=128.0)
|
||||
nav.route = {'580 Winchester dr, oxnard, CA': True}
|
||||
banner, progress, nav_data = nav._update_navigation()
|
||||
assert isinstance(banner, str)
|
||||
assert not progress # no route was actually set
|
||||
assert isinstance(nav_data, dict)
|
||||
|
||||
def test_build_navigation_message(self):
|
||||
if self.is_darwin:
|
||||
nav = Navigationd()
|
||||
msg = nav._build_navigation_message('', None, {}, True)
|
||||
assert msg.navigationd.bannerInstructions == ''
|
||||
assert msg.navigationd.valid is False
|
||||
else:
|
||||
sm = messaging.SubMaster(['navigationd'])
|
||||
nav = Navigationd()
|
||||
msg = nav._build_navigation_message('', None, {}, True)
|
||||
|
||||
nav.pm.send('navigationd', msg)
|
||||
sm.update()
|
||||
received_msg = sm['navigationd']
|
||||
|
||||
assert received_msg.bannerInstructions == msg.navigationd.bannerInstructions
|
||||
assert received_msg.valid == msg.navigationd.valid
|
||||
|
||||
def test_cancel_route(self):
|
||||
nav = Navigationd()
|
||||
nav.last_position = Coordinate(latitude=37.0, longitude=128.0)
|
||||
nav.route = {'580 Winchester dr, oxnard, CA': True}
|
||||
nav.cancel_route_counter = 30
|
||||
nav._update_params()
|
||||
assert nav.route is None
|
||||
assert nav.destination is None
|
||||
@@ -37,7 +37,7 @@ class CarSpecificEventsSP:
|
||||
# TODO-SP: add 1 m/s hysteresis
|
||||
if CS.vEgo >= self.CP.minEnableSpeed:
|
||||
self.low_speed_alert = False
|
||||
if CS.gearShifter != GearShifter.drive:
|
||||
if self.CP.minEnableSpeed >= 14.5 and CS.gearShifter != GearShifter.drive:
|
||||
self.low_speed_alert = True
|
||||
if self.low_speed_alert:
|
||||
events.add(EventName.belowSteerSpeed)
|
||||
|
||||
@@ -11,6 +11,7 @@ from opendbc.car.interfaces import CarInterfaceBase
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
from openpilot.sunnypilot.selfdrive.controls.lib.nnlc.helpers import get_nn_model_path
|
||||
from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.helpers import set_speed_limit_assist_availability
|
||||
|
||||
import openpilot.system.sentry as sentry
|
||||
|
||||
@@ -66,6 +67,29 @@ def _initialize_torque_lateral_control(CI: CarInterfaceBase, CP: structs.CarPara
|
||||
CI.configure_torque_tune(CP.carFingerprint, CP.lateralTuning)
|
||||
|
||||
|
||||
def _cleanup_unsupported_params(CP: structs.CarParams, CP_SP: structs.CarParamsSP, params: Params = None) -> None:
|
||||
if params is None:
|
||||
params = Params()
|
||||
|
||||
if CP.steerControlType == structs.CarParams.SteerControlType.angle:
|
||||
cloudlog.warning("SteerControlType is angle, cleaning up params")
|
||||
params.remove("NeuralNetworkLateralControl")
|
||||
params.remove("EnforceTorqueControl")
|
||||
|
||||
if not CP_SP.intelligentCruiseButtonManagementAvailable or CP.openpilotLongitudinalControl:
|
||||
cloudlog.warning("ICBM not available or openpilot Longitudinal Control enabled, cleaning up params")
|
||||
params.remove("IntelligentCruiseButtonManagement")
|
||||
|
||||
if not CP.openpilotLongitudinalControl and CP_SP.pcmCruiseSpeed:
|
||||
cloudlog.warning("openpilot Longitudinal Control and ICBM not available, cleaning up params")
|
||||
params.remove("DynamicExperimentalControl")
|
||||
params.remove("CustomAccIncrementsEnabled")
|
||||
params.remove("SmartCruiseControlVision")
|
||||
params.remove("SmartCruiseControlMap")
|
||||
|
||||
set_speed_limit_assist_availability(CP, CP_SP, params)
|
||||
|
||||
|
||||
def setup_interfaces(CI: CarInterfaceBase, params: Params = None) -> None:
|
||||
CP = CI.CP
|
||||
CP_SP = CI.CP_SP
|
||||
@@ -74,6 +98,7 @@ def setup_interfaces(CI: CarInterfaceBase, params: Params = None) -> None:
|
||||
nnlc_enabled = _initialize_neural_network_lateral_control(CP, CP_SP, params)
|
||||
_initialize_intelligent_cruise_button_management(CP, CP_SP, params)
|
||||
_initialize_torque_lateral_control(CI, CP, enforce_torque, nnlc_enabled)
|
||||
_cleanup_unsupported_params(CP, CP_SP)
|
||||
|
||||
|
||||
def initialize_params(params) -> list[dict[str, Any]]:
|
||||
@@ -84,4 +109,10 @@ def initialize_params(params) -> list[dict[str, Any]]:
|
||||
"HyundaiLongitudinalTuning"
|
||||
])
|
||||
|
||||
# subaru
|
||||
keys.extend([
|
||||
"SubaruStopAndGo",
|
||||
"SubaruStopAndGoManualParkingBrake",
|
||||
])
|
||||
|
||||
return [{k: params.get(k, return_default=True)} for k in keys]
|
||||
|
||||
@@ -12,47 +12,159 @@ from openpilot.common.realtime import DT_MDL
|
||||
from openpilot.sunnypilot import PARAMS_UPDATE_PERIOD
|
||||
from openpilot.sunnypilot.selfdrive.selfdrived.events import EventsSP
|
||||
|
||||
TRIGGER_THRESHOLD = 30
|
||||
GREEN_LIGHT_X_THRESHOLD = 30
|
||||
LEAD_DEPART_DIST_THRESHOLD = 1.0
|
||||
TRIGGER_TIMER_THRESHOLD = 0.3
|
||||
|
||||
|
||||
class E2EStates:
|
||||
INACTIVE = 0
|
||||
ARMED = 1
|
||||
CONSUMED = 2
|
||||
|
||||
|
||||
class E2EAlertsHelper:
|
||||
def __init__(self):
|
||||
self._params = Params()
|
||||
self._frame = -1
|
||||
self.frame = -1
|
||||
self.green_light_state = E2EStates.INACTIVE
|
||||
self.prev_green_light_state = E2EStates.INACTIVE
|
||||
self.lead_depart_state = E2EStates.INACTIVE
|
||||
self.prev_lead_depart_state = E2EStates.INACTIVE
|
||||
|
||||
self.green_light_alert = False
|
||||
self.green_light_alert_enabled = self._params.get_bool("GreenLightAlert")
|
||||
self.lead_depart_alert = False
|
||||
self.lead_depart_alert_enabled = self._params.get_bool("LeadDepartAlert")
|
||||
|
||||
self.green_light_trigger_timer = 0
|
||||
self.lead_depart_trigger_timer = 0
|
||||
self.last_lead_distance = -1
|
||||
self.last_moving_frame = -1
|
||||
|
||||
self.allowed = False
|
||||
self.last_allowed = False
|
||||
self.has_lead = False
|
||||
|
||||
self.lead_depart_arm_timer = 0
|
||||
self.lead_depart_confirmed_lead = False
|
||||
self.lead_depart_armed = False
|
||||
|
||||
def _read_params(self) -> None:
|
||||
if self._frame % int(PARAMS_UPDATE_PERIOD / DT_MDL) == 0:
|
||||
if self.frame % int(PARAMS_UPDATE_PERIOD / DT_MDL) == 0:
|
||||
self.green_light_alert_enabled = self._params.get_bool("GreenLightAlert")
|
||||
self.lead_depart_alert_enabled = self._params.get_bool("LeadDepartAlert")
|
||||
|
||||
self._frame += 1
|
||||
|
||||
def update(self, sm: messaging.SubMaster, events_sp: EventsSP) -> None:
|
||||
self._read_params()
|
||||
|
||||
if not (self.green_light_alert_enabled or self.lead_depart_alert_enabled):
|
||||
return
|
||||
|
||||
def update_alert_trigger(self, sm: messaging.SubMaster):
|
||||
CS = sm['carState']
|
||||
CC = sm['carControl']
|
||||
|
||||
model_x = sm['modelV2'].position.x
|
||||
max_idx = len(model_x) - 1
|
||||
has_lead = sm['radarState'].leadOne.status
|
||||
lead_vRel: float = sm['radarState'].leadOne.vRel
|
||||
self.has_lead = sm['radarState'].leadOne.status
|
||||
lead_dRel = sm['radarState'].leadOne.dRel
|
||||
|
||||
# Green light alert
|
||||
self.green_light_alert = (self.green_light_alert_enabled and model_x[max_idx] > TRIGGER_THRESHOLD
|
||||
and not has_lead and CS.standstill and not CS.gasPressed and not CC.enabled)
|
||||
standstill = CS.standstill
|
||||
moving = not standstill and CS.vEgo > 0.1
|
||||
|
||||
if moving:
|
||||
self.last_moving_frame = self.frame
|
||||
recent_moving = self.last_moving_frame == -1 or (self.frame - self.last_moving_frame) * DT_MDL < 2.0
|
||||
|
||||
self.allowed = not moving and not CS.gasPressed and not CC.enabled and not recent_moving
|
||||
|
||||
# Green Light Alert
|
||||
green_light_trigger = False
|
||||
if self.green_light_state == E2EStates.ARMED:
|
||||
if model_x[max_idx] > GREEN_LIGHT_X_THRESHOLD:
|
||||
self.green_light_trigger_timer += 1
|
||||
else:
|
||||
self.green_light_trigger_timer = 0
|
||||
|
||||
if self.green_light_trigger_timer * DT_MDL > TRIGGER_TIMER_THRESHOLD:
|
||||
green_light_trigger = True
|
||||
elif self.green_light_state != E2EStates.ARMED:
|
||||
self.green_light_trigger_timer = 0
|
||||
|
||||
# Lead Departure Alert
|
||||
self.lead_depart_alert = (self.lead_depart_alert_enabled and CS.standstill and model_x[max_idx] > 30
|
||||
and has_lead and lead_vRel > 1 and not CS.gasPressed)
|
||||
close_lead_valid = self.has_lead and lead_dRel < 8.0
|
||||
if self.allowed and not self.last_allowed and close_lead_valid:
|
||||
self.lead_depart_confirmed_lead = True
|
||||
elif not self.allowed:
|
||||
self.lead_depart_confirmed_lead = False
|
||||
|
||||
if self.allowed and self.lead_depart_confirmed_lead and close_lead_valid:
|
||||
self.lead_depart_arm_timer += 1
|
||||
|
||||
if self.lead_depart_arm_timer * DT_MDL >= 1.0:
|
||||
self.lead_depart_armed = True
|
||||
else:
|
||||
self.lead_depart_arm_timer = 0
|
||||
self.lead_depart_armed = False
|
||||
|
||||
lead_depart_trigger = False
|
||||
if self.lead_depart_state == E2EStates.ARMED:
|
||||
if self.last_lead_distance == -1 or lead_dRel < self.last_lead_distance:
|
||||
self.last_lead_distance = lead_dRel
|
||||
|
||||
if self.last_lead_distance != -1 and (lead_dRel - self.last_lead_distance > LEAD_DEPART_DIST_THRESHOLD):
|
||||
self.lead_depart_trigger_timer += 1
|
||||
else:
|
||||
self.lead_depart_trigger_timer = 0
|
||||
|
||||
if self.lead_depart_trigger_timer * DT_MDL > TRIGGER_TIMER_THRESHOLD:
|
||||
lead_depart_trigger = True
|
||||
elif self.lead_depart_state != E2EStates.ARMED:
|
||||
self.last_lead_distance = -1
|
||||
self.lead_depart_trigger_timer = 0
|
||||
|
||||
self.last_allowed = self.allowed
|
||||
|
||||
return green_light_trigger, lead_depart_trigger
|
||||
|
||||
@staticmethod
|
||||
def update_state_machine(state: int, enabled: bool, allowed: bool, triggered: bool) -> tuple[int, bool]:
|
||||
if state != E2EStates.INACTIVE:
|
||||
if not allowed or not enabled:
|
||||
state = E2EStates.INACTIVE
|
||||
|
||||
else:
|
||||
if state == E2EStates.ARMED:
|
||||
if triggered:
|
||||
state = E2EStates.CONSUMED
|
||||
|
||||
elif state == E2EStates.CONSUMED:
|
||||
pass
|
||||
|
||||
elif state == E2EStates.INACTIVE:
|
||||
if allowed and enabled:
|
||||
state = E2EStates.ARMED
|
||||
|
||||
return state, triggered
|
||||
|
||||
def update(self, sm: messaging.SubMaster, events_sp: EventsSP) -> None:
|
||||
self._read_params()
|
||||
|
||||
green_light_trigger, lead_depart_trigger = self.update_alert_trigger(sm)
|
||||
|
||||
self.prev_green_light_state = self.green_light_state
|
||||
self.prev_lead_depart_state = self.lead_depart_state
|
||||
|
||||
self.green_light_state, self.green_light_alert = self.update_state_machine(
|
||||
self.green_light_state,
|
||||
self.green_light_alert_enabled,
|
||||
self.allowed and not self.has_lead,
|
||||
green_light_trigger
|
||||
)
|
||||
|
||||
self.lead_depart_state, self.lead_depart_alert = self.update_state_machine(
|
||||
self.lead_depart_state,
|
||||
self.lead_depart_alert_enabled,
|
||||
self.allowed and self.lead_depart_armed,
|
||||
lead_depart_trigger
|
||||
)
|
||||
|
||||
if self.green_light_alert or self.lead_depart_alert:
|
||||
events_sp.add(custom.OnroadEventSP.EventName.e2eChime)
|
||||
|
||||
self.frame += 1
|
||||
|
||||
@@ -9,13 +9,15 @@ from cereal import custom
|
||||
from openpilot.common.constants import CV
|
||||
from openpilot.common.params import Params
|
||||
|
||||
TurnDirection = custom.ModelDataV2SP.TurnDirection
|
||||
|
||||
LANE_CHANGE_SPEED_MIN = 20 * CV.MPH_TO_MS
|
||||
|
||||
|
||||
class LaneTurnController:
|
||||
def __init__(self, desire_helper):
|
||||
self.DH = desire_helper
|
||||
self.turn_direction = custom.TurnDirection.none
|
||||
self.turn_direction = TurnDirection.none
|
||||
self.params = Params()
|
||||
self.lane_turn_value = float(self.params.get("LaneTurnValue", return_default=True)) * CV.MPH_TO_MS
|
||||
self.param_read_counter = 0
|
||||
@@ -33,13 +35,13 @@ class LaneTurnController:
|
||||
|
||||
def update_lane_turn(self, blindspot_left: bool, blindspot_right: bool, left_blinker: bool, right_blinker: bool, v_ego: float) -> None:
|
||||
if left_blinker and not right_blinker and v_ego < self.lane_turn_value and not blindspot_left:
|
||||
self.turn_direction = custom.TurnDirection.turnLeft
|
||||
self.turn_direction = TurnDirection.turnLeft
|
||||
elif right_blinker and not left_blinker and v_ego < self.lane_turn_value and not blindspot_right:
|
||||
self.turn_direction = custom.TurnDirection.turnRight
|
||||
self.turn_direction = TurnDirection.turnRight
|
||||
else:
|
||||
self.turn_direction = custom.TurnDirection.none
|
||||
self.turn_direction = TurnDirection.none
|
||||
|
||||
def get_turn_direction(self):
|
||||
if not self.enabled:
|
||||
return custom.TurnDirection.none
|
||||
return TurnDirection.none
|
||||
return self.turn_direction
|
||||
|
||||
@@ -16,22 +16,24 @@ from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.speed_limit_assist
|
||||
from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.speed_limit_resolver import SpeedLimitResolver
|
||||
from openpilot.sunnypilot.selfdrive.selfdrived.events import EventsSP
|
||||
from openpilot.sunnypilot.models.helpers import get_active_bundle
|
||||
from openpilot.sunnypilot.navd.event_builder import EventBuilder
|
||||
|
||||
DecState = custom.LongitudinalPlanSP.DynamicExperimentalControl.DynamicExperimentalControlState
|
||||
LongitudinalPlanSource = custom.LongitudinalPlanSP.LongitudinalPlanSource
|
||||
|
||||
|
||||
class LongitudinalPlannerSP:
|
||||
def __init__(self, CP: structs.CarParams, mpc):
|
||||
def __init__(self, CP: structs.CarParams, CP_SP: structs.CarParamsSP, mpc):
|
||||
self.events_sp = EventsSP()
|
||||
self.resolver = SpeedLimitResolver()
|
||||
self.dec = DynamicExperimentalController(CP, mpc)
|
||||
self.scc = SmartCruiseControl()
|
||||
self.resolver = SpeedLimitResolver()
|
||||
self.sla = SpeedLimitAssist(CP)
|
||||
self.sla = SpeedLimitAssist(CP, CP_SP)
|
||||
self.generation = int(model_bundle.generation) if (model_bundle := get_active_bundle()) else None
|
||||
self.source = LongitudinalPlanSource.cruise
|
||||
self.e2e_alerts_helper = E2EAlertsHelper()
|
||||
self.event_builder = EventBuilder()
|
||||
|
||||
self.output_v_target = 0.
|
||||
self.output_a_target = 0.
|
||||
@@ -77,10 +79,16 @@ class LongitudinalPlannerSP:
|
||||
self.output_v_target, self.output_a_target = targets[self.source]
|
||||
return self.output_v_target, self.output_a_target
|
||||
|
||||
def update_navigation_events(self, sm: messaging.SubMaster) -> None:
|
||||
nav_events = self.event_builder.update(sm)
|
||||
for event in nav_events:
|
||||
self.events_sp.add(event['name'])
|
||||
|
||||
def update(self, sm: messaging.SubMaster) -> None:
|
||||
self.events_sp.clear()
|
||||
self.dec.update(sm)
|
||||
self.e2e_alerts_helper.update(sm, self.events_sp)
|
||||
self.update_navigation_events(sm)
|
||||
|
||||
def publish_longitudinal_plan_sp(self, sm: messaging.SubMaster, pm: messaging.PubMaster) -> None:
|
||||
plan_sp_send = messaging.new_message('longitudinalPlanSP')
|
||||
|
||||
@@ -4,10 +4,11 @@ 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 enum import IntEnum
|
||||
|
||||
from openpilot.sunnypilot import IntEnumBase
|
||||
|
||||
|
||||
class Policy(IntEnum):
|
||||
class Policy(IntEnumBase):
|
||||
car_state_only = 0
|
||||
map_data_only = 1
|
||||
car_state_priority = 2
|
||||
@@ -15,13 +16,13 @@ class Policy(IntEnum):
|
||||
combined = 4
|
||||
|
||||
|
||||
class OffsetType(IntEnum):
|
||||
class OffsetType(IntEnumBase):
|
||||
off = 0
|
||||
fixed = 1
|
||||
percentage = 2
|
||||
|
||||
|
||||
class Mode(IntEnum):
|
||||
class Mode(IntEnumBase):
|
||||
off = 0
|
||||
information = 1
|
||||
warning = 2
|
||||
|
||||
@@ -5,7 +5,10 @@ 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 custom, car
|
||||
from openpilot.common.constants import CV
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.common import Mode as SpeedLimitMode
|
||||
|
||||
|
||||
def compare_cluster_target(v_cruise_cluster: float, target_set_speed: float, is_metric: bool) -> tuple[bool, bool]:
|
||||
@@ -17,3 +20,25 @@ def compare_cluster_target(v_cruise_cluster: float, target_set_speed: float, is_
|
||||
req_minus = v_cruise_cluster_conv > target_set_speed_conv
|
||||
|
||||
return req_plus, req_minus
|
||||
|
||||
|
||||
def set_speed_limit_assist_availability(CP: car.CarParams, CP_SP: custom.CarParamsSP, params: Params = None) -> bool:
|
||||
if params is None:
|
||||
params = Params()
|
||||
|
||||
is_release = params.get_bool("IsReleaseSpBranch")
|
||||
disallow_in_release = CP.brand == "tesla" and is_release
|
||||
always_disallow = CP.brand == "rivian"
|
||||
allowed = True
|
||||
|
||||
if disallow_in_release or always_disallow:
|
||||
allowed = False
|
||||
|
||||
if not CP.openpilotLongitudinalControl and CP_SP.pcmCruiseSpeed:
|
||||
allowed = False
|
||||
|
||||
if not allowed:
|
||||
if params.get("SpeedLimitMode", return_default=True) == SpeedLimitMode.assist:
|
||||
params.put("SpeedLimitMode", int(SpeedLimitMode.warning))
|
||||
|
||||
return allowed
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user