mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-10-21 11:33:44 +08:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e87a605fb2 | |||
| 11603b2de7 | |||
| 644e23f8ef | |||
| 7847dbf148 | |||
| 0af369cae0 | |||
| 77d0c2d665 | |||
| 4d2d3f69f5 | |||
| e9fc7a2465 | |||
| 61154655cd | |||
| ed4e8aeea6 | |||
| a3ed6bd2f0 | |||
| 46c0b31997 | |||
| 8a50860648 | |||
| 563fa3c782 | |||
| 0b3459fbdc | |||
| 54a22bd8bd | |||
| 6c674810ee | |||
| e64f67ec7e | |||
| 9c7655c45e | |||
| 60c6464a14 | |||
| 922513f312 | |||
| 0dacd0eec4 | |||
| 09b2f4bbe0 | |||
| 0120b66f05 | |||
| f09b04c267 | |||
| f0617e11a6 | |||
| bf93807e7b | |||
| f54f2d462e | |||
| 80494ec3cf |
@@ -115,7 +115,7 @@ jobs:
|
||||
env ${TG_FLAGS} python3 ${{ github.workspace }}/${{ env.COMPILER }}/compile_onnx.py \
|
||||
${{ github.workspace }}/${{ env.SMALL_ONNX }} \
|
||||
${{ github.workspace }}/${{ env.SMALL_PKL }} \
|
||||
--device-input "*" --out-of-band --benchmark-runs 1
|
||||
--out-of-band --benchmark-runs 1
|
||||
|
||||
- name: Prepare output
|
||||
env:
|
||||
@@ -440,9 +440,10 @@ jobs:
|
||||
TG_FLAGS="DEV=QCOM IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1"
|
||||
|
||||
taskset -c 7 env ${TG_FLAGS} python3 \
|
||||
${{ github.workspace }}/tinygrad_repo/examples/openpilot/compile3.py \
|
||||
${{ github.workspace }}/tinygrad_repo/examples/openpilot/compile_onnx.py \
|
||||
${{ github.workspace }}/${{ env.DM_ONNX }} \
|
||||
${{ github.workspace }}/${{ env.DM_PKL }}
|
||||
${{ github.workspace }}/${{ env.DM_PKL }} \
|
||||
--out-of-band --benchmark-runs 1
|
||||
|
||||
- name: Compile DM warp
|
||||
run: |
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
|
||||
- name: LFS Fetch
|
||||
run: |
|
||||
git lfs fetch
|
||||
git lfs fetch || echo "::warning::some objects are missing on commaai/openpilot-lfs; mirroring the rest"
|
||||
|
||||
- name: Set up Git
|
||||
run: |
|
||||
@@ -68,6 +68,7 @@ jobs:
|
||||
git config lfs.activitytimeout 300
|
||||
git config lfs.transfer.maxretries 5
|
||||
git config lfs.concurrenttransfers 4
|
||||
git config lfs.allowincompletepush true
|
||||
|
||||
- name: Push LFS
|
||||
id: sync-and-commit
|
||||
@@ -81,3 +82,80 @@ jobs:
|
||||
done
|
||||
echo "All push attempts failed"
|
||||
exit 1
|
||||
|
||||
- name: Checkout sunnypilot
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: 'sunnypilot/sunnypilot'
|
||||
path: sunnypilot
|
||||
fetch-depth: 0
|
||||
filter: blob:limit=1k
|
||||
lfs: false
|
||||
|
||||
- name: Reference objects on sunnypilot-lfs
|
||||
env:
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
GIT_LFS_SKIP_SMUDGE=1 git clone --quiet https://huggingface.co/sunnypilot/sunnypilot-lfs hf-lfs
|
||||
mkdir -p hf-lfs/all
|
||||
cat > scan_pointers.py <<'EOF'
|
||||
import re, subprocess, sys, threading
|
||||
repo = sys.argv[1]
|
||||
chk = subprocess.run(["git", "-C", repo, "cat-file", "--batch-all-objects",
|
||||
"--batch-check=%(objectname) %(objecttype) %(objectsize)"],
|
||||
capture_output=True, text=True, check=True).stdout
|
||||
small = [f[0] for f in (l.split() for l in chk.splitlines()) if f[1] == "blob" and int(f[2]) < 1024]
|
||||
p = subprocess.Popen(["git", "-C", repo, "cat-file", "--batch=%(objectsize)"],
|
||||
stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
||||
def feed():
|
||||
for sha in small:
|
||||
p.stdin.write(sha.encode() + b"\n")
|
||||
p.stdin.close()
|
||||
threading.Thread(target=feed, daemon=True).start()
|
||||
rx = re.compile(rb"^version https://git-lfs\.github\.com/spec/v1\noid sha256:([0-9a-f]{64})\nsize ([0-9]+)\n")
|
||||
seen = {}
|
||||
for _ in small:
|
||||
n = int(p.stdout.readline()); body = p.stdout.read(n); p.stdout.read(1)
|
||||
m = rx.match(body)
|
||||
if m:
|
||||
seen.setdefault(m.group(1).decode(), m.group(2).decode())
|
||||
for oid, size in sorted(seen.items()):
|
||||
print(oid, size)
|
||||
EOF
|
||||
for repo in . sunnypilot; do
|
||||
python3 scan_pointers.py "$repo"
|
||||
done | sort -u | while read -r oid size; do
|
||||
[ -e "hf-lfs/all/$oid" ] && continue
|
||||
printf 'version https://git-lfs.github.com/spec/v1\noid sha256:%s\nsize %s\n' "$oid" "$size" > "hf-lfs/all/$oid"
|
||||
echo "$oid"
|
||||
done > new_oids.txt
|
||||
echo "new pointer files: $(wc -l < new_oids.txt)"
|
||||
|
||||
dropped=0
|
||||
while mapfile -t -n 100 chunk && ((${#chunk[@]})); do
|
||||
body=$(for oid in "${chunk[@]}"; do
|
||||
size=$(awk '/^size /{print $2}' "hf-lfs/all/$oid")
|
||||
printf '{"oid":"%s","size":%s}\n' "$oid" "$size"
|
||||
done | jq -s '{operation:"download",transfers:["basic"],objects:.}')
|
||||
for attempt in 1 2 3 4 5; do
|
||||
resp=$(curl -sS -u "user:${HF_TOKEN}" -w '\n%{http_code}' \
|
||||
-H 'Accept: application/vnd.git-lfs+json' -H 'Content-Type: application/vnd.git-lfs+json' \
|
||||
--data "$body" "${LFS_URL}/objects/batch") && [ "$(tail -n1 <<<"$resp")" = 200 ] && break
|
||||
echo "batch check attempt $attempt failed ($(tail -n1 <<<"$resp")), retrying in 60s"; sleep 60
|
||||
done
|
||||
[ "$(tail -n1 <<<"$resp")" = 200 ] || { echo "batch check failed"; exit 1; }
|
||||
for oid in $(sed '$d' <<<"$resp" | jq -r '.objects[] | select(.error) | .oid'); do
|
||||
rm -f "hf-lfs/all/$oid"; dropped=$((dropped+1))
|
||||
done
|
||||
done < new_oids.txt
|
||||
echo "dropped (not on HF): $dropped"
|
||||
|
||||
cd hf-lfs
|
||||
git add -A all
|
||||
if git diff --cached --quiet; then
|
||||
echo "nothing new to reference"
|
||||
exit 0
|
||||
fi
|
||||
git commit -q -m "Reference $(git diff --cached --name-only | wc -l | tr -d ' ') new LFS objects"
|
||||
git push origin HEAD:main
|
||||
|
||||
@@ -594,8 +594,14 @@ jobs:
|
||||
- name: Publish chestnut branch
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
||||
run: |
|
||||
CHESTNUT_BRANCH="${{ needs.prepare_strategy.outputs.new_branch }}-chestnut"
|
||||
cp .lfsconfig "${{ env.OUTPUT_DIR }}/"
|
||||
echo 'openpilot/selfdrive/modeld/models/big_*_tinygrad.pkl filter=lfs diff=lfs merge=lfs -text' > "${{ env.OUTPUT_DIR }}/.gitattributes"
|
||||
git lfs install
|
||||
git config --global credential.helper store
|
||||
echo "https://user:${HF_TOKEN}@huggingface.co" > ~/.git-credentials
|
||||
|
||||
${{ env.CI_DIR }}/publish.sh \
|
||||
"${{ github.workspace }}" \
|
||||
|
||||
@@ -465,6 +465,10 @@ struct ModelDataV2SP @0xa1680744031fdb2d {
|
||||
laneTurnDirection @0 :TurnDirection;
|
||||
leftLaneChangeEdgeBlock @1 :Bool;
|
||||
rightLaneChangeEdgeBlock @2 :Bool;
|
||||
lanefulCurvature @3 :Float32;
|
||||
lanefulTrust @4 :Float32;
|
||||
modelCurvature @5 :Float32;
|
||||
lanefulGoingAround @6 :Bool;
|
||||
|
||||
enum TurnDirection {
|
||||
none @0;
|
||||
|
||||
@@ -94,7 +94,7 @@ _services: dict[str, tuple] = {
|
||||
"carControlSP": (True, 100., 10),
|
||||
"carStateSP": (True, 100., 10),
|
||||
"liveMapDataSP": (True, 1., 1),
|
||||
"modelDataV2SP": (True, 20., None, QueueSize.BIG),
|
||||
"modelDataV2SP": (True, 20., 10, QueueSize.BIG),
|
||||
"liveLocationKalman": (True, 20.),
|
||||
|
||||
# debug
|
||||
|
||||
@@ -256,6 +256,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"LagdValueCache", {PERSISTENT, FLOAT, "0.2"}},
|
||||
{"LaneTurnDesire", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"LaneTurnValue", {PERSISTENT | BACKUP, FLOAT, "19.0"}},
|
||||
{"Laneful", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"PlanplusControl", {PERSISTENT | BACKUP, FLOAT, "1.0"}},
|
||||
|
||||
// mapd
|
||||
|
||||
@@ -48,8 +48,9 @@ def compile_model(onnx_path, pkl_path):
|
||||
Action(cmd, " [ONNX] $TARGET"),
|
||||
)
|
||||
|
||||
compile_model('models/dmonitoring_model.onnx', 'models/dmonitoring_model_tinygrad.pkl')
|
||||
compile_model('models/driving_supercombo.onnx', 'models/driving_tinygrad.pkl')
|
||||
if not os.getenv('SKIP_TINYGRAD_COMPILE'):
|
||||
compile_model('models/dmonitoring_model.onnx', 'models/dmonitoring_model_tinygrad.pkl')
|
||||
compile_model('models/driving_supercombo.onnx', 'models/driving_tinygrad.pkl')
|
||||
|
||||
model_w, model_h = MEDMODEL_INPUT_SIZE
|
||||
for cam_w, cam_h in camera_configs:
|
||||
@@ -62,7 +63,7 @@ for cam_w, cam_h in camera_configs:
|
||||
lenv.Command(warp_pkl_path, tinygrad_files + warp_deps + [Value(cmd)], cmd)
|
||||
|
||||
dm_w, dm_h = DM_INPUT_SIZE
|
||||
for cam_w, cam_h in camera_configs:
|
||||
for cam_w, cam_h in [] if os.getenv('SKIP_TINYGRAD_COMPILE') else camera_configs:
|
||||
dm_pkl_path = File(f"models/dm_warp_{cam_w}x{cam_h}_tinygrad.pkl").abspath
|
||||
stride, y_height, uv_height, frame_size = get_nv12_info(cam_w, cam_h)
|
||||
cmd = (f'{tg_flags} {mac_brew_string} python3 "{compiler}/compile_warp.py" '
|
||||
|
||||
@@ -7,6 +7,7 @@ from functools import cached_property
|
||||
import os
|
||||
os.environ['GMMU'] = '0' # for chestnut fast loading, noop for qcom
|
||||
os.environ.setdefault('AM_POWER_LIMIT', '100')
|
||||
os.environ.setdefault('AM_PERF_LEVEL', 'high')
|
||||
from tinygrad.device import Buffer, Device
|
||||
from tinygrad.dtype import DType, dtypes
|
||||
from tinygrad.engine.realize import lower_and_compile
|
||||
|
||||
@@ -206,6 +206,8 @@ class AugmentedRoadView(CameraView):
|
||||
def _handle_mouse_release(self, mouse_pos: MousePos):
|
||||
# Don't trigger click callback if bookmark was triggered
|
||||
if not self._bookmark_icon.interacting():
|
||||
if self._model_renderer.laneful.handle_tap(mouse_pos):
|
||||
return
|
||||
super()._handle_mouse_release(mouse_pos)
|
||||
|
||||
def _render(self, _):
|
||||
|
||||
@@ -141,6 +141,7 @@ class ModelRenderer(Widget, ModelRendererSP):
|
||||
if ui_state.status != UIStatus.DISENGAGED:
|
||||
self._draw_lane_lines()
|
||||
self._draw_path()
|
||||
self.laneful.draw(self._rect)
|
||||
|
||||
if render_lead_indicator:
|
||||
self._update_leads(sm)
|
||||
@@ -219,6 +220,7 @@ class ModelRenderer(Widget, ModelRendererSP):
|
||||
# Update road edges using raw points
|
||||
for road_edge in self._road_edges:
|
||||
road_edge.projected_points = self._map_line_to_polygon(road_edge.raw_points, line_width_factor, 0.0, max_idx)
|
||||
self.laneful.update(self, max_idx)
|
||||
|
||||
# Update path using raw points
|
||||
if lead and lead.present:
|
||||
|
||||
@@ -8,7 +8,6 @@ from enum import IntEnum
|
||||
|
||||
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.cruise_sub_layouts.speed_limit_settings import SpeedLimitSettingsLayout
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||
from openpilot.sunnypilot.mapd import MAPD_AVAILABLE
|
||||
from openpilot.system.ui.lib.multilang import tr, tr_noop
|
||||
from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp, option_item_sp, simple_button_item_sp
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
@@ -30,7 +29,6 @@ ACC_ENABLED_DESCRIPTION = tr_noop("Enable custom Short & Long press increments f
|
||||
ACC_NOLONG_DESCRIPTION = tr_noop("This feature can only be used with sunnypilot longitudinal control enabled.")
|
||||
ACC_PCMCRUISE_DISABLED_DESCRIPTION = tr_noop("This feature is not supported on this platform due to vehicle limitations.")
|
||||
ONROAD_ONLY_DESCRIPTION = tr_noop("Start the vehicle to check vehicle compatibility.")
|
||||
MAP_UNAVAILABLE_DESCRIPTION = tr_noop("Map data is temporarily unavailable.")
|
||||
|
||||
|
||||
class CruiseLayout(Widget):
|
||||
@@ -56,8 +54,7 @@ class CruiseLayout(Widget):
|
||||
|
||||
self.scc_m_toggle = toggle_item_sp(
|
||||
title=tr("Smart Cruise Control - Map"),
|
||||
description=(f"<b>{tr(MAP_UNAVAILABLE_DESCRIPTION)}</b>\n\n" if not MAPD_AVAILABLE else "") +
|
||||
tr("Use map data to estimate the appropriate speed to drive through turns ahead."),
|
||||
description=tr("Use map data to estimate the appropriate speed to drive through turns ahead."),
|
||||
param="SmartCruiseControlMap")
|
||||
|
||||
self.custom_acc_toggle = toggle_item_sp(
|
||||
@@ -142,7 +139,7 @@ class CruiseLayout(Widget):
|
||||
if has_long or has_icbm:
|
||||
self.custom_acc_toggle.action_item.set_enabled(((has_long and not ui_state.CP.pcmCruise) or has_icbm) and ui_state.is_offroad())
|
||||
self.scc_v_toggle.action_item.set_enabled(True)
|
||||
self.scc_m_toggle.action_item.set_enabled(MAPD_AVAILABLE)
|
||||
self.scc_m_toggle.action_item.set_enabled(True)
|
||||
else:
|
||||
ui_state.params.remove("CustomAccIncrementsEnabled")
|
||||
ui_state.params.remove("SmartCruiseControlVision")
|
||||
|
||||
-6
@@ -8,8 +8,6 @@ from collections.abc import Callable
|
||||
|
||||
import pyray as rl
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||
from openpilot.sunnypilot.mapd import MAPD_AVAILABLE
|
||||
from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.common import Policy
|
||||
from openpilot.system.ui.lib.multilang import tr
|
||||
from openpilot.system.ui.sunnypilot.widgets.list_view import multiple_button_item_sp
|
||||
from openpilot.system.ui.widgets.network import NavButton
|
||||
@@ -45,8 +43,6 @@ class SpeedLimitPolicyLayout(Widget):
|
||||
param="SpeedLimitPolicy",
|
||||
button_width=250,
|
||||
)
|
||||
if not MAPD_AVAILABLE:
|
||||
self._speed_limit_policy.action_item.set_enabled_buttons({Policy.car_state_only})
|
||||
|
||||
items = [
|
||||
self._speed_limit_policy
|
||||
@@ -55,8 +51,6 @@ class SpeedLimitPolicyLayout(Widget):
|
||||
|
||||
@staticmethod
|
||||
def _get_policy_description():
|
||||
if not MAPD_AVAILABLE:
|
||||
return f"<b>{tr('Map data is temporarily unavailable. Using Speed Limit data only from Car.')}</b>"
|
||||
return get_highlighted_description(ui_state.params, "SpeedLimitPolicy", SPEED_LIMIT_POLICY_DESCRIPTIONS)
|
||||
|
||||
def _render(self, rect):
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
"""
|
||||
Copyright (c) 2021-, 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.selfdrive.ui.mici.widgets.button import BigParamControl
|
||||
from openpilot.system.ui.widgets.scroller import NavScroller
|
||||
|
||||
|
||||
class LanefulLayoutMici(NavScroller):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._toggle = BigParamControl("laneful", "Laneful",
|
||||
description="Keeps the car centered in its lane.\n" +
|
||||
"Leans on the lane lines when they are clear, drives laneless when they are not.")
|
||||
self._scroller.add_widget(self._toggle)
|
||||
|
||||
def show_event(self):
|
||||
super().show_event()
|
||||
self._toggle.refresh()
|
||||
@@ -11,6 +11,7 @@ from openpilot.selfdrive.ui.mici.widgets.button import BigCircleButton
|
||||
from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationDialog, BigDialog
|
||||
from openpilot.selfdrive.ui.sunnypilot.mici.layouts.sunnylink import SunnylinkLayoutMici
|
||||
from openpilot.selfdrive.ui.sunnypilot.mici.layouts.models import ModelsLayoutMici
|
||||
from openpilot.selfdrive.ui.sunnypilot.mici.layouts.laneful import LanefulLayoutMici
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
||||
from openpilot.system.ui.lib.multilang import tr
|
||||
@@ -50,6 +51,10 @@ class SettingsLayoutSP(OP.SettingsLayout):
|
||||
models_btn = SettingsBigButton(tr("models"), "", gui_app.texture("../../sunnypilot/selfdrive/assets/offroad/icon_models.png", ICON_SIZE, ICON_SIZE))
|
||||
models_btn.set_click_callback(lambda: gui_app.push_widget(models_panel))
|
||||
|
||||
laneful_panel = LanefulLayoutMici()
|
||||
laneful_btn = SettingsBigButton(tr("laneful"), "", gui_app.texture("icons_mici/settings/device/lkas.png", ICON_SIZE, ICON_SIZE))
|
||||
laneful_btn.set_click_callback(lambda: gui_app.push_widget(laneful_panel))
|
||||
|
||||
# onroad: enable button sits at the front (left of toggles)
|
||||
self._enable_offroad_btn_onroad = BigCircleButton(self.icon_offroad_enable, red=True)
|
||||
self._enable_offroad_btn_onroad.set_click_callback(lambda: self._handle_always_offroad(True))
|
||||
@@ -67,7 +72,8 @@ class SettingsLayoutSP(OP.SettingsLayout):
|
||||
items = self._scroller._items.copy()
|
||||
|
||||
items.insert(1, models_btn)
|
||||
items.insert(5, sunnylink_btn)
|
||||
items.insert(2, laneful_btn)
|
||||
items.insert(6, sunnylink_btn)
|
||||
|
||||
# front slots (only one ever visible at a time): exit-always-offroad, then enable-onroad
|
||||
items.insert(0, self._enable_offroad_btn_onroad)
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
"""
|
||||
Copyright (c) 2021-, 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.
|
||||
|
||||
Laneful visuals: cyan lane-center target, thin white "after" path (model path bent by laneful's nudge),
|
||||
and a top-center readout of the pull, from the curvature modeld publishes in modelDataV2SP.lanefulCurvature.
|
||||
"""
|
||||
import numpy as np
|
||||
import pyray as rl
|
||||
from openpilot.common.filter_simple import FirstOrderFilter
|
||||
from openpilot.common.realtime import DT_MDL
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||
from openpilot.sunnypilot.modeld_v2.laneful import LOOKAHEAD_TIME, MIN_LOOKAHEAD, MAX_ERROR
|
||||
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
||||
from openpilot.system.ui.lib.shader_polygon import draw_polygon
|
||||
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
||||
|
||||
CENTER_COLOR = (0, 200, 255)
|
||||
AFTER_COLOR = (255, 255, 255)
|
||||
|
||||
|
||||
class LanefulRenderer:
|
||||
def __init__(self):
|
||||
self._center_points = np.empty((0, 2), dtype=np.float32)
|
||||
self._after_points = np.empty((0, 2), dtype=np.float32)
|
||||
self._trust = 0.0
|
||||
self._font = gui_app.font(FontWeight.BOLD)
|
||||
# smoothed copies for the readout so the numbers don't flicker
|
||||
self._pull_display = FirstOrderFilter(0.0, 0.5, DT_MDL)
|
||||
self._trust_display = FirstOrderFilter(0.0, 0.5, DT_MDL)
|
||||
self._alpha = FirstOrderFilter(0.0, 0.3, DT_MDL) # fades that hoe
|
||||
self._readout_rect: rl.Rectangle | None = None
|
||||
self._settings_page = None
|
||||
|
||||
def update(self, renderer, max_idx: int) -> None:
|
||||
"""Called from the model renderer's update with its lane lines and path already in raw points."""
|
||||
laneful_curvature = ui_state.sm['modelDataV2SP'].lanefulCurvature
|
||||
self._alpha.update(float(laneful_curvature != 0.0))
|
||||
|
||||
left, right = renderer._lane_lines[1].raw_points, renderer._lane_lines[2].raw_points
|
||||
stds = list(ui_state.sm['modelV2'].laneLineStds)
|
||||
if self._alpha.x < 0.01 or len(left) == 0 or len(right) != len(left) or len(stds) < 3:
|
||||
self._center_points = np.empty((0, 2), dtype=np.float32)
|
||||
self._after_points = np.empty((0, 2), dtype=np.float32)
|
||||
return
|
||||
|
||||
probs = renderer._lane_line_probs
|
||||
l_prob = float(probs[1] * np.interp(stds[1], [0.15, 0.3], [1.0, 0.0]))
|
||||
r_prob = float(probs[2] * np.interp(stds[2], [0.15, 0.3], [1.0, 0.0]))
|
||||
width = np.clip(right[:, 1] - left[:, 1], 2.8, 4.5) if l_prob * r_prob > 0.5 else 3.7
|
||||
center = left.copy()
|
||||
center[:, 1] = (l_prob * (left[:, 1] + width / 2) + r_prob * (right[:, 1] - width / 2)) / (l_prob + r_prob + 1e-4)
|
||||
self._trust = l_prob + r_prob - l_prob * r_prob
|
||||
self._center_points = renderer._map_line_to_polygon(center, 0.1, 0.0, max_idx)
|
||||
|
||||
# "after" path: model path bent by laneful's curvature, ending at the lookahead
|
||||
path = renderer._path.raw_points
|
||||
lookahead = max(ui_state.sm['carState'].vEgo * LOOKAHEAD_TIME, MIN_LOOKAHEAD)
|
||||
self._pull_display.update(laneful_curvature * lookahead ** 2 / 2) # how far laneful moves the car at the lookahead
|
||||
self._trust_display.update(self._trust)
|
||||
after = path.copy()
|
||||
after[:, 1] += laneful_curvature * path[:, 0] ** 2 / 2
|
||||
self._after_points = renderer._map_line_to_polygon(after, 0.15, renderer._path_offset_z,
|
||||
renderer._get_path_length_idx(path[:, 0], lookahead))
|
||||
|
||||
def _color(self, rgb, alpha: float) -> rl.Color:
|
||||
return rl.Color(*rgb, int(alpha * self._alpha.x))
|
||||
|
||||
def draw(self, rect: rl.Rectangle) -> None:
|
||||
if self._center_points.size and self._trust > 0.05:
|
||||
draw_polygon(rect, self._center_points, self._color(CENTER_COLOR, np.clip(self._trust, 0.2, 1.0) * 255))
|
||||
self._readout_rect = None
|
||||
if self._after_points.size:
|
||||
draw_polygon(rect, self._after_points, self._color(AFTER_COLOR, 200))
|
||||
self._draw_readout(rect)
|
||||
|
||||
def handle_tap(self, pos) -> bool:
|
||||
"""Tapping the readout opens the laneful settings page. Returns True if the tap was consumed."""
|
||||
r = self._readout_rect
|
||||
if r is None or not (r.x - 20 <= pos.x <= r.x + r.width + 20 and r.y - 20 <= pos.y <= r.y + r.height + 20):
|
||||
return False
|
||||
if self._settings_page is None:
|
||||
from openpilot.selfdrive.ui.sunnypilot.mici.layouts.laneful import LanefulLayoutMici
|
||||
self._settings_page = LanefulLayoutMici()
|
||||
gui_app.push_widget(self._settings_page)
|
||||
return True
|
||||
|
||||
def _draw_readout(self, rect: rl.Rectangle) -> None:
|
||||
"""Top-center pill: pull toward lane center in cm at the lookahead, arrow = side,
|
||||
bar grows from center toward the pull (full at the cap), brighter with more lane line trust."""
|
||||
pull, trust = self._pull_display.x, self._trust_display.x
|
||||
w, h = 190, 76
|
||||
x, y = rect.x + (rect.width - w) / 2, rect.y + 10
|
||||
cx, cy = x + w / 2, y + 30
|
||||
self._readout_rect = rl.Rectangle(x, y, w, h)
|
||||
rl.draw_rectangle_rounded(self._readout_rect, 0.5, 12, self._color((0, 0, 0), 170))
|
||||
|
||||
text = f"{abs(pull) * 100:.0f}"
|
||||
text_w = measure_text_cached(self._font, text, 44, 0).x
|
||||
rl.draw_text_ex(self._font, text, rl.Vector2(cx - text_w / 2, cy - 24), 44, 0, self._color((255, 255, 255), 255))
|
||||
|
||||
active = self._color(CENTER_COLOR, np.clip(abs(pull) / 0.1, 0.0, 1.0) * 255)
|
||||
idle = self._color((255, 255, 255), 40)
|
||||
lx, rx = x + 18, x + w - 18
|
||||
rl.draw_triangle(rl.Vector2(lx, cy), rl.Vector2(lx + 16, cy + 12), rl.Vector2(lx + 16, cy - 12), active if pull < 0 else idle)
|
||||
rl.draw_triangle(rl.Vector2(rx - 16, cy - 12), rl.Vector2(rx - 16, cy + 12), rl.Vector2(rx, cy), active if pull > 0 else idle)
|
||||
|
||||
bar = rl.Rectangle(x + 24, y + h - 16, w - 48, 6)
|
||||
rl.draw_rectangle_rounded(bar, 1.0, 6, self._color((255, 255, 255), 40))
|
||||
rl.draw_rectangle(int(cx - 1), int(bar.y - 2), 2, int(bar.height + 4), self._color((255, 255, 255), 120))
|
||||
fill = bar.width / 2 * float(np.clip(abs(pull) / MAX_ERROR, 0.0, 1.0))
|
||||
if fill > 1:
|
||||
fill_x = cx if pull > 0 else cx - fill
|
||||
rl.draw_rectangle_rounded(rl.Rectangle(fill_x, bar.y, fill, bar.height), 1.0, 6,
|
||||
self._color(CENTER_COLOR, np.clip(0.3 + 0.7 * trust, 0.0, 1.0) * 255))
|
||||
@@ -6,6 +6,7 @@ See the LICENSE.md file in the root directory for more details.
|
||||
"""
|
||||
import pyray as rl
|
||||
from openpilot.selfdrive.ui.ui_state import UIStatus
|
||||
from openpilot.selfdrive.ui.sunnypilot.mici.onroad.laneful import LanefulRenderer
|
||||
from openpilot.selfdrive.ui.sunnypilot.onroad.rainbow_path import RainbowPath
|
||||
|
||||
LANE_LINE_COLORS_SP = {
|
||||
@@ -17,3 +18,4 @@ LANE_LINE_COLORS_SP = {
|
||||
class ModelRendererSP:
|
||||
def __init__(self):
|
||||
self.rainbow_path = RainbowPath()
|
||||
self.laneful = LanefulRenderer()
|
||||
|
||||
@@ -36,7 +36,7 @@ class UIStateSP:
|
||||
self.is_sp_release: bool = self.params.get_bool("IsReleaseSpBranch")
|
||||
self.sm_services_ext = [
|
||||
"modelManagerSP", "selfdriveStateSP", "longitudinalPlanSP", "backupManagerSP",
|
||||
"lateralTorqueParameters", "carStateSP", "liveMapDataSP", "carParamsSP", "lateralDelay"
|
||||
"lateralTorqueParameters", "carStateSP", "liveMapDataSP", "carParamsSP", "lateralDelay", "modelDataV2SP"
|
||||
]
|
||||
|
||||
self.sunnylink_state = SunnylinkState()
|
||||
|
||||
@@ -3,7 +3,3 @@ from openpilot.common.basedir import BASEDIR
|
||||
|
||||
MAPD_BIN_DIR = os.path.join(BASEDIR, 'openpilot', 'third_party/mapd_pfeiferj')
|
||||
MAPD_PATH = os.path.join(MAPD_BIN_DIR, 'mapd')
|
||||
|
||||
# mapd still runs (OSM downloads) but has no position source since ubloxd owns LastGPSPosition;
|
||||
# map-based features stay off until mapd is rebuilt
|
||||
MAPD_AVAILABLE = False
|
||||
|
||||
@@ -4,6 +4,7 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
This file is part of sunnypilot and is licensed under the MIT License.
|
||||
See the LICENSE.md file in the root directory for more details.
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
import platform
|
||||
|
||||
@@ -26,6 +27,21 @@ class OsmMapData(BaseMapData):
|
||||
self.last_bearing = math.degrees(location.calibratedOrientationNED.value[2])
|
||||
self.last_position = Coordinate(location.positionGeodetic.value[0], location.positionGeodetic.value[1])
|
||||
|
||||
if self.last_position is None:
|
||||
return
|
||||
|
||||
params = {
|
||||
"latitude": self.last_position.latitude,
|
||||
"longitude": self.last_position.longitude,
|
||||
}
|
||||
|
||||
if self.last_bearing is not None:
|
||||
params['bearing'] = self.last_bearing
|
||||
|
||||
# mapd reads its position from the memory params as JSON, so write it for the mapd binary.
|
||||
# LastGPSPosition is registered as BYTES (ubloxd owns the persistent copy for pigeond),
|
||||
# so the JSON payload has to be encoded here.
|
||||
self.mem_params.put("LastGPSPosition", json.dumps(params).encode(), block=True)
|
||||
|
||||
def get_current_speed_limit(self) -> float:
|
||||
return float(self.mem_params.get("MapSpeedLimit") or 0.0)
|
||||
|
||||
@@ -121,6 +121,10 @@ def update_osm_db() -> None:
|
||||
if not mem_params.get("OSMDownloadBounds"):
|
||||
mem_params.put("OSMDownloadBounds", "", block=True)
|
||||
|
||||
# mapd reads LastGPSPosition from the memory params as JSON, and it is registered as BYTES upstream
|
||||
if not mem_params.get("LastGPSPosition"):
|
||||
mem_params.put("LastGPSPosition", b"{}", block=True)
|
||||
|
||||
|
||||
def main_thread():
|
||||
update_installed_version(VERSION, params)
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
"""
|
||||
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
|
||||
This file is part of sunnypilot and is licensed under the MIT License.
|
||||
See the LICENSE.md file in the root directory for more details.
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
import platform
|
||||
|
||||
import openpilot.cereal.messaging as messaging
|
||||
from openpilot.cereal import log
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.test import OpenpilotTestCase
|
||||
from openpilot.sunnypilot.mapd.live_map_data.osm_map_data import OsmMapData
|
||||
|
||||
|
||||
class TestOsmMapData(OpenpilotTestCase):
|
||||
"""The mapd binary reads its position from the memory params as JSON, so mapd_manager has to feed it."""
|
||||
|
||||
LATITUDE = 32.875134
|
||||
LONGITUDE = -117.209777
|
||||
ALTITUDE = 42.0
|
||||
BEARING = 90.0
|
||||
|
||||
def setup_method(self):
|
||||
self.mem_params = Params("/dev/shm/params") if platform.system() != "Darwin" else Params()
|
||||
self.mem_params.remove("LastGPSPosition")
|
||||
self.map_data = OsmMapData()
|
||||
self.pm = messaging.PubMaster(['liveLocationKalman'])
|
||||
|
||||
def location_message(self, status: log.LiveLocationKalman.Status, position_valid: bool):
|
||||
msg = messaging.new_message('liveLocationKalman', valid=True)
|
||||
location = msg.liveLocationKalman
|
||||
location.status = status
|
||||
location.positionGeodetic = {'value': [self.LATITUDE, self.LONGITUDE, self.ALTITUDE], 'valid': position_valid}
|
||||
location.calibratedOrientationNED = {'value': [0.0, 0.0, math.radians(self.BEARING)], 'valid': True}
|
||||
return msg
|
||||
|
||||
def send_location(self, status: log.LiveLocationKalman.Status, position_valid: bool) -> None:
|
||||
# the first exchange synchronizes the PUB/SUB connection, the second one is actually received
|
||||
self.pm.send('liveLocationKalman', self.location_message(status, position_valid))
|
||||
self.map_data.sm.update(1000)
|
||||
self.pm.send('liveLocationKalman', self.location_message(status, position_valid))
|
||||
self.map_data.sm.update(1000)
|
||||
|
||||
def read_position(self) -> dict:
|
||||
value = self.mem_params.get("LastGPSPosition")
|
||||
self.assertIsNotNone(value, "LastGPSPosition was not written for mapd")
|
||||
return json.loads(value)
|
||||
|
||||
def test_writes_position_for_mapd(self):
|
||||
self.send_location(log.LiveLocationKalman.Status.valid, True)
|
||||
self.map_data.tick()
|
||||
|
||||
position = self.read_position()
|
||||
self.assertAlmostEqual(position['latitude'], self.LATITUDE, delta=1e-6)
|
||||
self.assertAlmostEqual(position['longitude'], self.LONGITUDE, delta=1e-6)
|
||||
self.assertAlmostEqual(position['bearing'], self.BEARING, delta=1e-6)
|
||||
|
||||
def test_invalid_localizer_keeps_last_known_position(self):
|
||||
self.send_location(log.LiveLocationKalman.Status.valid, True)
|
||||
self.map_data.tick()
|
||||
|
||||
self.send_location(log.LiveLocationKalman.Status.uninitialized, False)
|
||||
self.map_data.tick()
|
||||
|
||||
position = self.read_position()
|
||||
self.assertAlmostEqual(position['latitude'], self.LATITUDE, delta=1e-6)
|
||||
self.assertAlmostEqual(position['longitude'], self.LONGITUDE, delta=1e-6)
|
||||
@@ -0,0 +1,121 @@
|
||||
"""
|
||||
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
|
||||
This file is part of sunnypilot and is licensed under the MIT License.
|
||||
See the LICENSE.md file in the root directory for more details.
|
||||
"""
|
||||
import numpy as np
|
||||
|
||||
from openpilot.common.filter_simple import FirstOrderFilter
|
||||
from openpilot.common.realtime import DT_MDL
|
||||
from openpilot.sunnypilot.modeld_v2.constants import ModelConstants
|
||||
|
||||
LANE_WIDTH_DEFAULT = 3.6 # m (reseeded to model's lane width upon initialization)
|
||||
LANE_WIDTH_CLIP = (2.8, 4.5) # m
|
||||
LOOKAHEAD_TIME = 1.5 # s
|
||||
MIN_LOOKAHEAD = 10.0 # m
|
||||
MIN_SPEED = 5.0 # m/s
|
||||
LINE_STD = (0.2, 0.6)
|
||||
POINT_STD = 0.1
|
||||
MAX_ERROR = 1.0
|
||||
YIELD = (0.2, 0.45)
|
||||
X_IDXS = np.array(ModelConstants.X_IDXS)
|
||||
|
||||
|
||||
class Laneful: # TODO-SP: Rename this to blendful or something similar because it is not pure laneful anymore.
|
||||
def __init__(self):
|
||||
self.lane_width = FirstOrderFilter(LANE_WIDTH_DEFAULT, 10.0, DT_MDL, initialized=False)
|
||||
self.line_gone = FirstOrderFilter(np.zeros(2), 2.0, DT_MDL)
|
||||
self.line_probs_slow = FirstOrderFilter(np.zeros(2), 3.0, DT_MDL)
|
||||
self.line_probs = FirstOrderFilter(np.zeros(2), 0.5, DT_MDL)
|
||||
self.usual_offset = FirstOrderFilter(0.0, 10.0, DT_MDL, initialized=False)
|
||||
self.going_around = False
|
||||
self.trust = 0.0
|
||||
self.model_curvature = 0.0
|
||||
self.curvature = 0.0
|
||||
|
||||
def update(self, model_output: dict[str, np.ndarray], v_ego: float, active: bool, model_curvature: float) -> float:
|
||||
fading = not active or v_ego < MIN_SPEED
|
||||
if fading:
|
||||
self.trust = 0.0
|
||||
self.usual_offset.initialized = False
|
||||
self.lane_width.initialized = False
|
||||
self.going_around = False
|
||||
self.model_curvature = model_curvature
|
||||
target = 0.0 if fading else self._target(model_output, v_ego, model_curvature)
|
||||
releasing = not fading and (abs(target) < abs(self.curvature) or target * self.curvature < 0)
|
||||
step = (2.0 if releasing else 0.5) / max(v_ego, 1.0) ** 2 * DT_MDL # jerk over 1 second
|
||||
self.curvature = float(np.clip(target, self.curvature - step, self.curvature + step))
|
||||
return self.curvature
|
||||
|
||||
def _target(self, model_output: dict[str, np.ndarray], v_ego: float, model_curvature: float) -> float:
|
||||
if v_ego < MIN_SPEED or 'lane_lines' not in model_output:
|
||||
return 0.0
|
||||
|
||||
lookahead = max(v_ego * LOOKAHEAD_TIME, MIN_LOOKAHEAD)
|
||||
lines = model_output['lane_lines'][0, :, :, 0]
|
||||
stds = model_output['lane_lines_stds'][0, :, :, 0]
|
||||
|
||||
probs = self.line_probs.update(model_output['lane_lines_prob'][0, 1::2][1:3])
|
||||
gone = self.line_gone.update((model_output['lane_lines_prob'][0, 1::2][1:3] < 0.3).astype(float))
|
||||
dropping = bool(np.any(probs < 0.6 * self.line_probs_slow.update(model_output['lane_lines_prob'][0, 1::2][1:3])))
|
||||
l_prob = float(probs[0] * np.interp(np.interp(0.5 * lookahead, X_IDXS, stds[1]), LINE_STD, [1.0, 0.0]))
|
||||
r_prob = float(probs[1] * np.interp(np.interp(0.5 * lookahead, X_IDXS, stds[2]), LINE_STD, [1.0, 0.0]))
|
||||
# one line steadily on its own still centers at half say conf. a line that is disappearing gives control back to laneless
|
||||
if gone[1] > 0.8 and l_prob > 0.5:
|
||||
lane_trust = l_prob / 2
|
||||
elif gone[0] > 0.8 and r_prob > 0.5:
|
||||
lane_trust = r_prob / 2
|
||||
elif dropping:
|
||||
lane_trust = min(l_prob, r_prob)
|
||||
else:
|
||||
lane_trust = l_prob + r_prob - l_prob * r_prob
|
||||
if lane_trust <= 0.0:
|
||||
self.trust = 0.0
|
||||
return 0.0
|
||||
|
||||
win = (X_IDXS >= 0.5 * lookahead) & (X_IDXS <= 1.5 * lookahead)
|
||||
x = X_IDXS[win]
|
||||
half_width = self.lane_width.x / 2
|
||||
if l_prob * r_prob > 0.5:
|
||||
width = np.interp(lookahead, X_IDXS, lines[2]) - np.interp(lookahead, X_IDXS, lines[1])
|
||||
self.lane_width.update(float(np.clip(width, *LANE_WIDTH_CLIP)))
|
||||
half_width = np.clip(lines[2, win] - lines[1, win], *LANE_WIDTH_CLIP) / 2
|
||||
l_var = stds[1, win] ** 2 / max(l_prob, 1e-4)
|
||||
r_var = stds[2, win] ** 2 / max(r_prob, 1e-4)
|
||||
center = (r_var * (lines[1, win] + half_width) + l_var * (lines[2, win] - half_width)) / (l_var + r_var)
|
||||
weight = 1.0 / (l_var * r_var / (l_var + r_var) + POINT_STD ** 2)
|
||||
|
||||
# Fit curvature to place the plant in center: e ~ dk x^2 / 2
|
||||
plan_x = np.interp(x, model_output['plan'][0, :, 0], model_output['plan'][0, :, 1])
|
||||
plan_share = np.interp(abs(model_curvature) * lookahead ** 2 / 2, [0.5, 2.0], [0.3, 0.5]) # blending up to 50% plan with action during sharp curvature
|
||||
error = np.clip(center - ((1 - plan_share) * model_curvature * x ** 2 / 2 + plan_share * plan_x), -1.0, 1.0)
|
||||
dk = 2.0 * np.sum(weight * error * x ** 2) / np.sum(weight * x ** 4)
|
||||
|
||||
# A model planning to end up further from its usual spot in the lane than the plant is now is likely going around something
|
||||
hw_now = np.clip(lines[2, 0] - lines[1, 0], *LANE_WIDTH_CLIP) / 2 if l_prob * r_prob > 0.5 else self.lane_width.x / 2
|
||||
center_now = (l_prob * (lines[1, 0] + hw_now) + r_prob * (lines[2, 0] - hw_now)) / (l_prob + r_prob)
|
||||
plan_y = np.interp(lookahead, model_output['plan'][0, :, 0], model_output['plan'][0, :, 1])
|
||||
offset = plan_y - np.interp(lookahead, x, center)
|
||||
if min(gone) > 0.5: # both lines gone: turns, intersections, relearn ideal model position
|
||||
self.usual_offset.initialized = False
|
||||
usual = self.usual_offset.x if self.usual_offset.initialized else offset
|
||||
swerve = abs(offset - usual) - 0.25 * abs(model_curvature) * lookahead ** 2 / 2
|
||||
away = swerve - abs(-center_now - usual)
|
||||
lane_curvature = 2.0 * np.polyfit(x, center, 2)[0]
|
||||
inside_curve = abs(lane_curvature) * lookahead ** 2 / 2 > 0.1 and (offset - usual) * lane_curvature > 0
|
||||
if inside_curve:
|
||||
self.going_around = False
|
||||
elif away > YIELD[0]:
|
||||
self.going_around = True
|
||||
elif swerve < YIELD[0] / 2:
|
||||
self.going_around = False
|
||||
self.usual_offset.update_alpha(20.0 if self.going_around else 10.0)
|
||||
self.usual_offset.update(offset)
|
||||
if inside_curve:
|
||||
lane_trust *= np.interp(abs(offset) - abs(center_now) - 0.25 * abs(model_curvature) * lookahead ** 2 / 2, (0.5, 1.0), [1.0, 0.0])
|
||||
else:
|
||||
lane_trust *= np.interp(swerve, (YIELD[0] / 2, YIELD[0]), [1.0, 0.0]) if self.going_around else np.interp(away, YIELD, [1.0, 0.0])
|
||||
|
||||
self.trust = lane_trust if lane_trust < self.trust else self.trust + (lane_trust - self.trust) * DT_MDL / (1.5 + DT_MDL)
|
||||
return float(self.trust * dk)
|
||||
@@ -7,6 +7,7 @@ See the LICENSE.md file in the root directory for more details.
|
||||
"""
|
||||
|
||||
from collections.abc import Callable
|
||||
from functools import partial
|
||||
import gc
|
||||
import os
|
||||
os.environ['GMMU'] = '0'
|
||||
@@ -39,6 +40,7 @@ from openpilot.selfdrive.modeld.modeld import ChestnutGpuState
|
||||
|
||||
from openpilot.sunnypilot.modeld_v2.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState, get_curvature_from_output
|
||||
from openpilot.sunnypilot.modeld_v2.parse_model_outputs import Parser
|
||||
from openpilot.sunnypilot.modeld_v2.laneful import Laneful
|
||||
from openpilot.sunnypilot.modeld_v2.constants import ModelConstants, Plan
|
||||
from openpilot.sunnypilot.modeld_v2.meta_helper import load_meta_constants
|
||||
from openpilot.sunnypilot.modeld_v2.camera_offset_helper import CameraOffsetHelper
|
||||
@@ -228,7 +230,8 @@ class ModelState(ModelStateBase):
|
||||
return outputs
|
||||
|
||||
def get_action_from_model(self, model_output: dict[str, np.ndarray], prev_action: log.ModelDataV2.Action,
|
||||
lat_action_t: float, long_action_t: float, v_ego: float) -> log.ModelDataV2.Action:
|
||||
lat_action_t: float, long_action_t: float, v_ego: float,
|
||||
laneful: Callable[[float], float] | None = None) -> log.ModelDataV2.Action:
|
||||
if 'action' not in model_output:
|
||||
plan = model_output['plan'][0]
|
||||
desired_accel = get_accel_from_plan(plan[:, Plan.VELOCITY][:, 0], plan[:, Plan.ACCELERATION][:, 0], self.constants.T_IDXS,
|
||||
@@ -240,6 +243,8 @@ class ModelState(ModelStateBase):
|
||||
else:
|
||||
desired_accel = model_output['action'][0, 1]
|
||||
desired_curvature = model_output['action'][0, 0] / (max(1.0, v_ego))**2
|
||||
if laneful is not None:
|
||||
desired_curvature += laneful(desired_curvature)
|
||||
|
||||
stop = v_ego < 0.3 and desired_accel < 0.1
|
||||
desired_accel = smooth_value(desired_accel, prev_action.desiredAcceleration, self.LONG_SMOOTH_SECONDS)
|
||||
@@ -366,6 +371,8 @@ def main(demo=False):
|
||||
DH = DesireHelper()
|
||||
meta_constants = load_meta_constants()
|
||||
RELC = RoadEdgeLaneChangeController()
|
||||
laneful = Laneful()
|
||||
laneful_enabled = False
|
||||
|
||||
while True:
|
||||
# Keep receiving frames until we are at least 1 frame ahead of previous extra frame
|
||||
@@ -409,6 +416,7 @@ def main(demo=False):
|
||||
model.lat_delay = get_lat_delay(params, sm["lateralDelay"].lateralDelay)
|
||||
model.PLANPLUS_CONTROL = params.get("PlanplusControl", return_default=True)
|
||||
camera_offset_helper.set_offset(params.get("CameraOffset", return_default=True))
|
||||
laneful_enabled = params.get_bool("Laneful")
|
||||
lat_delay = model.lat_delay + model.LAT_SMOOTH_SECONDS
|
||||
if sm.updated["extrinsicsCalibration"] and sm.seen['narrowRoadCameraState'] and sm.seen['deviceState']:
|
||||
device_from_calib_euler = np.array(sm["extrinsicsCalibration"].rpyCalib, dtype=np.float32)
|
||||
@@ -482,7 +490,9 @@ def main(demo=False):
|
||||
|
||||
if not sm['carControl'].latActive:
|
||||
prev_action.desiredCurvature = sm['controlsState'].curvature
|
||||
action = model.get_action_from_model(model_output, prev_action, lat_action_t, long_action_t, v_ego)
|
||||
laneful_active = laneful_enabled and desire == log.Desire.none and DH.lane_change_state == log.LaneChangeState.off
|
||||
action = model.get_action_from_model(model_output, prev_action, lat_action_t, long_action_t, v_ego,
|
||||
partial(laneful.update, model_output, v_ego, laneful_active))
|
||||
prev_action = action
|
||||
fill_model_msg(drivingdata_send, modelv2_send, model_output, action,
|
||||
publish_state, meta_main.frame_id, meta_extra.frame_id, frame_id,
|
||||
@@ -499,6 +509,10 @@ def main(demo=False):
|
||||
modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction
|
||||
mdv2sp_send.valid = modelv2_send.valid
|
||||
mdv2sp_send.modelDataV2SP.laneTurnDirection = DH.lane_turn_direction
|
||||
mdv2sp_send.modelDataV2SP.lanefulCurvature = laneful.curvature
|
||||
mdv2sp_send.modelDataV2SP.lanefulTrust = float(laneful.trust)
|
||||
mdv2sp_send.modelDataV2SP.modelCurvature = float(laneful.model_curvature)
|
||||
mdv2sp_send.modelDataV2SP.lanefulGoingAround = bool(laneful.going_around)
|
||||
drivingdata_send.drivingModelData.meta.laneChangeState = DH.lane_change_state
|
||||
drivingdata_send.drivingModelData.meta.laneChangeDirection = DH.lane_change_direction
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ from openpilot.common.params import Params
|
||||
from openpilot.common.realtime import DT_MDL
|
||||
from openpilot.selfdrive.car.cruise import V_CRUISE_UNSET
|
||||
from openpilot.sunnypilot import PARAMS_UPDATE_PERIOD
|
||||
from openpilot.sunnypilot.mapd import MAPD_AVAILABLE
|
||||
from openpilot.sunnypilot.navd.helpers import Coordinate
|
||||
from openpilot.sunnypilot.selfdrive.controls.lib.smart_cruise_control import MIN_V
|
||||
|
||||
@@ -76,7 +75,7 @@ class SmartCruiseControlMap:
|
||||
def __init__(self):
|
||||
self.params = Params()
|
||||
self.mem_params = Params("/dev/shm/params") if platform.system() != "Darwin" else self.params
|
||||
self.enabled = MAPD_AVAILABLE and self.params.get_bool("SmartCruiseControlMap")
|
||||
self.enabled = self.params.get_bool("SmartCruiseControlMap")
|
||||
self.long_enabled = False
|
||||
self.long_override = False
|
||||
self.is_enabled = False
|
||||
@@ -102,7 +101,7 @@ class SmartCruiseControlMap:
|
||||
|
||||
def update_params(self):
|
||||
if self.frame % int(PARAMS_UPDATE_PERIOD / DT_MDL) == 0:
|
||||
self.enabled = MAPD_AVAILABLE and self.params.get_bool("SmartCruiseControlMap")
|
||||
self.enabled = self.params.get_bool("SmartCruiseControlMap")
|
||||
|
||||
def update_calculations(self, sm: messaging.SubMaster) -> None:
|
||||
gps = sm[self.gps_location_service]
|
||||
|
||||
+1
-14
@@ -8,7 +8,7 @@ import json
|
||||
import math
|
||||
import platform
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock, patch
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from openpilot.cereal import custom
|
||||
from openpilot.common.params import Params
|
||||
@@ -23,8 +23,6 @@ MapState = VisionState = custom.LongitudinalPlanSP.SmartCruiseControl.MapState
|
||||
class TestSmartCruiseControlMap(OpenpilotTestCase):
|
||||
|
||||
def setup_method(self):
|
||||
self.mapd_patch = patch("openpilot.sunnypilot.selfdrive.controls.lib.smart_cruise_control.map_controller.MAPD_AVAILABLE", True)
|
||||
self.mapd_patch.start()
|
||||
self.params = Params()
|
||||
self.mem_params = Params("/dev/shm/params") if platform.system() != "Darwin" else self.params
|
||||
self.reset_params()
|
||||
@@ -32,9 +30,6 @@ class TestSmartCruiseControlMap(OpenpilotTestCase):
|
||||
self.sm = MagicMock()
|
||||
self.sm.__getitem__.return_value = SimpleNamespace(latitude=0.0, longitude=0.0)
|
||||
|
||||
def teardown_method(self):
|
||||
self.mapd_patch.stop()
|
||||
|
||||
def reset_params(self):
|
||||
self.params.put_bool("SmartCruiseControlMap", True, block=True)
|
||||
|
||||
@@ -55,14 +50,6 @@ class TestSmartCruiseControlMap(OpenpilotTestCase):
|
||||
assert self.scc_m.state == VisionState.disabled
|
||||
assert not self.scc_m.is_active
|
||||
|
||||
def test_mapd_unavailable(self):
|
||||
self.mapd_patch.stop()
|
||||
self.scc_m = SmartCruiseControlMap()
|
||||
for _ in range(int(10. / DT_MDL)):
|
||||
self.scc_m.update(self.sm, True, False, 0., 0., 0.)
|
||||
assert self.scc_m.state == VisionState.disabled
|
||||
self.mapd_patch.start()
|
||||
|
||||
def test_disabled(self):
|
||||
for _ in range(int(10. / DT_MDL)):
|
||||
self.scc_m.update(self.sm, False, False, 0., 0., 0.)
|
||||
|
||||
@@ -13,7 +13,6 @@ from openpilot.common.gps import get_gps_location_service
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.realtime import DT_MDL
|
||||
from openpilot.sunnypilot import PARAMS_UPDATE_PERIOD, get_sanitize_int_param
|
||||
from openpilot.sunnypilot.mapd import MAPD_AVAILABLE
|
||||
from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit import LIMIT_MAX_MAP_DATA_AGE, LIMIT_ADAPT_ACC
|
||||
from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.common import Policy, OffsetType
|
||||
|
||||
@@ -49,8 +48,6 @@ class SpeedLimitResolver:
|
||||
Policy.max().value,
|
||||
self.params
|
||||
)
|
||||
if not MAPD_AVAILABLE:
|
||||
self.policy = Policy.car_state_only
|
||||
self._policy_to_sources_map = {
|
||||
Policy.car_state_only: [SpeedLimitSource.car],
|
||||
Policy.map_data_only: [SpeedLimitSource.map],
|
||||
@@ -94,7 +91,7 @@ class SpeedLimitResolver:
|
||||
|
||||
def update_params(self):
|
||||
if self.frame % int(PARAMS_UPDATE_PERIOD / DT_MDL) == 0:
|
||||
self.policy = self.params.get("SpeedLimitPolicy", return_default=True) if MAPD_AVAILABLE else Policy.car_state_only
|
||||
self.policy = self.params.get("SpeedLimitPolicy", return_default=True)
|
||||
self.is_metric = self.params.get_bool("IsMetric")
|
||||
self.offset_type = self.params.get("SpeedLimitOffsetType", return_default=True)
|
||||
self.offset_value = self.params.get("SpeedLimitValueOffset", return_default=True)
|
||||
|
||||
@@ -15,7 +15,6 @@ from opendbc.sunnypilot.car.tesla.values import TeslaFlagsSP
|
||||
from openpilot.common.params import Params
|
||||
from openpilot.common.swaglog import cloudlog
|
||||
from openpilot.common.hardware import HARDWARE
|
||||
from openpilot.sunnypilot.mapd import MAPD_AVAILABLE
|
||||
|
||||
|
||||
# Wire-protocol version for the capabilities payload. Bump on breaking changes
|
||||
@@ -44,7 +43,6 @@ CAPABILITY_FIELDS = (
|
||||
"device_type",
|
||||
"subaru_has_sng",
|
||||
"hyundai_alpha_long_available",
|
||||
"mapd_available",
|
||||
)
|
||||
|
||||
CAPABILITY_LABELS: dict[str, str] = {
|
||||
@@ -67,7 +65,6 @@ CAPABILITY_LABELS: dict[str, str] = {
|
||||
"device_type": "Device type",
|
||||
"subaru_has_sng": "Subaru Stop-and-Go available",
|
||||
"hyundai_alpha_long_available": "Hyundai Alpha Longitudinal available",
|
||||
"mapd_available": "Map data available",
|
||||
}
|
||||
|
||||
# Explicit defaults for non-boolean capability fields
|
||||
@@ -135,7 +132,6 @@ def generate_capabilities(params: Params | None = None) -> dict:
|
||||
caps["is_sp_release"] = params.get_bool("IsReleaseSpBranch")
|
||||
caps["is_development"] = params.get_bool("IsDevelopmentBranch")
|
||||
caps["stock_longitudinal"] = params.get_bool("ToyotaEnforceStockLongitudinal")
|
||||
caps["mapd_available"] = MAPD_AVAILABLE
|
||||
|
||||
bundle = params.get("CarPlatformBundle")
|
||||
bundle_brand = _bundle_field(bundle, "brand")
|
||||
|
||||
@@ -156,7 +156,7 @@ The tables below describe the **compiled** `settings_ui.json` schema — what th
|
||||
|
||||
**Enablement rules**: Grayed out (disabled) when rules fail. Frontend shows a contextual badge explaining why.
|
||||
|
||||
**Capability fields** (referenced in rules): `has_longitudinal_control`, `has_icbm`, `icbm_available`, `torque_allowed`, `brand`, `pcm_cruise`, `alpha_long_available`, `steer_control_type`, `has_bsm`, `is_release`, `is_sp_release`, `is_development`, `tesla_has_vehicle_bus`, `has_stop_and_go`, `stock_longitudinal`, `mapd_available`
|
||||
**Capability fields** (referenced in rules): `has_longitudinal_control`, `has_icbm`, `icbm_available`, `torque_allowed`, `brand`, `pcm_cruise`, `alpha_long_available`, `steer_control_type`, `has_bsm`, `is_release`, `is_sp_release`, `is_development`, `tesla_has_vehicle_bus`, `has_stop_and_go`, `stock_longitudinal`
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -857,47 +857,19 @@
|
||||
},
|
||||
{
|
||||
"value": 1,
|
||||
"label": "Map Data Only",
|
||||
"enablement": [
|
||||
{
|
||||
"type": "capability",
|
||||
"field": "mapd_available",
|
||||
"equals": true
|
||||
}
|
||||
]
|
||||
"label": "Map Data Only"
|
||||
},
|
||||
{
|
||||
"value": 2,
|
||||
"label": "Car State Priority",
|
||||
"enablement": [
|
||||
{
|
||||
"type": "capability",
|
||||
"field": "mapd_available",
|
||||
"equals": true
|
||||
}
|
||||
]
|
||||
"label": "Car State Priority"
|
||||
},
|
||||
{
|
||||
"value": 3,
|
||||
"label": "Map Data Priority",
|
||||
"enablement": [
|
||||
{
|
||||
"type": "capability",
|
||||
"field": "mapd_available",
|
||||
"equals": true
|
||||
}
|
||||
]
|
||||
"label": "Map Data Priority"
|
||||
},
|
||||
{
|
||||
"value": 4,
|
||||
"label": "Combined",
|
||||
"enablement": [
|
||||
{
|
||||
"type": "capability",
|
||||
"field": "mapd_available",
|
||||
"equals": true
|
||||
}
|
||||
]
|
||||
"label": "Combined"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -1029,11 +1001,6 @@
|
||||
}
|
||||
],
|
||||
"enablement": [
|
||||
{
|
||||
"type": "capability",
|
||||
"field": "mapd_available",
|
||||
"equals": true
|
||||
},
|
||||
{
|
||||
"type": "any",
|
||||
"conditions": [
|
||||
|
||||
@@ -167,28 +167,12 @@ sections:
|
||||
label: Car State Only
|
||||
- value: 1
|
||||
label: Map Data Only
|
||||
enablement:
|
||||
- type: capability
|
||||
field: mapd_available
|
||||
equals: true
|
||||
- value: 2
|
||||
label: Car State Priority
|
||||
enablement:
|
||||
- type: capability
|
||||
field: mapd_available
|
||||
equals: true
|
||||
- value: 3
|
||||
label: Map Data Priority
|
||||
enablement:
|
||||
- type: capability
|
||||
field: mapd_available
|
||||
equals: true
|
||||
- value: 4
|
||||
label: Combined
|
||||
enablement:
|
||||
- type: capability
|
||||
field: mapd_available
|
||||
equals: true
|
||||
- key: SpeedLimitOffsetType
|
||||
widget: multiple_button
|
||||
title: Speed Limit Offset Type
|
||||
@@ -262,9 +246,6 @@ sections:
|
||||
field: has_icbm
|
||||
equals: true
|
||||
enablement:
|
||||
- type: capability
|
||||
field: mapd_available
|
||||
equals: true
|
||||
- type: any
|
||||
conditions:
|
||||
- type: capability
|
||||
|
||||
Reference in New Issue
Block a user