Merge branch 'master-new' of https://github.com/sunnypilot/sunnypilot into master-new

This commit is contained in:
infiniteCable2
2025-04-20 14:18:56 +02:00
9 changed files with 200 additions and 89 deletions
+1
View File
@@ -1,3 +1,4 @@
Wen
REGIST
PullRequest
cancelled
+5 -1
View File
@@ -1,7 +1,11 @@
CI / testing:
ci:
- changed-files:
- any-glob-to-all-files: "{.github/**,**/test_*,Jenkinsfile}"
chore:
- changed-files:
- any-glob-to-all-files: "{.github/**}"
car:
- changed-files:
- any-glob-to-all-files: '{selfdrive/car/**,opendbc_repo}'
+58 -19
View File
@@ -9,6 +9,7 @@ jobs:
permissions:
contents: read
pull-requests: write
issues: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -28,25 +29,63 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
target: /^(?!master$).*/
exclude: /commaai:.*/
target: /^(?!master-new$).*/
exclude: /sunnypilot:.*/
change-to: ${{ github.base_ref }}
already-exists-action: close_this
already-exists-comment: "Your PR should be made against the `master` branch"
already-exists-comment: "Your PR should be made against the `master-new` branch"
# Welcome comment
- name: "First timers PR"
uses: actions/first-interaction@v1
if: github.event.pull_request.head.repo.full_name != 'sunnypilot/sunnypilot'
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
pr-message: |
<!-- _(run_id **${{ github.run_id }}**)_ -->
Thanks for contributing to openpilot! In order for us to review your PR as quickly as possible, check the following:
* Convert your PR to a draft unless it's ready to review
* Read the [contributing docs](https://github.com/sunnypilot/sunnypilot/blob/master/docs/CONTRIBUTING.md)
* Before marking as "ready for review", ensure:
* the goal is clearly stated in the description
* all the tests are passing
* the change is [something we merge](https://github.com/sunnypilot/sunnypilot/blob/master/docs/CONTRIBUTING.md#what-gets-merged)
* include a route or your device' dongle ID if relevant
update-pr-labels:
name: Update fork's PR Labels
runs-on: ubuntu-latest
if: (github.event.pull_request.head.repo.fork && (contains(github.event_name, 'pull_request') && github.event.action == 'synchronize'))
env:
PR_LABEL: 'dev-c3'
TRUST_FORK_PR_LABEL: 'trust-fork-pr'
steps:
- name: Check if PR has dev-c3 label
id: check-labels
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const prNumber = context.payload.pull_request.number;
const { data: labels } = await github.rest.issues.listLabelsOnIssue({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber
});
const hasDevC3Label = labels.some(label => label.name === process.env.PR_LABEL);
const hasTrustLabel = labels.some(label => label.name === process.env.TRUST_FORK_PR_LABEL);
console.log(`PR #${prNumber} has ${process.env.PR_LABEL} label: ${hasDevC3Label}`);
console.log(`PR #${prNumber} has ${process.env.TRUST_FORK_PR_LABEL} label: ${hasTrustLabel}`);
core.setOutput('has-dev-c3', hasDevC3Label ? 'true' : 'false');
core.setOutput('has-trust', hasTrustLabel ? 'true' : 'false');
- name: Remove trust-fork-pr label if present
if: steps.check-labels.outputs.has-dev-c3 == 'true' && steps.check-labels.outputs.has-trust == 'true'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const prNumber = context.payload.pull_request.number;
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
name: process.env.TRUST_FORK_PR_LABEL
});
console.log(`Removed '${process.env.TRUST_FORK_PR_LABEL}' label from PR #${prNumber} as it received new commits`);
// Add a comment to the PR
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: `The \`${process.env.TRUST_FORK_PR_LABEL}\` label has been automatically removed because new commits were pushed to this PR. This PR will need to be re-reviewed before the label can be applied again.`
});
@@ -21,15 +21,30 @@ on:
push:
branches: [ master, master-new, master-dev-c3-new ]
tags: [ '*' ]
pull_request_target:
types: [ labeled ]
workflow_dispatch:
inputs:
extra_version:
description: 'Extra version identifier'
wait_for_tests:
description: 'Wait for selfdrive_tests to finish'
required: false
default: ''
type: boolean
default: false
jobs:
validate_tests:
runs-on: ubuntu-24.04
if: ((github.event_name == 'workflow_dispatch' && inputs.wait_for_tests) || contains(github.event_name, 'pull_request') && (github.event.action == 'labeled' && github.event.label.name == 'prebuilt'))
steps:
- uses: actions/checkout@v4
- name: Wait for Tests
uses: ./.github/workflows/wait-for-action # Path to where you place the action
with:
workflow: selfdrive_tests.yaml # The workflow file to monitor
github-token: ${{ secrets.GITHUB_TOKEN }}
build:
needs: [ validate_tests ]
concurrency:
group: build-${{ github.head_ref || github.ref_name }}
cancel-in-progress: false
@@ -39,6 +54,7 @@ jobs:
version: ${{ steps.set-env.outputs.version }}
extra_version_identifier: ${{ steps.set-env.outputs.extra_version_identifier }}
commit_sha: ${{ steps.set-env.outputs.commit_sha }}
if: always() && !failure() && !cancelled()
steps:
- uses: actions/checkout@v4
with:
@@ -194,10 +210,10 @@ jobs:
concurrency:
group: publish-${{ github.head_ref || github.ref_name }}
cancel-in-progress: true
if: ${{ github.event_name != 'pull_request' || github.event_name == 'pull_request' && github.event.pull_request.draft }}
if: ${{ !contains(github.event_name, 'pull_request') || (github.event.action == 'labeled' && github.event.label.name == 'prebuilt') }}
needs: build
runs-on: ubuntu-24.04
environment: ${{ contains(fromJSON(vars.AUTO_DEPLOY_PREBUILT_BRANCHES), github.head_ref || github.ref_name) && 'auto-deploy' || 'feature-branch' }}
environment: ${{ (contains(fromJSON(vars.AUTO_DEPLOY_PREBUILT_BRANCHES), github.head_ref || github.ref_name) || contains(github.event.pull_request.labels.*.name, 'prebuilt')) && 'auto-deploy' || 'feature-branch' }}
steps:
- uses: actions/checkout@v4
@@ -268,4 +284,27 @@ jobs:
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}
shell: alpine.sh {0}
manage-pr-labels:
name: Remove prebuilt label
runs-on: ubuntu-latest
if: (always() && contains(github.event_name, 'pull_request') && (github.event.action == 'labeled' && github.event.label.name == 'prebuilt'))
env:
LABEL: prebuilt
steps:
- name: Remove trust-fork-pr label if present
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const prNumber = context.payload.pull_request.number;
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
name: process.env.LABEL
});
console.log(`Removed '${process.env.LABEL}' label from PR #${prNumber}`);
@@ -4,7 +4,6 @@ env:
DEFAULT_SOURCE_BRANCH: "master-new"
DEFAULT_TARGET_BRANCH: "master-dev-c3-new"
PR_LABEL: "dev-c3"
TRUST_FORK_PR_LABEL: "trust-fork-pr"
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'
@@ -14,7 +13,7 @@ on:
- master
- master-new
pull_request_target:
types: [ synchronize, opened, labeled ]
types: [ synchronize, labeled ]
branches:
- 'master'
- 'master-new'
@@ -31,65 +30,19 @@ on:
default: 'master-dev-c3-new'
type: string
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: ${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
run-name: ${{ contains(github.event_name, 'pull_request') && github.event.action == 'synchronize' && format('{0} - {1}', github.workflow, github.event.action) || format('{0} - {1}', github.workflow, github.event_name) }}
jobs:
manage-pr-labels:
name: Remove trust-fork-pr label if present
runs-on: ubuntu-latest
if: (github.event.pull_request.head.repo.fork && (github.event_name == 'pull_request_target' && github.event.action == 'synchronize'))
steps:
- name: Check if PR has dev-c3 label
id: check-labels
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const prNumber = context.payload.pull_request.number;
const { data: labels } = await github.rest.issues.listLabelsOnIssue({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber
});
const hasDevC3Label = labels.some(label => label.name === process.env.PR_LABEL);
const hasTrustLabel = labels.some(label => label.name === process.env.TRUST_FORK_PR_LABEL);
console.log(`PR #${prNumber} has ${process.env.PR_LABEL} label: ${hasDevC3Label}`);
console.log(`PR #${prNumber} has ${process.env.TRUST_FORK_PR_LABEL} label: ${hasTrustLabel}`);
core.setOutput('has-dev-c3', hasDevC3Label ? 'true' : 'false');
core.setOutput('has-trust', hasTrustLabel ? 'true' : 'false');
- name: Remove trust-fork-pr label if present
if: steps.check-labels.outputs.has-dev-c3 == 'true' && steps.check-labels.outputs.has-trust == 'true'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const prNumber = context.payload.pull_request.number;
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
name: process.env.TRUST_FORK_PR_LABEL
});
console.log(`Removed '${process.env.TRUST_FORK_PR_LABEL}' label from PR #${prNumber} as it received new commits`);
// Add a comment to the PR
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: `The \`${process.env.TRUST_FORK_PR_LABEL}\` label has been automatically removed because new commits were pushed to this PR. This PR will need to be re-reviewed before the label can be applied again.`
});
reset-and-squash:
runs-on: ubuntu-latest
if: (
(github.event_name == 'workflow_dispatch')
|| (github.event_name == 'push' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch))
|| (github.event_name == 'pull_request_target' && ((github.event.action == 'labeled' && (github.event.label.name == 'dev-c3' || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, 'dev-c3'))))
|| (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == 'dev-c3' || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, 'dev-c3'))))
)
steps:
- uses: actions/checkout@v4
@@ -101,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))
|| (github.event_name == 'pull_request_target' && ((github.event.action == 'labeled' && (github.event.label.name == 'dev-c3' || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, 'dev-c3'))))
|| (contains(github.event_name, 'pull_request') && ((github.event.action == 'labeled' && (github.event.label.name == 'dev-c3' || github.event.label.name == 'trust-fork-pr') && contains(github.event.pull_request.labels.*.name, 'dev-c3'))))
)
with:
workflow: selfdrive_tests.yaml # The workflow file to monitor
+28 -4
View File
@@ -76,9 +76,36 @@ def validate_pr(pr):
if not commits:
return False, "no commit data found"
# First check if we have the rollup status
status = commits[0].get('commit', {}).get('statusCheckRollup', {})
# If status is not SUCCESS, we need to check individual check runs
if not status or status.get('state') != 'SUCCESS':
return False, "not all checks have passed"
# Get detailed check runs for this PR
checks_output = subprocess.run(
['gh', 'pr', 'checks', str(pr_number), '--json', 'name,state'],
capture_output=True, text=True
)
try:
checks_data = json.loads(checks_output.stdout)
# Check if all checks are successful except for our reset-and-squash check
for check in checks_data:
check_name = check.get('name', '')
check_state = check.get('state', '')
# Skip our own check and any skipped checks
if check_name == 'reset-and-squash' or check_state == 'SKIPPED':
continue
# If any other check is not successful, the PR is not valid
if check_state != 'SUCCESS':
return False, f"check '{check_name}' has state '{check_state}'"
except json.JSONDecodeError:
# If we can't parse the JSON, fall back to the original check
return False, "unable to verify check status"
# Check for merge conflicts
merge_status = subprocess.run(['gh', 'pr', 'view', str(pr_number), '--json', 'mergeable,mergeStateStatus'],
@@ -87,9 +114,6 @@ def validate_pr(pr):
if not merge_data.get('mergeable'):
return False, "merge conflicts detected"
# if (mergeStateStatus := merge_data.get('mergeStateStatus')) == "BEHIND":
# return False, f"branch is `{mergeStateStatus}`"
return True, None
@@ -7,6 +7,8 @@ See the LICENSE.md file in the root directory for more details.
from json import load
import numpy as np
from openpilot.selfdrive.modeld.parse_model_outputs import safe_exp
# dict used to rename activation functions whose names aren't valid python identifiers
ACTIVATION_FUNCTION_NAMES = {'σ': 'sigmoid'}
@@ -40,7 +42,7 @@ class NNTorqueModel:
# These are called by name using the keys in the model json file
@staticmethod
def sigmoid(x):
return 1 / (1 + np.exp(-x))
return 1 / (1 + safe_exp(-x))
@staticmethod
def identity(x):
+17 -1
View File
@@ -12,7 +12,7 @@ from opendbc.car.interfaces import LatControlInputs
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.common.params import Params
from openpilot.selfdrive.modeld.constants import ModelConstants
from openpilot.sunnypilot.selfdrive.controls.lib.latcontrol_torque_ext_base import LatControlTorqueExtBase
from openpilot.sunnypilot.selfdrive.controls.lib.latcontrol_torque_ext_base import LatControlTorqueExtBase, sign
from openpilot.sunnypilot.selfdrive.controls.lib.nnlc.helpers import MOCK_MODEL_PATH
from openpilot.sunnypilot.selfdrive.controls.lib.nnlc.model import NNTorqueModel
@@ -99,6 +99,22 @@ class NeuralNetworkLateralControl(LatControlTorqueExtBase):
torque_from_measurement = self.model.evaluate(nnff_measurement_input)
self._pid_log.error = torque_from_setpoint - torque_from_measurement
# The "pure" NNLC error response can be too weak for cars whose models were trained
# with a lack of high-magnitude lateral acceleration data, for which the NNLC model
# torque response flattens out at high lateral accelerations.
# This workaround blends in a guaranteed stronger error response only when the
# desired lateral acceleration is high enough to warrant it, by using the lateral acceleration
# error as the input to the NNLC model. This is not ideal, and potentially degrades the NNLC
# accuracy for cars that don't have this issue, but it's necessary until a better NNLC model
# structure is used that doesn't create this issue when high-magnitude data is missing.
error_blend_factor = float(np.interp(abs(self._desired_lateral_accel), [1.0, 2.0], [0.0, 1.0]))
if error_blend_factor > 0.0: # blend in stronger error response when in high lat accel
# NNFF inputs 5+ are optional, and if left out are replaced with 0.0 inside the NNFF class
nnff_error_input = [CS.vEgo, self._setpoint - self._measurement, self.lateral_jerk_setpoint - self.lateral_jerk_measurement, 0.0]
torque_from_error = self.model.evaluate(nnff_error_input)
if sign(self._pid_log.error) == sign(torque_from_error) and abs(self._pid_log.error) < abs(torque_from_error):
self._pid_log.error = self._pid_log.error * (1.0 - error_blend_factor) + torque_from_error * error_blend_factor
# compute feedforward (same as nn setpoint output)
friction_input = self.update_friction_input(self._setpoint, self._measurement)
nn_input = [CS.vEgo, self._desired_lateral_accel, friction_input, roll] \
@@ -1,6 +1,7 @@
import numpy as np
from parameterized import parameterized
from cereal import car, log
from cereal import car, log, messaging
from opendbc.car.car_helpers import interfaces
from opendbc.car.honda.values import CAR as HONDA
from opendbc.car.hyundai.values import CAR as HYUNDAI
@@ -12,6 +13,30 @@ from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque
from openpilot.selfdrive.locationd.helpers import Pose
from openpilot.common.mock.generators import generate_livePose
from openpilot.sunnypilot.selfdrive.car import interfaces as sunnypilot_interfaces
from openpilot.selfdrive.modeld.constants import ModelConstants
def generate_modelV2():
model = messaging.new_message('modelV2')
position = log.XYZTData.new_message()
speed = 30
position.x = [float(x) for x in (speed + 0.5) * np.array(ModelConstants.T_IDXS)]
model.modelV2.position = position
orientation = log.XYZTData.new_message()
curvature = 0.05
orientation.x = [float(curvature) for _ in ModelConstants.T_IDXS]
orientation.y = [0.0 for _ in ModelConstants.T_IDXS]
model.modelV2.orientation = orientation
velocity = log.XYZTData.new_message()
velocity.x = [float(x) for x in (speed + 0.5) * np.ones_like(ModelConstants.T_IDXS)]
velocity.x[0] = float(speed) # always start at current speed
model.modelV2.velocity = velocity
acceleration = log.XYZTData.new_message()
acceleration.x = [float(x) for x in np.zeros_like(ModelConstants.T_IDXS)]
acceleration.y = [float(y) for y in np.zeros_like(ModelConstants.T_IDXS)]
model.modelV2.acceleration = acceleration
return model
class TestNeuralNetworkLateralControl:
@@ -42,15 +67,23 @@ class TestNeuralNetworkLateralControl:
lp = generate_livePose()
pose = Pose.from_live_pose(lp.livePose)
mdl = generate_modelV2()
sm = {'modelV2': mdl.modelV2}
model_v2 = sm['modelV2']
controller.extension.model_v2 = model_v2
# Saturate for curvature limited and controller limited
for _ in range(1000):
controller.extension.update_model_v2(model_v2)
_, _, lac_log = controller.update(True, CS, VM, params, False, 0, pose, True)
assert lac_log.saturated
for _ in range(1000):
controller.extension.update_model_v2(model_v2)
_, _, lac_log = controller.update(True, CS, VM, params, False, 0, pose, False)
assert not lac_log.saturated
for _ in range(1000):
controller.extension.update_model_v2(model_v2)
_, _, lac_log = controller.update(True, CS, VM, params, False, 1, pose, False)
assert lac_log.saturated