mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-08-07 13:05:43 +08:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| df83374927 | |||
| 5bdc0c23a9 | |||
| 1a07e47228 | |||
| 50b860c928 | |||
| 978ec800fe | |||
| 3a05c03079 |
@@ -34,6 +34,14 @@ on:
|
|||||||
required: false
|
required: false
|
||||||
default: true
|
default: true
|
||||||
type: boolean
|
type: boolean
|
||||||
|
target_hardware:
|
||||||
|
description: 'Hardware target to compile for'
|
||||||
|
required: false
|
||||||
|
type: choice
|
||||||
|
default: 'qcom'
|
||||||
|
options:
|
||||||
|
- qcom
|
||||||
|
- usbgpu
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
upstream_branch:
|
upstream_branch:
|
||||||
@@ -81,9 +89,17 @@ on:
|
|||||||
description: 'Minimum selector version'
|
description: 'Minimum selector version'
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
|
target_hardware:
|
||||||
|
description: 'Hardware target to compile for'
|
||||||
|
required: false
|
||||||
|
type: choice
|
||||||
|
default: 'qcom'
|
||||||
|
options:
|
||||||
|
- qcom
|
||||||
|
- usbgpu
|
||||||
env:
|
env:
|
||||||
RECOMPILED_DIR: recompiled${{ inputs.recompiled_dir }}
|
RECOMPILED_DIR: recompiled${{ inputs.recompiled_dir }}
|
||||||
JSON_FILE: docs/docs/driving_models_v${{ inputs.json_version }}.json
|
JSON_FILE: docs/docs/driving_models_${{ inputs.target_hardware == 'usbgpu' && 'usbgpu_v' || 'v' }}${{ inputs.json_version }}.json
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_model:
|
build_model:
|
||||||
@@ -93,6 +109,7 @@ jobs:
|
|||||||
custom_name: ${{ inputs.custom_name || inputs.upstream_branch }}
|
custom_name: ${{ inputs.custom_name || inputs.upstream_branch }}
|
||||||
is_20hz: ${{ inputs.is_20hz }}
|
is_20hz: ${{ inputs.is_20hz }}
|
||||||
artifact_suffix: ${{ inputs.artifact_suffix }}
|
artifact_suffix: ${{ inputs.artifact_suffix }}
|
||||||
|
target_hardware: ${{ inputs.target_hardware }}
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
|
|
||||||
publish_model:
|
publish_model:
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
name: prebuilt
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: '0 * * * *'
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
env:
|
|
||||||
DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
BUILD: release/ci/docker_build_sp.sh
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build_prebuilt:
|
|
||||||
name: build prebuilt
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.repository == 'sunnypilot/sunnypilot'
|
|
||||||
env:
|
|
||||||
PUSH_IMAGE: true
|
|
||||||
permissions:
|
|
||||||
checks: read
|
|
||||||
contents: read
|
|
||||||
packages: write
|
|
||||||
steps:
|
|
||||||
- name: Wait for green check mark
|
|
||||||
if: ${{ github.event_name != 'workflow_dispatch' }}
|
|
||||||
uses: lewagon/wait-on-check-action@ccfb013c15c8afb7bf2b7c028fb74dc5a068cccc
|
|
||||||
with:
|
|
||||||
ref: master
|
|
||||||
wait-interval: 30
|
|
||||||
running-workflow-name: 'build prebuilt'
|
|
||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
check-regexp: ^((?!.*(build master-ci|create badges).*).)*$
|
|
||||||
- uses: actions/checkout@v6
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- run: git lfs pull
|
|
||||||
- name: Build and Push docker image
|
|
||||||
run: |
|
|
||||||
$DOCKER_LOGIN
|
|
||||||
eval "$BUILD"
|
|
||||||
@@ -191,7 +191,7 @@ jobs:
|
|||||||
if [ "${{ inputs.target_hardware }}" == "usbgpu" ]; then
|
if [ "${{ inputs.target_hardware }}" == "usbgpu" ]; then
|
||||||
echo "USBGPU build"
|
echo "USBGPU build"
|
||||||
export USBGPU=1
|
export USBGPU=1
|
||||||
TG_FLAGS="DEV=AMD USBGPU=1 IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1"
|
TG_FLAGS="DEBUG=2 DEV=USB+AMD:LLVM WARP_DEV=QCOM FLOAT16=1 JIT_BATCH_SIZE=0 GMMU=0"
|
||||||
OUTPUT_PKL="${{ env.MODELS_DIR }}/big_driving_tinygrad.pkl"
|
OUTPUT_PKL="${{ env.MODELS_DIR }}/big_driving_tinygrad.pkl"
|
||||||
else
|
else
|
||||||
echo "QCOM build"
|
echo "QCOM build"
|
||||||
|
|||||||
+1
-1
Submodule opendbc_repo updated: d6b9c1adaa...4c64e8a95b
@@ -69,6 +69,8 @@ struct LeadData {
|
|||||||
struct SelfdriveStateSP @0x81c2f05a394cf4af {
|
struct SelfdriveStateSP @0x81c2f05a394cf4af {
|
||||||
mads @0 :ModularAssistiveDrivingSystem;
|
mads @0 :ModularAssistiveDrivingSystem;
|
||||||
intelligentCruiseButtonManagement @1 :IntelligentCruiseButtonManagement;
|
intelligentCruiseButtonManagement @1 :IntelligentCruiseButtonManagement;
|
||||||
|
buttonsPressed @2 :UInt16;
|
||||||
|
buttonsReleaseToggle @3 :UInt16;
|
||||||
|
|
||||||
enum AudibleAlert {
|
enum AudibleAlert {
|
||||||
none @0;
|
none @0;
|
||||||
|
|||||||
@@ -197,7 +197,9 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"ModelManager_DownloadIndex", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, INT}},
|
{"ModelManager_DownloadIndex", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, INT}},
|
||||||
{"ModelManager_Favs", {PERSISTENT | BACKUP, STRING}},
|
{"ModelManager_Favs", {PERSISTENT | BACKUP, STRING}},
|
||||||
{"ModelManager_LastSyncTime", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, INT, "0"}},
|
{"ModelManager_LastSyncTime", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, INT, "0"}},
|
||||||
|
{"ModelManager_LastSyncTime_USBGPU", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, INT, "0"}},
|
||||||
{"ModelManager_ModelsCache", {PERSISTENT | BACKUP, JSON}},
|
{"ModelManager_ModelsCache", {PERSISTENT | BACKUP, JSON}},
|
||||||
|
{"ModelManager_ModelsCache_USBGPU", {PERSISTENT | BACKUP, JSON}},
|
||||||
|
|
||||||
// Neural Network Lateral Control
|
// Neural Network Lateral Control
|
||||||
{"NeuralNetworkLateralControl", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"NeuralNetworkLateralControl", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
@@ -222,6 +224,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
|||||||
{"SubaruStopAndGo", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"SubaruStopAndGo", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
{"SubaruStopAndGoManualParkingBrake", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"SubaruStopAndGoManualParkingBrake", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
{"TeslaCoopSteering", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"TeslaCoopSteering", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
|
{"TeslaMadsScreenButton", {PERSISTENT | BACKUP, INT, "0"}},
|
||||||
{"ToyotaEnforceStockLongitudinal", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"ToyotaEnforceStockLongitudinal", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
{"ToyotaStopAndGoHack", {PERSISTENT | BACKUP, BOOL, "0"}},
|
{"ToyotaStopAndGoHack", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||||
|
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
|
|||||||
def publish(self, sm, pm):
|
def publish(self, sm, pm):
|
||||||
plan_send = messaging.new_message('longitudinalPlan')
|
plan_send = messaging.new_message('longitudinalPlan')
|
||||||
|
|
||||||
plan_send.valid = sm.all_checks(service_list=['carState', 'controlsState', 'selfdriveState', 'radarState'])
|
plan_send.valid = sm.all_checks()
|
||||||
|
|
||||||
longitudinalPlan = plan_send.longitudinalPlan
|
longitudinalPlan = plan_send.longitudinalPlan
|
||||||
longitudinalPlan.modelMonoTime = sm.logMonoTime['modelV2']
|
longitudinalPlan.modelMonoTime = sm.logMonoTime['modelV2']
|
||||||
|
|||||||
@@ -29,19 +29,19 @@ def main():
|
|||||||
longitudinal_planner = LongitudinalPlanner(CP, CP_SP)
|
longitudinal_planner = LongitudinalPlanner(CP, CP_SP)
|
||||||
pm = messaging.PubMaster(['longitudinalPlan', 'driverAssistance', 'longitudinalPlanSP'])
|
pm = messaging.PubMaster(['longitudinalPlan', 'driverAssistance', 'longitudinalPlanSP'])
|
||||||
sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'liveParameters', 'radarState', 'modelV2', 'selfdriveState',
|
sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'liveParameters', 'radarState', 'modelV2', 'selfdriveState',
|
||||||
'liveMapDataSP', 'carStateSP', gps_location_service],
|
'liveMapDataSP', 'carStateSP', 'selfdriveStateSP', gps_location_service],
|
||||||
poll='carState', ignore_alive=ignore_services, ignore_avg_freq=ignore_services, ignore_valid=ignore_services)
|
poll='modelV2', ignore_alive=ignore_services, ignore_avg_freq=ignore_services, ignore_valid=ignore_services)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
sm.update()
|
sm.update()
|
||||||
longitudinal_planner.sla.update_car_state(sm['carState'])
|
longitudinal_planner.sla.update_buttons(sm['selfdriveStateSP'].buttonsReleaseToggle)
|
||||||
if sm.updated['modelV2']:
|
if sm.updated['modelV2']:
|
||||||
longitudinal_planner.update(sm)
|
longitudinal_planner.update(sm)
|
||||||
longitudinal_planner.publish(sm, pm)
|
longitudinal_planner.publish(sm, pm)
|
||||||
|
|
||||||
ldw.update(sm.frame, sm['modelV2'], sm['carState'], sm['carControl'])
|
ldw.update(sm.frame, sm['modelV2'], sm['carState'], sm['carControl'])
|
||||||
msg = messaging.new_message('driverAssistance')
|
msg = messaging.new_message('driverAssistance')
|
||||||
msg.valid = sm.all_checks(['carState', 'carControl', 'modelV2', 'liveParameters'])
|
msg.valid = sm.all_checks()
|
||||||
msg.driverAssistance.leftLaneDeparture = ldw.left
|
msg.driverAssistance.leftLaneDeparture = ldw.left
|
||||||
msg.driverAssistance.rightLaneDeparture = ldw.right
|
msg.driverAssistance.rightLaneDeparture = ldw.right
|
||||||
pm.send('driverAssistance', msg)
|
pm.send('driverAssistance', msg)
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import argparse
|
|||||||
import atexit
|
import atexit
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
import pickle
|
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
import shutil
|
import shutil
|
||||||
@@ -13,7 +12,6 @@ from collections import namedtuple
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from openpilot.selfdrive.modeld.helpers import dump_oob, load_oob
|
from openpilot.selfdrive.modeld.helpers import dump_oob, load_oob
|
||||||
from openpilot.selfdrive.modeld.usbgpu_link import wait_usbgpu_link
|
|
||||||
|
|
||||||
def _patch_tinygrad_fetch_fw():
|
def _patch_tinygrad_fetch_fw():
|
||||||
import hashlib
|
import hashlib
|
||||||
@@ -31,22 +29,6 @@ def _patch_tinygrad_fetch_fw():
|
|||||||
helpers.fetch_fw = fetch_fw
|
helpers.fetch_fw = fetch_fw
|
||||||
_patch_tinygrad_fetch_fw()
|
_patch_tinygrad_fetch_fw()
|
||||||
|
|
||||||
def _patch_tinygrad_buffer_reduce():
|
|
||||||
from tinygrad.device import Buffer
|
|
||||||
def __reduce_ex__(self, protocol):
|
|
||||||
buf = None
|
|
||||||
if self._base is not None:
|
|
||||||
return self.__class__, (self.device, self.size, self.dtype, None, None, None, 0, self.base, self.offset, self.is_allocated())
|
|
||||||
if self.device == "NPY":
|
|
||||||
return self.__class__, (self.device, self.size, self.dtype, self._buf, self.options, None, self.uop_refcount)
|
|
||||||
if self.is_allocated():
|
|
||||||
buf = bytearray(self.nbytes)
|
|
||||||
self.copyout(memoryview(buf))
|
|
||||||
if protocol >= 5:
|
|
||||||
buf = pickle.PickleBuffer(buf)
|
|
||||||
return self.__class__, (self.device, self.size, self.dtype, None, self.options, buf, self.uop_refcount)
|
|
||||||
Buffer.__reduce_ex__ = __reduce_ex__
|
|
||||||
_patch_tinygrad_buffer_reduce()
|
|
||||||
|
|
||||||
from tinygrad.tensor import Tensor
|
from tinygrad.tensor import Tensor
|
||||||
from tinygrad.helpers import Context
|
from tinygrad.helpers import Context
|
||||||
@@ -312,9 +294,6 @@ if __name__ == "__main__":
|
|||||||
p.add_argument('--frame-skip', type=int, required=True)
|
p.add_argument('--frame-skip', type=int, required=True)
|
||||||
args = p.parse_args()
|
args = p.parse_args()
|
||||||
|
|
||||||
if 'USB+AMD' in os.environ.get('DEV', ''):
|
|
||||||
wait_usbgpu_link()
|
|
||||||
|
|
||||||
model_path = read_file_chunked_to_disk(args.onnx)
|
model_path = read_file_chunked_to_disk(args.onnx)
|
||||||
model_w, model_h = args.model_size
|
model_w, model_h = args.model_size
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,11 @@ import struct
|
|||||||
import tempfile
|
import tempfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from openpilot.common.file_chunker import get_manifest_path
|
||||||
|
from openpilot.common.hardware.usb import CHESTNUT_VENDOR_ID, CHESTNUT_PRODUCT_ID, USB_DEVICES_PATH
|
||||||
|
|
||||||
MODELS_DIR = Path(__file__).resolve().parent / 'models'
|
MODELS_DIR = Path(__file__).resolve().parent / 'models'
|
||||||
TG_INPUT_DEVICES_PATH = MODELS_DIR / 'tg_input_devices.json'
|
TG_INPUT_DEVICES_PATH = MODELS_DIR / 'tg_input_devices.json'
|
||||||
USBGPU_VID = 0xADD1
|
|
||||||
USBGPU_PID = 0x0001
|
|
||||||
|
|
||||||
|
|
||||||
def get_tg_input_devices(process_name: str, usbgpu: bool):
|
def get_tg_input_devices(process_name: str, usbgpu: bool):
|
||||||
@@ -38,22 +39,21 @@ def dump_oob(obj, f):
|
|||||||
def load_oob(f):
|
def load_oob(f):
|
||||||
opcodes = f.read(struct.unpack('<q', f.read(8))[0])
|
opcodes = f.read(struct.unpack('<q', f.read(8))[0])
|
||||||
def buffers():
|
def buffers():
|
||||||
prev = None
|
|
||||||
while (h := f.read(8)):
|
while (h := f.read(8)):
|
||||||
if prev is not None:
|
pb = pickle.PickleBuffer(bytearray(struct.unpack('<q', h)[0]))
|
||||||
prev.release()
|
f.readinto(pb)
|
||||||
buf = bytearray(struct.unpack('<q', h)[0])
|
yield pb
|
||||||
f.readinto(buf)
|
|
||||||
prev = pickle.PickleBuffer(buf)
|
|
||||||
yield prev
|
|
||||||
return pickle.load(io.BytesIO(opcodes), buffers=buffers())
|
return pickle.load(io.BytesIO(opcodes), buffers=buffers())
|
||||||
|
|
||||||
def usbgpu_present() -> bool:
|
def usbgpu_present() -> bool:
|
||||||
for d in Path("/sys/bus/usb/devices").glob("*"):
|
for d in USB_DEVICES_PATH.glob("*"):
|
||||||
try:
|
try:
|
||||||
if int((d / "idVendor").read_text(), 16) == USBGPU_VID and \
|
usb_id = (int((d / "idVendor").read_text(), 16), int((d / "idProduct").read_text(), 16))
|
||||||
int((d / "idProduct").read_text(), 16) == USBGPU_PID:
|
if usb_id == (CHESTNUT_VENDOR_ID, CHESTNUT_PRODUCT_ID):
|
||||||
return True
|
return True
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def usbgpu_compiled() -> bool:
|
||||||
|
return Path(get_manifest_path(modeld_pkl_path(usbgpu=True))).is_file()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import os
|
import os
|
||||||
os.environ['GMMU'] = '0' # for usbgpu fast loading, noop for qcom
|
os.environ['GMMU'] = '0' # for usbgpu fast loading, noop for qcom
|
||||||
from tinygrad.tensor import Tensor
|
from tinygrad.tensor import Tensor
|
||||||
|
import threading
|
||||||
import time
|
import time
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import openpilot.cereal.messaging as messaging
|
import openpilot.cereal.messaging as messaging
|
||||||
@@ -18,17 +19,13 @@ from openpilot.common.transformations.camera import DEVICE_CAMERAS
|
|||||||
from openpilot.system.camerad.cameras.nv12_info import get_nv12_info
|
from openpilot.system.camerad.cameras.nv12_info import get_nv12_info
|
||||||
from openpilot.common.transformations.model import get_warp_matrix
|
from openpilot.common.transformations.model import get_warp_matrix
|
||||||
from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper
|
from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper
|
||||||
from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, smooth_value, get_curvature_from_plan
|
from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, should_stop, smooth_value, get_curvature_from_plan
|
||||||
from openpilot.selfdrive.modeld.parse_model_outputs import Parser
|
from openpilot.selfdrive.modeld.parse_model_outputs import Parser
|
||||||
from openpilot.selfdrive.modeld.compile_modeld import make_input_queues, WARP_INPUTS, POLICY_INPUTS
|
from openpilot.selfdrive.modeld.compile_modeld import make_input_queues, WARP_INPUTS, POLICY_INPUTS
|
||||||
from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_driving_model_data, fill_pose_msg, PublishState
|
from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_driving_model_data, fill_pose_msg, PublishState
|
||||||
from openpilot.common.file_chunker import open_file_chunked, get_manifest_path
|
from openpilot.common.file_chunker import open_file_chunked
|
||||||
from openpilot.selfdrive.modeld.constants import ModelConstants, Plan
|
from openpilot.selfdrive.modeld.constants import ModelConstants, Plan
|
||||||
from openpilot.selfdrive.modeld.helpers import usbgpu_present, modeld_pkl_path, get_tg_input_devices, load_oob
|
from openpilot.selfdrive.modeld.helpers import usbgpu_present, usbgpu_compiled, modeld_pkl_path, get_tg_input_devices, load_oob
|
||||||
from openpilot.selfdrive.modeld.usbgpu_link import wait_usbgpu_link
|
|
||||||
|
|
||||||
from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
|
|
||||||
from openpilot.sunnypilot.modeld_v2.modeld_base import ModelStateBase
|
|
||||||
|
|
||||||
PROCESS_NAME = "openpilot.selfdrive.modeld.modeld"
|
PROCESS_NAME = "openpilot.selfdrive.modeld.modeld"
|
||||||
SEND_RAW_PRED = os.getenv('SEND_RAW_PRED')
|
SEND_RAW_PRED = os.getenv('SEND_RAW_PRED')
|
||||||
@@ -36,16 +33,17 @@ SEND_RAW_PRED = os.getenv('SEND_RAW_PRED')
|
|||||||
LAT_SMOOTH_SECONDS = 0.0
|
LAT_SMOOTH_SECONDS = 0.0
|
||||||
LONG_SMOOTH_SECONDS = 0.3
|
LONG_SMOOTH_SECONDS = 0.3
|
||||||
MIN_LAT_CONTROL_SPEED = 0.3
|
MIN_LAT_CONTROL_SPEED = 0.3
|
||||||
|
BIG_MODEL_TIMEOUT = 60
|
||||||
|
|
||||||
|
|
||||||
def get_action_from_model(model_output: dict[str, np.ndarray], prev_action: log.ModelDataV2.Action,
|
def get_action_from_model(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) -> log.ModelDataV2.Action:
|
||||||
if 'action' not in model_output:
|
if 'action' not in model_output:
|
||||||
plan = model_output['plan'][0]
|
plan = model_output['plan'][0]
|
||||||
desired_accel, should_stop = get_accel_from_plan(plan[:,Plan.VELOCITY][:,0],
|
desired_accel = get_accel_from_plan(plan[:,Plan.VELOCITY][:,0],
|
||||||
plan[:,Plan.ACCELERATION][:,0],
|
plan[:,Plan.ACCELERATION][:,0],
|
||||||
ModelConstants.T_IDXS,
|
ModelConstants.T_IDXS,
|
||||||
action_t=long_action_t)
|
action_t=long_action_t)
|
||||||
desired_curvature = get_curvature_from_plan(plan[:,Plan.T_FROM_CURRENT_EULER][:,2],
|
desired_curvature = get_curvature_from_plan(plan[:,Plan.T_FROM_CURRENT_EULER][:,2],
|
||||||
plan[:,Plan.ORIENTATION_RATE][:,2],
|
plan[:,Plan.ORIENTATION_RATE][:,2],
|
||||||
ModelConstants.T_IDXS,
|
ModelConstants.T_IDXS,
|
||||||
@@ -54,7 +52,7 @@ def get_action_from_model(model_output: dict[str, np.ndarray], prev_action: log.
|
|||||||
else:
|
else:
|
||||||
desired_accel = model_output['action'][0,1]
|
desired_accel = model_output['action'][0,1]
|
||||||
desired_curvature = model_output['action'][0,0] / (max(1.0, v_ego))**2
|
desired_curvature = model_output['action'][0,0] / (max(1.0, v_ego))**2
|
||||||
should_stop = (v_ego < 0.3 and desired_accel < 0.1)
|
stop = should_stop(v_ego, desired_accel)
|
||||||
desired_accel = smooth_value(desired_accel, prev_action.desiredAcceleration, LONG_SMOOTH_SECONDS)
|
desired_accel = smooth_value(desired_accel, prev_action.desiredAcceleration, LONG_SMOOTH_SECONDS)
|
||||||
if v_ego > MIN_LAT_CONTROL_SPEED:
|
if v_ego > MIN_LAT_CONTROL_SPEED:
|
||||||
desired_curvature = smooth_value(desired_curvature, prev_action.desiredCurvature, LAT_SMOOTH_SECONDS)
|
desired_curvature = smooth_value(desired_curvature, prev_action.desiredCurvature, LAT_SMOOTH_SECONDS)
|
||||||
@@ -63,7 +61,7 @@ def get_action_from_model(model_output: dict[str, np.ndarray], prev_action: log.
|
|||||||
|
|
||||||
return log.ModelDataV2.Action(desiredCurvature=float(desired_curvature),
|
return log.ModelDataV2.Action(desiredCurvature=float(desired_curvature),
|
||||||
desiredAcceleration=float(desired_accel),
|
desiredAcceleration=float(desired_accel),
|
||||||
shouldStop=bool(should_stop))
|
shouldStop=bool(stop))
|
||||||
|
|
||||||
|
|
||||||
class FrameMeta:
|
class FrameMeta:
|
||||||
@@ -76,12 +74,10 @@ class FrameMeta:
|
|||||||
self.frame_id, self.timestamp_sof, self.timestamp_eof = vipc.frame_id, vipc.timestamp_sof, vipc.timestamp_eof
|
self.frame_id, self.timestamp_sof, self.timestamp_eof = vipc.frame_id, vipc.timestamp_sof, vipc.timestamp_eof
|
||||||
|
|
||||||
|
|
||||||
class ModelState(ModelStateBase):
|
class ModelState:
|
||||||
prev_desire: np.ndarray # for tracking the rising edge of the pulse
|
prev_desire: np.ndarray # for tracking the rising edge of the pulse
|
||||||
|
|
||||||
def __init__(self, cam_w: int, cam_h: int, usbgpu: bool):
|
def __init__(self, cam_w: int, cam_h: int, usbgpu: bool):
|
||||||
ModelStateBase.__init__(self)
|
|
||||||
self.LAT_SMOOTH_SECONDS = LAT_SMOOTH_SECONDS
|
|
||||||
input_devices = get_tg_input_devices(PROCESS_NAME, usbgpu)
|
input_devices = get_tg_input_devices(PROCESS_NAME, usbgpu)
|
||||||
self.WARP_DEV, self.QUEUE_DEV = input_devices['WARP_DEV'], input_devices['QUEUE_DEV']
|
self.WARP_DEV, self.QUEUE_DEV = input_devices['WARP_DEV'], input_devices['QUEUE_DEV']
|
||||||
jits = load_oob(open_file_chunked(modeld_pkl_path(usbgpu)))
|
jits = load_oob(open_file_chunked(modeld_pkl_path(usbgpu)))
|
||||||
@@ -138,16 +134,24 @@ class ModelState(ModelStateBase):
|
|||||||
outputs_dict['raw_pred'] = model_output.copy()
|
outputs_dict['raw_pred'] = model_output.copy()
|
||||||
return outputs_dict
|
return outputs_dict
|
||||||
|
|
||||||
|
def warmup(self) -> None:
|
||||||
|
dummy_frames = {k: np.zeros(self.frame_buf_params[k][3], dtype=np.uint8) for k in self.vision_input_names}
|
||||||
|
eye = np.eye(3, dtype=np.float32)
|
||||||
|
dims = {'desire_pulse': ModelConstants.DESIRE_LEN, 'traffic_convention': 2, 'action_t': 2}
|
||||||
|
self.run(dummy_frames, dict.fromkeys(self.vision_input_names, eye), {k: np.zeros(v, dtype=np.float32) for k, v in dims.items()})
|
||||||
|
self.input_queues, self.npy = make_input_queues(self.input_shapes, self.frame_skip, device=self.QUEUE_DEV)
|
||||||
|
self.prev_desire[:] = 0
|
||||||
|
self.full_frames.clear()
|
||||||
|
self._blob_cache.clear()
|
||||||
|
|
||||||
|
|
||||||
def main(demo=False):
|
def main(demo=False):
|
||||||
cloudlog.warning("modeld init")
|
cloudlog.warning("modeld init")
|
||||||
|
|
||||||
_present = usbgpu_present()
|
USBGPU = usbgpu_present() and usbgpu_compiled()
|
||||||
_compiled = os.path.isfile(get_manifest_path(modeld_pkl_path(usbgpu=True)))
|
|
||||||
USBGPU = _present and _compiled
|
|
||||||
params = Params()
|
params = Params()
|
||||||
params.put_bool("UsbGpuPresent", _present)
|
params.put_bool("UsbGpuLoading", USBGPU)
|
||||||
params.put_bool("UsbGpuCompiled", _compiled)
|
params.remove("UsbGpuActive")
|
||||||
|
|
||||||
config_realtime_process(7, 54)
|
config_realtime_process(7, 54)
|
||||||
|
|
||||||
@@ -174,15 +178,33 @@ def main(demo=False):
|
|||||||
if use_extra_client:
|
if use_extra_client:
|
||||||
cloudlog.warning(f"connected extra cam with buffer size: {vipc_client_extra.buffer_len} ({vipc_client_extra.width} x {vipc_client_extra.height})")
|
cloudlog.warning(f"connected extra cam with buffer size: {vipc_client_extra.buffer_len} ({vipc_client_extra.width} x {vipc_client_extra.height})")
|
||||||
|
|
||||||
if USBGPU:
|
|
||||||
wait_usbgpu_link()
|
|
||||||
st = time.monotonic()
|
st = time.monotonic()
|
||||||
cloudlog.warning("loading model")
|
cloudlog.warning("loading model")
|
||||||
model = ModelState(vipc_client_main.width, vipc_client_main.height, USBGPU)
|
model = None
|
||||||
|
if USBGPU:
|
||||||
|
big_model = None
|
||||||
|
def load_big():
|
||||||
|
nonlocal big_model
|
||||||
|
try:
|
||||||
|
m = ModelState(vipc_client_main.width, vipc_client_main.height, True)
|
||||||
|
m.warmup()
|
||||||
|
big_model = m
|
||||||
|
except Exception:
|
||||||
|
cloudlog.exception("big model load failed")
|
||||||
|
loader = threading.Thread(target=load_big, daemon=True)
|
||||||
|
loader.start()
|
||||||
|
loader.join(BIG_MODEL_TIMEOUT)
|
||||||
|
model = big_model
|
||||||
|
params.put_bool("UsbGpuActive", model is not None)
|
||||||
|
|
||||||
|
small_model = ModelState(vipc_client_main.width, vipc_client_main.height, False) if model is None or USBGPU else None
|
||||||
|
if model is None:
|
||||||
|
model = small_model
|
||||||
|
params.put_bool("UsbGpuLoading", False)
|
||||||
cloudlog.warning(f"models loaded in {time.monotonic() - st:.1f}s, modeld starting")
|
cloudlog.warning(f"models loaded in {time.monotonic() - st:.1f}s, modeld starting")
|
||||||
|
|
||||||
# messaging
|
# messaging
|
||||||
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry", "modelDataV2SP"])
|
pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry"])
|
||||||
sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay"])
|
sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay"])
|
||||||
|
|
||||||
publish_state = PublishState()
|
publish_state = PublishState()
|
||||||
@@ -252,9 +274,7 @@ def main(demo=False):
|
|||||||
is_rhd = sm["driverMonitoringState"].isRHD
|
is_rhd = sm["driverMonitoringState"].isRHD
|
||||||
frame_id = sm["roadCameraState"].frameId
|
frame_id = sm["roadCameraState"].frameId
|
||||||
v_ego = max(sm["carState"].vEgo, 0.)
|
v_ego = max(sm["carState"].vEgo, 0.)
|
||||||
if sm.frame % 60 == 0:
|
lat_delay = sm["liveDelay"].lateralDelay + LAT_SMOOTH_SECONDS
|
||||||
model.lat_delay = get_lat_delay(params, sm["liveDelay"].lateralDelay)
|
|
||||||
lat_delay = model.lat_delay + LAT_SMOOTH_SECONDS
|
|
||||||
if sm.updated["liveCalibration"] and sm.seen['roadCameraState'] and sm.seen['deviceState']:
|
if sm.updated["liveCalibration"] and sm.seen['roadCameraState'] and sm.seen['deviceState']:
|
||||||
device_from_calib_euler = np.array(sm["liveCalibration"].rpyCalib, dtype=np.float32)
|
device_from_calib_euler = np.array(sm["liveCalibration"].rpyCalib, dtype=np.float32)
|
||||||
dc = DEVICE_CAMERAS[(str(sm['deviceState'].deviceType), str(sm['roadCameraState'].sensor))]
|
dc = DEVICE_CAMERAS[(str(sm['deviceState'].deviceType), str(sm['roadCameraState'].sensor))]
|
||||||
@@ -293,7 +313,17 @@ def main(demo=False):
|
|||||||
}
|
}
|
||||||
|
|
||||||
mt1 = time.perf_counter()
|
mt1 = time.perf_counter()
|
||||||
model_output = model.run(bufs, transforms, inputs)
|
try:
|
||||||
|
model_output = model.run(bufs, transforms, inputs)
|
||||||
|
except Exception:
|
||||||
|
if not params.get_bool("UsbGpuActive"):
|
||||||
|
raise
|
||||||
|
# fallback to small model
|
||||||
|
cloudlog.exception("big model failed, fall back to small")
|
||||||
|
params.put_bool("UsbGpuActive", False)
|
||||||
|
model = small_model
|
||||||
|
run_count = 0
|
||||||
|
model_output = None
|
||||||
mt2 = time.perf_counter()
|
mt2 = time.perf_counter()
|
||||||
model_execution_time = mt2 - mt1
|
model_execution_time = mt2 - mt1
|
||||||
|
|
||||||
@@ -301,7 +331,6 @@ def main(demo=False):
|
|||||||
modelv2_send = messaging.new_message('modelV2')
|
modelv2_send = messaging.new_message('modelV2')
|
||||||
drivingdata_send = messaging.new_message('drivingModelData')
|
drivingdata_send = messaging.new_message('drivingModelData')
|
||||||
posenet_send = messaging.new_message('cameraOdometry')
|
posenet_send = messaging.new_message('cameraOdometry')
|
||||||
mdv2sp_send = messaging.new_message('modelDataV2SP')
|
|
||||||
|
|
||||||
action = get_action_from_model(model_output, prev_action, lat_action_t, long_action_t, v_ego)
|
action = get_action_from_model(model_output, prev_action, lat_action_t, long_action_t, v_ego)
|
||||||
prev_action = action
|
prev_action = action
|
||||||
@@ -316,14 +345,12 @@ def main(demo=False):
|
|||||||
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob)
|
DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob)
|
||||||
modelv2_send.modelV2.meta.laneChangeState = DH.lane_change_state
|
modelv2_send.modelV2.meta.laneChangeState = DH.lane_change_state
|
||||||
modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction
|
modelv2_send.modelV2.meta.laneChangeDirection = DH.lane_change_direction
|
||||||
mdv2sp_send.modelDataV2SP.laneTurnDirection = DH.lane_turn_direction
|
|
||||||
|
|
||||||
fill_driving_model_data(drivingdata_send, modelv2_send)
|
fill_driving_model_data(drivingdata_send, modelv2_send)
|
||||||
fill_pose_msg(posenet_send, model_output, meta_main.frame_id, vipc_dropped_frames, meta_main.timestamp_eof, live_calib_seen)
|
fill_pose_msg(posenet_send, model_output, meta_main.frame_id, vipc_dropped_frames, meta_main.timestamp_eof, live_calib_seen)
|
||||||
pm.send('modelV2', modelv2_send)
|
pm.send('modelV2', modelv2_send)
|
||||||
pm.send('drivingModelData', drivingdata_send)
|
pm.send('drivingModelData', drivingdata_send)
|
||||||
pm.send('cameraOdometry', posenet_send)
|
pm.send('cameraOdometry', posenet_send)
|
||||||
pm.send('modelDataV2SP', mdv2sp_send)
|
|
||||||
last_vipc_frame_id = meta_main.frame_id
|
last_vipc_frame_id = meta_main.frame_id
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ from openpilot.sunnypilot import get_sanitize_int_param
|
|||||||
from openpilot.sunnypilot.selfdrive.car.car_specific import CarSpecificEventsSP
|
from openpilot.sunnypilot.selfdrive.car.car_specific import CarSpecificEventsSP
|
||||||
from openpilot.sunnypilot.selfdrive.car.cruise_helpers import CruiseHelper
|
from openpilot.sunnypilot.selfdrive.car.cruise_helpers import CruiseHelper
|
||||||
from openpilot.sunnypilot.selfdrive.car.intelligent_cruise_button_management.controller import IntelligentCruiseButtonManagement
|
from openpilot.sunnypilot.selfdrive.car.intelligent_cruise_button_management.controller import IntelligentCruiseButtonManagement
|
||||||
|
from openpilot.sunnypilot.selfdrive.selfdrived.button_state_tracker import ButtonStateTracker
|
||||||
from openpilot.sunnypilot.selfdrive.selfdrived.events import EventsSP
|
from openpilot.sunnypilot.selfdrive.selfdrived.events import EventsSP
|
||||||
|
|
||||||
REPLAY = "REPLAY" in os.environ
|
REPLAY = "REPLAY" in os.environ
|
||||||
@@ -177,6 +178,7 @@ class SelfdriveD(CruiseHelper):
|
|||||||
self.car_events_sp = CarSpecificEventsSP(self.CP, self.CP_SP)
|
self.car_events_sp = CarSpecificEventsSP(self.CP, self.CP_SP)
|
||||||
|
|
||||||
CruiseHelper.__init__(self, self.CP)
|
CruiseHelper.__init__(self, self.CP)
|
||||||
|
self.button_state_tracker = ButtonStateTracker()
|
||||||
|
|
||||||
def update_events(self, CS):
|
def update_events(self, CS):
|
||||||
"""Compute onroadEvents from carState"""
|
"""Compute onroadEvents from carState"""
|
||||||
@@ -597,6 +599,8 @@ class SelfdriveD(CruiseHelper):
|
|||||||
icbm.sendButton = self.icbm.cruise_button
|
icbm.sendButton = self.icbm.cruise_button
|
||||||
icbm.vTarget = self.icbm.v_target
|
icbm.vTarget = self.icbm.v_target
|
||||||
|
|
||||||
|
self.button_state_tracker.publish(ss_sp)
|
||||||
|
|
||||||
self.pm.send('selfdriveStateSP', ss_sp_msg)
|
self.pm.send('selfdriveStateSP', ss_sp_msg)
|
||||||
|
|
||||||
# onroadEventsSP - logged every second or on change
|
# onroadEventsSP - logged every second or on change
|
||||||
@@ -616,6 +620,7 @@ class SelfdriveD(CruiseHelper):
|
|||||||
self.mads.update(CS)
|
self.mads.update(CS)
|
||||||
self.update_alerts(CS)
|
self.update_alerts(CS)
|
||||||
|
|
||||||
|
self.button_state_tracker.update(CS)
|
||||||
self.publish_selfdriveState(CS)
|
self.publish_selfdriveState(CS)
|
||||||
|
|
||||||
self.CS_prev = CS
|
self.CS_prev = CS
|
||||||
|
|||||||
+5
-2
@@ -7,7 +7,7 @@ See the LICENSE.md file in the root directory for more details.
|
|||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
import pyray as rl
|
import pyray as rl
|
||||||
|
|
||||||
from opendbc.sunnypilot.car.tesla.values import TeslaFlagsSP
|
from opendbc.sunnypilot.car.tesla.values import MadsScreenButtonType, TeslaFlagsSP
|
||||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||||
from openpilot.sunnypilot.mads.helpers import MadsSteeringModeOnBrake
|
from openpilot.sunnypilot.mads.helpers import MadsSteeringModeOnBrake
|
||||||
from openpilot.system.ui.lib.multilang import tr, tr_noop
|
from openpilot.system.ui.lib.multilang import tr, tr_noop
|
||||||
@@ -96,7 +96,10 @@ class MadsSettingsLayout(Widget):
|
|||||||
if brand == "rivian":
|
if brand == "rivian":
|
||||||
return True
|
return True
|
||||||
elif brand == "tesla":
|
elif brand == "tesla":
|
||||||
return not (ui_state.CP_SP is not None and ui_state.CP_SP.flags & TeslaFlagsSP.HAS_VEHICLE_BUS)
|
if ui_state.CP_SP is None or not ui_state.CP_SP.flags & TeslaFlagsSP.HAS_VEHICLE_BUS:
|
||||||
|
return True
|
||||||
|
screen_button = int(ui_state.params.get("TeslaMadsScreenButton", return_default=True))
|
||||||
|
return screen_button == MadsScreenButtonType.OFF
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _update_steering_mode_description(self, button_index: int):
|
def _update_steering_mode_description(self, button_index: int):
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
|||||||
This file is part of sunnypilot and is licensed under the MIT License.
|
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.
|
See the LICENSE.md file in the root directory for more details.
|
||||||
"""
|
"""
|
||||||
|
from opendbc.sunnypilot.car.tesla.values import TeslaFlagsSP
|
||||||
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.base import BrandSettings
|
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle.brands.base import BrandSettings
|
||||||
from openpilot.selfdrive.ui.ui_state import ui_state
|
from openpilot.selfdrive.ui.ui_state import ui_state
|
||||||
from openpilot.system.ui.lib.multilang import tr
|
from openpilot.system.ui.lib.multilang import tr
|
||||||
from openpilot.system.ui.sunnypilot.widgets.list_view import toggle_item_sp
|
from openpilot.system.ui.sunnypilot.widgets.list_view import multiple_button_item_sp, toggle_item_sp
|
||||||
|
|
||||||
COOP_STEERING_MIN_KMH = 23
|
COOP_STEERING_MIN_KMH = 23
|
||||||
OEM_STEERING_MIN_KMH = 48
|
OEM_STEERING_MIN_KMH = 48
|
||||||
@@ -18,7 +19,14 @@ class TeslaSettings(BrandSettings):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.coop_steering_toggle = toggle_item_sp(tr("Cooperative Steering (Beta)"), "", param="TeslaCoopSteering")
|
self.coop_steering_toggle = toggle_item_sp(tr("Cooperative Steering (Beta)"), "", param="TeslaCoopSteering")
|
||||||
self.items = [self.coop_steering_toggle]
|
self.mads_screen_button = multiple_button_item_sp(
|
||||||
|
title=lambda: tr("MADS Screen Activation"),
|
||||||
|
description="",
|
||||||
|
buttons=[lambda: tr("Off"), lambda: tr("3-Finger"), lambda: tr("4-Finger"), lambda: tr("5-Finger")],
|
||||||
|
param="TeslaMadsScreenButton",
|
||||||
|
inline=False,
|
||||||
|
)
|
||||||
|
self.items = [self.coop_steering_toggle, self.mads_screen_button]
|
||||||
|
|
||||||
def update_settings(self):
|
def update_settings(self):
|
||||||
is_metric = ui_state.is_metric
|
is_metric = ui_state.is_metric
|
||||||
@@ -41,3 +49,18 @@ class TeslaSettings(BrandSettings):
|
|||||||
|
|
||||||
self.coop_steering_toggle.set_description(coop_steering_desc)
|
self.coop_steering_toggle.set_description(coop_steering_desc)
|
||||||
self.coop_steering_toggle.action_item.set_enabled(ui_state.is_offroad())
|
self.coop_steering_toggle.action_item.set_enabled(ui_state.is_offroad())
|
||||||
|
|
||||||
|
has_vehicle_bus = ui_state.CP_SP is not None and bool(ui_state.CP_SP.flags & TeslaFlagsSP.HAS_VEHICLE_BUS)
|
||||||
|
self.mads_screen_button.set_visible(has_vehicle_bus)
|
||||||
|
|
||||||
|
mads_screen_button_desc = (
|
||||||
|
f"{tr('Use a multi-finger press on the infotainment screen to toggle MADS.')} " +
|
||||||
|
f"{tr('This allows the use of full MADS functionality when enabled.')}<br><br>" +
|
||||||
|
f"{tr('Selecting a higher finger count may reduce accidental activations.')}<br><br>" +
|
||||||
|
f"<b>{tr('Note: Setting this to Off will reset your MADS settings to default.')}</b>"
|
||||||
|
)
|
||||||
|
if not ui_state.is_offroad():
|
||||||
|
mads_screen_button_disabled_msg = tr("Enable \"Always Offroad\" in Device panel, or turn vehicle off to change.")
|
||||||
|
mads_screen_button_desc = f"<b>{mads_screen_button_disabled_msg}</b><br><br>{mads_screen_button_desc}"
|
||||||
|
self.mads_screen_button.set_description(mads_screen_button_desc)
|
||||||
|
self.mads_screen_button.action_item.set_enabled(ui_state.is_offroad())
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from openpilot.common.params import Params
|
|||||||
from opendbc.car import structs
|
from opendbc.car import structs
|
||||||
from opendbc.safety import ALTERNATIVE_EXPERIENCE
|
from opendbc.safety import ALTERNATIVE_EXPERIENCE
|
||||||
from opendbc.sunnypilot.car.hyundai.values import HyundaiFlagsSP, HyundaiSafetyFlagsSP
|
from opendbc.sunnypilot.car.hyundai.values import HyundaiFlagsSP, HyundaiSafetyFlagsSP
|
||||||
from opendbc.sunnypilot.car.tesla.values import TeslaFlagsSP
|
from opendbc.sunnypilot.car.tesla.values import MadsScreenButtonType, TeslaFlagsSP
|
||||||
|
|
||||||
|
|
||||||
MADS_NO_ACC_MAIN_BUTTON = ("rivian", "tesla")
|
MADS_NO_ACC_MAIN_BUTTON = ("rivian", "tesla")
|
||||||
@@ -21,17 +21,20 @@ class MadsSteeringModeOnBrake:
|
|||||||
DISENGAGE = 2
|
DISENGAGE = 2
|
||||||
|
|
||||||
|
|
||||||
def get_mads_limited_brands(CP: structs.CarParams, CP_SP: structs.CarParamsSP) -> bool:
|
def get_mads_limited_brands(CP: structs.CarParams, CP_SP: structs.CarParamsSP, params: Params) -> bool:
|
||||||
if CP.brand == 'rivian':
|
if CP.brand == 'rivian':
|
||||||
return True
|
return True
|
||||||
if CP.brand == 'tesla':
|
if CP.brand == 'tesla':
|
||||||
return not CP_SP.flags & TeslaFlagsSP.HAS_VEHICLE_BUS
|
if not CP_SP.flags & TeslaFlagsSP.HAS_VEHICLE_BUS:
|
||||||
|
return True
|
||||||
|
screen_button = int(params.get("TeslaMadsScreenButton", return_default=True))
|
||||||
|
return screen_button == MadsScreenButtonType.OFF
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def read_steering_mode_param(CP: structs.CarParams, CP_SP: structs.CarParamsSP, params: Params):
|
def read_steering_mode_param(CP: structs.CarParams, CP_SP: structs.CarParamsSP, params: Params):
|
||||||
if get_mads_limited_brands(CP, CP_SP):
|
if get_mads_limited_brands(CP, CP_SP, params):
|
||||||
return MadsSteeringModeOnBrake.DISENGAGE
|
return MadsSteeringModeOnBrake.DISENGAGE
|
||||||
|
|
||||||
return params.get("MadsSteeringMode", return_default=True)
|
return params.get("MadsSteeringMode", return_default=True)
|
||||||
@@ -63,7 +66,7 @@ def set_car_specific_params(CP: structs.CarParams, CP_SP: structs.CarParamsSP, p
|
|||||||
# MADS is currently partially supported for these platforms due to lack of consistent states to engage controls
|
# MADS is currently partially supported for these platforms due to lack of consistent states to engage controls
|
||||||
# Only MadsSteeringModeOnBrake.DISENGAGE is supported for these platforms
|
# Only MadsSteeringModeOnBrake.DISENGAGE is supported for these platforms
|
||||||
# TODO-SP: To enable MADS full support for Rivian and most Tesla, identify consistent signals for MADS toggling
|
# TODO-SP: To enable MADS full support for Rivian and most Tesla, identify consistent signals for MADS toggling
|
||||||
mads_partial_support = get_mads_limited_brands(CP, CP_SP)
|
mads_partial_support = get_mads_limited_brands(CP, CP_SP, params)
|
||||||
if mads_partial_support:
|
if mads_partial_support:
|
||||||
params.put("MadsSteeringMode", 2, block=True)
|
params.put("MadsSteeringMode", 2, block=True)
|
||||||
params.put_bool("MadsUnifiedEngagementMode", True, block=True)
|
params.put_bool("MadsUnifiedEngagementMode", True, block=True)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from openpilot.selfdrive.selfdrived.events import Events
|
|||||||
from openpilot.sunnypilot.selfdrive.selfdrived.events import EventsSP
|
from openpilot.sunnypilot.selfdrive.selfdrived.events import EventsSP
|
||||||
from openpilot.sunnypilot.mads.helpers import MadsSteeringModeOnBrake, read_steering_mode_param
|
from openpilot.sunnypilot.mads.helpers import MadsSteeringModeOnBrake, read_steering_mode_param
|
||||||
from openpilot.sunnypilot.mads.mads import ModularAssistiveDrivingSystem
|
from openpilot.sunnypilot.mads.mads import ModularAssistiveDrivingSystem
|
||||||
from opendbc.sunnypilot.car.tesla.values import TeslaFlagsSP
|
from opendbc.sunnypilot.car.tesla.values import MadsScreenButtonType, TeslaFlagsSP
|
||||||
|
|
||||||
State = custom.ModularAssistiveDrivingSystem.ModularAssistiveDrivingSystemState
|
State = custom.ModularAssistiveDrivingSystem.ModularAssistiveDrivingSystemState
|
||||||
EventName = log.OnroadEvent.EventName
|
EventName = log.OnroadEvent.EventName
|
||||||
@@ -38,6 +38,12 @@ def make_panda_state(mocker, controls_allowed_lateral=True):
|
|||||||
return ps
|
return ps
|
||||||
|
|
||||||
|
|
||||||
|
def make_params_mock(mocker, values):
|
||||||
|
params = mocker.MagicMock()
|
||||||
|
params.get = mocker.MagicMock(side_effect=lambda k, **kwargs: values[k])
|
||||||
|
return params
|
||||||
|
|
||||||
|
|
||||||
def make_mads(mocker, steering_mode):
|
def make_mads(mocker, steering_mode):
|
||||||
sd = mocker.MagicMock()
|
sd = mocker.MagicMock()
|
||||||
sd.CP = structs.CarParams()
|
sd.CP = structs.CarParams()
|
||||||
@@ -223,15 +229,27 @@ class TestBrandSteeringModeRestrictions:
|
|||||||
params = mocker.MagicMock()
|
params = mocker.MagicMock()
|
||||||
assert read_steering_mode_param(CP, CP_SP, params) == MadsSteeringModeOnBrake.DISENGAGE
|
assert read_steering_mode_param(CP, CP_SP, params) == MadsSteeringModeOnBrake.DISENGAGE
|
||||||
|
|
||||||
def test_tesla_with_vehicle_bus_uses_param(self, mocker):
|
@pytest.mark.parametrize("screen_button", [MadsScreenButtonType.THREE_FINGER,
|
||||||
|
MadsScreenButtonType.FOUR_FINGER,
|
||||||
|
MadsScreenButtonType.FIVE_FINGER])
|
||||||
|
def test_tesla_with_vehicle_bus_uses_param(self, mocker, screen_button):
|
||||||
CP = structs.CarParams()
|
CP = structs.CarParams()
|
||||||
CP.brand = "tesla"
|
CP.brand = "tesla"
|
||||||
CP_SP = structs.CarParamsSP()
|
CP_SP = structs.CarParamsSP()
|
||||||
CP_SP.flags = TeslaFlagsSP.HAS_VEHICLE_BUS
|
CP_SP.flags = TeslaFlagsSP.HAS_VEHICLE_BUS
|
||||||
params = mocker.MagicMock()
|
params = make_params_mock(mocker, {"TeslaMadsScreenButton": screen_button,
|
||||||
params.get = mocker.MagicMock(return_value=MadsSteeringModeOnBrake.REMAIN_ACTIVE)
|
"MadsSteeringMode": MadsSteeringModeOnBrake.REMAIN_ACTIVE})
|
||||||
assert read_steering_mode_param(CP, CP_SP, params) == MadsSteeringModeOnBrake.REMAIN_ACTIVE
|
assert read_steering_mode_param(CP, CP_SP, params) == MadsSteeringModeOnBrake.REMAIN_ACTIVE
|
||||||
|
|
||||||
|
def test_tesla_with_vehicle_bus_screen_button_off_forced_to_disengage(self, mocker):
|
||||||
|
CP = structs.CarParams()
|
||||||
|
CP.brand = "tesla"
|
||||||
|
CP_SP = structs.CarParamsSP()
|
||||||
|
CP_SP.flags = TeslaFlagsSP.HAS_VEHICLE_BUS
|
||||||
|
params = make_params_mock(mocker, {"TeslaMadsScreenButton": MadsScreenButtonType.OFF,
|
||||||
|
"MadsSteeringMode": MadsSteeringModeOnBrake.REMAIN_ACTIVE})
|
||||||
|
assert read_steering_mode_param(CP, CP_SP, params) == MadsSteeringModeOnBrake.DISENGAGE
|
||||||
|
|
||||||
@pytest.mark.parametrize("brand", ["hyundai", "toyota", "honda", "gm"])
|
@pytest.mark.parametrize("brand", ["hyundai", "toyota", "honda", "gm"])
|
||||||
def test_other_brands_use_param(self, mocker, brand):
|
def test_other_brands_use_param(self, mocker, brand):
|
||||||
CP = structs.CarParams()
|
CP = structs.CarParams()
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import os
|
import os
|
||||||
import glob
|
import glob
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
|
||||||
from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye
|
from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye
|
||||||
from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE
|
from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE
|
||||||
from openpilot.common.hardware import HARDWARE, PC
|
from openpilot.common.hardware import HARDWARE, PC
|
||||||
|
from openpilot.selfdrive.modeld.helpers import usbgpu_present
|
||||||
|
|
||||||
Import('env', 'arch', 'release')
|
Import('env', 'arch', 'release')
|
||||||
lenv = env.Clone()
|
lenv = env.Clone()
|
||||||
@@ -22,14 +25,21 @@ def get_camera_configs():
|
|||||||
|
|
||||||
CAMERA_CONFIGS = get_camera_configs()
|
CAMERA_CONFIGS = get_camera_configs()
|
||||||
|
|
||||||
tg_flags = {
|
def probe_devices():
|
||||||
'larch64': 'DEV=QCOM FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0',
|
return set(subprocess.run(
|
||||||
'Darwin': f'DEV=CPU HOME={os.path.expanduser("~")}',
|
[sys.executable, '-c', 'from tinygrad import Device\nprint("\\n".join(Device.get_available_devices()))'],
|
||||||
}.get(arch, 'DEV=CPU:LLVM')
|
capture_output=True, text=True, check=True).stdout.strip().splitlines())
|
||||||
|
|
||||||
image_flag = {
|
available = probe_devices()
|
||||||
'larch64': 'IMAGE=2',
|
if 'CUDA' in available:
|
||||||
}.get(arch, 'IMAGE=0')
|
tg_backend = 'CUDA'
|
||||||
|
tg_flags = f'DEV={tg_backend}'
|
||||||
|
elif 'QCOM' in available:
|
||||||
|
tg_backend = 'QCOM'
|
||||||
|
tg_flags = f'DEV={tg_backend} IMAGE=1 FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1'
|
||||||
|
else:
|
||||||
|
tg_backend = 'CPU'
|
||||||
|
tg_flags = f'DEV=CPU HOME={os.path.expanduser("~")}' if arch == 'Darwin' else 'DEV=CPU:LLVM'
|
||||||
|
|
||||||
model_w, model_h = MEDMODEL_INPUT_SIZE
|
model_w, model_h = MEDMODEL_INPUT_SIZE
|
||||||
from openpilot.selfdrive.modeld.constants import ModelConstants
|
from openpilot.selfdrive.modeld.constants import ModelConstants
|
||||||
@@ -41,17 +51,30 @@ compile_modeld_script = File("compile_modeld.py").abspath
|
|||||||
upstream_compile_script = File(Dir("#openpilot/selfdrive/modeld").File("compile_modeld.py").abspath)
|
upstream_compile_script = File(Dir("#openpilot/selfdrive/modeld").File("compile_modeld.py").abspath)
|
||||||
script_deps = [File("compile_modeld.py"), upstream_compile_script]
|
script_deps = [File("compile_modeld.py"), upstream_compile_script]
|
||||||
|
|
||||||
|
USBGPU = usbgpu_present()
|
||||||
|
if USBGPU:
|
||||||
|
usbgpu_tg_flags = f'DEBUG=2 DEV=USB+AMD:LLVM WARP_DEV={tg_backend} FLOAT16=1 JIT_BATCH_SIZE=0 GMMU=0'
|
||||||
|
usbgpu_lock = File("models/.usb_gpu.lock").abspath
|
||||||
|
|
||||||
def compile_combined(model_type, onnx_args, output_name):
|
def compile_combined(model_type, onnx_args, output_name):
|
||||||
output_pkl = File(f"models/{output_name}").abspath
|
for usbgpu in ([False, True] if USBGPU else [False]):
|
||||||
cmd = (f'{pythonpath_string} {tg_flags} {image_flag} python3 {compile_modeld_script} '
|
prefix = 'big_' if usbgpu else ('big_' if os.getenv('BIG_INTO_SMALL') else '')
|
||||||
f'--model-type {model_type} '
|
final_output_name = prefix + output_name
|
||||||
f'--model-size {model_w}x{model_h} '
|
output_pkl = File(f"models/{final_output_name}").abspath
|
||||||
f'--camera-resolutions {camera_res_args} '
|
|
||||||
f'{onnx_args} '
|
active_tg_flags = usbgpu_tg_flags if usbgpu else tg_flags
|
||||||
f'--frame-skip {frame_skip} '
|
|
||||||
f'--output {output_pkl}')
|
cmd = (f'{pythonpath_string} {active_tg_flags} python3 {compile_modeld_script} '
|
||||||
onnx_files = [f for f in onnx_args.split() if f.endswith('.onnx')]
|
f'--model-type {model_type} '
|
||||||
return lenv.Command(output_pkl, tinygrad_files + script_deps + [File(f) for f in onnx_files if os.path.isfile(f)], cmd)
|
f'--model-size {model_w}x{model_h} '
|
||||||
|
f'--camera-resolutions {camera_res_args} '
|
||||||
|
f'{onnx_args} '
|
||||||
|
f'--frame-skip {frame_skip} '
|
||||||
|
f'--output {output_pkl}')
|
||||||
|
onnx_files = [f for f in onnx_args.split() if f.endswith('.onnx')]
|
||||||
|
node = lenv.Command(output_pkl, tinygrad_files + script_deps + [File(f) for f in onnx_files if os.path.isfile(f)], cmd)
|
||||||
|
if usbgpu:
|
||||||
|
lenv.SideEffect(usbgpu_lock, node)
|
||||||
|
|
||||||
# Vision + Policy (stock default model)
|
# Vision + Policy (stock default model)
|
||||||
vision_onnx = File("models/driving_vision.onnx").abspath
|
vision_onnx = File("models/driving_vision.onnx").abspath
|
||||||
@@ -82,18 +105,3 @@ if os.path.isfile(supercombo_onnx):
|
|||||||
compile_combined('supercombo',
|
compile_combined('supercombo',
|
||||||
f'--supercombo-onnx {supercombo_onnx}',
|
f'--supercombo-onnx {supercombo_onnx}',
|
||||||
'driving_combined_supercombo_tinygrad.pkl')
|
'driving_combined_supercombo_tinygrad.pkl')
|
||||||
|
|
||||||
if PC:
|
|
||||||
inputs = tinygrad_files + [File(Dir("#openpilot/sunnypilot/modeld_v2").File("install_models_pc.py").abspath)]
|
|
||||||
outputs = []
|
|
||||||
model_dir = Dir("models").abspath
|
|
||||||
cmd = f'python3 {Dir("#openpilot/sunnypilot/modeld_v2").abspath}/install_models_pc.py {model_dir}'
|
|
||||||
|
|
||||||
for model_name in ['supercombo', 'driving_vision', 'driving_off_policy', 'driving_on_policy', 'driving_policy']:
|
|
||||||
if File(f"models/{model_name}.onnx").exists():
|
|
||||||
inputs.append(File(f"models/{model_name}.onnx"))
|
|
||||||
inputs.append(File(f"models/{model_name}_tinygrad.pkl"))
|
|
||||||
outputs.append(File(f"models/{model_name}_metadata.pkl"))
|
|
||||||
if outputs:
|
|
||||||
lenv.Command(outputs, inputs, cmd)
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ See the LICENSE.md file in the root directory for more details.
|
|||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
import pickle
|
import tempfile
|
||||||
import time
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
from openpilot.selfdrive.modeld.helpers import dump_oob, load_oob
|
||||||
import numpy as np
|
import numpy as np
|
||||||
os.environ['GMMU'] = '0'
|
os.environ['GMMU'] = '0'
|
||||||
|
|
||||||
@@ -57,7 +57,26 @@ def derive_frame_skip(vision_input_shapes: dict, policy_input_shapes: dict) -> i
|
|||||||
return 1 if not features_buffer or features_buffer[1] >= 99 else 4
|
return 1 if not features_buffer or features_buffer[1] >= 99 else 4
|
||||||
|
|
||||||
|
|
||||||
def generate_queues_and_npy(input_shapes: dict, frame_skip: int, device: str = Device.DEFAULT) -> tuple[dict, dict]:
|
def get_policy_npy_shapes(input_shapes: dict, is_supercombo: bool = False) -> tuple[dict, list[int]]:
|
||||||
|
desire_key = _detect_desire_key(input_shapes)
|
||||||
|
shapes = {}
|
||||||
|
if desire_key:
|
||||||
|
shapes['desire'] = (input_shapes[desire_key][2],)
|
||||||
|
|
||||||
|
if is_supercombo and 'features_buffer' in input_shapes:
|
||||||
|
fb = input_shapes['features_buffer']
|
||||||
|
shapes['prev_feat'] = (fb[0], fb[2])
|
||||||
|
|
||||||
|
for key, shape in input_shapes.items():
|
||||||
|
if key not in (desire_key, 'features_buffer') and 'img' not in key:
|
||||||
|
shapes[key] = tuple(shape)
|
||||||
|
|
||||||
|
sizes = [int(np.prod(size)) for size in shapes.values()]
|
||||||
|
return shapes, sizes
|
||||||
|
|
||||||
|
|
||||||
|
def generate_queues_and_npy(input_shapes: dict, frame_skip: int, device: str = Device.DEFAULT,
|
||||||
|
is_supercombo: bool = False) -> tuple[dict, dict]:
|
||||||
road_key, _ = _detect_vision_keys(input_shapes)
|
road_key, _ = _detect_vision_keys(input_shapes)
|
||||||
if not road_key:
|
if not road_key:
|
||||||
raise ValueError("Vision road key missing from input shapes.")
|
raise ValueError("Vision road key missing from input shapes.")
|
||||||
@@ -74,36 +93,43 @@ def generate_queues_and_npy(input_shapes: dict, frame_skip: int, device: str = D
|
|||||||
features_buffer = input_shapes.get('features_buffer')
|
features_buffer = input_shapes.get('features_buffer')
|
||||||
|
|
||||||
npy_arrays = {
|
npy_arrays = {
|
||||||
'desire': np.zeros(desire_shape[2], dtype=np.float32),
|
|
||||||
'tfm': np.zeros((3, 3), dtype=np.float32),
|
'tfm': np.zeros((3, 3), dtype=np.float32),
|
||||||
'big_tfm': np.zeros((3, 3), dtype=np.float32)
|
'big_tfm': np.zeros((3, 3), dtype=np.float32)
|
||||||
}
|
}
|
||||||
|
|
||||||
for key, shape in input_shapes.items():
|
shapes, sizes = get_policy_npy_shapes(input_shapes, is_supercombo=is_supercombo)
|
||||||
if key not in npy_arrays and 'img' not in key and key not in ('features_buffer', desire_key):
|
packed_npy_inputs = np.zeros(sum(sizes), dtype=np.float32)
|
||||||
npy_arrays[key] = np.zeros(shape, dtype=np.float32)
|
|
||||||
|
split_indices = np.cumsum(sizes[:-1]) if len(sizes) > 1 else []
|
||||||
|
split_views = np.split(packed_npy_inputs, split_indices) if len(sizes) > 0 else []
|
||||||
|
for (k, s), v in zip(shapes.items(), split_views, strict=True):
|
||||||
|
npy_arrays[k] = v.reshape(s)
|
||||||
|
|
||||||
queues = {
|
queues = {
|
||||||
'img_q': Tensor(np.zeros(img_buf_shape, dtype=np.uint8), device=device).contiguous().realize(),
|
'img_q': Tensor(np.zeros(img_buf_shape, dtype=np.uint8), device=device).contiguous().realize(),
|
||||||
'big_img_q': Tensor(np.zeros(img_buf_shape, dtype=np.uint8), device=device).contiguous().realize(),
|
'big_img_q': Tensor(np.zeros(img_buf_shape, dtype=np.uint8), device=device).contiguous().realize(),
|
||||||
'desire_q': Tensor(np.zeros((frame_skip * desire_shape[1], desire_shape[0], desire_shape[2]),
|
'desire_q': Tensor(np.zeros((frame_skip * desire_shape[1], desire_shape[0], desire_shape[2]),
|
||||||
dtype=np.float32), device=device).contiguous().realize()
|
dtype=np.float32), device=device).contiguous().realize(),
|
||||||
|
'packed_npy_inputs': Tensor(packed_npy_inputs, device='NPY').realize(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if features_buffer:
|
if features_buffer:
|
||||||
queues['feat_q'] = Tensor(np.zeros((frame_skip * (features_buffer[1] - 1) + 1, features_buffer[0], features_buffer[2]),
|
queues['feat_q'] = Tensor(np.zeros((frame_skip * (features_buffer[1] - 1) + 1, features_buffer[0], features_buffer[2]),
|
||||||
dtype=np.float32), device=device).contiguous().realize()
|
dtype=np.float32), device=device).contiguous().realize()
|
||||||
|
|
||||||
queues.update({key: Tensor(value, device='NPY').realize() for key, value in npy_arrays.items()})
|
queues.update({key: Tensor(value, device='NPY').realize() for key, value in npy_arrays.items() if key in ('tfm', 'big_tfm')})
|
||||||
|
|
||||||
return queues, npy_arrays
|
return queues, npy_arrays
|
||||||
|
|
||||||
|
|
||||||
def make_split_input_queues(vision_input_shapes: dict, policy_input_shapes: dict, frame_skip: int, device: str = Device.DEFAULT) -> tuple[dict, dict]:
|
def make_split_input_queues(vision_input_shapes: dict, policy_input_shapes: dict,
|
||||||
return generate_queues_and_npy({**vision_input_shapes, **policy_input_shapes}, frame_skip, device)
|
frame_skip: int, device: str = Device.DEFAULT) -> tuple[dict, dict]:
|
||||||
|
return generate_queues_and_npy({**vision_input_shapes, **policy_input_shapes}, frame_skip, device, is_supercombo=False)
|
||||||
|
|
||||||
|
|
||||||
def make_supercombo_input_queues(input_shapes: dict, frame_skip: int, device: str = Device.DEFAULT) -> tuple[dict, dict]:
|
def make_supercombo_input_queues(input_shapes: dict, frame_skip: int,
|
||||||
return generate_queues_and_npy(input_shapes, frame_skip, device)
|
device: str = Device.DEFAULT) -> tuple[dict, dict]:
|
||||||
|
return generate_queues_and_npy(input_shapes, frame_skip, device, is_supercombo=True)
|
||||||
|
|
||||||
|
|
||||||
def create_jit_runner(vision_runner, policy_runners: list, nv12: NV12Frame, model_size: tuple[int, int],
|
def create_jit_runner(vision_runner, policy_runners: list, nv12: NV12Frame, model_size: tuple[int, int],
|
||||||
@@ -118,22 +144,17 @@ def create_jit_runner(vision_runner, policy_runners: list, nv12: NV12Frame, mode
|
|||||||
if not desire_key or not road_key or not wide_key:
|
if not desire_key or not road_key or not wide_key:
|
||||||
raise ValueError("Missing required vision or desire keys in input shapes.")
|
raise ValueError("Missing required vision or desire keys in input shapes.")
|
||||||
|
|
||||||
extra_keys = [key for key in input_shapes if key not in (desire_key, 'features_buffer', 'traffic_convention') and 'img' not in key]
|
is_supercombo = vision_runner is None
|
||||||
|
npy_shapes, npy_sizes = get_policy_npy_shapes(input_shapes, is_supercombo=is_supercombo)
|
||||||
|
|
||||||
def runner(img_q, big_img_q, feat_q, frame, big_frame, tfm, big_tfm, **kwargs):
|
def runner(img_q, big_img_q, feat_q, packed_npy_inputs, frame, big_frame, tfm, big_tfm, **kwargs):
|
||||||
desire_q = kwargs['desire_q']
|
desire_q = kwargs['desire_q']
|
||||||
desire = kwargs['desire']
|
|
||||||
traffic_convention = kwargs.get('traffic_convention')
|
|
||||||
|
|
||||||
npys = [tfm.to(Device.DEFAULT), big_tfm.to(Device.DEFAULT), desire.to(Device.DEFAULT)]
|
packed_npy_inputs_dev = packed_npy_inputs.to(Device.DEFAULT)
|
||||||
if traffic_convention is not None:
|
tfm_dev = tfm.to(Device.DEFAULT)
|
||||||
npys.append(traffic_convention.to(Device.DEFAULT))
|
big_tfm_dev = big_tfm.to(Device.DEFAULT)
|
||||||
|
|
||||||
extra_tensors = {key: kwargs[key].to(Device.DEFAULT) for key in extra_keys if key in kwargs}
|
Tensor.realize(packed_npy_inputs_dev, tfm_dev, big_tfm_dev)
|
||||||
Tensor.realize(*npys, *extra_tensors.values())
|
|
||||||
|
|
||||||
tfm_dev, big_tfm_dev, desire_dev = npys[:3]
|
|
||||||
traffic_conv_dev = npys[3] if traffic_convention is not None else None
|
|
||||||
|
|
||||||
img = shift_and_sample(img_q, frame_prepare(frame, tfm_dev).unsqueeze(0), sample_skip_fn).realize()
|
img = shift_and_sample(img_q, frame_prepare(frame, tfm_dev).unsqueeze(0), sample_skip_fn).realize()
|
||||||
big_img = shift_and_sample(big_img_q, frame_prepare(big_frame, big_tfm_dev).unsqueeze(0), sample_skip_fn).realize()
|
big_img = shift_and_sample(big_img_q, frame_prepare(big_frame, big_tfm_dev).unsqueeze(0), sample_skip_fn).realize()
|
||||||
@@ -141,22 +162,37 @@ def create_jit_runner(vision_runner, policy_runners: list, nv12: NV12Frame, mode
|
|||||||
if prepare_only:
|
if prepare_only:
|
||||||
return img, big_img
|
return img, big_img
|
||||||
|
|
||||||
desire_buf = shift_and_sample(desire_q, desire_dev.reshape(1, 1, -1), sample_desire_fn).realize()
|
unpacked_tensors = [tensor.reshape(shape) for tensor, shape in zip(packed_npy_inputs_dev.split(npy_sizes), npy_shapes.values(), strict=True)]
|
||||||
inputs = {desire_key: desire_buf, **extra_tensors}
|
unpacked_dict = dict(zip(npy_shapes.keys(), unpacked_tensors, strict=True))
|
||||||
|
|
||||||
if traffic_conv_dev is not None:
|
desire_dev = unpacked_dict['desire']
|
||||||
inputs['traffic_convention'] = traffic_conv_dev
|
desire_buf = shift_and_sample(desire_q, desire_dev.reshape(1, 1, -1), sample_desire_fn).realize()
|
||||||
|
|
||||||
|
inputs = {desire_key: desire_buf}
|
||||||
|
for key, tensor_val in unpacked_dict.items():
|
||||||
|
if key not in ('desire', 'prev_feat'):
|
||||||
|
inputs[key] = tensor_val
|
||||||
|
|
||||||
|
if 'prev_feat' in unpacked_dict:
|
||||||
|
prev_feat_dev = unpacked_dict['prev_feat']
|
||||||
|
inputs['features_buffer'] = shift_and_sample(feat_q, prev_feat_dev.reshape(1, 1, -1), sample_skip_fn).realize()
|
||||||
|
|
||||||
if vision_runner:
|
if vision_runner:
|
||||||
vision_out_cast = next(iter(vision_runner({road_key: img, wide_key: big_img}).values())).cast('float32').realize()
|
vision_out_cast = next(iter(vision_runner({road_key: img, wide_key: big_img}).values())).cast('float32').realize()
|
||||||
new_feat = vision_out_cast[:, features_slice].reshape(1, -1).unsqueeze(0)
|
if 'features_buffer' not in inputs:
|
||||||
inputs['features_buffer'] = shift_and_sample(feat_q, new_feat, sample_skip_fn).realize()
|
new_feat = vision_out_cast[:, features_slice].reshape(1, -1).unsqueeze(0)
|
||||||
|
inputs['features_buffer'] = shift_and_sample(feat_q, new_feat, sample_skip_fn).realize()
|
||||||
policy_outs = [next(iter(pol_runner(inputs).values())).cast('float32').realize() for pol_runner in policy_runners]
|
policy_outs = [next(iter(pol_runner(inputs).values())).cast('float32').realize() for pol_runner in policy_runners]
|
||||||
return (vision_out_cast, *policy_outs) if len(policy_outs) > 1 else (vision_out_cast, policy_outs[0])
|
return (vision_out_cast, *policy_outs) if len(policy_outs) > 1 else (vision_out_cast, policy_outs[0])
|
||||||
inputs.update({road_key: img, wide_key: big_img, 'features_buffer': sample_skip_fn(feat_q)})
|
|
||||||
|
inputs.update({road_key: img, wide_key: big_img})
|
||||||
|
if 'features_buffer' not in inputs:
|
||||||
|
inputs['features_buffer'] = sample_skip_fn(feat_q)
|
||||||
|
|
||||||
policy_out = next(iter(policy_runners[0](inputs).values())).cast('float32').realize()
|
policy_out = next(iter(policy_runners[0](inputs).values())).cast('float32').realize()
|
||||||
new_feat = policy_out[:, features_slice].reshape(1, -1).unsqueeze(0)
|
if 'features_buffer' not in inputs and features_slice is not None:
|
||||||
shift_and_sample(feat_q, new_feat, sample_skip_fn).realize()
|
new_feat = policy_out[:, features_slice].reshape(1, -1).unsqueeze(0)
|
||||||
|
shift_and_sample(feat_q, new_feat, sample_skip_fn).realize()
|
||||||
return policy_out
|
return policy_out
|
||||||
|
|
||||||
return runner
|
return runner
|
||||||
@@ -172,27 +208,34 @@ def compile_and_warmup(nv12: NV12Frame, model_size: tuple[int, int], prepare_onl
|
|||||||
raise ValueError("Could not find vision, model, or policy metadata.")
|
raise ValueError("Could not find vision, model, or policy metadata.")
|
||||||
|
|
||||||
features_slice = feat_meta['output_slices']['hidden_state']
|
features_slice = feat_meta['output_slices']['hidden_state']
|
||||||
WARP_DEV = 'CPU' if "USBGPU" in os.environ else Device.DEFAULT
|
WARP_DEV = os.getenv('WARP_DEV', Device.DEFAULT)
|
||||||
|
|
||||||
|
is_supercombo = vision_runner is None
|
||||||
run_func = create_jit_runner(vision_runner, policy_runners, nv12, model_size, features_slice, frame_skip, all_shapes, prepare_only)
|
run_func = create_jit_runner(vision_runner, policy_runners, nv12, model_size, features_slice, frame_skip, all_shapes, prepare_only)
|
||||||
run_jit = TinyJit(run_func, prune=True)
|
run_jit = TinyJit(run_func, prune=True)
|
||||||
queues, npy_arrays = generate_queues_and_npy(all_shapes, frame_skip, Device.DEFAULT)
|
def run_once(seed):
|
||||||
|
queues, npy = generate_queues_and_npy(all_shapes, frame_skip, Device.DEFAULT, is_supercombo=is_supercombo)
|
||||||
for i in range(3):
|
rng = np.random.default_rng(seed)
|
||||||
np.random.seed(42 + i)
|
|
||||||
frame = Tensor.randint(nv12.size, low=0, high=256, dtype=dtypes.uint8, device=WARP_DEV).realize()
|
frame = Tensor.randint(nv12.size, low=0, high=256, dtype=dtypes.uint8, device=WARP_DEV).realize()
|
||||||
big_frame = Tensor.randint(nv12.size, low=0, high=256, dtype=dtypes.uint8, device=WARP_DEV).realize()
|
big_frame = Tensor.randint(nv12.size, low=0, high=256, dtype=dtypes.uint8, device=WARP_DEV).realize()
|
||||||
for arr in npy_arrays.values():
|
for value in npy.values():
|
||||||
arr[:] = np.random.randn(*arr.shape).astype(arr.dtype)
|
value[:] = rng.standard_normal(value.shape).astype(value.dtype)
|
||||||
|
|
||||||
Device.default.synchronize()
|
Device.default.synchronize()
|
||||||
start_time = time.perf_counter()
|
outs = run_jit(**queues, frame=frame, big_frame=big_frame)
|
||||||
run_jit(**queues, frame=frame, big_frame=big_frame)
|
|
||||||
mid_time = time.perf_counter()
|
|
||||||
Device.default.synchronize()
|
Device.default.synchronize()
|
||||||
print(f" [{i + 1}/3] enqueue {(mid_time - start_time) * 1e3:6.2f} ms -- total {(time.perf_counter() - start_time) * 1e3:6.2f} ms")
|
return [np.copy(value.numpy()) for value in (outs if isinstance(outs, tuple) else [outs])] if outs is not None else []
|
||||||
|
|
||||||
return pickle.loads(pickle.dumps(run_jit)) if not prepare_only else run_jit
|
for i in range(3):
|
||||||
|
run_once(42 + i)
|
||||||
|
|
||||||
|
if not prepare_only:
|
||||||
|
baseline = run_once(42)
|
||||||
|
with tempfile.TemporaryFile(dir=".") as f:
|
||||||
|
dump_oob(run_jit, f)
|
||||||
|
f.seek(0)
|
||||||
|
run_jit = load_oob(f)
|
||||||
|
assert all(np.array_equal(baseline, deserialized) for baseline, deserialized in zip(baseline, run_once(42), strict=True)), "OOB pickling regression"
|
||||||
|
return run_jit
|
||||||
|
|
||||||
|
|
||||||
def _parse_size(size_str: str) -> tuple[int, int]:
|
def _parse_size(size_str: str) -> tuple[int, int]:
|
||||||
@@ -289,7 +332,7 @@ if __name__ == "__main__":
|
|||||||
vision_runner, policy_runners, output_data['metadata']))
|
vision_runner, policy_runners, output_data['metadata']))
|
||||||
|
|
||||||
with open(args.output, "wb") as file:
|
with open(args.output, "wb") as file:
|
||||||
pickle.dump(output_data, file)
|
dump_oob(output_data, file)
|
||||||
|
|
||||||
pkl_size = os.path.getsize(args.output)
|
pkl_size = os.path.getsize(args.output)
|
||||||
print(f"Saved combined JIT to {args.output} ({pkl_size / 1e6:.2f} MB)")
|
print(f"Saved combined JIT to {args.output} ({pkl_size / 1e6:.2f} MB)")
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
import sys
|
|
||||||
import shutil
|
|
||||||
import pickle
|
|
||||||
import codecs
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from openpilot.common.hardware.hw import Paths
|
|
||||||
from openpilot.sunnypilot.modeld_v2.get_model_metadata import MetadataOnnxPBParser, get_name_and_shape, get_metadata_value_by_name
|
|
||||||
|
|
||||||
|
|
||||||
def generate_metadata_pkl(model_path, output_path):
|
|
||||||
try:
|
|
||||||
model = MetadataOnnxPBParser(model_path).parse()
|
|
||||||
output_slices = get_metadata_value_by_name(model, 'output_slices')
|
|
||||||
if not output_slices:
|
|
||||||
return False
|
|
||||||
metadata = {
|
|
||||||
'model_checkpoint': get_metadata_value_by_name(model, 'model_checkpoint'),
|
|
||||||
'output_slices': pickle.loads(codecs.decode(output_slices.encode(), "base64")),
|
|
||||||
'input_shapes': dict(get_name_and_shape(x) for x in model["graph"]["input"]),
|
|
||||||
'output_shapes': dict(get_name_and_shape(x) for x in model["graph"]["output"]),
|
|
||||||
}
|
|
||||||
with open(output_path, 'wb') as f:
|
|
||||||
pickle.dump(metadata, f)
|
|
||||||
return True
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def install_models(model_dir):
|
|
||||||
model_dir = Path(model_dir)
|
|
||||||
models = ["driving_off_policy", "driving_on_policy", "driving_vision"]
|
|
||||||
found_models = []
|
|
||||||
|
|
||||||
for model in models:
|
|
||||||
if (model_dir / f"{model}.onnx").exists():
|
|
||||||
found_models.append(model)
|
|
||||||
|
|
||||||
if not found_models:
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
custom_name = input(f"Found models ({', '.join(found_models)}). Enter model short name (e.g. wmiv4): ").strip()
|
|
||||||
except EOFError:
|
|
||||||
return
|
|
||||||
|
|
||||||
if not custom_name:
|
|
||||||
print("No name provided, skipping installation.")
|
|
||||||
return
|
|
||||||
|
|
||||||
dest_dir = Path(Paths.model_root())
|
|
||||||
dest_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
for model in found_models:
|
|
||||||
onnx_path = model_dir / f"{model}.onnx"
|
|
||||||
tinygrad_pkl = model_dir / f"{model}_tinygrad.pkl"
|
|
||||||
metadata_pkl = model_dir / f"{model}_metadata.pkl"
|
|
||||||
|
|
||||||
if not metadata_pkl.exists():
|
|
||||||
generate_metadata_pkl(onnx_path, metadata_pkl)
|
|
||||||
|
|
||||||
dest_tinygrad = dest_dir / f"{model}_{custom_name}_tinygrad.pkl"
|
|
||||||
dest_metadata = dest_dir / f"{model}_{custom_name}_metadata.pkl"
|
|
||||||
|
|
||||||
if tinygrad_pkl.exists():
|
|
||||||
shutil.move(str(tinygrad_pkl), str(dest_tinygrad))
|
|
||||||
if metadata_pkl.exists():
|
|
||||||
shutil.move(str(metadata_pkl), str(dest_metadata))
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
if len(sys.argv) < 2:
|
|
||||||
print("Usage: install_models_pc.py <model_dir>")
|
|
||||||
sys.exit(1)
|
|
||||||
install_models(sys.argv[1])
|
|
||||||
@@ -10,11 +10,14 @@ import os
|
|||||||
os.environ['GMMU'] = '0'
|
os.environ['GMMU'] = '0'
|
||||||
from openpilot.common.hardware import TICI
|
from openpilot.common.hardware import TICI
|
||||||
os.environ['DEV'] = 'QCOM' if TICI else 'CPU'
|
os.environ['DEV'] = 'QCOM' if TICI else 'CPU'
|
||||||
USBGPU = "USBGPU" in os.environ
|
|
||||||
|
from openpilot.selfdrive.modeld.helpers import usbgpu_present, load_oob
|
||||||
|
from openpilot.selfdrive.modeld.usbgpu_link import wait_usbgpu_link
|
||||||
|
|
||||||
|
USBGPU = usbgpu_present()
|
||||||
if USBGPU:
|
if USBGPU:
|
||||||
os.environ['DEV'] = 'AMD'
|
os.environ['DEV'] = 'AMD'
|
||||||
os.environ['AMD_IFACE'] = 'USB'
|
os.environ['AMD_IFACE'] = 'USB'
|
||||||
import pickle
|
|
||||||
import time
|
import time
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import openpilot.cereal.messaging as messaging
|
import openpilot.cereal.messaging as messaging
|
||||||
@@ -24,6 +27,11 @@ from setproctitle import setproctitle
|
|||||||
from openpilot.cereal.messaging import PubMaster, SubMaster
|
from openpilot.cereal.messaging import PubMaster, SubMaster
|
||||||
from msgq.visionipc import VisionIpcClient, VisionStreamType, VisionBuf
|
from msgq.visionipc import VisionIpcClient, VisionStreamType, VisionBuf
|
||||||
from opendbc.car.car_helpers import get_demo_car_params
|
from opendbc.car.car_helpers import get_demo_car_params
|
||||||
|
|
||||||
|
from tinygrad.tensor import Tensor
|
||||||
|
from tinygrad.device import Device
|
||||||
|
|
||||||
|
from openpilot.common.file_chunker import open_file_chunked
|
||||||
from openpilot.common.swaglog import cloudlog
|
from openpilot.common.swaglog import cloudlog
|
||||||
from openpilot.common.params import Params
|
from openpilot.common.params import Params
|
||||||
from openpilot.common.filter_simple import FirstOrderFilter
|
from openpilot.common.filter_simple import FirstOrderFilter
|
||||||
@@ -31,6 +39,7 @@ from openpilot.common.realtime import config_realtime_process, DT_MDL
|
|||||||
from openpilot.common.transformations.camera import DEVICE_CAMERAS
|
from openpilot.common.transformations.camera import DEVICE_CAMERAS
|
||||||
from openpilot.common.transformations.model import get_warp_matrix
|
from openpilot.common.transformations.model import get_warp_matrix
|
||||||
from openpilot.system import sentry
|
from openpilot.system import sentry
|
||||||
|
from openpilot.system.camerad.cameras.nv12_info import get_nv12_info
|
||||||
from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper
|
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.controls.lib.drive_helpers import get_accel_from_plan, smooth_value
|
||||||
|
|
||||||
@@ -38,6 +47,7 @@ from openpilot.sunnypilot.modeld_v2.fill_model_msg import fill_model_msg, fill_p
|
|||||||
from openpilot.sunnypilot.modeld_v2.constants import Plan
|
from openpilot.sunnypilot.modeld_v2.constants import Plan
|
||||||
from openpilot.sunnypilot.modeld_v2.meta_helper import load_meta_constants
|
from openpilot.sunnypilot.modeld_v2.meta_helper import load_meta_constants
|
||||||
from openpilot.sunnypilot.modeld_v2.camera_offset_helper import CameraOffsetHelper
|
from openpilot.sunnypilot.modeld_v2.camera_offset_helper import CameraOffsetHelper
|
||||||
|
from openpilot.sunnypilot.modeld_v2.compile_modeld import derive_frame_skip, make_split_input_queues
|
||||||
|
|
||||||
from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
|
from openpilot.sunnypilot.livedelay.helpers import get_lat_delay
|
||||||
from openpilot.sunnypilot.modeld_v2.modeld_base import ModelStateBase
|
from openpilot.sunnypilot.modeld_v2.modeld_base import ModelStateBase
|
||||||
@@ -100,31 +110,29 @@ class ModelState(ModelStateBase):
|
|||||||
self._init_combined(pkl_path, cam_w, cam_h, model_bundle)
|
self._init_combined(pkl_path, cam_w, cam_h, model_bundle)
|
||||||
|
|
||||||
def _init_combined(self, pkl_path, cam_w, cam_h, bundle):
|
def _init_combined(self, pkl_path, cam_w, cam_h, bundle):
|
||||||
from tinygrad.tensor import Tensor
|
|
||||||
from openpilot.system.camerad.cameras.nv12_info import get_nv12_info
|
|
||||||
from openpilot.sunnypilot.modeld_v2.compile_modeld import derive_frame_skip, make_split_input_queues
|
|
||||||
from tinygrad.device import Device
|
|
||||||
|
|
||||||
from openpilot.common.file_chunker import open_file_chunked
|
|
||||||
|
|
||||||
cloudlog.warning(f"loading combined pkl: {pkl_path}")
|
cloudlog.warning(f"loading combined pkl: {pkl_path}")
|
||||||
jits = pickle.load(open_file_chunked(pkl_path))
|
jits = load_oob(open_file_chunked(pkl_path))
|
||||||
|
|
||||||
self.DEV = Device.DEFAULT
|
self.DEV = Device.DEFAULT
|
||||||
self.WARP_DEV = 'CPU' if USBGPU else self.DEV
|
self.WARP_DEV = ('QCOM' if TICI else 'CPU') if USBGPU else self.DEV
|
||||||
self.QUEUE_DEV = self.DEV
|
self.QUEUE_DEV = self.DEV
|
||||||
|
|
||||||
metadata = jits['metadata']
|
metadata = jits['metadata']
|
||||||
|
|
||||||
|
self._run_policy = jits[(cam_w, cam_h)]['run_policy']
|
||||||
|
self._warp_enqueue = jits[(cam_w, cam_h)]['warp_enqueue']
|
||||||
|
|
||||||
if 'model' in metadata:
|
if 'model' in metadata:
|
||||||
model_metadata = metadata['model']
|
model_metadata = metadata['model']
|
||||||
self.vision_output_slices = model_metadata['output_slices']
|
self.vision_output_slices = model_metadata['output_slices']
|
||||||
self.policy_output_slices = {}
|
self.policy_output_slices = {}
|
||||||
self._policy_slices_list = []
|
self._policy_slices_list = []
|
||||||
self._combined_model_type = 'supercombo'
|
self._combined_model_type = 'supercombo'
|
||||||
self._vision_input_names = [k for k in model_metadata['input_shapes'] if 'img' in k]
|
self._vision_input_names = [key for key in model_metadata['input_shapes'] if 'img' in key]
|
||||||
from openpilot.sunnypilot.modeld_v2.compile_modeld import make_supercombo_input_queues
|
from openpilot.sunnypilot.modeld_v2.compile_modeld import make_supercombo_input_queues
|
||||||
frame_skip = derive_frame_skip({}, model_metadata['input_shapes'])
|
frame_skip = derive_frame_skip({}, model_metadata['input_shapes'])
|
||||||
self.input_queues, self.numpy_inputs = make_supercombo_input_queues(model_metadata['input_shapes'], frame_skip, device=self.QUEUE_DEV)
|
self.input_queues, self.numpy_inputs = make_supercombo_input_queues(model_metadata['input_shapes'],
|
||||||
|
frame_skip, device=self.QUEUE_DEV)
|
||||||
else:
|
else:
|
||||||
vision_metadata = metadata['vision']
|
vision_metadata = metadata['vision']
|
||||||
policy_keys = [k for k in metadata if k != 'vision']
|
policy_keys = [k for k in metadata if k != 'vision']
|
||||||
@@ -142,16 +150,13 @@ class ModelState(ModelStateBase):
|
|||||||
policy_input_shapes = first_policy_metadata['input_shapes']
|
policy_input_shapes = first_policy_metadata['input_shapes']
|
||||||
self._vision_input_names = [k for k in vision_input_shapes if 'img' in k]
|
self._vision_input_names = [k for k in vision_input_shapes if 'img' in k]
|
||||||
frame_skip = derive_frame_skip(vision_input_shapes, policy_input_shapes)
|
frame_skip = derive_frame_skip(vision_input_shapes, policy_input_shapes)
|
||||||
self.input_queues, self.numpy_inputs = make_split_input_queues(vision_input_shapes, policy_input_shapes, frame_skip, device=self.QUEUE_DEV)
|
self.input_queues, self.numpy_inputs = make_split_input_queues(vision_input_shapes, policy_input_shapes,
|
||||||
|
frame_skip, device=self.QUEUE_DEV)
|
||||||
|
|
||||||
self._desire_key = next(key for key in self.numpy_inputs if key.startswith('desire'))
|
self._desire_key = next(key for key in self.numpy_inputs if key.startswith('desire'))
|
||||||
self._road_key = next(key for key in self._vision_input_names if 'big' not in key)
|
self._road_key = next(key for key in self._vision_input_names if 'big' not in key)
|
||||||
self._wide_key = next(key for key in self._vision_input_names if 'big' in key)
|
self._wide_key = next(key for key in self._vision_input_names if 'big' in key)
|
||||||
|
|
||||||
from openpilot.sunnypilot.modeld_v2.parse_model_outputs_split import Parser as SplitParser
|
|
||||||
from openpilot.sunnypilot.modeld_v2.parse_model_outputs import Parser as CombinedParser
|
|
||||||
self.parser = SplitParser() if self._combined_model_type != 'supercombo' else CombinedParser()
|
|
||||||
|
|
||||||
is_20hz = bundle.is20hz if bundle else self._combined_model_type in ('split', 'multi_policy')
|
is_20hz = bundle.is20hz if bundle else self._combined_model_type in ('split', 'multi_policy')
|
||||||
if is_20hz:
|
if is_20hz:
|
||||||
from openpilot.sunnypilot.models.split_model_constants import SplitModelConstants
|
from openpilot.sunnypilot.models.split_model_constants import SplitModelConstants
|
||||||
@@ -160,20 +165,45 @@ class ModelState(ModelStateBase):
|
|||||||
from openpilot.sunnypilot.modeld_v2.constants import ModelConstants
|
from openpilot.sunnypilot.modeld_v2.constants import ModelConstants
|
||||||
self.constants = ModelConstants()
|
self.constants = ModelConstants()
|
||||||
|
|
||||||
|
if self._combined_model_type != 'supercombo':
|
||||||
|
from openpilot.sunnypilot.modeld_v2.parse_model_outputs_split import Parser as SplitParser
|
||||||
|
self.parser = SplitParser()
|
||||||
|
else:
|
||||||
|
from openpilot.sunnypilot.modeld_v2.parse_model_outputs import Parser as CombinedParser
|
||||||
|
self.parser = CombinedParser()
|
||||||
|
|
||||||
self.prev_desire = np.zeros(self.constants.DESIRE_LEN, dtype=np.float32)
|
self.prev_desire = np.zeros(self.constants.DESIRE_LEN, dtype=np.float32)
|
||||||
self.full_frames: dict = {}
|
self.full_frames: dict = {}
|
||||||
self._blob_cache: dict = {}
|
self._blob_cache: dict = {}
|
||||||
nv12_info = get_nv12_info(cam_w, cam_h)
|
nv12_info = get_nv12_info(cam_w, cam_h)
|
||||||
self.frame_buf_params = dict.fromkeys(self._vision_input_names, nv12_info)
|
self.frame_buf_params = dict.fromkeys(self._vision_input_names, nv12_info)
|
||||||
|
|
||||||
self._run_policy = jits[(cam_w, cam_h)]['run_policy']
|
|
||||||
self._warp_enqueue = jits[(cam_w, cam_h)]['warp_enqueue']
|
|
||||||
yuv_size = self.frame_buf_params[self._road_key][3]
|
yuv_size = self.frame_buf_params[self._road_key][3]
|
||||||
self._warp_enqueue(
|
self._warp_enqueue(
|
||||||
**self.input_queues,
|
**self.input_queues,
|
||||||
frame=Tensor(np.zeros(yuv_size, dtype=np.uint8), device=self.WARP_DEV).contiguous().realize(),
|
frame=Tensor(np.zeros(yuv_size, dtype=np.uint8), device=self.WARP_DEV).contiguous().realize(),
|
||||||
big_frame=Tensor(np.zeros(yuv_size, dtype=np.uint8), device=self.WARP_DEV).contiguous().realize())
|
big_frame=Tensor(np.zeros(yuv_size, dtype=np.uint8), device=self.WARP_DEV).contiguous().realize())
|
||||||
|
|
||||||
|
if USBGPU:
|
||||||
|
self.warmup()
|
||||||
|
|
||||||
|
def warmup(self) -> None:
|
||||||
|
dummy_frames = {k: np.zeros(self.frame_buf_params[k][3], dtype=np.uint8) for k in self._vision_input_names}
|
||||||
|
transforms = {k: np.eye(3, dtype=np.float32) for k in [self._road_key, self._wide_key] if k}
|
||||||
|
|
||||||
|
dummy_inputs = {}
|
||||||
|
for k, v in self.numpy_inputs.items():
|
||||||
|
if k not in ['tfm', 'big_tfm', 'prev_feat']:
|
||||||
|
dummy_inputs[k] = np.zeros(v.shape, dtype=v.dtype)
|
||||||
|
|
||||||
|
self.run(dummy_frames, transforms, dummy_inputs, prepare_only=False)
|
||||||
|
|
||||||
|
for v in self.numpy_inputs.values():
|
||||||
|
v[:] = 0
|
||||||
|
self.prev_desire[:] = 0
|
||||||
|
self.full_frames.clear()
|
||||||
|
self._blob_cache.clear()
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mlsim(self) -> bool:
|
def mlsim(self) -> bool:
|
||||||
@@ -189,8 +219,6 @@ class ModelState(ModelStateBase):
|
|||||||
|
|
||||||
def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray],
|
def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray],
|
||||||
inputs: dict[str, np.ndarray], prepare_only: bool) -> dict[str, np.ndarray] | None:
|
inputs: dict[str, np.ndarray], prepare_only: bool) -> dict[str, np.ndarray] | None:
|
||||||
from tinygrad.tensor import Tensor
|
|
||||||
|
|
||||||
for key in bufs.keys():
|
for key in bufs.keys():
|
||||||
ptr = np.frombuffer(bufs[key].data, dtype=np.uint8).ctypes.data
|
ptr = np.frombuffer(bufs[key].data, dtype=np.uint8).ctypes.data
|
||||||
yuv_size = self.frame_buf_params[key][3]
|
yuv_size = self.frame_buf_params[key][3]
|
||||||
@@ -222,11 +250,16 @@ class ModelState(ModelStateBase):
|
|||||||
model_output = raw_outputs.numpy().flatten()
|
model_output = raw_outputs.numpy().flatten()
|
||||||
sliced = {k: model_output[np.newaxis, v] for k, v in self.vision_output_slices.items()}
|
sliced = {k: model_output[np.newaxis, v] for k, v in self.vision_output_slices.items()}
|
||||||
outputs = self.parser.parse_outputs(sliced)
|
outputs = self.parser.parse_outputs(sliced)
|
||||||
|
if 'prev_feat' in self.numpy_inputs:
|
||||||
|
self.numpy_inputs['prev_feat'][:] = model_output[self.vision_output_slices['hidden_state']]
|
||||||
else:
|
else:
|
||||||
vision_output = raw_outputs[0].numpy().flatten()
|
vision_output = raw_outputs[0].numpy().flatten()
|
||||||
vision_sliced = {k: vision_output[np.newaxis, v] for k, v in self.vision_output_slices.items()}
|
vision_sliced = {k: vision_output[np.newaxis, v] for k, v in self.vision_output_slices.items()}
|
||||||
outputs = self.parser.parse_vision_outputs(vision_sliced)
|
outputs = self.parser.parse_vision_outputs(vision_sliced)
|
||||||
|
|
||||||
|
if 'prev_feat' in self.numpy_inputs and 'hidden_state' in self.vision_output_slices:
|
||||||
|
self.numpy_inputs['prev_feat'][:] = vision_output[self.vision_output_slices['hidden_state']]
|
||||||
|
|
||||||
for i, policy_slices in enumerate(self._policy_slices_list):
|
for i, policy_slices in enumerate(self._policy_slices_list):
|
||||||
policy_output = raw_outputs[i + 1].numpy().flatten()
|
policy_output = raw_outputs[i + 1].numpy().flatten()
|
||||||
policy_sliced = {k: policy_output[np.newaxis, v] for k, v in policy_slices.items()}
|
policy_sliced = {k: policy_output[np.newaxis, v] for k, v in policy_slices.items()}
|
||||||
@@ -255,7 +288,6 @@ class ModelState(ModelStateBase):
|
|||||||
plan = model_output['plan'][0]
|
plan = model_output['plan'][0]
|
||||||
desired_accel, should_stop = get_accel_from_plan(plan[:, Plan.VELOCITY][:, 0], plan[:, Plan.ACCELERATION][:, 0], self.constants.T_IDXS,
|
desired_accel, should_stop = get_accel_from_plan(plan[:, Plan.VELOCITY][:, 0], plan[:, Plan.ACCELERATION][:, 0], self.constants.T_IDXS,
|
||||||
action_t=long_action_t)
|
action_t=long_action_t)
|
||||||
desired_accel = smooth_value(desired_accel, prev_action.desiredAcceleration, self.LONG_SMOOTH_SECONDS)
|
|
||||||
|
|
||||||
curvature_plan = (plan + (self.PLANPLUS_CONTROL - 1.0) * model_output['planplus'][0]
|
curvature_plan = (plan + (self.PLANPLUS_CONTROL - 1.0) * model_output['planplus'][0]
|
||||||
if 'planplus' in model_output and self.PLANPLUS_CONTROL != 1.0 else plan)
|
if 'planplus' in model_output and self.PLANPLUS_CONTROL != 1.0 else plan)
|
||||||
@@ -265,6 +297,8 @@ class ModelState(ModelStateBase):
|
|||||||
desired_curvature = model_output['action'][0, 0] / (max(1.0, v_ego))**2
|
desired_curvature = model_output['action'][0, 0] / (max(1.0, v_ego))**2
|
||||||
should_stop = (v_ego < 0.3 and desired_accel < 0.1)
|
should_stop = (v_ego < 0.3 and desired_accel < 0.1)
|
||||||
|
|
||||||
|
desired_accel = smooth_value(desired_accel, prev_action.desiredAcceleration, self.LONG_SMOOTH_SECONDS)
|
||||||
|
|
||||||
if self.generation is not None and self.generation >= 10: # smooth curvature for post FOF models
|
if self.generation is not None and self.generation >= 10: # smooth curvature for post FOF models
|
||||||
if v_ego > self.MIN_LAT_CONTROL_SPEED:
|
if v_ego > self.MIN_LAT_CONTROL_SPEED:
|
||||||
desired_curvature = smooth_value(desired_curvature, prev_action.desiredCurvature, self.LAT_SMOOTH_SECONDS)
|
desired_curvature = smooth_value(desired_curvature, prev_action.desiredCurvature, self.LAT_SMOOTH_SECONDS)
|
||||||
@@ -282,6 +316,9 @@ def main(demo=False):
|
|||||||
setproctitle(PROCESS_NAME)
|
setproctitle(PROCESS_NAME)
|
||||||
config_realtime_process(7, 54)
|
config_realtime_process(7, 54)
|
||||||
|
|
||||||
|
if USBGPU:
|
||||||
|
wait_usbgpu_link()
|
||||||
|
|
||||||
# visionipc clients
|
# visionipc clients
|
||||||
while True:
|
while True:
|
||||||
available_streams = VisionIpcClient.available_streams("camerad", block=False)
|
available_streams = VisionIpcClient.available_streams("camerad", block=False)
|
||||||
@@ -316,6 +353,9 @@ def main(demo=False):
|
|||||||
publish_state = PublishState()
|
publish_state = PublishState()
|
||||||
params = Params()
|
params = Params()
|
||||||
|
|
||||||
|
params.put_bool("UsbGpuPresent", USBGPU)
|
||||||
|
params.put_bool("UsbGpuCompiled", USBGPU)
|
||||||
|
|
||||||
# setup filter to track dropped frames
|
# setup filter to track dropped frames
|
||||||
frame_dropped_filter = FirstOrderFilter(0., 10., 1. / model.constants.MODEL_FREQ)
|
frame_dropped_filter = FirstOrderFilter(0., 10., 1. / model.constants.MODEL_FREQ)
|
||||||
frame_id = 0
|
frame_id = 0
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from openpilot.sunnypilot.modeld_v2.constants import ModelConstants
|
from openpilot.sunnypilot.modeld_v2.constants import ModelConstants
|
||||||
|
|
||||||
|
|
||||||
def safe_exp(x, out=None):
|
def safe_exp(x, out=None):
|
||||||
# -11 is around 10**14, more causes float16 overflow
|
# -11 is around 10**14, more causes float16 overflow
|
||||||
return np.exp(np.clip(x, -np.inf, 11), out=out)
|
return np.exp(np.clip(x, -np.inf, 11), out=out)
|
||||||
|
|
||||||
|
|
||||||
def sigmoid(x):
|
def sigmoid(x):
|
||||||
return 1. / (1. + safe_exp(-x))
|
return 1. / (1. + safe_exp(-x))
|
||||||
|
|
||||||
|
|
||||||
def softmax(x, axis=-1):
|
def softmax(x, axis=-1):
|
||||||
x -= np.max(x, axis=axis, keepdims=True)
|
x -= np.max(x, axis=axis, keepdims=True)
|
||||||
if x.dtype == np.float32 or x.dtype == np.float64:
|
if x.dtype == np.float32 or x.dtype == np.float64:
|
||||||
@@ -17,6 +20,19 @@ def softmax(x, axis=-1):
|
|||||||
x /= np.sum(x, axis=axis, keepdims=True)
|
x /= np.sum(x, axis=axis, keepdims=True)
|
||||||
return x
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
def _infer_mhp(slice_size: int, prod_out_shape: int, max_in_n: int = 16, max_out_n: int = 6) -> tuple[int, int]:
|
||||||
|
for out_n in range(max_out_n + 1):
|
||||||
|
per = 2 * prod_out_shape + out_n
|
||||||
|
if per <= 0:
|
||||||
|
continue
|
||||||
|
if slice_size % per == 0:
|
||||||
|
in_n = slice_size // per
|
||||||
|
if 1 <= in_n <= max_in_n:
|
||||||
|
return in_n, out_n
|
||||||
|
return 1, 0 # single hypothesis, no weights — matches a non-MDN output
|
||||||
|
|
||||||
|
|
||||||
class Parser:
|
class Parser:
|
||||||
def __init__(self, ignore_missing=False):
|
def __init__(self, ignore_missing=False):
|
||||||
self.ignore_missing = ignore_missing
|
self.ignore_missing = ignore_missing
|
||||||
@@ -40,17 +56,22 @@ class Parser:
|
|||||||
raw = outs[name]
|
raw = outs[name]
|
||||||
outs[name] = sigmoid(raw)
|
outs[name] = sigmoid(raw)
|
||||||
|
|
||||||
def parse_mdn(self, name, outs, in_N=0, out_N=1, out_shape=None):
|
def parse_mdn(self, name, outs, out_shape, in_N=0, out_N=0):
|
||||||
if self.check_missing(outs, name):
|
if self.check_missing(outs, name):
|
||||||
return
|
return
|
||||||
raw = outs[name]
|
raw = outs[name]
|
||||||
raw = raw.reshape((raw.shape[0], max(in_N, 1), -1))
|
|
||||||
|
if in_N == 0 and out_N == 0:
|
||||||
|
prod = int(np.prod(out_shape))
|
||||||
|
in_N, out_N = _infer_mhp(raw.shape[1], prod)
|
||||||
|
|
||||||
|
raw = raw.reshape((raw.shape[0], in_N, -1))
|
||||||
|
|
||||||
n_values = (raw.shape[2] - out_N)//2
|
n_values = (raw.shape[2] - out_N)//2
|
||||||
pred_mu = raw[:,:,:n_values]
|
pred_mu = raw[:,:,:n_values]
|
||||||
pred_std = safe_exp(raw[:,:,n_values: 2*n_values])
|
pred_std = safe_exp(raw[:,:,n_values: 2*n_values])
|
||||||
|
|
||||||
if in_N > 1:
|
if in_N > 1 and out_N > 0:
|
||||||
weights = np.zeros((raw.shape[0], in_N, out_N), dtype=raw.dtype)
|
weights = np.zeros((raw.shape[0], in_N, out_N), dtype=raw.dtype)
|
||||||
for i in range(out_N):
|
for i in range(out_N):
|
||||||
weights[:,:,i - out_N] = softmax(raw[:,:,i - out_N], axis=-1)
|
weights[:,:,i - out_N] = softmax(raw[:,:,i - out_N], axis=-1)
|
||||||
@@ -61,7 +82,6 @@ class Parser:
|
|||||||
weights[fidx] = weights[fidx][idxs]
|
weights[fidx] = weights[fidx][idxs]
|
||||||
pred_mu[fidx] = pred_mu[fidx][idxs]
|
pred_mu[fidx] = pred_mu[fidx][idxs]
|
||||||
pred_std[fidx] = pred_std[fidx][idxs]
|
pred_std[fidx] = pred_std[fidx][idxs]
|
||||||
assert out_shape is not None
|
|
||||||
full_shape = tuple([raw.shape[0], in_N] + list(out_shape))
|
full_shape = tuple([raw.shape[0], in_N] + list(out_shape))
|
||||||
outs[name + '_weights'] = weights
|
outs[name + '_weights'] = weights
|
||||||
outs[name + '_hypotheses'] = pred_mu.reshape(full_shape)
|
outs[name + '_hypotheses'] = pred_mu.reshape(full_shape)
|
||||||
@@ -74,37 +94,43 @@ class Parser:
|
|||||||
idxs = np.argsort(weights[fidx,:,hidx])[::-1]
|
idxs = np.argsort(weights[fidx,:,hidx])[::-1]
|
||||||
pred_mu_final[fidx, hidx] = pred_mu[fidx, idxs[0]]
|
pred_mu_final[fidx, hidx] = pred_mu[fidx, idxs[0]]
|
||||||
pred_std_final[fidx, hidx] = pred_std[fidx, idxs[0]]
|
pred_std_final[fidx, hidx] = pred_std[fidx, idxs[0]]
|
||||||
|
elif in_N > 1 and out_N == 0:
|
||||||
|
# MHP without weights: keep every hypothesis intact, surface them as
|
||||||
|
# ``*_hypotheses`` and propagate the full multi-hypothesis tensor.
|
||||||
|
full_shape = tuple([raw.shape[0], in_N] + list(out_shape))
|
||||||
|
outs[name + '_hypotheses'] = pred_mu.reshape(full_shape)
|
||||||
|
outs[name + '_stds_hypotheses'] = pred_std.reshape(full_shape)
|
||||||
|
pred_mu_final = pred_mu
|
||||||
|
pred_std_final = pred_std
|
||||||
else:
|
else:
|
||||||
pred_mu_final = pred_mu
|
pred_mu_final = pred_mu
|
||||||
pred_std_final = pred_std
|
pred_std_final = pred_std
|
||||||
|
|
||||||
if out_N > 1:
|
if out_N > 1 or (in_N > 1 and out_N == 0):
|
||||||
assert out_shape is not None
|
n_selections = out_N if out_N > 1 else in_N
|
||||||
final_shape = tuple([raw.shape[0], out_N] + list(out_shape))
|
final_shape = tuple([raw.shape[0], n_selections] + list(out_shape))
|
||||||
else:
|
else:
|
||||||
assert out_shape is not None
|
|
||||||
final_shape = tuple([raw.shape[0],] + list(out_shape))
|
final_shape = tuple([raw.shape[0],] + list(out_shape))
|
||||||
outs[name] = pred_mu_final.reshape(final_shape)
|
outs[name] = pred_mu_final.reshape(final_shape)
|
||||||
outs[name + '_stds'] = pred_std_final.reshape(final_shape)
|
outs[name + '_stds'] = pred_std_final.reshape(final_shape)
|
||||||
|
|
||||||
def parse_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
|
def parse_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
|
||||||
self.parse_mdn('plan', outs, in_N=ModelConstants.PLAN_MHP_N, out_N=ModelConstants.PLAN_MHP_SELECTION,
|
# supercombo (4955 / 102) and newer variants (e.g. 990 / 144).
|
||||||
out_shape=(ModelConstants.IDX_N,ModelConstants.PLAN_WIDTH))
|
self.parse_mdn('plan', outs, out_shape=(ModelConstants.IDX_N, ModelConstants.PLAN_WIDTH))
|
||||||
self.parse_mdn('lane_lines', outs, in_N=0, out_N=0, out_shape=(ModelConstants.NUM_LANE_LINES,ModelConstants.IDX_N,ModelConstants.LANE_LINES_WIDTH))
|
self.parse_mdn('lane_lines', outs, out_shape=(ModelConstants.NUM_LANE_LINES, ModelConstants.IDX_N, ModelConstants.LANE_LINES_WIDTH))
|
||||||
self.parse_mdn('road_edges', outs, in_N=0, out_N=0, out_shape=(ModelConstants.NUM_ROAD_EDGES,ModelConstants.IDX_N,ModelConstants.LANE_LINES_WIDTH))
|
self.parse_mdn('road_edges', outs, out_shape=(ModelConstants.NUM_ROAD_EDGES, ModelConstants.IDX_N, ModelConstants.LANE_LINES_WIDTH))
|
||||||
self.parse_mdn('pose', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,))
|
self.parse_mdn('pose', outs, out_shape=(ModelConstants.POSE_WIDTH,))
|
||||||
self.parse_mdn('road_transform', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,))
|
self.parse_mdn('road_transform', outs, out_shape=(ModelConstants.POSE_WIDTH,))
|
||||||
if 'sim_pose' in outs:
|
if 'sim_pose' in outs:
|
||||||
self.parse_mdn('sim_pose', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,))
|
self.parse_mdn('sim_pose', outs, out_shape=(ModelConstants.POSE_WIDTH,))
|
||||||
self.parse_mdn('wide_from_device_euler', outs, in_N=0, out_N=0, out_shape=(ModelConstants.WIDE_FROM_DEVICE_WIDTH,))
|
self.parse_mdn('wide_from_device_euler', outs, out_shape=(ModelConstants.WIDE_FROM_DEVICE_WIDTH,))
|
||||||
self.parse_mdn('lead', outs, in_N=ModelConstants.LEAD_MHP_N, out_N=ModelConstants.LEAD_MHP_SELECTION,
|
self.parse_mdn('lead', outs, out_shape=(ModelConstants.LEAD_TRAJ_LEN, ModelConstants.LEAD_WIDTH))
|
||||||
out_shape=(ModelConstants.LEAD_TRAJ_LEN,ModelConstants.LEAD_WIDTH))
|
|
||||||
if 'lat_planner_solution' in outs:
|
if 'lat_planner_solution' in outs:
|
||||||
self.parse_mdn('lat_planner_solution', outs, in_N=0, out_N=0, out_shape=(ModelConstants.IDX_N,ModelConstants.LAT_PLANNER_SOLUTION_WIDTH))
|
self.parse_mdn('lat_planner_solution', outs, out_shape=(ModelConstants.IDX_N, ModelConstants.LAT_PLANNER_SOLUTION_WIDTH))
|
||||||
if 'desired_curvature' in outs:
|
if 'desired_curvature' in outs:
|
||||||
self.parse_mdn('desired_curvature', outs, in_N=0, out_N=0, out_shape=(ModelConstants.DESIRED_CURV_WIDTH,))
|
self.parse_mdn('desired_curvature', outs, out_shape=(ModelConstants.DESIRED_CURV_WIDTH,))
|
||||||
for k in ['lead_prob', 'lane_lines_prob', 'meta']:
|
for k in ['lead_prob', 'lane_lines_prob', 'meta']:
|
||||||
self.parse_binary_crossentropy(k, outs)
|
self.parse_binary_crossentropy(k, outs)
|
||||||
self.parse_categorical_crossentropy('desire_state', outs, out_shape=(ModelConstants.DESIRE_PRED_WIDTH,))
|
self.parse_categorical_crossentropy('desire_state', outs, out_shape=(ModelConstants.DESIRE_PRED_WIDTH,))
|
||||||
self.parse_categorical_crossentropy('desire_pred', outs, out_shape=(ModelConstants.DESIRE_PRED_LEN,ModelConstants.DESIRE_PRED_WIDTH))
|
self.parse_categorical_crossentropy('desire_pred', outs, out_shape=(ModelConstants.DESIRE_PRED_LEN, ModelConstants.DESIRE_PRED_WIDTH))
|
||||||
return outs
|
return outs
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ class Parser:
|
|||||||
self.parse_categorical_crossentropy('desire_state', outs, out_shape=(SplitModelConstants.DESIRE_PRED_WIDTH,))
|
self.parse_categorical_crossentropy('desire_state', outs, out_shape=(SplitModelConstants.DESIRE_PRED_WIDTH,))
|
||||||
if 'lane_lines' in outs:
|
if 'lane_lines' in outs:
|
||||||
self.parse_mdn('lane_lines', outs, in_N=0, out_N=0,
|
self.parse_mdn('lane_lines', outs, in_N=0, out_N=0,
|
||||||
out_shape=(SplitModelConstants.NUM_LANE_LINES,SplitModelConstants.IDX_N,SplitModelConstants.LANE_LINES_WIDTH))
|
out_shape=(SplitModelConstants.NUM_LANE_LINES,SplitModelConstants.IDX_N,SplitModelConstants.LANE_LINES_WIDTH))
|
||||||
if 'lane_lines_prob' in outs:
|
if 'lane_lines_prob' in outs:
|
||||||
self.parse_binary_crossentropy('lane_lines_prob', outs)
|
self.parse_binary_crossentropy('lane_lines_prob', outs)
|
||||||
if 'lead_prob' in outs:
|
if 'lead_prob' in outs:
|
||||||
@@ -134,7 +134,7 @@ class Parser:
|
|||||||
self.parse_binary_crossentropy('meta', outs)
|
self.parse_binary_crossentropy('meta', outs)
|
||||||
if 'road_edges' in outs:
|
if 'road_edges' in outs:
|
||||||
self.parse_mdn('road_edges', outs, in_N=0, out_N=0,
|
self.parse_mdn('road_edges', outs, in_N=0, out_N=0,
|
||||||
out_shape=(SplitModelConstants.NUM_ROAD_EDGES,SplitModelConstants.IDX_N,SplitModelConstants.LANE_LINES_WIDTH))
|
out_shape=(SplitModelConstants.NUM_ROAD_EDGES,SplitModelConstants.IDX_N,SplitModelConstants.LANE_LINES_WIDTH))
|
||||||
if 'sim_pose' in outs:
|
if 'sim_pose' in outs:
|
||||||
self.parse_mdn('sim_pose', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.POSE_WIDTH,))
|
self.parse_mdn('sim_pose', outs, in_N=0, out_N=0, out_shape=(SplitModelConstants.POSE_WIDTH,))
|
||||||
if 'action' in outs:
|
if 'action' in outs:
|
||||||
|
|||||||
@@ -67,16 +67,12 @@ class TestStockEquivalence:
|
|||||||
state = model_state_factory(ARCHETYPES['vision_policy_split'])
|
state = model_state_factory(ARCHETYPES['vision_policy_split'])
|
||||||
|
|
||||||
frame_skip = derive_frame_skip(SPLIT_VISION_INPUT_SHAPES, SPLIT_POLICY_INPUT_SHAPES)
|
frame_skip = derive_frame_skip(SPLIT_VISION_INPUT_SHAPES, SPLIT_POLICY_INPUT_SHAPES)
|
||||||
# action_t is a deep-model prerequisite the SP loader doesn't provide yet; see skip_keys below
|
|
||||||
stock_shapes = {**SPLIT_VISION_INPUT_SHAPES, **SPLIT_POLICY_INPUT_SHAPES, 'action_t': (1, 2)}
|
stock_shapes = {**SPLIT_VISION_INPUT_SHAPES, **SPLIT_POLICY_INPUT_SHAPES, 'action_t': (1, 2)}
|
||||||
stock_queues, stock_npy = make_input_queues(stock_shapes, frame_skip, device='NPY')
|
stock_queues, stock_npy = make_input_queues(stock_shapes, frame_skip, device='NPY')
|
||||||
|
|
||||||
# TODO-SP: remove action_t skip once SP adds prerequisite for deep models (action_t input queue)
|
assert set(state.input_queues.keys()) == set(stock_queues.keys())
|
||||||
skip_keys = {'action_t'}
|
assert {'desire', 'traffic_convention'} <= set(state.numpy_inputs.keys())
|
||||||
assert set(state.input_queues.keys()) == set(stock_queues.keys()) - skip_keys, \
|
assert set(state.numpy_inputs.keys()) == set(stock_npy.keys()) - {'action_t', 'prev_feat'}
|
||||||
f"Queue keys differ: v2={set(state.input_queues.keys())}, stock={set(stock_queues.keys())}"
|
|
||||||
assert set(state.numpy_inputs.keys()) == set(stock_npy.keys()) - skip_keys, \
|
|
||||||
f"Npy keys differ: v2={set(state.numpy_inputs.keys())}, stock={set(stock_npy.keys())}"
|
|
||||||
|
|
||||||
def test_split_queue_keys_work_with_desire_key(self, model_state_factory):
|
def test_split_queue_keys_work_with_desire_key(self, model_state_factory):
|
||||||
from openpilot.sunnypilot.modeld_v2.compile_modeld import derive_frame_skip, make_split_input_queues
|
from openpilot.sunnypilot.modeld_v2.compile_modeld import derive_frame_skip, make_split_input_queues
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from openpilot.common.params import Params
|
|||||||
from openpilot.common.swaglog import cloudlog
|
from openpilot.common.swaglog import cloudlog
|
||||||
from openpilot.common.hardware.hw import Paths
|
from openpilot.common.hardware.hw import Paths
|
||||||
from openpilot.sunnypilot.models.helpers import is_bundle_version_compatible
|
from openpilot.sunnypilot.models.helpers import is_bundle_version_compatible
|
||||||
|
from openpilot.selfdrive.modeld.helpers import usbgpu_present
|
||||||
|
|
||||||
from openpilot.cereal import custom
|
from openpilot.cereal import custom
|
||||||
|
|
||||||
@@ -64,8 +65,6 @@ class ModelParser:
|
|||||||
|
|
||||||
model.type = model_data.get("type")
|
model.type = model_data.get("type")
|
||||||
model.artifact = ModelParser._parse_artifact(model_data.get("artifact", {}))
|
model.artifact = ModelParser._parse_artifact(model_data.get("artifact", {}))
|
||||||
if metadata := model_data.get("metadata"):
|
|
||||||
model.metadata = ModelParser._parse_artifact(metadata)
|
|
||||||
return model
|
return model
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -105,11 +104,11 @@ class ModelParser:
|
|||||||
class ModelCache:
|
class ModelCache:
|
||||||
"""Handles caching of model data to avoid frequent remote fetches"""
|
"""Handles caching of model data to avoid frequent remote fetches"""
|
||||||
|
|
||||||
def __init__(self, params: Params, cache_timeout: int = int(3600 * 1e9)):
|
def __init__(self, params: Params, cache_timeout: int = int(3600 * 1e9), suffix: str = ""):
|
||||||
self.params = params
|
self.params = params
|
||||||
self.cache_timeout = cache_timeout
|
self.cache_timeout = cache_timeout
|
||||||
self._LAST_SYNC_KEY = "ModelManager_LastSyncTime"
|
self._LAST_SYNC_KEY = f"ModelManager_LastSyncTime{suffix}"
|
||||||
self._CACHE_KEY = "ModelManager_ModelsCache"
|
self._CACHE_KEY = f"ModelManager_ModelsCache{suffix}"
|
||||||
|
|
||||||
def _is_expired(self) -> bool:
|
def _is_expired(self) -> bool:
|
||||||
"""Checks if the cache has expired"""
|
"""Checks if the cache has expired"""
|
||||||
@@ -141,24 +140,28 @@ class ModelCache:
|
|||||||
|
|
||||||
class ModelFetcher:
|
class ModelFetcher:
|
||||||
"""Handles fetching and caching of model data from remote source"""
|
"""Handles fetching and caching of model data from remote source"""
|
||||||
MODEL_URL = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-models/refs/heads/gh-pages/docs/driving_models_v18.json"
|
|
||||||
|
|
||||||
def __init__(self, params: Params):
|
def __init__(self, params: Params):
|
||||||
self.params = params
|
self.params = params
|
||||||
self.model_cache = ModelCache(params)
|
|
||||||
self.model_parser = ModelParser()
|
self.model_parser = ModelParser()
|
||||||
|
if usbgpu_present():
|
||||||
|
self.model_cache = ModelCache(params, suffix="_USBGPU")
|
||||||
|
self.model_url = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-models/refs/heads/gh-pages/docs/driving_models_usbgpu_v18.json"
|
||||||
|
else:
|
||||||
|
self.model_cache = ModelCache(params)
|
||||||
|
self.model_url = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-models/refs/heads/gh-pages/docs/driving_models_v18.json"
|
||||||
|
|
||||||
def _fetch_and_cache_models(self) -> list[custom.ModelManagerSP.ModelBundle] | None:
|
def _fetch_and_cache_models(self) -> list[custom.ModelManagerSP.ModelBundle] | None:
|
||||||
"""Fetches fresh model data from remote and updates cache.
|
"""Fetches fresh model data from remote and updates cache.
|
||||||
Returns None on transport errors. Raises on 404 and other fatal HTTP errors.
|
Returns None on transport errors. Raises on 404 and other fatal HTTP errors.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
response = requests.get(self.MODEL_URL, timeout=10)
|
response = requests.get(self.model_url, timeout=10)
|
||||||
|
|
||||||
# Explicitly handle 404 differently
|
# Explicitly handle 404 differently
|
||||||
if response.status_code == 404:
|
if response.status_code == 404:
|
||||||
cloudlog.error(f"Models URL returned 404 Not Found: {self.MODEL_URL}")
|
cloudlog.error(f"Models URL returned 404 Not Found: {self.model_url}")
|
||||||
raise HTTPError(f"404 Not Found: {self.MODEL_URL}", response=response)
|
raise HTTPError(f"404 Not Found: {self.model_url}", response=response)
|
||||||
|
|
||||||
# Raise for any other 4xx/5xx
|
# Raise for any other 4xx/5xx
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
@@ -211,5 +214,3 @@ if __name__ == "__main__":
|
|||||||
# Print metadata details
|
# Print metadata details
|
||||||
if model.artifact.chunks:
|
if model.artifact.chunks:
|
||||||
print(f"Contains {len(model.artifact.chunks)} chunks.")
|
print(f"Contains {len(model.artifact.chunks)} chunks.")
|
||||||
if hasattr(model, 'metadata') and model.metadata and model.metadata.fileName:
|
|
||||||
print(f"Metadata: {model.metadata.fileName}, Download URI: {model.metadata.downloadUri.uri}")
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ from openpilot.sunnypilot.models.constants import Meta, MetaSimPose, MetaTombRai
|
|||||||
from openpilot.common.hardware.hw import Paths
|
from openpilot.common.hardware.hw import Paths
|
||||||
|
|
||||||
# SET ME TO THE EXACT JSON VERSION WE SET IN SUNNYPILOT_MODELS REPO
|
# SET ME TO THE EXACT JSON VERSION WE SET IN SUNNYPILOT_MODELS REPO
|
||||||
REQUIRED_JSON_VERSION = 15
|
REQUIRED_JSON_VERSION = 16
|
||||||
|
|
||||||
CUSTOM_MODEL_PATH = Paths.model_root()
|
CUSTOM_MODEL_PATH = Paths.model_root()
|
||||||
METADATA_PATH = Path(__file__).parent / '../models/supercombo_metadata.pkl'
|
METADATA_PATH = Path(__file__).parent / '../models/supercombo_metadata.pkl'
|
||||||
@@ -56,12 +56,20 @@ def is_bundle_version_compatible(bundle: dict) -> bool:
|
|||||||
|
|
||||||
def _bundle_artifacts(bundle: custom.ModelManagerSP.ModelBundle) -> list[tuple[str, str]]:
|
def _bundle_artifacts(bundle: custom.ModelManagerSP.ModelBundle) -> list[tuple[str, str]]:
|
||||||
artifacts = []
|
artifacts = []
|
||||||
|
from openpilot.common.file_chunker import get_chunk_name
|
||||||
for model in getattr(bundle, 'models', []) or []:
|
for model in getattr(bundle, 'models', []) or []:
|
||||||
for artifact in (getattr(model, 'artifact', None), getattr(model, 'metadata', None)):
|
for artifact in (getattr(model, 'artifact', None),):
|
||||||
if artifact and getattr(artifact, 'fileName', None) and getattr(artifact, 'downloadUri', None):
|
if artifact and getattr(artifact, 'fileName', None):
|
||||||
sha256 = getattr(artifact.downloadUri, 'sha256', None)
|
if len(artifact.chunks) > 0:
|
||||||
if sha256:
|
for i, chunk in enumerate(artifact.chunks):
|
||||||
artifacts.append((artifact.fileName, sha256))
|
chunk_name = get_chunk_name(artifact.fileName, i, len(artifact.chunks))
|
||||||
|
if getattr(chunk, 'sha256', None):
|
||||||
|
artifacts.append((chunk_name, chunk.sha256))
|
||||||
|
else:
|
||||||
|
if getattr(artifact, 'downloadUri', None):
|
||||||
|
sha256 = getattr(artifact.downloadUri, 'sha256', None)
|
||||||
|
if sha256:
|
||||||
|
artifacts.append((artifact.fileName, sha256))
|
||||||
return artifacts
|
return artifacts
|
||||||
|
|
||||||
|
|
||||||
@@ -156,8 +164,7 @@ def _get_model():
|
|||||||
|
|
||||||
|
|
||||||
def load_metadata():
|
def load_metadata():
|
||||||
model = _get_model()
|
metadata_path = METADATA_PATH
|
||||||
metadata_path = f"{CUSTOM_MODEL_PATH}/{model.metadata.fileName}" if model else METADATA_PATH
|
|
||||||
|
|
||||||
with open(metadata_path, 'rb') as f:
|
with open(metadata_path, 'rb') as f:
|
||||||
return pickle.load(f)
|
return pickle.load(f)
|
||||||
|
|||||||
@@ -38,11 +38,11 @@ class ModelManagerSP:
|
|||||||
if not self.selected_bundle:
|
if not self.selected_bundle:
|
||||||
return
|
return
|
||||||
for model in self.selected_bundle.models:
|
for model in self.selected_bundle.models:
|
||||||
for artifact in (model.artifact, model.metadata):
|
artifact = model.artifact
|
||||||
if artifact is not source_artifact and artifact.fileName == source_artifact.fileName:
|
if artifact is not source_artifact and artifact.fileName == source_artifact.fileName:
|
||||||
artifact.downloadProgress.status = source_artifact.downloadProgress.status
|
artifact.downloadProgress.status = source_artifact.downloadProgress.status
|
||||||
artifact.downloadProgress.progress = source_artifact.downloadProgress.progress
|
artifact.downloadProgress.progress = source_artifact.downloadProgress.progress
|
||||||
artifact.downloadProgress.eta = source_artifact.downloadProgress.eta
|
artifact.downloadProgress.eta = source_artifact.downloadProgress.eta
|
||||||
|
|
||||||
def _calculate_eta(self, filename: str, progress: float) -> int:
|
def _calculate_eta(self, filename: str, progress: float) -> int:
|
||||||
"""Calculate ETA based on elapsed time and current progress"""
|
"""Calculate ETA based on elapsed time and current progress"""
|
||||||
@@ -136,7 +136,22 @@ class ModelManagerSP:
|
|||||||
full_path = os.path.join(destination_path, filename)
|
full_path = os.path.join(destination_path, filename)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if await verify_file(full_path, expected_hash):
|
is_cached = False
|
||||||
|
if len(artifact.chunks) > 0:
|
||||||
|
from openpilot.common.file_chunker import get_chunk_name
|
||||||
|
chunks_valid = True
|
||||||
|
for i, chunk in enumerate(artifact.chunks):
|
||||||
|
chunk_path = get_chunk_name(full_path, i, len(artifact.chunks))
|
||||||
|
if not await verify_file(chunk_path, chunk.sha256):
|
||||||
|
chunks_valid = False
|
||||||
|
break
|
||||||
|
if chunks_valid and len(artifact.chunks) > 0:
|
||||||
|
is_cached = True
|
||||||
|
else:
|
||||||
|
if await verify_file(full_path, expected_hash):
|
||||||
|
is_cached = True
|
||||||
|
|
||||||
|
if is_cached:
|
||||||
artifact.downloadProgress.status = custom.ModelManagerSP.DownloadStatus.cached
|
artifact.downloadProgress.status = custom.ModelManagerSP.DownloadStatus.cached
|
||||||
artifact.downloadProgress.progress = 100
|
artifact.downloadProgress.progress = 100
|
||||||
artifact.downloadProgress.eta = 0
|
artifact.downloadProgress.eta = 0
|
||||||
@@ -146,11 +161,15 @@ class ModelManagerSP:
|
|||||||
|
|
||||||
if len(artifact.chunks) > 0:
|
if len(artifact.chunks) > 0:
|
||||||
await self._download_chunked(url, full_path, artifact)
|
await self._download_chunked(url, full_path, artifact)
|
||||||
|
from openpilot.common.file_chunker import get_chunk_name
|
||||||
|
for i, chunk in enumerate(artifact.chunks):
|
||||||
|
chunk_path = get_chunk_name(full_path, i, len(artifact.chunks))
|
||||||
|
if not await verify_file(chunk_path, chunk.sha256):
|
||||||
|
raise ValueError(f"Hash validation failed for chunk {i+1} of {filename}")
|
||||||
else:
|
else:
|
||||||
await self._download_file(url, full_path, artifact)
|
await self._download_file(url, full_path, artifact)
|
||||||
|
if not await verify_file(full_path, expected_hash):
|
||||||
if not await verify_file(full_path, expected_hash):
|
raise ValueError(f"Hash validation failed for {filename}")
|
||||||
raise ValueError(f"Hash validation failed for {filename}")
|
|
||||||
|
|
||||||
artifact.downloadProgress.status = custom.ModelManagerSP.DownloadStatus.downloaded
|
artifact.downloadProgress.status = custom.ModelManagerSP.DownloadStatus.downloaded
|
||||||
artifact.downloadProgress.progress = 100
|
artifact.downloadProgress.progress = 100
|
||||||
@@ -198,16 +217,16 @@ class ModelManagerSP:
|
|||||||
try:
|
try:
|
||||||
seen_artifacts: set[str] = set()
|
seen_artifacts: set[str] = set()
|
||||||
for model in self.selected_bundle.models:
|
for model in self.selected_bundle.models:
|
||||||
for artifact in (model.metadata, model.artifact):
|
artifact = model.artifact
|
||||||
if not artifact.fileName:
|
if not artifact.fileName:
|
||||||
continue
|
continue
|
||||||
if artifact.fileName in seen_artifacts:
|
if artifact.fileName in seen_artifacts:
|
||||||
artifact.downloadProgress.status = custom.ModelManagerSP.DownloadStatus.cached
|
artifact.downloadProgress.status = custom.ModelManagerSP.DownloadStatus.cached
|
||||||
artifact.downloadProgress.progress = 100
|
artifact.downloadProgress.progress = 100
|
||||||
artifact.downloadProgress.eta = 0
|
artifact.downloadProgress.eta = 0
|
||||||
else:
|
else:
|
||||||
seen_artifacts.add(artifact.fileName)
|
seen_artifacts.add(artifact.fileName)
|
||||||
await self._process_artifact(artifact, destination_path)
|
await self._process_artifact(artifact, destination_path)
|
||||||
|
|
||||||
self.active_bundle = self.selected_bundle
|
self.active_bundle = self.selected_bundle
|
||||||
self.active_bundle.status = custom.ModelManagerSP.DownloadStatus.downloaded
|
self.active_bundle.status = custom.ModelManagerSP.DownloadStatus.downloaded
|
||||||
@@ -268,8 +287,6 @@ class ModelManagerSP:
|
|||||||
for model in self.active_bundle.models:
|
for model in self.active_bundle.models:
|
||||||
if hasattr(model, 'artifact') and model.artifact.fileName:
|
if hasattr(model, 'artifact') and model.artifact.fileName:
|
||||||
active_files.append(model.artifact.fileName)
|
active_files.append(model.artifact.fileName)
|
||||||
if hasattr(model, 'metadata') and model.metadata.fileName:
|
|
||||||
active_files.append(model.metadata.fileName)
|
|
||||||
|
|
||||||
# Remove all files except active ones (including their chunk files)
|
# Remove all files except active ones (including their chunk files)
|
||||||
model_dir = Paths.model_root()
|
model_dir = Paths.model_root()
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
from openpilot.common.params import Params
|
||||||
from openpilot.sunnypilot.models.tinygrad_ref import get_tinygrad_ref
|
from openpilot.sunnypilot.models.tinygrad_ref import get_tinygrad_ref
|
||||||
from openpilot.sunnypilot.models.fetcher import ModelFetcher
|
from openpilot.sunnypilot.models.fetcher import ModelFetcher
|
||||||
|
|
||||||
|
|
||||||
def fetch_tinygrad_ref():
|
def fetch_tinygrad_ref():
|
||||||
response = requests.get(ModelFetcher.MODEL_URL, timeout=10)
|
fetcher = ModelFetcher(Params())
|
||||||
|
response = requests.get(fetcher.model_url, timeout=10)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
json_data = response.json()
|
json_data = response.json()
|
||||||
return json_data.get("tinygrad_ref")
|
return json_data.get("tinygrad_ref")
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ def initialize_params(params) -> list[dict[str, Any]]:
|
|||||||
# tesla
|
# tesla
|
||||||
keys.extend([
|
keys.extend([
|
||||||
"TeslaCoopSteering",
|
"TeslaCoopSteering",
|
||||||
|
"TeslaMadsScreenButton",
|
||||||
])
|
])
|
||||||
|
|
||||||
# toyota
|
# toyota
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ class SpeedLimitAssist:
|
|||||||
|
|
||||||
self._plus_hold = 0.
|
self._plus_hold = 0.
|
||||||
self._minus_hold = 0.
|
self._minus_hold = 0.
|
||||||
self._last_carstate_ts = 0.
|
self._release_toggle_prev = 0
|
||||||
|
|
||||||
# TODO-SP: SLA's own output_a_target for planner
|
# TODO-SP: SLA's own output_a_target for planner
|
||||||
# Solution functions mapped to respective states
|
# Solution functions mapped to respective states
|
||||||
@@ -146,16 +146,16 @@ class SpeedLimitAssist:
|
|||||||
set_speed_limit_assist_availability(self.CP, self.CP_SP, self.params)
|
set_speed_limit_assist_availability(self.CP, self.CP_SP, self.params)
|
||||||
self.enabled = self.params.get("SpeedLimitMode", return_default=True) == Mode.assist
|
self.enabled = self.params.get("SpeedLimitMode", return_default=True) == Mode.assist
|
||||||
|
|
||||||
def update_car_state(self, CS: car.CarState) -> None:
|
def update_buttons(self, release_toggle: int) -> None:
|
||||||
|
released = self._release_toggle_prev ^ release_toggle
|
||||||
|
self._release_toggle_prev = release_toggle
|
||||||
|
if not released:
|
||||||
|
return
|
||||||
now = time.monotonic()
|
now = time.monotonic()
|
||||||
self._last_carstate_ts = now
|
if any((released >> b) & 1 for b in CRUISE_BUTTONS_PLUS):
|
||||||
|
self._plus_hold = max(self._plus_hold, now + CRUISE_BUTTON_CONFIRM_HOLD)
|
||||||
for b in CS.buttonEvents:
|
if any((released >> b) & 1 for b in CRUISE_BUTTONS_MINUS):
|
||||||
if not b.pressed:
|
self._minus_hold = max(self._minus_hold, now + CRUISE_BUTTON_CONFIRM_HOLD)
|
||||||
if b.type in CRUISE_BUTTONS_PLUS:
|
|
||||||
self._plus_hold = max(self._plus_hold, now + CRUISE_BUTTON_CONFIRM_HOLD)
|
|
||||||
elif b.type in CRUISE_BUTTONS_MINUS:
|
|
||||||
self._minus_hold = max(self._minus_hold, now + CRUISE_BUTTON_CONFIRM_HOLD)
|
|
||||||
|
|
||||||
def _get_button_release(self, req_plus: bool, req_minus: bool) -> bool:
|
def _get_button_release(self, req_plus: bool, req_minus: bool) -> bool:
|
||||||
now = time.monotonic()
|
now = time.monotonic()
|
||||||
|
|||||||
+91
-1
@@ -5,11 +5,14 @@ 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.
|
See the LICENSE.md file in the root directory for more details.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from openpilot.cereal import custom
|
from openpilot.cereal import custom
|
||||||
from opendbc.car.car_helpers import interfaces
|
from opendbc.car.car_helpers import interfaces
|
||||||
from opendbc.car.rivian.values import CAR as RIVIAN
|
from opendbc.car.rivian.values import CAR as RIVIAN
|
||||||
|
from opendbc.car.structs import car
|
||||||
from opendbc.car.tesla.values import CAR as TESLA
|
from opendbc.car.tesla.values import CAR as TESLA
|
||||||
from opendbc.car.toyota.values import CAR as TOYOTA
|
from opendbc.car.toyota.values import CAR as TOYOTA
|
||||||
from openpilot.common.constants import CV
|
from openpilot.common.constants import CV
|
||||||
@@ -21,9 +24,13 @@ from openpilot.sunnypilot.selfdrive.car import interfaces as sunnypilot_interfac
|
|||||||
from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit import PCM_LONG_REQUIRED_MAX_SET_SPEED
|
from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit import PCM_LONG_REQUIRED_MAX_SET_SPEED
|
||||||
from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.common import Mode
|
from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.common import Mode
|
||||||
from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.speed_limit_assist import SpeedLimitAssist, \
|
from openpilot.sunnypilot.selfdrive.controls.lib.speed_limit.speed_limit_assist import SpeedLimitAssist, \
|
||||||
PRE_ACTIVE_GUARD_PERIOD, ACTIVE_STATES
|
PRE_ACTIVE_GUARD_PERIOD, ACTIVE_STATES, CRUISE_BUTTON_CONFIRM_HOLD
|
||||||
|
from openpilot.sunnypilot.selfdrive.selfdrived.button_state_tracker import ButtonStateTracker
|
||||||
from openpilot.sunnypilot.selfdrive.selfdrived.events import EventsSP
|
from openpilot.sunnypilot.selfdrive.selfdrived.events import EventsSP
|
||||||
|
|
||||||
|
ButtonEvent = car.CarState.ButtonEvent
|
||||||
|
ButtonType = car.CarState.ButtonEvent.Type
|
||||||
|
|
||||||
SpeedLimitAssistState = custom.LongitudinalPlanSP.SpeedLimit.AssistState
|
SpeedLimitAssistState = custom.LongitudinalPlanSP.SpeedLimit.AssistState
|
||||||
|
|
||||||
ALL_STATES = tuple(SpeedLimitAssistState.schema.enumerants.values())
|
ALL_STATES = tuple(SpeedLimitAssistState.schema.enumerants.values())
|
||||||
@@ -276,3 +283,86 @@ class TestSpeedLimitAssist:
|
|||||||
assert self.sla.state in [SpeedLimitAssistState.preActive, SpeedLimitAssistState.active]
|
assert self.sla.state in [SpeedLimitAssistState.preActive, SpeedLimitAssistState.active]
|
||||||
elif initial_state in ACTIVE_STATES:
|
elif initial_state in ACTIVE_STATES:
|
||||||
assert self.sla.state in ACTIVE_STATES
|
assert self.sla.state in ACTIVE_STATES
|
||||||
|
|
||||||
|
|
||||||
|
class TestButtonStateTrackerSLAIntegration:
|
||||||
|
|
||||||
|
def setup_method(self, method):
|
||||||
|
self.tracker = ButtonStateTracker()
|
||||||
|
self.params = Params()
|
||||||
|
self.params.put("IsReleaseSpBranch", True, block=True)
|
||||||
|
self.params.put("SpeedLimitMode", int(Mode.assist), block=True)
|
||||||
|
self.params.put_bool("IsMetric", False, block=True)
|
||||||
|
self.params.put("SpeedLimitOffsetType", 0, block=True)
|
||||||
|
self.params.put("SpeedLimitValueOffset", 0, block=True)
|
||||||
|
|
||||||
|
CarInterface = interfaces[DEFAULT_CAR]
|
||||||
|
CP = CarInterface.get_non_essential_params(DEFAULT_CAR)
|
||||||
|
CP.openpilotLongitudinalControl = True
|
||||||
|
CP_SP = CarInterface.get_non_essential_params_sp(CP, DEFAULT_CAR)
|
||||||
|
self.sla = SpeedLimitAssist(CP, CP_SP)
|
||||||
|
|
||||||
|
def _make_cs(self, events=None) -> car.CarState:
|
||||||
|
CS = car.CarState()
|
||||||
|
CS.buttonEvents = events or []
|
||||||
|
return CS
|
||||||
|
|
||||||
|
def _run_ctrl_frames(self, frames: list[car.CarState]) -> None:
|
||||||
|
for cs in frames:
|
||||||
|
self.tracker.update(cs)
|
||||||
|
|
||||||
|
def test_button_confirm_via_tracker(self) -> None:
|
||||||
|
self._run_ctrl_frames([
|
||||||
|
self._make_cs([ButtonEvent(type=ButtonType.accelCruise, pressed=True)]),
|
||||||
|
self._make_cs(),
|
||||||
|
self._make_cs([ButtonEvent(type=ButtonType.accelCruise, pressed=False)]),
|
||||||
|
self._make_cs(),
|
||||||
|
self._make_cs(),
|
||||||
|
])
|
||||||
|
self.sla.update_buttons(self.tracker.release_toggle)
|
||||||
|
assert self.sla._get_button_release(req_plus=True, req_minus=False)
|
||||||
|
|
||||||
|
def test_rapid_press_release_between_polls(self) -> None:
|
||||||
|
self.sla.update_buttons(self.tracker.release_toggle)
|
||||||
|
|
||||||
|
self._run_ctrl_frames([
|
||||||
|
self._make_cs([ButtonEvent(type=ButtonType.decelCruise, pressed=True)]),
|
||||||
|
self._make_cs([ButtonEvent(type=ButtonType.decelCruise, pressed=False)]),
|
||||||
|
self._make_cs(),
|
||||||
|
self._make_cs(),
|
||||||
|
self._make_cs(),
|
||||||
|
])
|
||||||
|
self.sla.update_buttons(self.tracker.release_toggle)
|
||||||
|
assert self.sla._get_button_release(req_plus=False, req_minus=True)
|
||||||
|
|
||||||
|
def test_multiple_releases_between_polls(self) -> None:
|
||||||
|
self.sla.update_buttons(self.tracker.release_toggle)
|
||||||
|
|
||||||
|
self._run_ctrl_frames([
|
||||||
|
self._make_cs([
|
||||||
|
ButtonEvent(type=ButtonType.accelCruise, pressed=True),
|
||||||
|
ButtonEvent(type=ButtonType.decelCruise, pressed=True),
|
||||||
|
]),
|
||||||
|
self._make_cs([
|
||||||
|
ButtonEvent(type=ButtonType.accelCruise, pressed=False),
|
||||||
|
ButtonEvent(type=ButtonType.decelCruise, pressed=False),
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
self.sla.update_buttons(self.tracker.release_toggle)
|
||||||
|
assert self.sla._get_button_release(req_plus=True, req_minus=False)
|
||||||
|
assert self.sla._get_button_release(req_plus=False, req_minus=True)
|
||||||
|
|
||||||
|
def test_no_false_positive_same_toggle(self) -> None:
|
||||||
|
self.sla.update_buttons(self.tracker.release_toggle)
|
||||||
|
self.sla.update_buttons(self.tracker.release_toggle)
|
||||||
|
assert not self.sla._get_button_release(req_plus=True, req_minus=False)
|
||||||
|
assert not self.sla._get_button_release(req_plus=False, req_minus=True)
|
||||||
|
|
||||||
|
def test_button_confirm_expires(self) -> None:
|
||||||
|
self._run_ctrl_frames([
|
||||||
|
self._make_cs([ButtonEvent(type=ButtonType.accelCruise, pressed=True)]),
|
||||||
|
self._make_cs([ButtonEvent(type=ButtonType.accelCruise, pressed=False)]),
|
||||||
|
])
|
||||||
|
self.sla.update_buttons(self.tracker.release_toggle)
|
||||||
|
time.sleep(CRUISE_BUTTON_CONFIRM_HOLD + 0.1)
|
||||||
|
assert not self.sla._get_button_release(req_plus=True, req_minus=False)
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
"""
|
||||||
|
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 import structs
|
||||||
|
|
||||||
|
|
||||||
|
class ButtonStateTracker:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.pressed: int = 0
|
||||||
|
self.release_toggle: int = 0
|
||||||
|
|
||||||
|
def update(self, CS: structs.CarState) -> None:
|
||||||
|
for b in CS.buttonEvents:
|
||||||
|
bit = 1 << b.type.raw
|
||||||
|
if b.pressed:
|
||||||
|
self.pressed |= bit
|
||||||
|
else:
|
||||||
|
self.pressed &= ~bit
|
||||||
|
self.release_toggle ^= bit
|
||||||
|
|
||||||
|
def publish(self, ss_sp) -> None:
|
||||||
|
ss_sp.buttonsPressed = self.pressed
|
||||||
|
ss_sp.buttonsReleaseToggle = self.release_toggle
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
"""
|
||||||
|
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.structs import car
|
||||||
|
from openpilot.sunnypilot.selfdrive.selfdrived.button_state_tracker import ButtonStateTracker
|
||||||
|
|
||||||
|
ButtonEvent = car.CarState.ButtonEvent
|
||||||
|
ButtonType = car.CarState.ButtonEvent.Type
|
||||||
|
|
||||||
|
|
||||||
|
class TestButtonStateTracker:
|
||||||
|
def setup_method(self) -> None:
|
||||||
|
self.tracker = ButtonStateTracker()
|
||||||
|
|
||||||
|
def make_cs(self, events: list) -> car.CarState:
|
||||||
|
CS = car.CarState()
|
||||||
|
CS.buttonEvents = events
|
||||||
|
return CS
|
||||||
|
|
||||||
|
def test_initial_state(self) -> None:
|
||||||
|
assert self.tracker.pressed == 0
|
||||||
|
assert self.tracker.release_toggle == 0
|
||||||
|
|
||||||
|
def test_press_sets_bit(self) -> None:
|
||||||
|
self.tracker.update(self.make_cs([ButtonEvent(type=ButtonType.accelCruise, pressed=True)]))
|
||||||
|
assert self.tracker.pressed == (1 << ButtonType.accelCruise)
|
||||||
|
assert self.tracker.release_toggle == 0
|
||||||
|
|
||||||
|
def test_release_clears_and_toggles(self) -> None:
|
||||||
|
self.tracker.update(self.make_cs([ButtonEvent(type=ButtonType.accelCruise, pressed=True)]))
|
||||||
|
self.tracker.update(self.make_cs([ButtonEvent(type=ButtonType.accelCruise, pressed=False)]))
|
||||||
|
assert self.tracker.pressed == 0
|
||||||
|
assert self.tracker.release_toggle == (1 << ButtonType.accelCruise)
|
||||||
|
|
||||||
|
def test_multiple_buttons(self) -> None:
|
||||||
|
self.tracker.update(self.make_cs([
|
||||||
|
ButtonEvent(type=ButtonType.accelCruise, pressed=True),
|
||||||
|
ButtonEvent(type=ButtonType.decelCruise, pressed=True),
|
||||||
|
]))
|
||||||
|
assert self.tracker.pressed == (1 << ButtonType.accelCruise) | (1 << ButtonType.decelCruise)
|
||||||
|
|
||||||
|
self.tracker.update(self.make_cs([ButtonEvent(type=ButtonType.accelCruise, pressed=False)]))
|
||||||
|
assert self.tracker.pressed == (1 << ButtonType.decelCruise)
|
||||||
|
assert self.tracker.release_toggle == (1 << ButtonType.accelCruise)
|
||||||
|
|
||||||
|
def test_release_toggle_flips(self) -> None:
|
||||||
|
for _ in range(2):
|
||||||
|
self.tracker.update(self.make_cs([ButtonEvent(type=ButtonType.gapAdjustCruise, pressed=True)]))
|
||||||
|
self.tracker.update(self.make_cs([ButtonEvent(type=ButtonType.gapAdjustCruise, pressed=False)]))
|
||||||
|
assert self.tracker.release_toggle == 0
|
||||||
|
|
||||||
|
def test_publish(self) -> None:
|
||||||
|
self.tracker.update(self.make_cs([ButtonEvent(type=ButtonType.accelCruise, pressed=True)]))
|
||||||
|
self.tracker.update(self.make_cs([ButtonEvent(type=ButtonType.decelCruise, pressed=True)]))
|
||||||
|
self.tracker.update(self.make_cs([ButtonEvent(type=ButtonType.accelCruise, pressed=False)]))
|
||||||
|
|
||||||
|
class MockSP:
|
||||||
|
buttonsPressed = 0
|
||||||
|
buttonsReleaseToggle = 0
|
||||||
|
|
||||||
|
sp = MockSP()
|
||||||
|
self.tracker.publish(sp)
|
||||||
|
assert sp.buttonsPressed == self.tracker.pressed
|
||||||
|
assert sp.buttonsReleaseToggle == self.tracker.release_toggle
|
||||||
@@ -2161,6 +2161,42 @@
|
|||||||
"type": "offroad_only"
|
"type": "offroad_only"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "TeslaMadsScreenButton",
|
||||||
|
"widget": "multiple_button",
|
||||||
|
"title": "MADS Screen Activation",
|
||||||
|
"description": "Use a multi-finger press on the infotainment screen to toggle MADS. This allows the use of full MADS functionality when enabled. Selecting a higher finger count may reduce accidental activations. Note: Setting this to Off will reset your MADS settings to default.",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"value": 0,
|
||||||
|
"label": "Off"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": 1,
|
||||||
|
"label": "3-Finger"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": 2,
|
||||||
|
"label": "4-Finger"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": 3,
|
||||||
|
"label": "5-Finger"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"visibility": [
|
||||||
|
{
|
||||||
|
"type": "capability",
|
||||||
|
"field": "tesla_has_vehicle_bus",
|
||||||
|
"equals": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"enablement": [
|
||||||
|
{
|
||||||
|
"type": "offroad_only"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -56,6 +56,28 @@ sections:
|
|||||||
title: Cooperative Steering (Beta)
|
title: Cooperative Steering (Beta)
|
||||||
enablement:
|
enablement:
|
||||||
- $ref: '#/macros/offroad'
|
- $ref: '#/macros/offroad'
|
||||||
|
- key: TeslaMadsScreenButton
|
||||||
|
widget: multiple_button
|
||||||
|
title: MADS Screen Activation
|
||||||
|
description: 'Use a multi-finger press on the infotainment screen to toggle MADS.
|
||||||
|
This allows the use of full MADS functionality when enabled. Selecting a higher
|
||||||
|
finger count may reduce accidental activations. Note: Setting this to Off will
|
||||||
|
reset your MADS settings to default.'
|
||||||
|
options:
|
||||||
|
- value: 0
|
||||||
|
label: 'Off'
|
||||||
|
- value: 1
|
||||||
|
label: 3-Finger
|
||||||
|
- value: 2
|
||||||
|
label: 4-Finger
|
||||||
|
- value: 3
|
||||||
|
label: 5-Finger
|
||||||
|
visibility:
|
||||||
|
- type: capability
|
||||||
|
field: tesla_has_vehicle_bus
|
||||||
|
equals: true
|
||||||
|
enablement:
|
||||||
|
- $ref: '#/macros/offroad'
|
||||||
- id: toyota
|
- id: toyota
|
||||||
title: Toyota / Lexus Settings
|
title: Toyota / Lexus Settings
|
||||||
description: ''
|
description: ''
|
||||||
|
|||||||
@@ -17,6 +17,26 @@ ONROAD_BRIGHTNESS_TIMER_VALUES = {0: 3, 1: 5, 2: 7, 3: 10, 4: 15, 5: 30, **{i: (
|
|||||||
VALID_TIMER_VALUES = set(ONROAD_BRIGHTNESS_TIMER_VALUES.values())
|
VALID_TIMER_VALUES = set(ONROAD_BRIGHTNESS_TIMER_VALUES.values())
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_brand(_params) -> str:
|
||||||
|
bundle = _params.get("CarPlatformBundle")
|
||||||
|
if isinstance(bundle, dict) and bundle.get("brand"):
|
||||||
|
return str(bundle["brand"])
|
||||||
|
|
||||||
|
# Auto-fingerprinted cars have no bundle, fall back to the last known CarParams.
|
||||||
|
CP_bytes = _params.get("CarParamsPersistent")
|
||||||
|
if CP_bytes is None:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# Never raises: callers rely on "" to mean "brand unknown, skip the migration".
|
||||||
|
try:
|
||||||
|
from openpilot.cereal import messaging # lazy: avoids heavy import at module level
|
||||||
|
from opendbc.car.structs import car
|
||||||
|
return str(messaging.log_from_bytes(CP_bytes, car.CarParams).brand)
|
||||||
|
except Exception as e:
|
||||||
|
cloudlog.exception(f"params_migration: failed to resolve brand from CarParamsPersistent: {e}")
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def _migrate_car_platform_bundle(_params):
|
def _migrate_car_platform_bundle(_params):
|
||||||
bundle = _params.get("CarPlatformBundle")
|
bundle = _params.get("CarPlatformBundle")
|
||||||
if bundle is None:
|
if bundle is None:
|
||||||
@@ -47,6 +67,23 @@ def _migrate_car_platform_bundle(_params):
|
|||||||
cloudlog.info(f"params_migration: CarPlatformBundle migrated {old_platform!r} -> {new_platform!r}")
|
cloudlog.info(f"params_migration: CarPlatformBundle migrated {old_platform!r} -> {new_platform!r}")
|
||||||
|
|
||||||
|
|
||||||
|
def _migrate_tesla_mads_screen_button(_params):
|
||||||
|
# TeslaMadsScreenButton defaults to Off for fresh installs, but the screen button was previously always
|
||||||
|
# active on Teslas with a vehicle bus. Seed existing Tesla installs with 3-finger to preserve that.
|
||||||
|
try:
|
||||||
|
if _params.get("TeslaMadsScreenButton") is not None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if _resolve_brand(_params) != "tesla":
|
||||||
|
return
|
||||||
|
|
||||||
|
from opendbc.sunnypilot.car.tesla.values import MadsScreenButtonType # lazy: avoids heavy import at module level
|
||||||
|
_params.put("TeslaMadsScreenButton", MadsScreenButtonType.THREE_FINGER, block=True)
|
||||||
|
cloudlog.info("params_migration: seeded TeslaMadsScreenButton with 3-finger to preserve existing behavior")
|
||||||
|
except Exception as e:
|
||||||
|
cloudlog.exception(f"Error migrating TeslaMadsScreenButton: {e}")
|
||||||
|
|
||||||
|
|
||||||
def run_migration(_params):
|
def run_migration(_params):
|
||||||
# migrate OnroadScreenOffBrightness
|
# migrate OnroadScreenOffBrightness
|
||||||
if _params.get("OnroadScreenOffBrightnessMigrated") != ONROAD_BRIGHTNESS_MIGRATION_VERSION:
|
if _params.get("OnroadScreenOffBrightnessMigrated") != ONROAD_BRIGHTNESS_MIGRATION_VERSION:
|
||||||
@@ -80,3 +117,6 @@ def run_migration(_params):
|
|||||||
cloudlog.exception(f"Error migrating OnroadScreenOffTimer: {e}")
|
cloudlog.exception(f"Error migrating OnroadScreenOffTimer: {e}")
|
||||||
|
|
||||||
_migrate_car_platform_bundle(_params)
|
_migrate_car_platform_bundle(_params)
|
||||||
|
|
||||||
|
# seed TeslaMadsScreenButton for existing Tesla installs
|
||||||
|
_migrate_tesla_mads_screen_button(_params)
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
|
|
||||||
SCRIPT_DIR=$(dirname "$0")
|
|
||||||
OPENPILOT_DIR=$SCRIPT_DIR/../../
|
|
||||||
|
|
||||||
DOCKER_IMAGE=sunnypilot
|
|
||||||
DOCKER_FILE=Dockerfile.openpilot
|
|
||||||
DOCKER_REGISTRY=ghcr.io/sunnypilot
|
|
||||||
COMMIT_SHA=$(git rev-parse HEAD)
|
|
||||||
|
|
||||||
if [ -n "$TARGET_ARCHITECTURE" ]; then
|
|
||||||
PLATFORM="linux/$TARGET_ARCHITECTURE"
|
|
||||||
TAG_SUFFIX="-$TARGET_ARCHITECTURE"
|
|
||||||
else
|
|
||||||
PLATFORM="linux/$(uname -m)"
|
|
||||||
TAG_SUFFIX=""
|
|
||||||
fi
|
|
||||||
|
|
||||||
LOCAL_TAG=$DOCKER_IMAGE$TAG_SUFFIX
|
|
||||||
REMOTE_TAG=$DOCKER_REGISTRY/$LOCAL_TAG
|
|
||||||
REMOTE_SHA_TAG=$DOCKER_REGISTRY/$LOCAL_TAG:$COMMIT_SHA
|
|
||||||
|
|
||||||
DOCKER_BUILDKIT=1 docker buildx build --provenance false --pull --platform $PLATFORM --load -t $DOCKER_IMAGE:latest -t $REMOTE_TAG -t $LOCAL_TAG -f $OPENPILOT_DIR/$DOCKER_FILE $OPENPILOT_DIR
|
|
||||||
|
|
||||||
if [ -n "$PUSH_IMAGE" ]; then
|
|
||||||
docker push $REMOTE_TAG
|
|
||||||
docker tag $REMOTE_TAG $REMOTE_SHA_TAG
|
|
||||||
docker push $REMOTE_SHA_TAG
|
|
||||||
fi
|
|
||||||
Reference in New Issue
Block a user