mirror of
https://github.com/infiniteCable2/openpilot.git
synced 2026-08-03 08:41:41 +08:00
Merge branch 'master' of https://github.com/sunnypilot/sunnypilot
This commit is contained in:
@@ -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"
|
||||
+1
-1
Submodule opendbc_repo updated: 077429f40e...1c35c81316
@@ -69,6 +69,8 @@ struct LeadData {
|
||||
struct SelfdriveStateSP @0x81c2f05a394cf4af {
|
||||
mads @0 :ModularAssistiveDrivingSystem;
|
||||
intelligentCruiseButtonManagement @1 :IntelligentCruiseButtonManagement;
|
||||
buttonsPressed @2 :UInt16;
|
||||
buttonsReleaseToggle @3 :UInt16;
|
||||
|
||||
enum AudibleAlert {
|
||||
none @0;
|
||||
|
||||
@@ -244,6 +244,7 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"SubaruStopAndGo", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"SubaruStopAndGoManualParkingBrake", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"TeslaCoopSteering", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"TeslaMadsScreenButton", {PERSISTENT | BACKUP, INT, "0"}},
|
||||
{"ToyotaEnforceStockLongitudinal", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
{"ToyotaStopAndGoHack", {PERSISTENT | BACKUP, BOOL, "0"}},
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ class LongitudinalPlanner(LongitudinalPlannerSP):
|
||||
def publish(self, sm, pm):
|
||||
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.modelMonoTime = sm.logMonoTime['modelV2']
|
||||
|
||||
@@ -29,19 +29,19 @@ def main():
|
||||
longitudinal_planner = LongitudinalPlanner(CP, CP_SP)
|
||||
pm = messaging.PubMaster(['longitudinalPlan', 'driverAssistance', 'longitudinalPlanSP', 'longitudinalPlanIC'])
|
||||
sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'liveParameters', 'radarState', 'modelV2', 'selfdriveState',
|
||||
'liveMapDataSP', 'carStateSP', gps_location_service],
|
||||
poll='carState', ignore_alive=ignore_services, ignore_avg_freq=ignore_services, ignore_valid=ignore_services)
|
||||
'liveMapDataSP', 'carStateSP', 'selfdriveStateSP', gps_location_service],
|
||||
poll='modelV2', ignore_alive=ignore_services, ignore_avg_freq=ignore_services, ignore_valid=ignore_services)
|
||||
|
||||
while True:
|
||||
sm.update()
|
||||
longitudinal_planner.sla.update_car_state(sm['carState'])
|
||||
longitudinal_planner.sla.update_buttons(sm['selfdriveStateSP'].buttonsReleaseToggle)
|
||||
if sm.updated['modelV2']:
|
||||
longitudinal_planner.update(sm)
|
||||
longitudinal_planner.publish(sm, pm)
|
||||
|
||||
ldw.update(sm.frame, sm['modelV2'], sm['carState'], sm['carControl'])
|
||||
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.rightLaneDeparture = ldw.right
|
||||
pm.send('driverAssistance', msg)
|
||||
|
||||
@@ -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.cruise_helpers import CruiseHelper
|
||||
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
|
||||
|
||||
REPLAY = "REPLAY" in os.environ
|
||||
@@ -191,6 +192,7 @@ class SelfdriveD(CruiseHelper):
|
||||
self.car_events_sp = CarSpecificEventsSP(self.CP, self.CP_SP)
|
||||
|
||||
CruiseHelper.__init__(self, self.CP)
|
||||
self.button_state_tracker = ButtonStateTracker()
|
||||
|
||||
def update_events(self, CS):
|
||||
"""Compute onroadEvents from carState"""
|
||||
@@ -615,6 +617,8 @@ class SelfdriveD(CruiseHelper):
|
||||
icbm.sendButton = self.icbm.cruise_button
|
||||
icbm.vTarget = self.icbm.v_target
|
||||
|
||||
self.button_state_tracker.publish(ss_sp)
|
||||
|
||||
self.pm.send('selfdriveStateSP', ss_sp_msg)
|
||||
|
||||
# onroadEventsSP - logged every second or on change
|
||||
@@ -634,6 +638,7 @@ class SelfdriveD(CruiseHelper):
|
||||
self.mads.update(CS)
|
||||
self.update_alerts(CS)
|
||||
|
||||
self.button_state_tracker.update(CS)
|
||||
self.publish_selfdriveState(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
|
||||
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.sunnypilot.mads.helpers import MadsSteeringModeOnBrake
|
||||
from openpilot.system.ui.lib.multilang import tr, tr_noop
|
||||
@@ -96,7 +96,10 @@ class MadsSettingsLayout(Widget):
|
||||
if brand == "rivian":
|
||||
return True
|
||||
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
|
||||
|
||||
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.
|
||||
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.ui_state import ui_state
|
||||
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
|
||||
OEM_STEERING_MIN_KMH = 48
|
||||
@@ -18,7 +19,14 @@ class TeslaSettings(BrandSettings):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
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):
|
||||
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.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.safety import ALTERNATIVE_EXPERIENCE
|
||||
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")
|
||||
@@ -21,17 +21,20 @@ class MadsSteeringModeOnBrake:
|
||||
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':
|
||||
return True
|
||||
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
|
||||
|
||||
|
||||
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 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
|
||||
# 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
|
||||
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:
|
||||
params.put("MadsSteeringMode", 2, 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.mads.helpers import MadsSteeringModeOnBrake, read_steering_mode_param
|
||||
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
|
||||
EventName = log.OnroadEvent.EventName
|
||||
@@ -38,6 +38,12 @@ def make_panda_state(mocker, controls_allowed_lateral=True):
|
||||
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):
|
||||
sd = mocker.MagicMock()
|
||||
sd.CP = structs.CarParams()
|
||||
@@ -223,15 +229,27 @@ class TestBrandSteeringModeRestrictions:
|
||||
params = mocker.MagicMock()
|
||||
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.brand = "tesla"
|
||||
CP_SP = structs.CarParamsSP()
|
||||
CP_SP.flags = TeslaFlagsSP.HAS_VEHICLE_BUS
|
||||
params = mocker.MagicMock()
|
||||
params.get = mocker.MagicMock(return_value=MadsSteeringModeOnBrake.REMAIN_ACTIVE)
|
||||
params = make_params_mock(mocker, {"TeslaMadsScreenButton": screen_button,
|
||||
"MadsSteeringMode": 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"])
|
||||
def test_other_brands_use_param(self, mocker, brand):
|
||||
CP = structs.CarParams()
|
||||
|
||||
@@ -123,6 +123,7 @@ def initialize_params(params) -> list[dict[str, Any]]:
|
||||
# tesla
|
||||
keys.extend([
|
||||
"TeslaCoopSteering",
|
||||
"TeslaMadsScreenButton",
|
||||
])
|
||||
|
||||
# toyota
|
||||
|
||||
@@ -91,7 +91,7 @@ class SpeedLimitAssist:
|
||||
|
||||
self._plus_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
|
||||
# Solution functions mapped to respective states
|
||||
@@ -146,16 +146,16 @@ class SpeedLimitAssist:
|
||||
set_speed_limit_assist_availability(self.CP, self.CP_SP, self.params)
|
||||
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()
|
||||
self._last_carstate_ts = now
|
||||
|
||||
for b in CS.buttonEvents:
|
||||
if not b.pressed:
|
||||
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)
|
||||
if any((released >> b) & 1 for b in CRUISE_BUTTONS_PLUS):
|
||||
self._plus_hold = max(self._plus_hold, now + CRUISE_BUTTON_CONFIRM_HOLD)
|
||||
if any((released >> b) & 1 for b 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:
|
||||
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.
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
import pytest
|
||||
|
||||
from openpilot.cereal import custom
|
||||
from opendbc.car.car_helpers import interfaces
|
||||
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.toyota.values import CAR as TOYOTA
|
||||
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.common import Mode
|
||||
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
|
||||
|
||||
ButtonEvent = car.CarState.ButtonEvent
|
||||
ButtonType = car.CarState.ButtonEvent.Type
|
||||
|
||||
SpeedLimitAssistState = custom.LongitudinalPlanSP.SpeedLimit.AssistState
|
||||
|
||||
ALL_STATES = tuple(SpeedLimitAssistState.schema.enumerants.values())
|
||||
@@ -277,3 +284,86 @@ class TestSpeedLimitAssist:
|
||||
assert self.sla.state in [SpeedLimitAssistState.preActive, SpeedLimitAssistState.active]
|
||||
elif initial_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"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"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)
|
||||
enablement:
|
||||
- $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
|
||||
title: Toyota / Lexus Settings
|
||||
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())
|
||||
|
||||
|
||||
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):
|
||||
bundle = _params.get("CarPlatformBundle")
|
||||
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}")
|
||||
|
||||
|
||||
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):
|
||||
# migrate OnroadScreenOffBrightness
|
||||
if _params.get("OnroadScreenOffBrightnessMigrated") != ONROAD_BRIGHTNESS_MIGRATION_VERSION:
|
||||
@@ -80,3 +117,6 @@ def run_migration(_params):
|
||||
cloudlog.exception(f"Error migrating OnroadScreenOffTimer: {e}")
|
||||
|
||||
_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