Compare commits

...

18 Commits

Author SHA1 Message Date
James Vecellio
9e131b4a69 TCPv3 plus gWM9 Model 2025-11-04 18:39:26 -08:00
James Vecellio
f402487f9e This one is actually cool
CgWM + ST
2025-11-04 17:05:46 -08:00
James Vecellio
2c922afa12 tcpv3 and uhhh i forgot the other model
model_checkpoint: merged with 0.875w from (model1: 'fd9a6816-8758-466b-bbde-3c1413b98f0a/400') and (model2: '0e620593-e85f-40c2-9adf-1e945651ed13/400')
2025-11-04 16:01:15 -08:00
discountchubbs
342abcd45a tcpv3 + gwmv9 2025-11-04 15:44:20 -08:00
discountchubbs
6e8586e566 NNMv2 + gWM9 2025-11-04 06:52:47 -08:00
Matt Purnell
071147baaf docs: Update README installation branches and discord links (#1453)
* Use sunnypilot CARS.md, update number of supported cars, add comma

* Update device reference

* Update discord links to forum links

* Update references to -c3-new branches and release

* Update broken link to branches table

* Update README.md

---------

Co-authored-by: DevTekVE <devtekve@gmail.com>
2025-11-03 06:52:17 +01:00
DevTekVE
18af4d6ad6 ui: Fix spacing in sunnylink panel (#1450)
Fix spacing
2025-11-02 20:26:17 +01:00
DevTekVE
b81d5bca3c ui: update discord references and add forum widget (#1440)
* sunnylink: introduce community popup with QR code embedding

- Added `SunnylinkCommunityPopup` widget to promote the sunnypilot Community Forum.
- Integrated a QR code generator and display for quick access.
- Updated `WiFiPromptWidget` to include a "Learn More" button triggering the community popup.

* sunnylink: adjust community popup styling for better layout

- Reduced font size of description text slightly for consistency.
- Decreased QR code dimensions to improve visual balance.

* Making more space out of thin air

* sunnylink: update community references to use forum links

- Replaced Discord links with Community Forum URLs for better alignment.
- Improved clarity in sponsorship instructions.
2025-11-02 06:50:41 +01:00
Amy Jeanes
682d738ffa Tesla: Coop Steering (#1283)
* Tesla: Coop Steering

* bump

* bump

* sync with opendbc/master

* resolve comment

* add oscillation warning and add confirmation

* styling desc

* beta

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-11-01 22:47:30 -04:00
DevTekVE
f60c2b6a83 sunnylink: update uploader button logic to support novice tier and above (#1438)
* sunnylink: update uploader button logic to support novice tier and above

- Adjusted the enable condition to include SponsorTier::Novice and above.

* sunnylink: improve uploader button visibility and accessibility logic

- Made uploader button conditionally visible based on user tier and settings.
- Clarified button label to specify testing purposes only.
2025-11-01 12:14:57 +01:00
DevTekVE
f833819143 ci: update trigger for prebuilt (#1439)
Updated workflow `if` conditions to use `vars.PREBUILT_PR_LABEL`.
2025-10-31 17:54:39 +01:00
THERoenPR
707e2aedae controlsd: add CP_SP to get_pid_accel_limits (#1410)
* Add CP_SP to get_pid_accel_limits() call in controlsd

Match input parameters of CP_SP commit

* bump

* bump

---------

Co-authored-by: roenthomas <43324106+roenthomas@users.noreply.github.com>
Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-10-28 22:13:39 -04:00
DevTekVE
55147d8a55 ci: use environment variable for PR label in query (#1436)
* ci: use environment variable for PR label in query

- Replaced static `PR_LABEL` references with `${{ env.PR_LABEL }}` for consistency.
- Ensures flexibility and reduces hardcoded values in the workflow.

* does this work better?

* fuck this

* aight
2025-10-28 18:58:04 +01:00
DevTekVE
de7acc5466 ci: integrate Discourse notifications and refactor notification logic (#1435)
* ci: integrate Discourse notifications and refactor notification logic

- Replaced Discord webhook notifications with Discourse topic updates.
- Introduced reusable `post-to-discourse` composite action.
- Added `test-discourse.yaml` workflow for debugging and verification.

* ci: adjust notification dependencies and prepare_strategy reference

- Updated `notify` step to depend on `prepare_strategy` instead of `build`.
- Adjusted variable references to use `prepare_strategy` outputs.

* Forcing debug

* ci: update environment variable references and add commit information

- Switched `PUBLIC_REPO_URL` source to environment variable for consistency.
- Added commit SHA variables to enhance template generation logic.

* more tweaks!

* more tweaks!

* bad bot lmao

* Test?

* i mean....

* i mean....

* getting there

* testing the if

* testing the if

* ci: re-enable notify steps for prebuilt workflow

- Uncommented `build` and `publish` dependencies.
- Restored conditional logic to trigger only for relevant events.

* ci: enhance Discourse action to support new topic creation

- Added support for creating new topics with `category-id` and `title`.
- Improved input validation and response handling for flexibility.

* ci: improve conditions for prebuilt workflow notifications

- Refined `if` clause to ensure branches in `DEV_FEEDBACK_NOTIFICATION_BRANCHES` are targeted.
- Adjusted logic for accurate topic ID mapping in Discourse integration.

* forgot to rename
2025-10-28 16:01:21 +01:00
Nayan
e4aada10a4 Bug: Model UI Crash Fix (#1431)
Model UI Crash Fix
2025-10-26 21:43:30 -04:00
James Vecellio-Grant
b460d5804c LiveLocationKalman: skip tests on unsupported msgq (#1407)
* locationd llk: skip tests on unsupported msgq

* Update sunnypilot/selfdrive/locationd/tests/test_locationd.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-25 23:47:37 -04:00
James Vecellio-Grant
eecb8e5c19 models: bump model json to v8 (#1430)
models: bump model json to v8 post release

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
2025-10-25 22:55:26 -04:00
Jason Wen
1a4ea66987 version: bump to 2025.002.000 2025-10-25 22:45:17 -04:00
27 changed files with 502 additions and 71 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -1,3 +1,6 @@
sunnypilot Version 2025.002.000 (2025-xx-xx)
========================
sunnypilot Version 2025.001.000 (2025-10-25)
========================
* 🛠️ Major rewrite

View File

@@ -3,11 +3,9 @@
## 🌞 What is sunnypilot?
[sunnypilot](https://github.com/sunnyhaibin/sunnypilot) is a fork of comma.ai's openpilot, an open source driver assistance system. sunnypilot offers the user a unique driving experience for over 300+ supported car makes and models with modified behaviors of driving assist engagements. sunnypilot complies with comma.ai's safety rules as accurately as possible.
## 💭 Join our Discord
Join the official sunnypilot Discord server to stay up to date with all the latest features and be a part of shaping the future of sunnypilot!
* https://discord.gg/sunnypilot
![](https://dcbadge.vercel.app/api/server/wRW3meAgtx?style=flat) ![Discord Shield](https://discordapp.com/api/guilds/880416502577266699/widget.png?style=shield)
## 💭 Join our Community Forum
Join the official sunnypilot community forum to stay up to date with all the latest features and be a part of shaping the future of sunnypilot!
* https://community.sunnypilot.ai/
## Documentation
https://docs.sunnypilot.ai/ is your one stop shop for everything from features to installation to FAQ about the sunnypilot
@@ -16,13 +14,13 @@ https://docs.sunnypilot.ai/ is your one stop shop for everything from features t
* A supported device to run this software
* a [comma three](https://comma.ai/shop/products/three) or a [C3X](https://comma.ai/shop/comma-3x)
* This software
* One of [the 300+ supported cars](https://github.com/commaai/openpilot/blob/master/docs/CARS.md). We support Honda, Toyota, Hyundai, Nissan, Kia, Chrysler, Lexus, Acura, Audi, VW, Ford and more. If your car is not supported but has adaptive cruise control and lane-keeping assist, it's likely able to run sunnypilot.
* One of [the 325+ supported cars](https://github.com/sunnypilot/sunnypilot/blob/master/docs/CARS.md). We support Honda, Toyota, Hyundai, Nissan, Kia, Chrysler, Lexus, Acura, Audi, VW, Ford, and more. If your car is not supported but has adaptive cruise control and lane-keeping assist, it's likely able to run sunnypilot.
* A [car harness](https://comma.ai/shop/products/car-harness) to connect to your car
Detailed instructions for [how to mount the device in a car](https://comma.ai/setup).
## Installation
Please refer to [Recommended Branches](#-recommended-branches) to find your preferred/supported branch. This guide will assume you want to install the latest `staging-c3-new` branch.
Please refer to [Recommended Branches](#recommended-branches) to find your preferred/supported branch. This guide will assume you want to install the latest `staging` branch.
### If you want to use our newest branches (our rewrite)
> [!TIP]
@@ -31,28 +29,28 @@ Please refer to [Recommended Branches](#-recommended-branches) to find your pref
* sunnypilot not installed or you installed a version before 0.8.17?
1. [Factory reset/uninstall](https://github.com/commaai/openpilot/wiki/FAQ#how-can-i-reset-the-device) the previous software if you have another software/fork installed.
2. After factory reset/uninstall and upon reboot, select `Custom Software` when given the option.
3. Input the installation URL per [Recommended Branches](#-recommended-branches). Example: ```https://staging-c3-new.sunnypilot.ai```.
3. Input the installation URL per [Recommended Branches](#recommended-branches). Example: ```https://staging.sunnypilot.ai```.
4. Complete the rest of the installation following the onscreen instructions.
* sunnypilot already installed and you installed a version after 0.8.17?
1. On the comma three, go to `Settings` ▶️ `Software`.
1. On the comma three/3X, go to `Settings` ▶️ `Software`.
2. At the `Download` option, press `CHECK`. This will fetch the list of latest branches from sunnypilot.
3. At the `Target Branch` option, press `SELECT` to open the Target Branch selector.
4. Scroll to select the desired branch per Recommended Branches (see below). Example: `staging-c3-new`
4. Scroll to select the desired branch per Recommended Branches (see below). Example: `staging`
| Branch | Installation URL |
|:----------------:|:---------------------------------------------:|
| `staging-c3-new` | `https://staging-c3-new.sunnypilot.ai` |
| `dev-c3-new` | `https://dev-c3-new.sunnypilot.ai` |
| `custom-branch` | `https://install.sunnypilot.ai/{branch_name}` |
| `release-c3-new` | **Not yet available**. |
### Recommended Branches
| Branch | Installation URL |
|:---------------:|:---------------------------------------------:|
| `release` | `https://release.sunnypilot.ai` |
| `staging` | `https://staging.sunnypilot.ai` |
| `dev` | `https://dev.sunnypilot.ai` |
| `custom-branch` | `https://install.sunnypilot.ai/{branch_name}` |
> [!TIP]
> You can use sunnypilot/targetbranch as an install URL. Example: 'sunnypilot/staging-c3-new'.
> You can use sunnypilot/targetbranch as an install URL. Example: 'sunnypilot/staging'.
> [!NOTE]
> Do you require further assistance with software installation? Join the [sunnypilot Discord server](https://discord.sunnypilot.com) and message us in the `#installation-help` channel.
> Do you require further assistance with software installation? Join the [sunnypilot community forum](https://community.sunnypilot.ai/new-topic?category=general/qa) and create a topic in the General/Q&A Category channel.
<details>

View File

@@ -1 +1 @@
#define DEFAULT_MODEL "Firehose (Default)"
#define DEFAULT_MODEL "TCPv3 + gWMv9 (Default)"

View File

@@ -208,6 +208,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
{"HyundaiLongitudinalTuning", {PERSISTENT | BACKUP, INT, "0"}},
{"SubaruStopAndGo", {PERSISTENT | BACKUP, BOOL, "0"}},
{"SubaruStopAndGoManualParkingBrake", {PERSISTENT | BACKUP, BOOL, "0"}},
{"TeslaCoopSteering", {PERSISTENT | BACKUP, BOOL, "0"}},
{"DynamicExperimentalControl", {PERSISTENT | BACKUP, BOOL, "0"}},
{"BlindSpot", {PERSISTENT | BACKUP, BOOL, "0"}},

View File

@@ -132,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

View File

@@ -11,17 +11,18 @@ WiFiPromptWidget::WiFiPromptWidget(QWidget *parent) : QFrame(parent) {
main_layout->setContentsMargins(56, 40, 56, 40);
main_layout->setSpacing(42);
QLabel *title = new QLabel(tr("<span style='font-family: \"Noto Color Emoji\";'>🔥</span> Firehose Mode <span style='font-family: Noto Color Emoji;'>🔥</span>"));
title->setStyleSheet("font-size: 64px; font-weight: 500;");
community_popup = new SunnylinkCommunityPopup(this);
QLabel *title = new QLabel(tr("sunnypilot Community"));
title->setStyleSheet("font-size: 56px; font-weight: 500;");
main_layout->addWidget(title);
QLabel *desc = new QLabel(tr("Maximize your training data uploads to improve openpilot's driving models."));
QLabel *desc = new QLabel(tr("Need help or have ideas?<br><b>Join</b> our community now!"));
desc->setStyleSheet("font-size: 40px; font-weight: 400;");
desc->setWordWrap(true);
main_layout->addWidget(desc);
QPushButton *settings_btn = new QPushButton(tr("Open"));
connect(settings_btn, &QPushButton::clicked, [=]() { emit openSettings(1, "FirehosePanel"); });
QPushButton *settings_btn = new QPushButton(tr("Learn More"));
connect(settings_btn, &QPushButton::clicked, [=]() { community_popup->exec(); });
settings_btn->setStyleSheet(R"(
QPushButton {
font-size: 48px;

View File

@@ -3,12 +3,17 @@
#include <QFrame>
#include <QWidget>
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/community_widget.h"
class WiFiPromptWidget : public QFrame {
Q_OBJECT
public:
explicit WiFiPromptWidget(QWidget* parent = 0);
private:
SunnylinkCommunityPopup *community_popup;
signals:
void openSettings(int index = 0, const QString &param = "");
};

View File

@@ -35,6 +35,7 @@ qt_src = [
"sunnypilot/qt/offroad/settings/software_panel.cc",
"sunnypilot/qt/offroad/settings/sunnylink_panel.cc",
"sunnypilot/qt/offroad/settings/sunnylink/sponsor_widget.cc",
"sunnypilot/qt/offroad/settings/sunnylink/community_widget.cc",
"sunnypilot/qt/offroad/settings/trips_panel.cc",
"sunnypilot/qt/offroad/settings/vehicle_panel.cc",
"sunnypilot/qt/offroad/settings/visuals_panel.cc",

View File

@@ -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()) {

View File

@@ -0,0 +1,139 @@
/**
* Copyright (c) 2025-, sunnypilot contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/sunnylink/community_widget.h"
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/util.h"
using qrcodegen::QrCode;
// --- SunnylinkCommunityQRWidget ---
SunnylinkCommunityQRWidget::SunnylinkCommunityQRWidget(QWidget* parent)
: QWidget(parent) {}
void SunnylinkCommunityQRWidget::showEvent(QShowEvent *event) {
updateQrCode(SUNNYLINK_COMMUNITY_URL);
update();
}
void SunnylinkCommunityQRWidget::updateQrCode(const QString &text) {
QrCode qr = QrCode::encodeText(text.toUtf8().data(), QrCode::Ecc::LOW);
qint32 sz = qr.getSize();
QImage im(sz, sz, QImage::Format_RGB32);
QRgb black = qRgb(0, 0, 0);
QRgb white = qRgb(255, 255, 255);
for (int y = 0; y < sz; y++) {
for (int x = 0; x < sz; x++) {
im.setPixel(x, y, qr.getModule(x, y) ? black : white);
}
}
int final_sz = ((width() / sz) - 1) * sz;
img = QPixmap::fromImage(im.scaled(final_sz, final_sz, Qt::KeepAspectRatio), Qt::MonoOnly);
}
void SunnylinkCommunityQRWidget::paintEvent(QPaintEvent *e) {
QPainter p(this);
p.fillRect(rect(), Qt::white);
if (!img.isNull()) {
QSize s = (size() - img.size()) / 2;
p.drawPixmap(s.width(), s.height(), img);
}
}
// --- SunnylinkCommunityPopup ---
QStringList SunnylinkCommunityPopup::getInstructions() {
QStringList instructions;
instructions << tr("Scan the QR code and join us!");
return instructions;
}
SunnylinkCommunityPopup::SunnylinkCommunityPopup(QWidget* parent)
: DialogBase(parent) {
auto *mainLayout = new QVBoxLayout(this);
mainLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->setSpacing(0);
// Solarized Light base3 background
setStyleSheet("SunnylinkCommunityPopup { background-color: #FDF6E3; }");
// Header spanning full width
auto headerWidget = new QWidget(this);
auto headerLayout = new QHBoxLayout(headerWidget);
headerLayout->setContentsMargins(85, 50, 85, 30);
headerLayout->setSpacing(30);
auto close = new QPushButton(QIcon(":/icons/close.svg"), "", this);
close->setIconSize(QSize(80, 80));
close->setStyleSheet("border: none;");
connect(close, &QPushButton::clicked, this, &QDialog::reject);
headerLayout->addWidget(close, 0, Qt::AlignLeft | Qt::AlignVCenter);
const auto title = new QLabel(tr("Join the sunnypilot Community Forum"), this);
// Solarized base02 for text
title->setStyleSheet("font-size: 65px; color: #073642;");
title->setWordWrap(false);
title->setAlignment(Qt::AlignCenter);
headerLayout->addWidget(title, 1);
// Spacer to balance the close button on the right
auto spacer = new QWidget(this);
spacer->setFixedSize(80, 80);
headerLayout->addWidget(spacer, 0);
mainLayout->addWidget(headerWidget);
// Two-column content layout
auto contentLayout = new QHBoxLayout();
contentLayout->setContentsMargins(0, 0, 0, 0);
contentLayout->setSpacing(0);
mainLayout->addLayout(contentLayout, 66);
// Left side: description
auto leftLayout = new QVBoxLayout();
leftLayout->setContentsMargins(85, 40, 50, 70);
leftLayout->setSpacing(35);
contentLayout->addLayout(leftLayout, 40);
// Hype / intro paragraph
const auto desc = new QLabel(tr(
"We're excited to announce our <b>sunnypilot Community Forum</b><br><br>"
"Over the years, Discord just hasn't scaled well for our growing community.<br>"
"It's noisy, unsearchable, and great discussions disappear too easily.<br>"
"Our new community forum aims to fix that by making it easier to <b>find answers, share ideas, track feedback, report bugs, help newcomers</b> and more!<br><br>"
"<b>Here's what's waiting for you:</b><br>"
"• Fully <b>indexable</b> and discoverable through search engines 🔎<br>"
"• <b>AI-powered</b>🤖 topic and chat summaries, spam detection, and more<br>"
"• A <b>trust-level system</b>✅ that rewards meaningful contributions<br>"
"• Designed to work <b>on your own time</b>.🧘<br><br>"
"Scan the QR code on the right and join the discussion!"
), this);
// Solarized base01 for body text
desc->setStyleSheet("font-size: 40px; color: #586E75;");
desc->setWordWrap(true);
leftLayout->addWidget(desc);
leftLayout->addStretch();
// Right side: QR code and instructions
auto rightLayout = new QVBoxLayout();
rightLayout->setContentsMargins(50, 40, 85, 70);
rightLayout->setSpacing(40);
contentLayout->addLayout(rightLayout, 1);
// QR code (smaller, fixed size)
auto *qr = new SunnylinkCommunityQRWidget(this);
qr->setFixedSize(500, 500);
rightLayout->addStretch();
rightLayout->addWidget(qr, 0, Qt::AlignCenter);
rightLayout->addStretch();
}

View File

@@ -0,0 +1,40 @@
/**
* Copyright (c) 2025-, sunnypilot contributors.
*
* This file is part of sunnypilot and is licensed under the MIT License.
* See the LICENSE.md file in the root directory for more details.
*/
#pragma once
#include <QrCode.hpp>
#include <QtCore/qjsonobject.h>
#include "common/util.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/controls.h"
const QString SUNNYLINK_COMMUNITY_URL = "https://community.sunnypilot.ai/sp-qr";
class SunnylinkCommunityQRWidget : public QWidget {
Q_OBJECT
public:
explicit SunnylinkCommunityQRWidget(QWidget* parent = nullptr);
void paintEvent(QPaintEvent*) override;
private:
QPixmap img;
void updateQrCode(const QString &text);
void showEvent(QShowEvent *event) override;
};
// Popup widget
class SunnylinkCommunityPopup : public DialogBase {
Q_OBJECT
public:
explicit SunnylinkCommunityPopup(QWidget* parent = nullptr);
private:
static QStringList getInstructions();
};

View File

@@ -79,11 +79,11 @@ QStringList SunnylinkSponsorPopup::getInstructions(bool sponsor_pair) {
instructions << tr("Scan the QR code to login to your GitHub account")
<< tr("Follow the prompts to complete the pairing process")
<< tr("Re-enter the \"sunnylink\" panel to verify sponsorship status")
<< tr("If sponsorship status was not updated, please contact a moderator on Discord at https://discord.gg/sunnypilot");
<< tr("If sponsorship status was not updated, please contact a moderator on our forum at https://community.sunnypilot.ai");
} else {
instructions << tr("Scan the QR code to visit sunnyhaibin's GitHub Sponsors page")
<< tr("Choose your sponsorship tier and confirm your support")
<< tr("Join our community on Discord at https://discord.gg/sunnypilot and reach out to a moderator to confirm your sponsor status");
<< tr("Join our Community Forum at https://community.sunnypilot.ai and reach out to a moderator if you have issues");
}
return instructions;
}

View File

@@ -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 (infrastructure test)"),
sunnylinkUploaderDesc,
"", nullptr, true);
list->addItem(sunnylinkUploaderEnabledBtn);
@@ -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("");

View File

@@ -8,7 +8,41 @@
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/vehicle/tesla_settings.h"
TeslaSettings::TeslaSettings(QWidget *parent) : BrandSettingsInterface(parent) {
constexpr int coopSteeringMinKmh = 23; // minimum speed for cooperative steering (enforced by Tesla firmware)
constexpr int oemSteeringMinKmh = 48; // minimum speed for OEM lane departure avoidance (enforced by Tesla firmware)
bool is_metric = params.getBool("IsMetric");
QString unit = is_metric ? "km/h" : "mph";
int display_value_coop;
int display_value_oem;
if (is_metric) {
display_value_coop = coopSteeringMinKmh;
display_value_oem = oemSteeringMinKmh;
} else {
display_value_coop = static_cast<int>(std::round(coopSteeringMinKmh * KM_TO_MILE));
display_value_oem = static_cast<int>(std::round(oemSteeringMinKmh * KM_TO_MILE));
}
const QString coop_desc = QString("<b>%1</b><br><br>"
"%2<br>"
"%3<br>")
.arg(tr("Warning: May experience steering oscillations below %5 %6 during turns, recommend disabling this feature if you experience these."))
.arg(tr("Allows the driver to provide limited steering input while openpilot is engaged."))
.arg(tr("Only works above %4 %6."))
.arg(display_value_coop)
.arg(display_value_oem)
.arg(unit);
coopSteeringToggle = new ParamControlSP(
"TeslaCoopSteering",
tr("Cooperative Steering (Beta)"),
coop_desc,
"",
this
);
list->addItem(coopSteeringToggle);
coopSteeringToggle->showDescription();
coopSteeringToggle->setConfirmation(true, false);
}
void TeslaSettings::updateSettings() {
coopSteeringToggle->setEnabled(offroad);
}

View File

@@ -22,5 +22,5 @@ public:
void updateSettings() override;
private:
bool offroad = false;
ParamControlSP *coopSteeringToggle = nullptr;
};

View File

@@ -1 +1 @@
#define SUNNYPILOT_VERSION "2025.001.000"
#define SUNNYPILOT_VERSION "2025.002.000"

View File

@@ -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

View File

@@ -1 +1 @@
70406ab4dd66d0e384734a8a56632ae4a62bc9670c2e630a0f71588c4e212cd8
5584d697233d147e0b6402e485b7cbf8fdddb70bde4b9e3b2f6919ed5f69475f

View File

@@ -115,4 +115,9 @@ def initialize_params(params) -> list[dict[str, Any]]:
"SubaruStopAndGoManualParkingBrake",
])
# tesla
keys.extend([
"TeslaCoopSteering",
])
return [{k: params.get(k, return_default=True)} for k in keys]

View File

@@ -1,4 +1,5 @@
import pytest
import platform
import json
import random
import time
@@ -12,6 +13,10 @@ from openpilot.common.transformations.coordinates import ecef2geodetic
from openpilot.system.manager.process_config import managed_processes
if platform.system() == 'Darwin':
pytest.skip("Skipping locationd test on macOS due to unsupported msgq.", allow_module_level=True)
class TestLocationdProc:
LLD_MSGS = ['gpsLocationExternal', 'cameraOdometry', 'carState', 'liveCalibration',
'accelerometer', 'gyroscope', 'magnetometer']