mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-10-24 15:24:00 +08:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f7f9a3efae | |||
| 5d7fb82291 | |||
| 6433886a65 | |||
| 723ebf374e | |||
| b24f546a0f | |||
| ddf83be52b | |||
| 3163577bc9 | |||
| 84fda467b5 | |||
| 2a84e1481c | |||
| 04a8fb81ab | |||
| 05099ae19c | |||
| e6d035b639 | |||
| 45bfd88e7e | |||
| 86f2e7b255 | |||
| eb7a6fc0f8 | |||
| 033ba0d973 | |||
| 32460cd5b1 | |||
| 0042016791 | |||
| f6e2fb1c33 | |||
| 43cbdc65cd | |||
| 5823cf4f84 | |||
| 023ef9419a | |||
| 374659bf43 | |||
| 07f755a4c7 | |||
| f4996a0529 | |||
| 2f6e2560ea | |||
| 0414843e2f | |||
| 5acb9d2c8e | |||
| 2bf8f88978 | |||
| e6e5b1c165 | |||
| b7d06aa440 | |||
| a9ccd8fedf | |||
| 6220734b2d | |||
| f3f7296776 | |||
| 9f3a2607ee | |||
| b4c3a19db1 | |||
| 04098893d9 | |||
| bc064e892f | |||
| be51bc793d | |||
| f7c65f7d6a | |||
| bd27f3b54f | |||
| d7c1827134 | |||
| 1344824746 | |||
| 75b5926f4f |
@@ -33,7 +33,7 @@ jobs:
|
||||
|
||||
- name: LFS Fetch
|
||||
run: |
|
||||
git lfs fetch || echo "::warning::some objects are missing on commaai/openpilot-lfs; mirroring the rest"
|
||||
git lfs fetch
|
||||
|
||||
- name: Set up Git
|
||||
run: |
|
||||
@@ -68,7 +68,6 @@ 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
|
||||
@@ -109,19 +108,19 @@ jobs:
|
||||
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()
|
||||
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())
|
||||
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)
|
||||
print(oid, size)
|
||||
EOF
|
||||
for repo in . sunnypilot; do
|
||||
python3 scan_pointers.py "$repo"
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
[submodule "opendbc"]
|
||||
path = opendbc_repo
|
||||
url = https://github.com/sunnypilot/opendbc.git
|
||||
branch = toyotabsmhack
|
||||
[submodule "msgq"]
|
||||
path = msgq_repo
|
||||
url = https://github.com/sunnypilot/msgq.git
|
||||
|
||||
+1
-1
Submodule opendbc_repo updated: afc409a445...5be7507143
@@ -138,9 +138,6 @@ struct ModelManagerSP @0xaedffd8f31e7b55d {
|
||||
status @0 :DownloadStatus;
|
||||
progress @1 :Float32;
|
||||
eta @2 :UInt32;
|
||||
speed @3 :Float32;
|
||||
canceling @4 :Bool;
|
||||
resuming @5 :Bool;
|
||||
}
|
||||
|
||||
struct Chunk {
|
||||
|
||||
@@ -246,7 +246,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"TeslaMadsScreenButton", {PERSISTENT | BACKUP, INT, "0"}},
|
||||
{"ToyotaEnforceStockLongitudinal", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"ToyotaStopAndGoHack", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"ToyotaEnhancedBsm", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
|
||||
{"BlindSpot", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ 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.engine.realize import lower_and_compile
|
||||
from tinygrad.dtype import DType, dtypes
|
||||
from tinygrad.engine.realize import lower_and_compile
|
||||
from tinygrad.tensor import Tensor
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
|
||||
|
||||
def latest_gps_source(sm) -> str | None:
|
||||
@@ -7,3 +10,34 @@ def latest_gps_source(sm) -> str | None:
|
||||
sources = [s for s in ('gpsLocation', 'gpsLocationExternal')
|
||||
if sm.valid[s] and sm[s].hasFix and 0 <= now - sm.recv_time[s] < 2]
|
||||
return max(sources, key=lambda s: sm.logMonoTime[s]) if sources else None
|
||||
|
||||
|
||||
# convert mapbox steps to valhalla enums the model was trained on
|
||||
VALHALLA_COMMAND_COUNT = 46
|
||||
VALHALLA_SIDED_TYPES = { # (straight, right, left)
|
||||
'depart': (1, 2, 3), 'arrive': (4, 5, 6),
|
||||
'on ramp': (17, 18, 19), 'off ramp': (20, 20, 21), 'fork': (22, 23, 24),
|
||||
'roundabout': (26, 26, 26), 'rotary': (26, 26, 26), 'roundabout turn': (26, 26, 26),
|
||||
'exit roundabout': (27, 27, 27), 'exit rotary': (27, 27, 27), 'notification': (28, 28, 28),
|
||||
}
|
||||
VALHALLA_TURN_TYPES = {'straight': 8, 'slight right': 9, 'right': 10, 'sharp right': 11, 'uturn': 13,
|
||||
'sharp left': 14, 'left': 15, 'slight left': 16} # turn, continue, new name, end of road
|
||||
|
||||
|
||||
def valhalla_type(maneuver_type: str, modifier: str) -> int | None:
|
||||
if maneuver_type == 'merge':
|
||||
return None # Valhalla gives no maneuver at a merge, so the model never saw one
|
||||
if maneuver_type in VALHALLA_SIDED_TYPES:
|
||||
straight, right, left = VALHALLA_SIDED_TYPES[maneuver_type]
|
||||
return right if 'right' in modifier else left if 'left' in modifier else straight
|
||||
return VALHALLA_TURN_TYPES.get(modifier, 8)
|
||||
|
||||
|
||||
def navigation_input(maneuvers: Any, traveled: float) -> np.ndarray:
|
||||
navigation = np.zeros(VALHALLA_COMMAND_COUNT + 1, dtype=np.float32)
|
||||
for maneuver in maneuvers:
|
||||
command, distance = valhalla_type(maneuver.type, maneuver.modifier), maneuver.distance - traveled
|
||||
if command is not None and command > 6 and distance >= 0:
|
||||
navigation[command], navigation[-1] = 1, distance / 100
|
||||
break
|
||||
return navigation
|
||||
|
||||
@@ -235,8 +235,6 @@ class ModelsLayout(Widget):
|
||||
progress = sum(p.progress for p in progresses) / len(progresses)
|
||||
ds = custom.ModelManagerSP.DownloadStatus
|
||||
|
||||
if any(p.canceling for p in progresses):
|
||||
return {"name": name, "downloading": True, "progress": progress, "status_text": tr("canceling...")}
|
||||
if ds.failed in statuses:
|
||||
# close.png is authored black and a tint cannot lift it, hence close2
|
||||
return {"name": name, "status_text": tr("download failed"), "text_color": rl.RED, "icon": "icons/close2.png"}
|
||||
|
||||
@@ -4,8 +4,6 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
||||
This file is part of sunnypilot and is licensed under the MIT License.
|
||||
See the LICENSE.md file in the root directory for more details.
|
||||
"""
|
||||
from opendbc.car.toyota.values import ToyotaFlags
|
||||
from opendbc.sunnypilot.car.toyota.values import ToyotaFlagsSP
|
||||
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.base import BrandSettings
|
||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||
from openpilot.system.ui.lib.application import gui_app
|
||||
@@ -17,10 +15,6 @@ from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp
|
||||
|
||||
ONROAD_ONLY_DESCRIPTION = tr_noop("Start the vehicle to check vehicle compatibility.")
|
||||
SNG_HACK_UNAVAILABLE = tr_noop("sunnypilot Longitudinal Control must be available and enabled for your vehicle to use this feature.")
|
||||
BSM_UNAVAILABLE = tr_noop("Enhanced Blind Spot Monitoring requires a TSS2 Toyota/Lexus without SecOC.")
|
||||
BSM_STOCK_LONG_UNAVAILABLE = tr_noop("Enhanced Blind Spot Monitoring is not available while Enforce Factory Longitudinal Control is enabled.")
|
||||
BSM_REQUIRED = tr_noop("This vehicle does not report blind spot status over the standard CAN signals. " +
|
||||
"Enhanced Blind Spot Monitoring is required for blind spot detection.")
|
||||
|
||||
DESCRIPTIONS = {
|
||||
'enforce_stock_longitudinal': tr_noop(
|
||||
@@ -29,10 +23,6 @@ DESCRIPTIONS = {
|
||||
'stop_and_go_hack': tr_noop(
|
||||
'sunnypilot will allow some Toyota/Lexus cars to auto resume during stop and go traffic. ' +
|
||||
'This feature is only applicable to certain models that are able to use longitudinal control. This is an alpha feature. Use at your own risk.'
|
||||
),
|
||||
'enhanced_bsm': tr_noop(
|
||||
'sunnypilot will poll the blind spot monitor diagnostics directly from the radar. Use this on vehicles whose blind spot monitors ' +
|
||||
'do not report blind spot status over the standard CAN signals. This is an alpha feature. Use at your own risk.'
|
||||
)
|
||||
}
|
||||
|
||||
@@ -57,18 +47,9 @@ class ToyotaSettings(BrandSettings):
|
||||
enabled=lambda: not ui_state.engaged,
|
||||
)
|
||||
|
||||
self.enhanced_bsm = toggle_item_sp(
|
||||
lambda: tr("Enhanced Blind Spot Monitoring (Alpha)"),
|
||||
description=lambda: tr(DESCRIPTIONS["enhanced_bsm"]),
|
||||
initial_state=ui_state.params.get_bool("ToyotaEnhancedBsm"),
|
||||
callback=self._on_enable_enhanced_bsm,
|
||||
enabled=lambda: not ui_state.engaged,
|
||||
)
|
||||
|
||||
self.items = [
|
||||
self.enforce_stock_longitudinal,
|
||||
self.stop_and_go_hack,
|
||||
self.enhanced_bsm,
|
||||
]
|
||||
|
||||
def _on_enable_enforce_stock_longitudinal(self, state: bool):
|
||||
@@ -113,25 +94,6 @@ class ToyotaSettings(BrandSettings):
|
||||
ui_state.params.put_bool("ToyotaStopAndGoHack", False)
|
||||
ui_state.params.put_bool("OnroadCycleRequested", True)
|
||||
|
||||
def _on_enable_enhanced_bsm(self, state: bool):
|
||||
if state:
|
||||
def confirm_callback(result: int):
|
||||
if result == DialogResult.CONFIRM:
|
||||
ui_state.params.put_bool("ToyotaEnhancedBsm", True)
|
||||
ui_state.params.put_bool("OnroadCycleRequested", True)
|
||||
else:
|
||||
self.enhanced_bsm.action_item.set_state(False)
|
||||
|
||||
content = (f"<h1>{self.enhanced_bsm.title}</h1><br>" +
|
||||
f"<p>{self.enhanced_bsm.description}</p>")
|
||||
|
||||
dlg = ConfirmDialog(content, tr("Enable"), rich=True, callback=confirm_callback)
|
||||
gui_app.push_widget(dlg)
|
||||
|
||||
else:
|
||||
ui_state.params.put_bool("ToyotaEnhancedBsm", False)
|
||||
ui_state.params.put_bool("OnroadCycleRequested", True)
|
||||
|
||||
def update_settings(self):
|
||||
if ui_state.CP is not None:
|
||||
longitudinal = ui_state.CP.openpilotLongitudinalControl
|
||||
@@ -151,36 +113,9 @@ class ToyotaSettings(BrandSettings):
|
||||
self.stop_and_go_hack.set_description(new_desc)
|
||||
if show_desc:
|
||||
self.stop_and_go_hack.show_description(True)
|
||||
|
||||
# Enhanced BSM needs the radar diagnostic address, which is only in the TX allowlist for
|
||||
# TSS2 cars without SecOC that are not enforcing factory longitudinal control
|
||||
bsm_supported = bool(ui_state.CP.flags & ToyotaFlags.TSS2) and not bool(ui_state.CP.flags & ToyotaFlags.SECOC)
|
||||
bsm_available = bsm_supported and not enforce_stock
|
||||
self.enhanced_bsm.action_item.set_enabled(bsm_available and not ui_state.engaged)
|
||||
|
||||
bsm_desc = tr(DESCRIPTIONS["enhanced_bsm"])
|
||||
if not bsm_supported:
|
||||
self.enhanced_bsm.action_item.set_state(False)
|
||||
bsm_desc = "<b>" + tr(BSM_UNAVAILABLE) + "</b>\n\n" + bsm_desc
|
||||
elif enforce_stock:
|
||||
self.enhanced_bsm.action_item.set_state(False)
|
||||
bsm_desc = "<b>" + tr(BSM_STOCK_LONG_UNAVAILABLE) + "</b>\n\n" + bsm_desc
|
||||
elif ui_state.CP_SP is not None and ui_state.CP_SP.flags & ToyotaFlagsSP.SP_NEED_DEBUG_BSM:
|
||||
bsm_desc = "<b>" + tr(BSM_REQUIRED) + "</b>\n\n" + bsm_desc
|
||||
|
||||
if self.enhanced_bsm.description != bsm_desc:
|
||||
self.enhanced_bsm.set_description(bsm_desc)
|
||||
if not bsm_available:
|
||||
self.enhanced_bsm.show_description(True)
|
||||
else:
|
||||
self.stop_and_go_hack.action_item.set_enabled(False)
|
||||
new_desc = "<b>" + tr(ONROAD_ONLY_DESCRIPTION) + "</b>\n\n" + tr(DESCRIPTIONS["stop_and_go_hack"])
|
||||
if self.stop_and_go_hack.description != new_desc:
|
||||
self.stop_and_go_hack.set_description(new_desc)
|
||||
self.stop_and_go_hack.show_description(True)
|
||||
|
||||
self.enhanced_bsm.action_item.set_enabled(False)
|
||||
bsm_desc = "<b>" + tr(ONROAD_ONLY_DESCRIPTION) + "</b>\n\n" + tr(DESCRIPTIONS["enhanced_bsm"])
|
||||
if self.enhanced_bsm.description != bsm_desc:
|
||||
self.enhanced_bsm.set_description(bsm_desc)
|
||||
self.enhanced_bsm.show_description(True)
|
||||
|
||||
@@ -251,12 +251,11 @@ class ModelsLayoutMici(NavScroller):
|
||||
self.current_model_info.info_text.set_text(tr("download failed"))
|
||||
|
||||
elif manager.selectedBundle and manager.selectedBundle.status == custom.ModelManagerSP.DownloadStatus.downloading:
|
||||
self.cancel_download_btn.set_visible(ui_state.params.get("ModelManager_DownloadRef") is not None)
|
||||
self.cancel_download_btn.set_visible(True)
|
||||
device.set_override_interactive_timeout(5)
|
||||
progress = 0.0
|
||||
count = 0
|
||||
verifying = False
|
||||
canceling = False
|
||||
for model in manager.selectedBundle.models:
|
||||
count += 1
|
||||
p = model.artifact.downloadProgress
|
||||
@@ -264,13 +263,11 @@ class ModelsLayoutMici(NavScroller):
|
||||
custom.ModelManagerSP.DownloadStatus.verifying):
|
||||
progress += p.progress
|
||||
verifying = verifying or p.status == custom.ModelManagerSP.DownloadStatus.verifying
|
||||
canceling = canceling or p.canceling
|
||||
elif p.status in (custom.ModelManagerSP.DownloadStatus.downloaded,
|
||||
custom.ModelManagerSP.DownloadStatus.cached):
|
||||
progress += 100.0
|
||||
|
||||
header = tr("canceling...") if canceling else tr("verifying") if verifying else tr("downloading")
|
||||
self.current_model_info.current_model_header.set_text(header)
|
||||
self.current_model_info.current_model_header.set_text(tr("verifying") if verifying else tr("downloading"))
|
||||
self.cancel_download_btn.set_text(tr("cancel verification") if verifying else tr("cancel download"))
|
||||
self.current_model_info.current_model_header._shimmer = True
|
||||
name_text = manager.selectedBundle.internalName.lower()
|
||||
|
||||
@@ -9,14 +9,16 @@ import itertools
|
||||
import math
|
||||
from functools import partial
|
||||
|
||||
import numpy as np
|
||||
|
||||
from openpilot.common.basedir import BASEDIR
|
||||
from openpilot.sunnypilot.modeld_v2.helpers import _dtype, read_parts
|
||||
|
||||
|
||||
def pack_layout(shapes: dict[str, tuple], frame_size: int) -> tuple[dict[str, int], int]:
|
||||
def pack_layout(specs: dict[str, tuple], frame_size: int) -> tuple[dict[str, int], int]:
|
||||
offsets, offset = {}, 0
|
||||
for name, shape in ({"tfm": (2, 3, 3)} | shapes).items():
|
||||
offsets[name], offset = offset, offset + -(-math.prod(shape) * 4 // 128) * 128
|
||||
for name, (shape, dtype) in ({"tfm": ((2, 3, 3), np.float32)} | specs).items():
|
||||
offsets[name], offset = offset, offset + -(-math.prod(shape) * np.dtype(dtype).itemsize // 128) * 128
|
||||
return offsets | {"frames": offset}, offset + 2 * frame_size
|
||||
|
||||
|
||||
@@ -38,7 +40,7 @@ def frame_part(warp_path: str, parts: dict, extras: dict) -> dict:
|
||||
return {}
|
||||
(model, model_blob), (warp, warp_blob) = parts["run"], read_parts(warp_path)[0]["run"]
|
||||
states = sorted(name for name in extras["input_specs"] if f"next_{name}" in extras["metadata"]["output_shapes"])
|
||||
host = {name: shape for name, (shape, _, _) in extras["input_specs"].items() if name not in states and name != "new_img"}
|
||||
host = {name: (shape, dtype) for name, (shape, dtype, _) in extras["input_specs"].items() if name not in states and name != "new_img"}
|
||||
frame_size = next(s["numel"] for s in warp["inputs"] if s["name"] == "input_frame") // 2
|
||||
offsets, packed = pack_layout(host, frame_size)
|
||||
shift, upload, u8 = len(model_blob), len(model["buffers"]) + len(warp["buffers"]), repr(_dtype("dtypes.uint8"))
|
||||
|
||||
@@ -172,12 +172,12 @@ class NativeTinygradAdapter(BaseModelAdapter):
|
||||
|
||||
self.input_queues = {name: Tensor(np.zeros(shape, dtype=dtype), device=self.model_device).realize()
|
||||
for name, (shape, dtype) in self.input_shapes.items() if name in self.state_pairs}
|
||||
shapes = {name: shape for name, (shape, _) in self.input_shapes.items() if name not in self.state_pairs and name != 'new_img'}
|
||||
offsets, packed = pack_layout(shapes, self.warp_frame_size)
|
||||
specs = {name: spec for name, spec in self.input_shapes.items() if name not in self.state_pairs and name != 'new_img'}
|
||||
offsets, packed = pack_layout(specs, self.warp_frame_size)
|
||||
self.packed_input = np.zeros(packed, dtype=np.uint8)
|
||||
self.host = Tensor(self.packed_input, device='NPY')
|
||||
self.numpy_inputs = {name: np.ndarray(shape, dtype=np.float32, buffer=self.packed_input, offset=offsets[name])
|
||||
for name, shape in ({'tfm': (2, 3, 3)} | shapes).items()}
|
||||
self.numpy_inputs = {name: np.ndarray(shape, dtype=dtype, buffer=self.packed_input, offset=offsets[name])
|
||||
for name, (shape, dtype) in ({'tfm': ((2, 3, 3), np.dtype(np.float32))} | specs).items()}
|
||||
self.frames = self.packed_input[offsets['frames']:].reshape(2, self.warp_frame_size)
|
||||
|
||||
self.outputs = {name: Tensor(np.zeros(shape, dtype=dtype), device=device).realize() for name, (shape, dtype, device) in self.jits['output_specs'].items()}
|
||||
|
||||
@@ -35,6 +35,7 @@ from openpilot.common.transformations.camera import DEVICE_CAMERAS
|
||||
from openpilot.common.transformations.model import get_warp_matrix
|
||||
from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper
|
||||
from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, smooth_value
|
||||
from openpilot.selfdrive.navigation.helpers import navigation_input
|
||||
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
|
||||
@@ -172,6 +173,9 @@ class ModelState(ModelStateBase):
|
||||
for key in ('traffic_convention', 'lateral_control_params', 'action_t'):
|
||||
if key in self.numpy_inputs and key in inputs:
|
||||
self.numpy_inputs[key][:] = inputs[key]
|
||||
for name in ('nav_map', 'navigation'):
|
||||
if name in self.numpy_inputs:
|
||||
self.numpy_inputs[name][:] = inputs.get(name, 0)
|
||||
|
||||
if self.adapter.is_native:
|
||||
for i, key in enumerate(self._vision_input_names):
|
||||
@@ -253,6 +257,10 @@ class ModelState(ModelStateBase):
|
||||
return log.ModelDataV2.Action(desiredCurvature=float(desired_curvature), desiredAcceleration=float(desired_accel), shouldStop=bool(stop))
|
||||
|
||||
|
||||
def takes_nav(*models) -> bool:
|
||||
return any(name in m.numpy_inputs for m in models if m is not None for name in ('nav_map', 'navigation'))
|
||||
|
||||
|
||||
def main(demo=False):
|
||||
cloudlog.warning("modeld init")
|
||||
|
||||
@@ -282,6 +290,7 @@ def main(demo=False):
|
||||
vipc_client_main_stream = VisionStreamType.VISION_STREAM_WIDE_ROAD if main_wide_camera else VisionStreamType.VISION_STREAM_NARROW_ROAD
|
||||
vipc_client_main = VisionIpcClient("camerad", vipc_client_main_stream, True)
|
||||
vipc_client_extra = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD, False)
|
||||
vipc_client_map = VisionIpcClient("mapsd", VisionStreamType.VISION_STREAM_MAP, True)
|
||||
cloudlog.warning(f"vision stream set up, main_wide_camera: {main_wide_camera}, use_extra_client: {use_extra_client}")
|
||||
|
||||
while not vipc_client_main.connect(False):
|
||||
@@ -332,8 +341,9 @@ def main(demo=False):
|
||||
# messaging
|
||||
pub_socks = ["modelV2", "drivingModelData", "cameraOdometry", "modelDataV2SP"] + (["chestnutGpuState"] if CHESTNUT else [])
|
||||
pm = PubMaster(pub_socks)
|
||||
nav = takes_nav(model, small_model)
|
||||
sm = SubMaster(["deviceState", "carState", "narrowRoadCameraState", "extrinsicsCalibration", "driverMonitoringState",
|
||||
"carControl", "controlsState", "lateralDelay"])
|
||||
"carControl", "controlsState", "lateralDelay"] + (["navInstruction"] if nav else []))
|
||||
|
||||
publish_state = PublishState()
|
||||
chestnut_state = ChestnutGpuState(pm, model.chestnut) if CHESTNUT else None
|
||||
@@ -366,6 +376,7 @@ def main(demo=False):
|
||||
DH = DesireHelper()
|
||||
meta_constants = load_meta_constants()
|
||||
RELC = RoadEdgeLaneChangeController()
|
||||
nav_map, nav_map_time = None, 0.
|
||||
|
||||
while True:
|
||||
# Keep receiving frames until we are at least 1 frame ahead of previous extra frame
|
||||
@@ -455,6 +466,16 @@ def main(demo=False):
|
||||
if 'action_t' in model.numpy_inputs:
|
||||
inputs['action_t'] = np.array([lat_action_t, long_action_t], dtype=np.float32)
|
||||
|
||||
now = time.monotonic()
|
||||
if nav and (vipc_client_map.is_connected() or vipc_client_map.connect(False)):
|
||||
if (buf_map := vipc_client_map.recv(0)) is not None:
|
||||
nav_map = np.frombuffer(buf_map.data, dtype=np.uint8, count=buf_map.width * buf_map.height).reshape(buf_map.height, buf_map.width).copy()
|
||||
nav_map_time = now
|
||||
if nav_map is not None and now - nav_map_time < 0.5: # map timeout
|
||||
inputs['nav_map'] = nav_map
|
||||
if nav and sm.valid['navInstruction'] and now - sm.recv_time['navInstruction'] < 2:
|
||||
inputs['navigation'] = navigation_input(sm['navInstruction'].allManeuvers, v_ego * (now - sm.recv_time['navInstruction']))
|
||||
|
||||
mt1 = time.perf_counter()
|
||||
try:
|
||||
send_chestnut = (chestnut_state is not None and
|
||||
|
||||
@@ -7,10 +7,7 @@ See the LICENSE.md file in the root directory for more details.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import re
|
||||
import threading
|
||||
import time
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
||||
import requests
|
||||
from openpilot.common.params import Params
|
||||
@@ -25,99 +22,12 @@ from openpilot.sunnypilot.models.helpers import (ACTIVE_BUNDLE_KEYS, get_active_
|
||||
|
||||
# (connect, read) seconds. read is per-request inactivity, not a total cap
|
||||
DOWNLOAD_TIMEOUT = (30, 30)
|
||||
# HuggingFace throttles each connection to ~1-2 MB/s but not the connection count; 12 saturates the device link
|
||||
MAX_CONCURRENT_PIECES = 12
|
||||
PIECE_SIZE = 8 * 1024 * 1024
|
||||
PIECE_ATTEMPTS = 3
|
||||
PIECE_BACKOFF = 1.0 # seconds, growing linearly: waits 1 s then 2 s between a piece's three attempts
|
||||
TRANSIENT_HTTP_STATUS = frozenset({408, 429, 500, 502, 503, 504})
|
||||
REPORT_INTERVAL = 0.5 # seconds between progress publications
|
||||
# byte ranges count encoded bytes but iter_content yields decoded ones, so ask for an unencoded body
|
||||
NO_COMPRESSION = {"Accept-Encoding": "identity"}
|
||||
|
||||
|
||||
class DownloadCancelled(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def _is_transient(e: BaseException) -> bool:
|
||||
if isinstance(e, requests.HTTPError):
|
||||
return e.response is not None and e.response.status_code in TRANSIENT_HTTP_STATUS
|
||||
return isinstance(e, (requests.ConnectionError, requests.Timeout, requests.exceptions.ChunkedEncodingError))
|
||||
|
||||
|
||||
class _Progress:
|
||||
def __init__(self):
|
||||
self._bytes = 0
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def add_bytes(self, n: int) -> None:
|
||||
with self._lock:
|
||||
self._bytes += n
|
||||
|
||||
def snapshot(self) -> int:
|
||||
with self._lock:
|
||||
return self._bytes
|
||||
|
||||
|
||||
def _piece_ranges(total: int, piece_size: int) -> list[tuple[int, int]]:
|
||||
return [(start, min(start + piece_size, total)) for start in range(0, total, piece_size)]
|
||||
|
||||
|
||||
def _prepare_target(path: str, total: int) -> None:
|
||||
with open(path, "wb") as f:
|
||||
f.truncate(total)
|
||||
|
||||
|
||||
def _content_range_total(response: requests.Response) -> int | None:
|
||||
if response.status_code != 206:
|
||||
return None
|
||||
match = re.fullmatch(r"bytes \d+-\d+/(\d+)", response.headers.get("Content-Range", ""))
|
||||
return int(match.group(1)) if match else None
|
||||
|
||||
|
||||
def _probe_size(url: str) -> tuple[int, bool]:
|
||||
with requests.get(url, headers={**NO_COMPRESSION, "Range": "bytes=0-0"}, stream=True, timeout=DOWNLOAD_TIMEOUT) as response:
|
||||
response.raise_for_status()
|
||||
total = _content_range_total(response)
|
||||
if total is not None:
|
||||
return total, True
|
||||
return int(response.headers.get("Content-Length", 0)), False
|
||||
|
||||
|
||||
def _fetch_piece(url: str, path: str, index: int, start: int, end: int | None, progress: _Progress,
|
||||
cancel: threading.Event, block_size: int) -> None:
|
||||
headers = {**NO_COMPRESSION, "Range": f"bytes={start}-{end - 1}"} if end is not None else NO_COMPRESSION
|
||||
label = f"{os.path.basename(path)} piece {index}"
|
||||
for attempt in range(PIECE_ATTEMPTS):
|
||||
written = 0
|
||||
try:
|
||||
with requests.get(url, headers=headers, stream=True, timeout=DOWNLOAD_TIMEOUT) as response:
|
||||
response.raise_for_status()
|
||||
if end is not None and response.status_code != 206:
|
||||
raise ValueError(f"server ignored byte range for {label}")
|
||||
with open(path, "r+b") as f:
|
||||
f.seek(start)
|
||||
for block in response.iter_content(chunk_size=block_size):
|
||||
if cancel.is_set():
|
||||
raise DownloadCancelled("Download cancelled")
|
||||
f.write(block)
|
||||
written += len(block)
|
||||
progress.add_bytes(len(block))
|
||||
if end is None:
|
||||
f.truncate() # unknown length: the body defines the file size
|
||||
if end is not None and written != end - start:
|
||||
raise requests.exceptions.ConnectionError(f"short read: {written} of {end - start} bytes")
|
||||
return
|
||||
except requests.RequestException as e:
|
||||
progress.add_bytes(-written) # the piece restarts from scratch
|
||||
if not _is_transient(e) or attempt == PIECE_ATTEMPTS - 1:
|
||||
raise
|
||||
cloudlog.warning(f"retrying {label} after {type(e).__name__}: {e}")
|
||||
if cancel.wait(PIECE_BACKOFF * (1 + attempt)):
|
||||
raise DownloadCancelled("Download cancelled") from None
|
||||
|
||||
|
||||
class ModelManagerSP:
|
||||
"""Manages model downloads and status reporting"""
|
||||
|
||||
@@ -131,7 +41,7 @@ class ModelManagerSP:
|
||||
self.source_models: dict[str, list[custom.ModelManagerSP.ModelBundle]] = {}
|
||||
self.selected_bundle: custom.ModelManagerSP.ModelBundle = None
|
||||
self.active_bundle: custom.ModelManagerSP.ModelBundle = get_active_bundle(self.params, chestnut=self.chestnut_present)
|
||||
self._block_size = 128 * 1000 # 128 KB network read blocks
|
||||
self._chunk_size = 128 * 1000 # 128 KB chunks
|
||||
self._download_start_times: dict[str, float] = {} # Track start time per model
|
||||
self._download_ref: bytes | str | None = None
|
||||
|
||||
@@ -146,6 +56,7 @@ class ModelManagerSP:
|
||||
self._download_ref = None
|
||||
|
||||
def _sync_artifact_progress(self, source_artifact) -> None:
|
||||
"""Mirror download progress to all artifacts sharing the same filename in the selected bundle."""
|
||||
if not self.selected_bundle:
|
||||
return
|
||||
for model in self.selected_bundle.models:
|
||||
@@ -156,6 +67,7 @@ class ModelManagerSP:
|
||||
artifact.downloadProgress.eta = source_artifact.downloadProgress.eta
|
||||
|
||||
def _calculate_eta(self, filename: str, progress: float) -> int:
|
||||
"""Calculate ETA based on elapsed time and current progress"""
|
||||
if filename not in self._download_start_times or progress <= 0:
|
||||
return 60 # Default ETA for new downloads
|
||||
|
||||
@@ -163,58 +75,39 @@ class ModelManagerSP:
|
||||
if elapsed_time <= 0:
|
||||
return 60
|
||||
|
||||
# If we're at X% after Y seconds, we can estimate total time as (Y / X) * 100
|
||||
total_estimated_time = (elapsed_time / progress) * 100
|
||||
eta = total_estimated_time - elapsed_time
|
||||
|
||||
return max(1, int(eta)) # Return at least 1 second if download is ongoing
|
||||
|
||||
def _set_progress(self, artifact, status, progress: float, eta: int = 0) -> None:
|
||||
artifact.downloadProgress.status = status
|
||||
artifact.downloadProgress.progress = progress
|
||||
artifact.downloadProgress.eta = eta
|
||||
self._sync_artifact_progress(artifact)
|
||||
self._report_status()
|
||||
async def _download_file(self, url: str, path: str, model) -> None:
|
||||
"""Downloads a file with progress tracking"""
|
||||
self._download_start_times[model.fileName] = time.monotonic()
|
||||
|
||||
def _publish_progress(self, artifact, done_bytes: int, total: int) -> None:
|
||||
# held at 99 until the file passes its hash check
|
||||
progress = min(99.0, done_bytes / total * 100) if total > 0 else 0.0
|
||||
eta = self._calculate_eta(artifact.fileName, progress)
|
||||
self._set_progress(artifact, custom.ModelManagerSP.DownloadStatus.downloading, progress, eta)
|
||||
with requests.get(url, stream=True, timeout=DOWNLOAD_TIMEOUT) as response: # noqa: ASYNC210
|
||||
response.raise_for_status()
|
||||
total_size = int(response.headers.get("content-length", 0))
|
||||
bytes_downloaded = 0
|
||||
|
||||
async def _report_until_done(self, tasks: list[asyncio.Future], artifact, progress: _Progress, total: int) -> None:
|
||||
pending = set(tasks)
|
||||
while pending:
|
||||
done, pending = await asyncio.wait(pending, timeout=REPORT_INTERVAL)
|
||||
for task in done:
|
||||
task.result() # re-raises a worker failure
|
||||
if self._download_interrupted():
|
||||
raise DownloadCancelled("Download cancelled")
|
||||
self._publish_progress(artifact, progress.snapshot(), total)
|
||||
with open(path, 'wb') as f: # noqa: ASYNC230
|
||||
for chunk in response.iter_content(chunk_size=self._chunk_size): # type: bytes
|
||||
f.write(chunk)
|
||||
bytes_downloaded += len(chunk)
|
||||
|
||||
async def _download_file(self, url: str, path: str, artifact) -> None:
|
||||
self._download_start_times[artifact.fileName] = time.monotonic()
|
||||
loop = asyncio.get_running_loop()
|
||||
if self._download_interrupted():
|
||||
raise DownloadCancelled("Download cancelled")
|
||||
|
||||
total, ranged = await loop.run_in_executor(None, _probe_size, url)
|
||||
pieces: list[tuple[int, int | None]] = list(_piece_ranges(total, PIECE_SIZE)) if ranged else [(0, None)]
|
||||
await loop.run_in_executor(None, _prepare_target, path, total)
|
||||
progress = _Progress()
|
||||
cancel = threading.Event()
|
||||
if total_size > 0:
|
||||
progress = (bytes_downloaded / total_size) * 100
|
||||
model.downloadProgress.status = custom.ModelManagerSP.DownloadStatus.downloading
|
||||
model.downloadProgress.progress = progress
|
||||
model.downloadProgress.eta = self._calculate_eta(model.fileName, progress)
|
||||
self._sync_artifact_progress(model)
|
||||
self._report_status()
|
||||
|
||||
with ThreadPoolExecutor(max_workers=MAX_CONCURRENT_PIECES) as pool:
|
||||
tasks = [loop.run_in_executor(pool, _fetch_piece, url, path, i, start, end, progress, cancel, self._block_size)
|
||||
for i, (start, end) in enumerate(pieces)]
|
||||
try:
|
||||
await self._report_until_done(tasks, artifact, progress, total)
|
||||
except BaseException as e:
|
||||
cancel.set()
|
||||
if isinstance(e, DownloadCancelled):
|
||||
artifact.downloadProgress.canceling = True
|
||||
self._set_progress(artifact, custom.ModelManagerSP.DownloadStatus.downloading, artifact.downloadProgress.progress)
|
||||
for task in tasks:
|
||||
task.cancel() # drops queued pieces; running ones see the event at their next block
|
||||
await asyncio.gather(*tasks, return_exceptions=True)
|
||||
raise
|
||||
del self._download_start_times[artifact.fileName]
|
||||
# Clean up start time after download completes
|
||||
del self._download_start_times[model.fileName]
|
||||
|
||||
async def _process_artifact(self, artifact, destination_path: str) -> None:
|
||||
if not artifact.downloadUri.uri:
|
||||
@@ -226,30 +119,33 @@ class ModelManagerSP:
|
||||
expected_hash = artifact.downloadUri.sha256
|
||||
filename = artifact.fileName
|
||||
full_path = os.path.join(destination_path, filename)
|
||||
status = custom.ModelManagerSP.DownloadStatus
|
||||
artifact.downloadProgress.canceling = False
|
||||
|
||||
try:
|
||||
self._set_progress(artifact, status.verifying, 0)
|
||||
if await verify_file(full_path, expected_hash):
|
||||
self._set_progress(artifact, status.cached, 100)
|
||||
artifact.downloadProgress.status = custom.ModelManagerSP.DownloadStatus.cached
|
||||
artifact.downloadProgress.progress = 100
|
||||
artifact.downloadProgress.eta = 0
|
||||
self._sync_artifact_progress(artifact)
|
||||
self._report_status()
|
||||
return
|
||||
|
||||
self._set_progress(artifact, status.downloading, 0)
|
||||
await self._download_file(url, full_path, artifact)
|
||||
self._set_progress(artifact, status.verifying, 99)
|
||||
if not await verify_file(full_path, expected_hash):
|
||||
raise ValueError(f"Hash validation failed for {filename}")
|
||||
|
||||
self._set_progress(artifact, status.downloaded, 100)
|
||||
artifact.downloadProgress.status = custom.ModelManagerSP.DownloadStatus.downloaded
|
||||
artifact.downloadProgress.progress = 100
|
||||
artifact.downloadProgress.eta = 0
|
||||
self._sync_artifact_progress(artifact)
|
||||
self._report_status()
|
||||
|
||||
except DownloadCancelled:
|
||||
self._download_start_times.pop(artifact.fileName, None)
|
||||
artifact.downloadProgress.status = status.failed
|
||||
artifact.downloadProgress.status = custom.ModelManagerSP.DownloadStatus.failed
|
||||
artifact.downloadProgress.eta = 0
|
||||
self._sync_artifact_progress(artifact)
|
||||
if self.selected_bundle:
|
||||
self.selected_bundle.status = status.failed
|
||||
self.selected_bundle.status = custom.ModelManagerSP.DownloadStatus.failed
|
||||
self._report_status()
|
||||
raise
|
||||
|
||||
@@ -257,11 +153,11 @@ class ModelManagerSP:
|
||||
cloudlog.error(f"Error downloading {filename}: {str(e)}")
|
||||
if os.path.isfile(full_path): # noqa: ASYNC240
|
||||
os.remove(full_path)
|
||||
artifact.downloadProgress.status = status.failed
|
||||
artifact.downloadProgress.status = custom.ModelManagerSP.DownloadStatus.failed
|
||||
artifact.downloadProgress.eta = 0
|
||||
self._sync_artifact_progress(artifact)
|
||||
if self.selected_bundle:
|
||||
self.selected_bundle.status = status.failed
|
||||
self.selected_bundle.status = custom.ModelManagerSP.DownloadStatus.failed
|
||||
self._report_status()
|
||||
self._download_start_times.pop(artifact.fileName, None)
|
||||
raise
|
||||
@@ -376,7 +272,12 @@ class ModelManagerSP:
|
||||
rk.keep_time()
|
||||
|
||||
def clear_model_cache(self) -> None:
|
||||
""" Clears the model cache directory of all files except those in the active model bundle."""
|
||||
"""
|
||||
Clears the model cache directory of all files except those in the active model bundle.
|
||||
"""
|
||||
|
||||
# Get list of files used by both slots' selected bundles (either may become
|
||||
# the truly active bundle depending on hardware availability)
|
||||
active_files = []
|
||||
for source in ACTIVE_BUNDLE_KEYS:
|
||||
if selected_bundle := get_selected_bundle(self.params, source):
|
||||
@@ -384,6 +285,7 @@ class ModelManagerSP:
|
||||
if model.artifact.fileName:
|
||||
active_files.append(model.artifact.fileName)
|
||||
|
||||
# Remove all files except active ones
|
||||
model_dir = Paths.model_root()
|
||||
try:
|
||||
for filename in os.listdir(model_dir):
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -126,7 +126,6 @@ def initialize_params(params) -> list[dict[str, Any]]:
|
||||
keys.extend([
|
||||
"ToyotaEnforceStockLongitudinal",
|
||||
"ToyotaStopAndGoHack",
|
||||
"ToyotaEnhancedBsm",
|
||||
])
|
||||
|
||||
return [{k: params.get(k, return_default=True)} for k in keys]
|
||||
|
||||
@@ -10,7 +10,6 @@ from openpilot.cereal import custom, messaging
|
||||
from opendbc.car.structs import car
|
||||
from opendbc.car.hyundai.values import CAR as HYUNDAI_CAR, UNSUPPORTED_LONGITUDINAL_CAR
|
||||
from opendbc.car.subaru.values import CAR as SUBARU_CAR, SubaruFlags
|
||||
from opendbc.car.toyota.values import CAR as TOYOTA_CAR, ToyotaFlags
|
||||
from opendbc.sunnypilot.car.interfaces import has_bsm
|
||||
from opendbc.sunnypilot.car.tesla.values import TeslaFlagsSP
|
||||
from openpilot.common.params import Params
|
||||
@@ -44,7 +43,6 @@ CAPABILITY_FIELDS = (
|
||||
"device_type",
|
||||
"subaru_has_sng",
|
||||
"hyundai_alpha_long_available",
|
||||
"toyota_enhanced_bsm_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",
|
||||
"toyota_enhanced_bsm_available": "Toyota Enhanced BSM available",
|
||||
}
|
||||
|
||||
# Explicit defaults for non-boolean capability fields
|
||||
@@ -114,16 +111,6 @@ def _resolve_brand_capabilities(caps: dict, bundle_platform: str, CP) -> None:
|
||||
caps["subaru_has_sng"] = not bool(CP.flags & (SubaruFlags.GLOBAL_GEN2 | SubaruFlags.HYBRID))
|
||||
caps["has_stop_and_go"] = caps["subaru_has_sng"]
|
||||
|
||||
elif brand == "toyota":
|
||||
if bundle_platform:
|
||||
try:
|
||||
flags = TOYOTA_CAR[bundle_platform].config.flags
|
||||
caps["toyota_enhanced_bsm_available"] = bool(flags & ToyotaFlags.TSS2) and not bool(flags & ToyotaFlags.SECOC)
|
||||
except KeyError:
|
||||
cloudlog.exception(f"capabilities: unknown toyota platform {bundle_platform!r}")
|
||||
elif CP is not None:
|
||||
caps["toyota_enhanced_bsm_available"] = bool(CP.flags & ToyotaFlags.TSS2) and not bool(CP.flags & ToyotaFlags.SECOC)
|
||||
|
||||
|
||||
def generate_capabilities(params: Params | None = None) -> dict:
|
||||
"""Generate a SettingsCapabilities dict from CarParams + boolean params.
|
||||
|
||||
@@ -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`, `toyota_enhanced_bsm_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`
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -2324,28 +2324,6 @@
|
||||
"equals": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"key": "ToyotaEnhancedBsm",
|
||||
"widget": "toggle",
|
||||
"needs_onroad_cycle": true,
|
||||
"title": "Enhanced Blind Spot Monitoring (Alpha)",
|
||||
"description": "sunnypilot will poll the blind spot monitor diagnostics directly from the radar. Use this on vehicles whose blind spot monitors do not report blind spot status over the standard CAN signals. This is an alpha feature. Use at your own risk.",
|
||||
"enablement": [
|
||||
{
|
||||
"type": "not_engaged"
|
||||
},
|
||||
{
|
||||
"type": "capability",
|
||||
"field": "toyota_enhanced_bsm_available",
|
||||
"equals": true
|
||||
},
|
||||
{
|
||||
"type": "capability",
|
||||
"field": "stock_longitudinal",
|
||||
"equals": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -101,18 +101,3 @@ sections:
|
||||
- type: param
|
||||
key: ToyotaEnforceStockLongitudinal
|
||||
equals: false
|
||||
- key: ToyotaEnhancedBsm
|
||||
widget: toggle
|
||||
needs_onroad_cycle: true
|
||||
title: Enhanced Blind Spot Monitoring (Alpha)
|
||||
description: sunnypilot will poll the blind spot monitor diagnostics directly from the radar. Use this on vehicles whose
|
||||
blind spot monitors do not report blind spot status over the standard CAN signals. This is an alpha feature. Use at
|
||||
your own risk.
|
||||
enablement:
|
||||
- $ref: '#/macros/not_engaged'
|
||||
- type: capability
|
||||
field: toyota_enhanced_bsm_available
|
||||
equals: true
|
||||
- type: capability
|
||||
field: stock_longitudinal
|
||||
equals: false
|
||||
|
||||
@@ -69,18 +69,12 @@ class TestOpaquePerBrandFlags(OpenpilotTestCase):
|
||||
def test_hyundai_alpha_long_available_field_present(self):
|
||||
assert "hyundai_alpha_long_available" in CAPABILITY_FIELDS
|
||||
|
||||
def test_toyota_enhanced_bsm_available_field_present(self):
|
||||
assert "toyota_enhanced_bsm_available" in CAPABILITY_FIELDS
|
||||
|
||||
def test_subaru_has_sng_default_false(self, caps):
|
||||
assert caps["subaru_has_sng"] is False
|
||||
|
||||
def test_hyundai_alpha_long_available_default_false(self, caps):
|
||||
assert caps["hyundai_alpha_long_available"] is False
|
||||
|
||||
def test_toyota_enhanced_bsm_available_default_false(self, caps):
|
||||
assert caps["toyota_enhanced_bsm_available"] is False
|
||||
|
||||
|
||||
class TestCapabilitiesShape(OpenpilotTestCase):
|
||||
def test_all_fields_present(self, caps):
|
||||
|
||||
@@ -207,7 +207,6 @@ class TestNotEngagedReplacement(OpenpilotTestCase):
|
||||
"AlphaLongitudinalEnabled",
|
||||
"ToyotaEnforceStockLongitudinal",
|
||||
"ToyotaStopAndGoHack",
|
||||
"ToyotaEnhancedBsm",
|
||||
], names=["key"])
|
||||
def test_offroad_only_replaced_with_not_engaged(self, schema, key):
|
||||
"""These items should use not_engaged, not offroad_only."""
|
||||
|
||||
@@ -286,7 +286,6 @@ class TestKnownVehicleSettings(OpenpilotTestCase):
|
||||
keys = {i["key"] for i in _brand_items(schema["vehicle_settings"].get("toyota"))}
|
||||
assert "ToyotaEnforceStockLongitudinal" in keys
|
||||
assert "ToyotaStopAndGoHack" in keys
|
||||
assert "ToyotaEnhancedBsm" in keys
|
||||
|
||||
def test_tesla_has_coop_steering(self, schema):
|
||||
keys = {i["key"] for i in _brand_items(schema["vehicle_settings"].get("tesla"))}
|
||||
|
||||
@@ -57,7 +57,7 @@ class DownloadStatusAction(ItemAction):
|
||||
self.icon: str | None = None
|
||||
self.icon_color: rl.Color | None = None
|
||||
self._font = gui_app.font(FontWeight.NORMAL)
|
||||
# raw progress arrives in steps, one per progress report from the manager
|
||||
# raw progress arrives in steps, one per 128KB chunk the manager publishes
|
||||
self._progress = FirstOrderFilter(0.0, 0.5, 1 / gui_app.target_fps)
|
||||
# integrated per frame; (t * speed) % span jumps whenever the fill width changes
|
||||
self._sweep = 0.0
|
||||
|
||||
+1
-1
Submodule tinygrad_repo updated: 92b742f711...69fd4d34d4
Reference in New Issue
Block a user