diff --git a/common/params_keys.h b/common/params_keys.h index 80602de257..26e264decf 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -165,15 +165,22 @@ inline static std::unordered_map keys = { {"ShowPlotMode", {PERSISTENT, INT, "0"}}, {"CarrotTireTrajectory", {PERSISTENT, INT, "0"}}, {"CarrotLearningActive", {PERSISTENT, INT, "0"}}, // Auto-Tuner: 학습 활성화 (0=off, 1=on) + {"CarrotTunerApplyLat", {PERSISTENT, INT, "0"}}, // Auto-Tuner: 조향(LAT) 적용 여부 (0=off, 1=on) + {"CarrotTunerApplyLong", {PERSISTENT, INT, "0"}}, // Auto-Tuner: 가감속(LONG) 적용 여부 (0=off, 1=on) {"CarrotLearningData", {PERSISTENT, BYTES, ""}}, // Auto-Tuner: 누적 데이터 (JSON) {"CarrotLearningRecommend", {PERSISTENT, BYTES, ""}}, // Auto-Tuner: 추천값 (JSON) {"CarrotLearningPopupReady", {PERSISTENT, BOOL, "0"}}, // Auto-Tuner: 팝업 신호 {"CarrotLearningClear", {PERSISTENT, BOOL, "0"}}, // Auto-Tuner: 데이터 초기화 신호 {"CarrotLearningHistory", {PERSISTENT, BYTES, ""}}, // Auto-Tuner: 튜닝 이력 (JSON) + {"CarrotLearningPopupSource", {PERSISTENT, STRING, ""}}, // Auto-Tuner: 팝업 발생 소스 ("stop", "timer", "parking", etc.) + {"CarrotLearningAutoApply", {PERSISTENT, BOOL, "0"}}, // Auto-Tuner: 추천 파라미터 자동 적용 여부 + {"CarrotTunerFactoryReset", {PERSISTENT, BOOL, "0"}}, // Auto-Tuner: 튜닝 파라미터 공장초기화 신호 {"CarrotDSPData", {PERSISTENT, BYTES, ""}}, // DSP: 수동 주행 프로파일 데이터 (JSON) {"CarrotDSPRecommend", {PERSISTENT, BYTES, ""}}, // DSP: 초기값 추천 (JSON) {"CarrotDSPPopupReady", {PERSISTENT, BOOL, "0"}}, // DSP: 팝업 신호 {"CarrotDSPComplete", {PERSISTENT, BOOL, "0"}}, // DSP: 프로파일링 완료 여부 + {"TimezoneName", {PERSISTENT, STRING, ""}}, // 로컬 타임존 IANA 이름 (예: "Asia/Seoul"), 앱/WiFi/GPS로 해석되면 기록 + {"TimezoneSource", {PERSISTENT, STRING, ""}}, // 타임존 출처 우선순위 추적 ("app" > "wifi" > "gps") {"ShowCustomBrightness", {PERSISTENT, INT, "100"}}, {"ShowModelView", {PERSISTENT, INT, "0"}}, {"ClusterHud", {PERSISTENT, INT, "0"}}, @@ -207,6 +214,8 @@ inline static std::unordered_map keys = { {"AutoCurveSpeedLowerLimit", {PERSISTENT, INT, "30"}}, {"AutoCurveSpeedFactor", {PERSISTENT, INT, "120"}}, {"AutoCurveSpeedAggressiveness", {PERSISTENT, INT, "100"}}, + {"WiperActive", {PERSISTENT, BOOL, "0"}}, + {"CarrotRainWet", {PERSISTENT, BOOL, "0"}}, {"AutoTurnControl", {PERSISTENT, INT, "0"}}, {"AutoTurnControlSpeedTurn", {PERSISTENT, INT, "20"}}, @@ -270,6 +279,7 @@ inline static std::unordered_map keys = { {"LongTuningKf", {PERSISTENT, INT, "100"}}, {"LongActuatorDelay", {PERSISTENT, INT, "20"}}, {"VEgoStopping", {PERSISTENT, INT, "50"}}, + {"LongCoastBand", {PERSISTENT, INT, "0"}}, {"RadarReactionFactor", {PERSISTENT, INT, "100"}}, {"EnableRadarTracks", {PERSISTENT, INT, "0"}}, @@ -302,7 +312,7 @@ inline static std::unordered_map keys = { {"TFollowDecelBoost", {PERSISTENT, INT, "10"}}, {"EnableSpeedTF", {PERSISTENT, INT, "0"}}, {"AChangeCostStarting", {PERSISTENT, INT, "10"}}, - {"TrafficStopDistanceAdjust", {PERSISTENT, INT, "-150"}}, + {"TrafficStopDistanceAdjust", {PERSISTENT, INT, "-100"}}, {"HapticFeedbackWhenSpeedCamera", {PERSISTENT, INT, "0"}}, {"UseLaneLineSpeed", {PERSISTENT, INT, "0"}}, diff --git a/opendbc_repo/opendbc/car/hyundai/carcontroller.py b/opendbc_repo/opendbc/car/hyundai/carcontroller.py index 79301a653d..9209045b63 100644 --- a/opendbc_repo/opendbc/car/hyundai/carcontroller.py +++ b/opendbc_repo/opendbc/car/hyundai/carcontroller.py @@ -715,7 +715,10 @@ def check_carrot_cruise(self, CC, CS, hud_control, stopping, accel, a_target): def make_jerk(self, CP, CS, accel, actuators, hud_control): if actuators.longControlState == LongCtrlState.stopping: - self.jerk = self.jerk_u_min / 2 - CS.out.aEgo + jerk_base = self.jerk_u_min / 2 - CS.out.aEgo + # 저속 정차 시 jerk 제한: 극저속(< 0.05 m/s)에서 더 세밀한 보간으로 마지막 순간 충격 최소화 + jerk_stop_max = np.interp(CS.out.vEgo, [0.0, 0.05, 0.1, 0.3, 1.0, 3.0], [0.1, 0.15, 0.25, 0.5, 0.8, 5.0]) + self.jerk = min(jerk_base, jerk_stop_max) else: jerk = actuators.jerk if actuators.longControlState == LongCtrlState.pid else 0.0 #a_error = actuators.aTarget - CS.out.aEgo diff --git a/selfdrive/carrot/README.md b/selfdrive/carrot/README.md index 6d11ed006c..d499db44fb 100644 --- a/selfdrive/carrot/README.md +++ b/selfdrive/carrot/README.md @@ -9,6 +9,7 @@ selfdrive/carrot/ carrot_server.py main web server entry carrot_controls.py carrot control helpers carrot_functions.py carrot shared functions + carrot_learning.py carrot learning logic carrot_man.py carrot manager carrot_serv.py carrot service/runtime cweb_push.py CWP push client (device IP / git update notify) diff --git a/selfdrive/carrot/carrot_functions.py b/selfdrive/carrot/carrot_functions.py index 6de61ae8e3..db57adef40 100644 --- a/selfdrive/carrot/carrot_functions.py +++ b/selfdrive/carrot/carrot_functions.py @@ -8,10 +8,39 @@ from openpilot.common.conversions import Conversions as CV from openpilot.common.filter_simple import MyMovingAverage from openpilot.selfdrive.selfdrived.events import Events +from openpilot.selfdrive.carrot.carrot_learning import CarrotLearner, DrivingStyleProfiler +from openpilot.common.swaglog import cloudlog EventName = log.OnroadEvent.EventName LaneChangeState = log.LaneChangeState +# 속도-가변 차간거리: 고정 stop_distance가 저속 time-gap을 부풀려 'time-gap 역전'이 생긴다 +# (로그 f7: 5-15kph 3.4s vs 45kph+ 1.6s → 저속 과도하게 넓고 중고속은 좁음). 속도가 낮을수록 +# t_follow를 약간 줄여(≤30 좁게) 저속 간격을 당기고, 높을수록 늘려(≥30 넓게) time-gap을 +# 정상화한다(저속 좁게/고속 넓게 — 사용자 요청). +_SPDTF_BP = [20.0, 32.0, 50.0, 80.0] # 속도 보간점(km/h) +_SPDTF_DELTA = [-0.20, 0.0, 0.18, 0.28] # 위 속도에서 t_follow 가감(초) +_SPDTF_MIN = 0.55 # 보정 후 t_follow 안전 하한(초) +# t_follow 감소(앞차 가속 등으로 간격 좁힐 때) 변화율 제한 — 즉시 스냅하면 '순간 가속'으로 +# 느껴지므로 부드럽게. 증가(0.1)보다 빠르게 둬 추종 반응성은 유지. (스텝/호출당, *DT_MDL) +_TF_DECREASE_RATE = 1.5 + +# 정차 후 출발 catch-up: 완전정지→출발 구간에서 일시적으로 Gap1(최근접)으로 추종해 +# 선행차를 민첩하게 따라잡고, 일정 속도 이상이면 원래 Gap으로 복귀한다. dynamic_t_follow의 +# catch-up이 저속(<30)에서 꺼져 있어(사각지대) 이 구간을 보완. 거리(t_follow)에만 적용하고 +# jerk/가속 특성은 원래 Gap을 유지하며, 복귀 시 apply_t_follow 증가율제한이 부드럽게 처리. +_LAUNCH_GAP_ARM_KPH = 3.0 # 이 속도 미만(정차/near-stop)에서 Gap1 무장 +_LAUNCH_GAP_REVERT_KPH = 40.0 # 이 속도 이상이면 원래 Gap으로 복귀 + +# 정차 후 출발 가속 부스트: 위 launch 구간에서 가속 상한을 HIGH 모드 수준으로 일시 확대해 +# 캐치업 가속력을 확보한다(NORMAL 모드 CruiseMaxVals 상한에 막히는 문제 보완). 급발진 방지를 +# 위해 배수를 S-커브(smoothstep)로 이징: 출발 초반(급발진 방지)·40km/h 복귀(툭 끊김 방지) +# 양 끝을 완만하게 하고 중속 구간에서 최대. 상한(ceiling)만 키우므로 catch-up이 필요할 때만 +# MPC가 실제로 사용한다(정속·근접 추종에선 미사용). +_LAUNCH_ACCEL_GAIN = 1.4 # 부스트 최대 배수(≈HIGH 모드 factor 상당) +_LAUNCH_EASE_IN_KPH = 10.0 # 0→이 속도까지 S-커브로 부스트 상승(급발진 방지) +_LAUNCH_EASE_OUT_KPH = 30.0 # 이 속도→REVERT까지 S-커브로 부스트 하강(복귀 부드럽게) + class XState(Enum): lead = 0 cruise = 1 @@ -105,6 +134,7 @@ def __init__(self): self.enableSpeedTF = 0 self.tFollowDecelBoost = 0.0 self.personality = 1 + self.launch_close_gap = False # 정차 후 출발 catch-up: Gap1 일시 적용 상태 self.cruiseMaxVals0 = 1.6 self.cruiseMaxVals1 = 1.6 @@ -136,9 +166,14 @@ def __init__(self): self.xDistToTurn = 0 self.atcType = "" self.atc_active = False + self.tFollowSpeedFactor = 0.0 # 고속 주행 시 추가 차간 거리 가중치 self._stop_x_rl = None self.last_event_time = 0.0 + self.learner = CarrotLearner() + self.profiler = DrivingStyleProfiler() + self.filtered_j_lead = 0.0 + self._v_ego_kph = 0.0 def _params_update(self): self.frame += 1 @@ -163,6 +198,7 @@ def _params_update(self): self.tFollowGap2 = self.params.get_float("TFollowGap2") / 100. self.tFollowGap3 = self.params.get_float("TFollowGap3") / 100. self.tFollowGap4 = self.params.get_float("TFollowGap4") / 100. + self.tFollowSpeedFactor = self.params.get_float("TFollowSpeedFactor") / 100. self.dynamicTFollow = self.params.get_float("DynamicTFollow") / 100. self.dynamicTFollowLC = self.params.get_float("DynamicTFollowLC") / 100. self.enableSpeedTF = self.params.get_int("EnableSpeedTF") @@ -186,10 +222,27 @@ def _params_update(self): self.params_count = 0 + def _launch_accel_factor(self, v_ego): + # 정차 후 출발 구간에서만 가속 상한 배수를 S-커브(smoothstep)로 이징해 반환. + # 양 끝(출발 0, 복귀 REVERT)에서 1.0으로 수렴 → 급발진·복귀 툭 끊김 없이 매끄럽게. + if not self.launch_close_gap: + return 1.0 + v_kph = v_ego * CV.MS_TO_KPH + if v_kph <= _LAUNCH_EASE_IN_KPH: + s = v_kph / max(1.0, _LAUNCH_EASE_IN_KPH) # 0→1 (이징 인) + elif v_kph >= _LAUNCH_EASE_OUT_KPH: + span = max(1.0, _LAUNCH_GAP_REVERT_KPH - _LAUNCH_EASE_OUT_KPH) + s = float(np.clip((_LAUNCH_GAP_REVERT_KPH - v_kph) / span, 0.0, 1.0)) # 1→0 (이징 아웃) + else: + s = 1.0 # 중속: 최대 부스트 + w = s * s * (3.0 - 2.0 * s) # smoothstep(양 끝 기울기 0) + return 1.0 + (_LAUNCH_ACCEL_GAIN - 1.0) * w + def get_carrot_accel(self, v_ego): cruiseMaxVals = [self.cruiseMaxVals0, self.cruiseMaxVals1, self.cruiseMaxVals2, self.cruiseMaxVals3, self.cruiseMaxVals4, self.cruiseMaxVals5, self.cruiseMaxVals6] factor = self.myHighModeFactor if self.myDrivingMode == DrivingMode.High else self.mySafeFactor - return np.interp(v_ego, A_CRUISE_MAX_BP_CARROT, cruiseMaxVals) * factor + accel = float(np.interp(v_ego, A_CRUISE_MAX_BP_CARROT, cruiseMaxVals) * factor) + return accel * self._launch_accel_factor(v_ego) def _get_base_t_follow(self, personality, v_ego): if self.enableSpeedTF < 0: @@ -251,6 +304,12 @@ def _apply_speed_t_follow_scale(self, tf_base, v_ego): scale = (1.0 - reduce) + reduce * s tf_target *= scale + # 고속 주행 시 추가 차간 거리 가중치 (TFollowSpeedFactor) + # v_ego가 높을수록 차간 거리를 추가로 확보함 + if self.tFollowSpeedFactor > 0: + speed_boost = float(np.clip((v_ego * CV.MS_TO_KPH - 60.0) / 100.0, 0.0, 1.0)) + tf_target += speed_boost * self.tFollowSpeedFactor + return float(tf_target) @@ -280,12 +339,19 @@ def _clip_t_follow(self, t_follow): return float(np.clip(t_follow, max(0.3, tf_min), tf_max)) def get_T_FOLLOW(self, personality=log.LongitudinalPersonality.standard, v_ego=0.0, a_ego=0.0): + if self.launch_close_gap: + personality = log.LongitudinalPersonality.aggressive # 정차 후 출발: 최근접 Gap으로 catch-up tf_base = self._get_base_t_follow(personality, v_ego) tf_target = self._apply_speed_t_follow_scale(tf_base, v_ego) tf_adjusted = self._apply_decel_hold_and_boost_t_follow(tf_target, a_ego) tf_safe = float(tf_adjusted * self.mySafeFactor) tf_final = self._clip_t_follow(tf_safe) + # 속도-가변 간격 보정 (clip 이후 적용 — clip 하한에 막히지 않게). 자체 안전 하한 유지. + # 저속(≤30): t_follow 약간↓(간격 좁힘) / 고속(≥30): ↑(간격 넓힘) → time-gap 역전 정상화. + v_kph = v_ego * CV.MS_TO_KPH + tf_final = max(tf_final + float(np.interp(v_kph, _SPDTF_BP, _SPDTF_DELTA)), _SPDTF_MIN) self._tf_applied = float(tf_final) + self._v_ego_kph = float(v_kph) # dynamic_t_follow catch-up 속도 게이트용 return self.apply_t_follow(tf_final) @@ -310,17 +376,39 @@ def dynamic_t_follow(self, t_follow, lead, desired_follow_distance, prev_a): t_follow *= dynamicTFollowLC self.jerk_factor_apply = self.jerk_factor * dynamicTFollowLC - # 일반 lead follow: lead.jLead 기반 동적 조절 - elif lead.status and self.dynamicTFollow > 0.0: - # lead.jLead < 0 : 앞차가 감속 방향으로 변함 -> 차간거리 증가 - # lead.jLead > 0 : 앞차가 가속 방향으로 변함 -> 차간거리 감소 - t_follow += np.interp(lead.jLead, [-3.0, -0.5, 0.5, 2.0], [1.0, 0.0, 0.0, -1.0]) * self.dynamicTFollow - - # 앞차가 풀어주는 상황에서는 jerk factor 약간 낮춰서 더 민첩하게 - if lead.jLead > 0.2: - self.jerk_factor_apply = self.jerk_factor * 0.5 - - t_follow = np.clip(t_follow, 0.3, 2.0) + # 일반 lead follow: + elif lead.status: + if self.dynamicTFollow > 0.0: + # lead.jLead 필터링을 통해 고주파 노이즈 제거 + self.filtered_j_lead = 0.9 * self.filtered_j_lead + 0.1 * lead.jLead + # lead.jLead < 0 : 앞차가 감속 방향으로 변함 -> 차간거리 증가 + # lead.jLead > 0 : 앞차가 가속 방향으로 변함 -> 차간거리 감소 + t_follow += np.interp(self.filtered_j_lead, [-3.0, -0.5, 0.5, 2.0], [1.0, 0.0, 0.0, -1.0]) * self.dynamicTFollow + t_follow = np.clip(t_follow, 0.3, 2.0) + + # 선행차가 '정속으로 멀어지는'(vRel>0, jLead~0) 경우엔 위 jLead 로직이 반응하지 않아 + # 중고속에서 재가속(catch-up)이 약했다(로그 0101: 40-70 +0.5, 70+ +0.2). 간격목표를 + # 속도비례로 살짝 좁혀 catch-up을 민첩하게. 저속(≤30, 이미 충분)·밀착(≤6m)엔 미적용. + v_kph = getattr(self, "_v_ego_kph", 0.0) + if lead.vRel > 0.5 and lead.dRel > 6.0: + catchup = float(np.interp(v_kph, [30.0, 50.0, 90.0], [0.0, 0.15, 0.30])) + catchup *= float(np.interp(lead.vRel, [0.5, 3.0], [0.0, 1.0])) + t_follow = max(t_follow - catchup, 0.3) + + # Dynamic Jerk Control for early & gentle braking: + # If lead deceleration is detected and we are not braking hard, increase jerk penalty (make it smoother/gentler). + # Relax it back to normal jerk factor as distance error grows or ego deceleration increases. + if lead.aLeadK < -0.5 or lead.jLead < -0.2: + dist_err = desired_follow_distance - lead.dRel + prev_a_scalar = float(prev_a[0]) if hasattr(prev_a, "__len__") else float(prev_a) + scale_err = float(np.interp(dist_err, [0.0, 5.0], [1.8, 1.0])) + scale_decel = float(np.interp(prev_a_scalar, [-1.5, -0.5], [1.0, 1.8])) + jerk_scale = min(scale_err, scale_decel) + self.jerk_factor_apply = self.jerk_factor * jerk_scale + elif self.filtered_j_lead > 0.5 or lead.vRel > 1.0: + # 선행차가 가속/정속으로 멀어짐: jerk penalty를 약간 낮춰 재가속을 민첩하게 (부분 복원). + # jLead(가속 변화)뿐 아니라 vRel>1(정속 이탈)에도 적용 → 중고속 catch-up 보강. + self.jerk_factor_apply = self.jerk_factor * 0.7 return self.apply_t_follow(t_follow, 0.0) @@ -330,6 +418,9 @@ def apply_t_follow(self, t_follow, adjust_t_follow=0.0): # 증가 방향만 천천히 반영 if t_follow > self.t_follow_last: t_follow = min(t_follow, self.t_follow_last + 0.1 * DT_MDL) + elif t_follow < self.t_follow_last: + # 감소(앞차 가속 등으로 간격 좁힐 때)도 즉시 스냅하지 않고 부드럽게 → 순간 가속 완화. + t_follow = max(t_follow, self.t_follow_last - _TF_DECREASE_RATE * DT_MDL) self.t_follow_last = float(t_follow) return float(t_follow + adjust_t_follow) @@ -595,6 +686,10 @@ def update(self, sm, v_cruise_kph, mode): mode = 'blended' if self.xState in [XState.e2ePrepare] else 'acc' self.comfort_brake *= self.mySafeFactor + # Low-Speed Comfort Brake Scaling + v_ego_kph = v_ego * CV.MS_TO_KPH + low_speed_factor = float(np.interp(v_ego_kph, [2.0, 10.0], [0.7, 1.0])) + self.comfort_brake *= low_speed_factor self.actual_stop_distance = max(0, self.actual_stop_distance - (v_ego * DT_MDL)) if stop_model_x == 1000.0: ## e2eCruise, lead�ΰ�� @@ -628,6 +723,47 @@ def update(self, sm, v_cruise_kph, mode): self.mode = mode #return v_cruise, stop_dist, mode + # Auto-Tuner: 학습 데이터 수집 + from cereal import car + gear_park = carstate.gearShifter == car.CarState.GearShifter.park + engaged = sm.alive.get('selfdriveState', False) and sm['selfdriveState'].enabled + + # 정차 후 출발 catch-up: 완전정지(near-stop)에서 Gap1 무장 → 출발하며 선행차 민첩 추종, + # 일정 속도 이상/선행차 없음/미인게이지 시 원래 Gap 복귀. (get_T_FOLLOW에서 소비) + if not engaged or not lead_detected or v_ego_kph >= _LAUNCH_GAP_REVERT_KPH: + self.launch_close_gap = False + elif v_ego_kph < _LAUNCH_GAP_ARM_KPH: + self.launch_close_gap = True + + # 현재 GAP 단계 파악 (Personality 기반) + personality = sm['selfdriveState'].personality + current_gap = 2 # default standard + if personality == log.LongitudinalPersonality.moreRelaxed: current_gap = 4 + elif personality == log.LongitudinalPersonality.relaxed: current_gap = 3 + elif personality == log.LongitudinalPersonality.standard: current_gap = 2 + elif personality == log.LongitudinalPersonality.aggressive: current_gap = 1 + self.learner.set_current_gap(current_gap) + + # Auto-Tuner는 비핵심 학습 기능이므로, 여기서 예외가 나도 안전필수 + # 종방향 플래너(plannerd)가 죽지 않도록 반드시 격리한다. + try: + self.learner.update(v_ego_kph, carstate.gasPressed, engaged, gear_park, + steer_deg=carstate.steeringAngleDeg, steer_pressed=carstate.steeringPressed, + brake_pressed=carstate.brakePressed, + lead_drel=leadOne.dRel if leadOne.status else 0.0, + lead_v_kph=leadOne.vLead * CV.MS_TO_KPH if leadOne.status else 0.0, + a_ego=a_ego, lead_jlead=leadOne.jLead if leadOne.status else 0.0, + v_cruise_kph=v_cruise_kph, + gas_val=carstate.gas, brake_val=carstate.brake, sm=sm) + + # DSP: 수동 주행 성향 프로파일링 (오픈파일럿 미인게이지 상태에서만 수집) + self.profiler.update(v_ego_kph, engaged, gear_park, + a_ego=a_ego, brake_pressed=carstate.brakePressed, + lead_drel=leadOne.dRel if leadOne.status else 0.0, + lead_v_kph=leadOne.vLead * CV.MS_TO_KPH if leadOne.status else 0.0) + except Exception: + cloudlog.exception("CarrotLearner/Profiler update failed") + return v_cruise_kph class DrivingModeDetector: diff --git a/selfdrive/carrot/carrot_learning.py b/selfdrive/carrot/carrot_learning.py new file mode 100644 index 0000000000..5ed86449ce --- /dev/null +++ b/selfdrive/carrot/carrot_learning.py @@ -0,0 +1,2524 @@ +""" +CarrotLearning - Phase 1~4: Longitudinal + Lateral Learning +karpathy.md 원칙 준수: 최소 구현, 단일 목적. + +[Phase 1] CruiseMaxVals0~6 (속도구간별 가속 강도) + 트리거: 크루즈 인게이지 중 gasPressed + 추천 발동: 구간당 누적 ≥ 10초 + +[Phase 2] SteerActuatorDelay / PathOffset / SteerRatioRate (조향 딜레이 / 중심선 편차 / SR 비율) + 트리거: + - 직진 시 steeringAngleDeg 편차 → PathOffset 추천 + - 커브 진입 중 steeringPressed 비율 → SteerActuatorDelay 추천 + - 커브 진입 중 steeringPressed 비율 (고비율) → SteerRatioRate 추천 (SR 부족 시) + 추천 발동: 샘플 ≥ 200개 (약 20초 직진 데이터) + +[Phase 3] JLeadFactor3 (제동 반응성) + 트리거: 선행차가 가깝고 느릴 때 brakePressed + 추천 발동: 수동 제동 횟수 ≥ 5회 + +[Phase 4] TFollowGap1~4 (차간 거리 설정) + 트리거: 크루즈 인게이지 중 선행차 있음 + gasPressed + → 거리가 너무 넓어 운전자가 능동적으로 좁히려는 패턴 + 추천: 현재 GAP 단계의 TFollowGap 값 감소 추천 (거리 좁히기) + 추천 발동: 인게이지 상태 선행차 추종 중 gas 개입 누적 ≥ 15초 + +저장: Params("CarrotLearningData") — JSON 문자열 +팝업: gearShifter == park 시 CarrotLearningPopupReady = True +""" + +import json +import math +from collections import deque +import numpy as np +from openpilot.common.params import Params +from openpilot.common.conversions import Conversions as CV + +# ── Phase 1 상수 ───────────────────────────────────────────────────── +_BP_KPH = [0, 10, 40, 60, 80, 110, 140] +_NUM_BANDS = len(_BP_KPH) +_ACCEL_KEYS = [f"CruiseMaxVals{i}" for i in range(_NUM_BANDS)] +_GAS_THRESHOLD_SEC = 10.0 # 구간당 누적 개입 시간 기준 (초) +_GAS_RECOMMEND_RATIO = 0.10 # 추천 증가 비율 (10%) +_GAS_REDUCE_RATIO = -0.07 # 추천 감소 비율 (-7%, 가속 과다 시) +_GAS_REDUCE_THRESHOLD_SEC = 5.0 # 가속 중 브레이크 개입 누적 기준 (초) +# 저속 밴드(0/1) 양방향 균형용 약한 하향 보정(decay) +# (저속대는 과가속 패널티가 ≥40km/h에서만 수집되어 단방향 상승하던 문제 보완) +_LOWBAND_DECAY_BANDS = (0, 1) # 하향 보정을 적용할 저속 밴드 인덱스 +_LOWBAND_DECAY_MIN_SEC = 60.0 # 해당 밴드를 이만큼 주행했을 때만 decay (오판 방지) +_LOWBAND_DECAY_GAS_DEADBAND = 1.0 # 가속요청 누적이 이 미만이면 '요청 없음'으로 간주 +_LOWBAND_DECAY_STEP = 3 # 1회 decay 변화량 (기본값 방향으로 약하게) + +# ── Phase 2 상수 ───────────────────────────────────────────────────── +_STRAIGHT_DEG = 5.0 # 직진 판단 조향각 임계값 (도) +_CURVE_RATE_DEG_S = 10.0 # (구) 커브 진입 판단: 조향각 변화율 임계값 (도/초) +_CURVE_DEG = 8.0 # 커브 구간 판단 조향각 임계값 (도). 이 이상이면 코너로 보고 매 틱 표본 수집 +_LATERAL_MIN_SAMPLES = 200 # PathOffset 추천을 위한 최소 직진 샘플 수 +_LATERAL_MIN_CURVE = 100 # SteerActuatorDelay 추천 최소 커브 표본 수 (per-tick, _DT=0.1 → 약 10초) +# PathOffset 신호: 차선중심 대비 횡편차(m). (구버전: 직진 평균 '조향각'을 신호로 썼으나 +# PathOffset은 경로 횡위치만 옮길 뿐 조향각 평균(캠버/얼라인먼트로 결정)을 못 바꿔 신호가 +# 수렴하지 않고 ±150(=±1.5m!)까지 폭주 → 차선 쏠림 유발. 차선중심 편차는 보정이 반영되면 +# 0으로 수렴하는 자기제한 신호.) +_PATH_OFFSET_LC_MIN_M = 0.08 # 평균 차선중심 편차가 이 이상(m)이면 추천 +_PATH_OFFSET_LC_GAIN = 0.5 # 편차(m)→units 변환 감쇠(0.5: 과보정 방지, 2~3사이클 수렴) +_PATH_OFFSET_STEP_MAX = 10 # 1회 추천 최대 변화량(units) +_PATH_OFFSET_ABS_MAX = 30 # 절대 상한(units, =0.30m). 구버전 ±150에서 대폭 축소 +_CURVE_OVERRIDE_RATIO = 0.5 # 커브 진입의 50% 이상에서 override → SteerActuatorDelay 증가 +_DELAY_STEP_UNIT = 10 # SteerActuatorDelay 한 번 추천 시 변화량 (UI 단위, +0.1s) +_SAD_LEARN_MIN = 15 # SteerActuatorDelay 학습 하한 (0.15s) — 과거 50은 너무 높았음 +_SAD_LEARN_MAX = 60 # 학습 상한 (0.60s) — 과거 300(=3s)은 비현실적 +_SAD_AUTO_BASELINE = 20 # 현재값 0(=liveDelay 자동)일 때 환산 기준 (0.20s) +_SR_RATE_OVERRIDE_RATIO = 0.7 # 커브 진입의 70% 이상에서 override → SteerRatioRate 추가 추천 +_SR_RATE_STEP_UNIT = 3 # SteerRatioRate 한 번 추천 시 변화량 (+3%) + +# ── 토크 피드백(KpV/KiV) 진동-우선 학습 상수 (고속 핑퐁/커브 탈출 진동 대응) ── +# 핵심: 추종오차의 '크기'가 아니라 '부호 패턴'으로 비례게인 방향을 정한다. +# 부호 반전(zero-crossing) 多 = 진동 → Kp 과다 → 하향 (발산 차단) +# 부호 유지 = 정상상태 lag → FF(Kf) 강화 우선, Kp는 보조만 +_TORQUE_OSC_MIN = 8 # 커브 추종오차 부호반전(진동) 판정 최소 누적 +_TORQUE_STEADY_MIN = 25 # 정상상태 lag(부호유지) 판정 최소 누적 +_TORQUE_KPV_STEP = 5 # LateralTorqueKpV 변화량 +_TORQUE_KPV_MIN = 30 # KpV 학습 하한 (default 100보다 낮게 — 고속 핑퐁 차량 수렴 허용) +_TORQUE_KPV_MAX = 200 # KpV 학습 상한 +_TORQUE_KIV_MIN = 0 # KiV 학습 하한 +_TORQUE_KIV_MAX = 50 # KiV 학습 상한 +_TORQUE_KF_FF_PREF = 100 # Kf가 이 이상이면 'FF 충분' → 정상상태 lag을 Kp로 보조 +_TORQUE_PINGPONG_MIN_KPH = 70.0 # 고속 직선 핑퐁 검출 최소 속도 +_TORQUE_PINGPONG_DEG = 0.4 # 핑퐁 반전 판정 조향각 데드밴드 (도) +_TORQUE_PINGPONG_MIN_COUNT = 15 # 직선 핑퐁 판정 최소 반전 횟수 + +# ── Phase 3 상수 ───────────────────────────────────────────────────── +_BRAKE_MIN_COUNT = 6 # 추천을 위한 최소 수동 브레이크 횟수 (5 -> 6, 민감도 완화) +_JLEAD_LATE_TTC = 3.5 # 이 TTC(초) 미만의 제동만 '늦은 제동'으로 인정(정상 제동 누적 방지) +_JLEAD_PROACTIVE_TTC = 6.0 # 선제적 '교육용' 제동으로 인정할 TTC 상한 (3.5~6.0s) +_JLEAD_PROACTIVE_DECEL = 1.0 # 선제 제동이 '굼뜬 반응' 신호로 인정될 최소 감속도 (m/s^2) +_JLEAD_PROACTIVE_STEP = 5 # 선제 제동 1회 추천 시 약한 증가량 (정상 제동 과누적 방지) +_JLEAD_AUTO_TTC = 6.0 # 자율 제동을 '늦은 제동'으로 셀 TTC 상한 (이상이면 여유 감속으로 간주, 누적 제외) +_JLEAD_AUTO_PANIC_DECEL = -2.5 # TTC와 무관하게 늦은 제동으로 인정할 패닉 감속도 (m/s^2) +_JLEAD_STEP_UNIT = 20 # JLeadFactor3 한 번 추천 시 변화량 (강화: 10 -> 20) +_JLEAD_REDUCE_STEP = -7 # 제동 과다 시 변화량 +_JLEAD_GAS_THRESHOLD_SEC = 5.0 # 제동 중 가속 개입 누적 기준 (초) +# JLeadFactor3 추천 상한(80→20). late-braking 신호로 무한 상향되어(runaway) 37까지 올라 +# 선행차 감지 초기 지령 jerk 스파이크(급격한 onset)를 유발한 문제 대응. 고속 선제 제동은 +# 별도 Lever A/C(HIGH_SPEED_*)가 담당하므로 이 상한을 낮춰도 고속 안전성은 유지된다. +_JLEAD_MAX_RECO = 20 + +# ── Phase 5 상수 (DynamicTFollow / TFollowDecelBoost) ──────────────── +# DynamicTFollow: 앞차 급감속(jLead↓) 중 브레이크 개입 횟수 기반 +_DYN_TFOLLOW_BRAKE_MIN = 6 # 추천 발동 최소 이벤트 수 (4 -> 6, 단방향 누적 완화) +_DYN_JLEAD_THRESHOLD = -1.0 # 앞차 가감속도 (m/s^3) 이하이면 '급감속' 판정 +_DYN_TFOLLOW_STEP = 3 # DynamicTFollow 한 번 추천 시 변화량 (+0.03) +_DYN_TFOLLOW_MAX = 50 # 최대값 (=0.5) +# TFollowDecelBoost: 내 차 감속 중 브레이크 개입 횟수 기반 +_DECEL_BOOST_BRAKE_MIN = 6 # 추천 발동 최소 이벤트 수 (4 -> 6, 단방향 누적 완화) +_DECEL_A_THRESHOLD = -0.8 # 내 차 감속도 (m/s^2) 이하이면 '강한 감속' 판정 +_DECEL_BOOST_STEP = 3 # TFollowDecelBoost 한 번 추천 시 변화량 (+0.03) +_DECEL_BOOST_MAX = 40 # 최대값 + +# ── Phase 4 상수 ───────────────────────────────────────────────────── +# TFollowGap1~4: Gap1(가장 좁음, 공격적), Gap4(가장 넓음, 여유) +# 각 Gap에 해당하는 Params key +_TFOLLOW_KEYS = ["TFollowGap1", "TFollowGap2", "TFollowGap3", "TFollowGap4"] +_TFOLLOW_NAMES = ["GAP1", "GAP2", "GAP3", "GAP4"] +_TFOLLOW_GAS_THRESHOLD_SEC = 15.0 # 선행차 추종 중 gas 누적 개입 시간 기준 +_TFOLLOW_GAP_DIFF_MIN = 10 # 축소 최소 근거: 운전자가 설정보다 ≥0.10s 더 바짝 따라간 경우만 +_TFOLLOW_STEP_UNIT = -5 # 감소 추천 (-5 units = -0.05s) +_TFOLLOW_MAX_LEAD_DREL = 150.0 # 선행차 거리 인식 최대 한계선 +_TFOLLOW_MIN_V_KPH = 40.0 # 학습을 개시할 최소 주행 속도 (60 -> 40) +_TFOLLOW_WIDEN_STEP = 5 # 증가 추천 (+0.05s) +_TFOLLOW_BRAKE_THRESHOLD_SEC = 10.0 # 거리 부족으로 인한 브레이크 누적 기준 (초) +_TFOLLOW_SPEED_FACTOR_THRESHOLD_SEC = 15.0 # 고속 보정 발동 기준 (10 -> 15, 단방향 누적 완화) +_TFOLLOW_SPEED_FACTOR_STEP = 5 # 고속 보정치 증가 단위 (+0.05) +_AUTO_HUNTING_THRESHOLD = 0.8 # 자율 주행 중 가감속 변동(Hunting) 감지 임계치 (m/s^2) + +# ── Phase 7 상수 (정차/출발: StoppingAccel / VEgoStopping / StopDistanceCarrot) ── +_STOP_APPROACH_V_KPH = 25.0 # 이 속도 이하에서 자율 감속 중이면 '정차 접근'으로 판정 +_STOP_DECEL_THRESHOLD = -0.3 # 정차 접근 판정 감속도 (m/s^2) +_STOP_FULLSTOP_V_KPH = 0.8 # 완전 정지 판정 속도 (km/h) +_STOP_MIN_EVENTS = 5 # 추천 발동 최소 정차 완료 횟수 +_STOP_HARSH_JERK = 2.5 # 정차 직전 급격한 감속도 변화(저크) 임계값 (m/s^3) +_STOP_ACCEL_STEP = 5 # StoppingAccel 변화량 (UI, +0.05m/s^2) +_STOP_VEGO_STEP = 3 # VEgoStopping 변화량 (UI, +0.03m/s) +_STOP_DIST_STEP = 20 # StopDistanceCarrot 변화량 (UI, +0.20m) +_STOP_GAP_WIDE_M = 5.0 # 정지 시 선행차와 이 거리 이상이면 '너무 멀리 정지' (선행차에 더 가깝게: 6.0→5.0) +_STOP_GAP_NEAR_M = 3.0 # 정지 시 선행차와 이 거리 이하이면 '너무 가까이 정지' (하한 당김: 3.5→3.0) + +# ── Phase 8 상수 (종방향 PID: LongTuningKpV / LongTuningKf / LongActuatorDelay) ── +_LONG_MIN_SAMPLES = 300 # 추천 발동 최소 샘플 수 (~30초 자율 가감속) +_LONG_ERR_THRESHOLD = 0.4 # 추종 오차(지령-실측 가속도) 유의 임계값 (m/s^2) +_LONG_LAG_RATIO = 0.30 # 둔감(lag) 비율 임계값 → Kf/Delay 상향 +_LONG_OVERSHOOT_RATIO = 0.30 # 진동(overshoot) 비율 임계값 → KpV 하향 +# lag는 '지령이 변하는 중(transient)'에서만 측정한다. 정상상태(정속)에서 남는 추종오차는 +# 도로 경사(aEgo가 중력성분 포함)·게인 오프셋이라 '시간지연(lag)'이 아니며, 이를 lag로 +# 오인하면 LongActuatorDelay/Kf를 잘못된 방향으로 무한 상향한다. (로그 분석으로 확인: +# 지령을 시간이동해도 오차가 안 줄고, 같은 코드라도 도로에 따라 오차가 크게 달라짐) +_LONG_CMD_CHANGE = 0.03 # 지령가속도가 1스텝(_DT)에 이만큼(m/s^2) 변하면 transient 시작 +_LONG_TRANS_HOLD_STEPS = 8 # 지령 변화 후 transient로 유지할 스텝 수 (8*_DT=0.8s, 추종 정착) +_LONG_TRANS_MIN = 120 # transient lag_ratio를 신뢰할 최소 transient 표본 수 +_LONG_KP_STEP = 5 # LongTuningKpV 변화량 +_LONG_KF_STEP = 3 # LongTuningKf 변화량 +_LONG_DELAY_STEP = 5 # LongActuatorDelay 변화량 + +# ── Phase 9 상수 (수동주행 기준분포 로거 → LongCoastBand 추천) ────────── +# 인게이지 '개입 카운팅'과 달리, 사람이 직접 운전하는 동안의 (상황 → 가감속/추종거리/ +# 페달상태)를 통째로 누적하여 '사람이라면 어떻게 했을까'의 기준분포(정답)로 삼는다. +# 1차 적용: 무페달(코스팅) 구간의 자연 감속(회생제동/엔진브레이크)을 측정하여 +# 종방향 코스팅 데드밴드(LongCoastBand)를 직접 보정 — 역문제가 없는 깨끗한 학습 대상. +_MANUAL_MIN_V_KPH = 20.0 # 수동주행 기준분포 수집 최소 속도 (정체 stop&go 잡음 배제) +_MANUAL_COAST_MIN_N = 300 # LongCoastBand 추천 발동 최소 코스팅 표본 (~30초) +_MANUAL_COAST_MIN_SEC = 60.0 # LongCoastBand 추천 발동 최소 누적 코스팅 시간 +_MANUAL_COAST_GAIN = 0.25 # 측정 코스팅 감속 → 데드밴드 변환 계수(차의 코스트 권한 일부만 사용) + +# ── 공통 ───────────────────────────────────────────────────────────── +_DT = 0.1 # update() 호출 주기 (초) + + +# ── 파라미터 스펙 레지스트리 (오픈파일럿 파라미터 문서1/2/3 기준) ────── +# 흩어져 있던 변환식·범위·방향성을 단일 출처로 코드화하여 부호 반전/ +# 범위 이탈을 원천 차단한다. 신규 학습 항목 추가 시 여기에 1줄만 등록. +# min/max : UI 정수 기준 안전 범위 (문서 '범위' 컬럼) +# default : 공장 기본값 (문서 '기본값' 컬럼) +# conv : UI 정수 → 실제값 변환 계수 (참고용) +# direction : +1 = 값↑이 효과↑ / -1 = 값↑이 효과↓ (분모 등 역방향) +# apply : "lat" | "long" → CarrotTunerApplyLat/Long 필터 카테고리 +_PARAM_SPEC = { + # 종방향 PID / 액추에이터 (문서2) + "LongTuningKpV": {"min": 0, "max": 150, "default": 100, "conv": 0.01, "direction": 1, "apply": "long"}, + "LongTuningKiV": {"min": 0, "max": 2000, "default": 0, "conv": 0.001, "direction": 1, "apply": "long"}, + # max 200→120/30: lag 지표가 action_t(=LongActuatorDelay) 선행분만큼 cmd-actual 차이를 + # 부풀려 무한 상향(runaway, 20→90)시키고 그 결과 output이 궤적을 과도히 앞서 감지 순간 + # 급제동을 유발 → 상한을 실제 물리값 근방으로 제한. + "LongTuningKf": {"min": 0, "max": 120, "default": 100, "conv": 0.01, "direction": 1, "apply": "long"}, + "LongActuatorDelay": {"min": 0, "max": 30, "default": 20, "conv": 0.01, "direction": 1, "apply": "long"}, + # 코스팅 데드밴드(0=비활성). 값↑ = 더 넓은 무가감속 구간 → 코스팅(회생제동) 더 적극 사용 + "LongCoastBand": {"min": 0, "max": 40, "default": 0, "conv": 0.01, "direction": 1, "apply": "long"}, + # 커브 감속 (문서1/실제 knob: vturn_speed의 AutoCurveSpeedFactor) + # 값↑ = 곡률을 더 민감하게 인식 → 커브 목표속도↓ → 더 일찍/충분히 감속 + # 안전 학습 밴드는 80~200 (params_keys.h 절대범위 50~300 내) + "AutoCurveSpeedFactor": {"min": 80, "max": 160, "default": 120, "conv": 0.01, "direction": 1, "apply": "long"}, + # 정차 / 출발 (문서2) + "StoppingAccel": {"min": -100, "max": 0, "default": 0, "conv": 0.01, "direction": 1, "apply": "long"}, + "VEgoStopping": {"min": 1, "max": 100, "default": 50, "conv": 0.01, "direction": 1, "apply": "long"}, + "StopDistanceCarrot": {"min": 350, "max": 1000, "default": 550, "conv": 0.01, "direction": 1, "apply": "long"}, + # 토크 조향 (문서2) — AccelFactor는 분모이므로 direction=-1 + "LateralTorqueAccelFactor": {"min": 1000, "max": 6000, "default": 2500, "conv": 0.001, "direction": -1, "apply": "lat"}, + "LateralTorqueKf": {"min": 0, "max": 200, "default": 100, "conv": 0.01, "direction": 1, "apply": "lat"}, +} + + +def _clamp_spec(key: str, raw) -> int: + """레지스트리 범위로 안전 클램프. 미등록 키는 원값(int)으로 반환.""" + spec = _PARAM_SPEC.get(key) + if spec is None: + return int(raw) + return int(np.clip(raw, spec["min"], spec["max"])) + + +def _decay_toward(cur, target, step) -> int: + """cur를 target 방향으로 최대 step만큼만 이동(넘어가지 않음). 양방향 균형 수렴용. + '잘 달릴 때 게인을 0까지 무한 감쇄'하던 단방향 drift를 막고 기본값으로 수렴시킨다.""" + cur = int(cur) + target = int(target) + step = abs(int(step)) + if cur > target: + return max(target, cur - step) + if cur < target: + return min(target, cur + step) + return cur + + +# ── 공장 기본값 (params_keys.h 기준 단일 출처) ──────────────────────── +# 오토튜너가 변경할 수 있는 모든 파라미터의 설치 시 기본값. +# '공장초기화(Factory Reset)' 시 이 값으로 일괄 복원한다. +_FACTORY_DEFAULTS = { + "CruiseMaxVals0": 160, "CruiseMaxVals1": 200, "CruiseMaxVals2": 160, + "CruiseMaxVals3": 130, "CruiseMaxVals4": 110, "CruiseMaxVals5": 95, + "CruiseMaxVals6": 80, + "JLeadFactor3": 0, + "TFollowGap1": 110, "TFollowGap2": 120, "TFollowGap3": 140, "TFollowGap4": 160, + "TFollowSpeedFactor": 0, "DynamicTFollow": 0, "TFollowDecelBoost": 10, + "PathOffset": 0, "SteerActuatorDelay": 0, "SteerRatioRate": 100, + "LateralTorqueAccelFactor": 2500, "LateralTorqueKf": 100, + "LateralTorqueFriction": 100, "LateralTorqueKiV": 10, "LateralTorqueKpV": 100, + "AutoCurveSpeedFactor": 120, "AutoCurveSpeedAggressiveness": 100, + "StoppingAccel": 0, "VEgoStopping": 50, "StopDistanceCarrot": 550, + "LongTuningKf": 100, "LongTuningKpV": 100, "LongActuatorDelay": 20, + "LongCoastBand": 0, +} + + +# 추천 키 → 소속 Phase. '적용'된 Phase의 누적치만 선택적으로 리셋하기 위한 매핑. +# (과거: 적용 여부와 무관하게 전체 Phase를 리셋 → 느리게 쌓이는 조향 학습이 +# 무관한 longitudinal 적용 시마다 초기화되어 문턱에 도달하지 못했음) +_KEY_RESET_PHASE = { + **{k: 1 for k in _ACCEL_KEYS}, + "PathOffset": 2, "SteerActuatorDelay": 2, "SteerRatioRate": 2, + "LateralTorqueAccelFactor": 2, "LateralTorqueKf": 2, "LateralTorqueFriction": 2, + "LateralTorqueKiV": 2, "LateralTorqueKpV": 2, + "JLeadFactor3": 3, + **{k: 4 for k in _TFOLLOW_KEYS}, "TFollowSpeedFactor": 4, + "DynamicTFollow": 5, "TFollowDecelBoost": 5, + "AutoCurveSpeedFactor": 6, + "StoppingAccel": 7, "VEgoStopping": 7, "StopDistanceCarrot": 7, + "LongTuningKf": 8, "LongActuatorDelay": 8, "LongTuningKpV": 8, + "LongCoastBand": 9, +} + + +def _speed_band(v_ego_kph: float) -> int: + """속도에 해당하는 가장 가까운 하위 구간 인덱스 반환""" + for i in range(_NUM_BANDS - 1, -1, -1): + if v_ego_kph >= _BP_KPH[i]: + return i + return 0 + + +class CarrotLearner: + """ + CarrotPlanner.update()에서 매 프레임 호출됨. + Phase 1: 가속 개입 데이터 누적 → CruiseMaxVals 추천 + Phase 2: 조향 편차/커브오버라이드 누적 → PathOffset / SteerActuatorDelay / SteerRatioRate 추천 + Phase 3: 수동 제동 누적 → JLeadFactor3 추천 + Phase 4: 선행차 추종 중 가속 개입 누적 → TFollowGap 감소 추천 + Parking 전환 시 추천값을 Params에 기록. + """ + + def __init__(self): + self._params = Params() + # Phase 1 + self._gas_acc = [0.0] * _NUM_BANDS + self._gas_dec_acc = [0.0] * _NUM_BANDS # 가속 중 수동 브레이크 개입 + self._gas_dec_auto_acc = [0.0] * _NUM_BANDS # 자율 주행 중 급가속 감지 + self._band_sec = [0.0] * _NUM_BANDS # 밴드별 인게이지 주행시간 (저속 decay 판정용) + # Phase 2 + self._steer_acc = 0.0 # 직진 구간 조향각 누적합 (도) + self._steer_count = 0 # 직진 샘플 수 + self._lane_center_acc = 0.0 # 직진 구간 차선중심 y좌표 누적합 (m, 모델 프레임 — path.y와 동일 좌표계) + self._lane_center_n = 0 # 차선중심 표본 수 (세션 한정, 비영속) + self._prev_steer_deg = 0.0 # 이전 프레임 조향각 + self._curve_entries = 0 # 커브 진입 이벤트 수 + self._curve_overrides = 0 # 커브 진입 중 steeringPressed 이벤트 수 + self._curve_overrides_understeer = 0 # 조향 부족 개입 횟수 (안쪽으로 더 꺾음) + self._curve_overrides_inner_hugging = 0 # 안쪽 쏠림 개입 횟수 (바깥쪽으로 풀어줌) + self._in_curve_entry = False # 커브 진입 상태 플래그 (중복 카운트 방지) + # Phase 3 + self._brake_count = 0 # 수동 브레이크 개입 횟수 + self._brake_auto_count = 0 # 자율 주행 중 급제동 횟수 + self._jlead_gas_acc = 0.0 # 제동 중 수동 가속 개입 + self._prev_brake = False + self._prev_auto_brake = False + # Phase 4 (TFollowGap) + self._tfollow_gas_acc = [0.0] * 4 + self._tfollow_brake_acc = [0.0] * 4 # 수동 브레이크 개입 + self._tfollow_brake_auto_acc = [0.0] * 4 # 자율 주행 중 헌팅 감지 + self._tfollow_speed_brake_acc = 0.0 # 고속 주행 시 브레이크 개입 누적 + self._current_gap = 1 # 현재 활성화된 GAP 단계 (1~4) + self._speed_factor_sec = 0.0 # TFollowSpeedFactor decay용 고속 주행 누적 시간 + # Phase 5 (DynamicTFollow / TFollowDecelBoost) + self._dyn_brake_count = 0 # 앞차 급감속 중 브레이크 개입 횟수 + self._decel_brake_count = 0 # 내 차 강한 감속 중 브레이크 개입 횟수 + self._dyn_sec = 0.0 # DynamicTFollow decay용 인게이지+선행차 급감속 없음 시간 + self._decel_sec = 0.0 # TFollowDecelBoost decay용 인게이지 감속 없음 시간 + self._jlead_sec = 0.0 # JLeadFactor3 decay용 인게이지+선행차 근접 주행 시간 + + # v3 Override Intensity & Dynamics Logging + self._gas_max_accel = [0.0] * _NUM_BANDS + self._gas_max_pedal = [0.0] * _NUM_BANDS + self._brake_max_decel = 0.0 + self._brake_min_ttc = 999.0 + self._tfollow_min_gap = [999.0] * 4 + + self._prev_gear_park = True # 초기값(시동 시 P단 간주) + self._has_driven = False # 주행(D단/이동) 여부 플래그 + self._prev_a_ego = 0.0 # 이전 프레임 가속도 + self._accel_swing_count = 0 # 가감속 반전(Hunting) 카운트 + + # ── 조향 방식 자동 감지 및 토크 학습 변수 ────────────────────────── + self.is_angle_control = None + self._torque_curve_entries = 0 + self._torque_curve_overrides = 0 + self._torque_curve_overrides_understeer = 0 + self._torque_curve_overrides_inner_hugging = 0 + self._torque_straight_entries = 0 + self._torque_straight_overrides = 0 + self._torque_error_count = 0 + # 진동-우선 학습용: 추종오차 '부호 패턴' 추적 + self._torque_osc_count = 0 # 커브 추종오차 부호반전(진동) 누적 + self._torque_steady_count = 0 # 커브 추종오차 부호유지(정상상태 lag) 누적 + self._torque_straight_reversal = 0 # 고속 직선 조향각 반전(핑퐁) 누적 + self._prev_torque_err_sign = 0 # 직전 유의 추종오차 부호 (커브 진동 검출) + self._prev_torque_straight_sign = 0 # 직전 직선 조향각 부호 (핑퐁 검출) + + + # ── 주행 중 팝업 타이머 ───────────────────────────────────────────── + # Trigger 1: 인게이지 30분 경과 → 즉시 팝업 (주행 중 포함) + self._engaged_elapsed_sec = 0.0 # 인게이지 누적 시간 + self._popup_interval_sec = 1800.0 # 30분 = 1800초 + # Trigger 2: 5분 주기 추천 체크 → 정차 시 팝업 + self._check_elapsed_sec = 0.0 # 추천 체크 타이머 + self._check_interval_sec = 300.0 # 5분마다 체크 + self._pending_popup = False # 정차 대기 팝업 플래그 + # 공통: 팝업 후 쿨다운 (중복 발동 방지) + self._popup_cooldown_sec = 0.0 # 팝업 발동 후 5분 쿨다운 + + # Phase 6 (Curve Decel Aggressiveness) + self._curve_override_gas_sec = 0.0 + self._curve_override_brake_sec = 0.0 + self._curve_override_brake_count = 0 + self._curve_max_decel = 0.0 + self._curve_steer_error_sec = 0.0 + self._curve_brake_min_v = 999.0 + self._curve_active_sec = 0.0 # 커브 주행 누적 시간(개입 무관). decay(편안한 커브 판정)용 + + # Phase 7 (정차/출발: StoppingAccel / VEgoStopping / StopDistanceCarrot) + self._stop_events = 0 # 완전 정지 완료 횟수 (denominator) + self._stop_brake_sec = 0.0 # 정차 접근 중 운전자 추가 제동 누적 (정차가 약/늦음) + self._stop_gas_sec = 0.0 # 정차 접근 중 운전자 가속 개입 누적 (정차가 강/이름) + self._stop_harsh_count = 0 # 정차 직전 급격한 저크 발생 횟수 (거친 정지) + self._stop_lead_gap_sum = 0.0 # 선행차 뒤 정지 시 최종 거리 합 (m) + self._stop_lead_gap_count = 0 # 선행차 뒤 정지 표본 수 + self._stop_approaching = False # 정차 접근 상태 플래그 + self._stop_max_jerk = 0.0 # 현재 정차 접근의 피크 저크 (transient) + self._prev_v_kph = 0.0 # 이전 프레임 속도 + + # Phase 8 (종방향 PID: LongTuningKpV / LongTuningKf / LongActuatorDelay) + self._long_samples = 0 # 자율 가감속 추종 표본 수(전체) + self._long_err_sum = 0.0 # |지령가속도 - 실측가속도| 누적(전체, 참고용) + self._long_lag_count = 0 # 둔감 표본 수(전체, 참고용) + self._long_overshoot_count = 0 # 진동(부호 반전) 표본 수 + self._prev_long_err = 0.0 # 이전 프레임 추종 오차 + self._prev_cmd_accel = 0.0 # 이전 프레임 지령가속도 (transient 감지용) + self._long_trans_hold = 0 # transient 유지 카운터(스텝) + self._cmd_hist = deque(maxlen=12) # 지령가속도 이력(지연보상 비교용, 1.2s, 비영속) + self._long_delay_units = 20 # LongActuatorDelay 캐시(UI units, 주기 갱신) + self._long_trans_samples = 0 # transient(지령 변화 중) 표본 수 → lag 분모 + self._long_trans_lag = 0 # transient 중 lag(둔감) 표본 수 + self._long_trans_err_sum = 0.0 # transient 중 |추종오차| 누적 + + # Phase 9 (수동주행 기준분포 로거 → LongCoastBand) + # 모두 밴드별(_NUM_BANDS) 누적. 사람이 직접 운전하는 동안의 '정답' 분포. + self._manual_coast_sec = [0.0] * _NUM_BANDS # 무페달(코스팅) 주행 누적 시간 + self._manual_coast_decel_sum = [0.0] * _NUM_BANDS # 코스팅 중 자연 감속 크기 합 (m/s², 양수) + self._manual_coast_decel_n = [0] * _NUM_BANDS # 코스팅 중 감속 표본 수 + self._manual_gas_accel_sum = [0.0] * _NUM_BANDS # 가속페달 시 사람의 가속도 합 (m/s²) + self._manual_gas_n = [0] * _NUM_BANDS # 가속페달 표본 수 + self._manual_brake_decel_sum = [0.0] * _NUM_BANDS # 브레이크 시 사람의 감속 크기 합 (m/s², 양수) + self._manual_brake_n = [0] * _NUM_BANDS # 브레이크 표본 수 + self._manual_gap_sum = 0.0 # 수동 추종 차간시간(time gap) 합 (s) + self._manual_gap_n = 0 # 수동 추종 표본 수 + + self._load() + + # ------------------------------------------------------------------ + # 공개 API + # ------------------------------------------------------------------ + + def _detect_steer_control_type(self): + try: + from cereal import car + import cereal.messaging as messaging + cp_bytes = self._params.get("CarParams") + if cp_bytes is not None: + cp = messaging.log_from_bytes(cp_bytes, car.CarParams) + self.is_angle_control = (cp.steerControlType == car.CarParams.SteerControlType.angle) + print(f"CarrotLearner: Detected steerControlType = {cp.steerControlType} (is_angle_control: {self.is_angle_control})") + else: + # CarParams가 아직 로드되지 않은 경우 다음 프레임에서 재시도하도록 None 유지 + pass + except Exception as e: + self.is_angle_control = False # 에러 시 기본값 torque(False)로 롤백 + print(f"CarrotLearner: Error detecting steerControlType: {e}") + + def set_current_gap(self, gap: int): + """현재 GAP 단계 설정 (1~4). CarrotPlanner에서 매 프레임 전달.""" + self._current_gap = max(1, min(4, gap)) + + def _cur_or_default(self, key: str) -> int: + """현재 파라미터값(get_int). 미설정(≤0)이면 레지스트리 기본값으로 대체. + (추천 계산에서 반복되던 'get_int 후 ≤0이면 default' 패턴을 통일.)""" + cur = self._params.get_int(key) + if cur <= 0: + cur = _PARAM_SPEC[key]["default"] + return cur + + def update(self, v_ego_kph: float, gas_pressed: bool, engaged: bool, gear_park: bool, + steer_deg: float = 0.0, steer_pressed: bool = False, + brake_pressed: bool = False, lead_drel: float = 0.0, lead_v_kph: float = 0.0, + a_ego: float = 0.0, lead_jlead: float = 0.0, v_cruise_kph: float = 0.0, + gas_val: float = 0.0, brake_val: float = 0.0, sm=None): + """ + 매 프레임 호출. + - Phase 1: engaged + gas_pressed → 속도구간 누적 + - Phase 2: engaged + 직진 → 조향 편차 누적 + engaged + 커브 진입 + steer_pressed → override 카운트 + - Phase 3: brake_pressed + 선행차 근접 → 브레이크 개입 카운트 + - Phase 4: engaged + 선행차 존재 + gas_pressed + 고속 → TFollowGap gas 누적 + - gear_park=True → 추천 계산 및 Params 저장 + """ + # 공장초기화 신호 (학습 활성 여부와 무관하게 항상 처리) + # 신규 키가 params 바인딩에 아직 없을 수 있으므로 방어적으로 처리한다. + # (여기서 예외가 나도 아래 학습 로직은 정상 진행되어야 함) + try: + if self._params.get_bool("CarrotTunerFactoryReset"): + self._factory_reset() + self._params.put_bool("CarrotTunerFactoryReset", False) + except Exception: + pass + + if not self._is_active(): + if self._params.get_bool("CarrotLearningPopupReady"): + self._params.put_bool("CarrotLearningPopupReady", False) + return + + # Apply 토글(LAT/LONG)이 꺼져 있으면 해당 카테고리의 학습(데이터 누적)도 멈춘다. + # (과거: 토글은 추천 계산/적용만 막고 누적은 계속됨 → 끈 동안 쌓인 데이터가 + # 재활성화 시 한꺼번에 반영되던 문제. 이제 OFF면 누적 자체를 건너뛴다.) + # - apply_lat : Phase 2(조향) 누적 게이트 + # - apply_long : Phase 1/3/4/5/6/7/8/9(가속·제동·추종·곡선·정차·PID·수동분포) 게이트 + apply_lat = self._params.get_bool("CarrotTunerApplyLat") if self._params.get("CarrotTunerApplyLat") is not None else True + apply_long = self._params.get_bool("CarrotTunerApplyLong") if self._params.get("CarrotTunerApplyLong") is not None else True + + # 조향 제어 방식 lazy loading 감지 + if self.is_angle_control is None: + self._detect_steer_control_type() + + prev_brake = self._prev_brake + + # ── 학습 제외 조건 판정 ────────────────────────── + left_prob = 1.0 + right_prob = 1.0 + left_blinker = False + right_blinker = False + + if sm is not None: + if 'modelV2' in sm.data and sm.alive.get('modelV2', False): + try: + model = sm['modelV2'] + if hasattr(model, 'laneLineProbs') and len(model.laneLineProbs) >= 4: + left_prob = model.laneLineProbs[1] + right_prob = model.laneLineProbs[2] + elif hasattr(model, 'laneLineProbs') and len(model.laneLineProbs) >= 2: + left_prob = model.laneLineProbs[0] + right_prob = model.laneLineProbs[1] + except Exception: + pass + + if 'carState' in sm.data and sm.alive.get('carState', False): + try: + car_state = sm['carState'] + left_blinker = car_state.leftBlinker + right_blinker = car_state.rightBlinker + except Exception: + pass + + poor_lanes = (left_prob < 0.5 and right_prob < 0.5) + blinker_on = (left_blinker or right_blinker) + extreme_acceleration = (a_ego > 2.2 or gas_val > 0.7) + + exclude_override = poor_lanes or blinker_on + exclude_steer_learning = exclude_override + exclude_gas_learning = extreme_acceleration or exclude_override + exclude_brake_learning = exclude_override + + # UI로부터 초기화(Clear) 신호가 오면 내부 메모리를 비움 + if self._params.get_bool("CarrotLearningClear"): + self._clear_all_data() + self._params.put_bool("CarrotLearningClear", False) + + # 주행 여부 판단 (오픈파일럿 인게이지가 단 한 번이라도 실행되었을 때만 유효 주행 세션으로 판단) + if engaged: + self._has_driven = True + + # ── Phase 1: 가속 개입 ──────────────────────────────────────────── + # 밴드별 주행시간 누적 (저속 decay 판정용: 실제로 그 속도대를 달렸는지 확인) + if apply_long and engaged and v_ego_kph >= 1.0: + self._band_sec[_speed_band(v_ego_kph)] += _DT + + # 단순히 설정속도에 도달했는데 더 빨리 가고 싶어 밟는 경우는 제외 (설정속도 오버라이드) + # 즉, 설정속도보다 충분히 낮은데도 가속이 답답할 때만 학습에 포함 + if apply_long and engaged and gas_pressed and v_ego_kph >= 1.0: + # 선행차를 추종하며 간격을 좁히려는 가속(=차간거리 선호)은 '가속 능력 부족'이 아니므로 + # CruiseMaxVals(최대 가속도) 학습에서 제외한다. (선행차 거리·상대속도 동특성 반영) + # - 가까운 선행차(60m 이내)가 있고, 그 차가 나보다 크게 빠르지 않으면(멀어지지 않으면) + # 운전자의 가속은 간격 좁히기/추종 목적 → 가속능력 부족 신호로 보지 않음. + # - 저속 정체(stop&go)에서 CruiseMaxVals0가 단방향 누적되던 주원인. + lead_follow_gas = False + if 0.0 < lead_drel < 60.0: + v_rel_kph = lead_v_kph - v_ego_kph # 양수면 선행차가 더 빠름(멀어짐) + if v_rel_kph < 10.0: + lead_follow_gas = True + if v_ego_kph < (v_cruise_kph - 3.0) and not exclude_gas_learning and not lead_follow_gas: + idx = _speed_band(v_ego_kph) + self._gas_acc[idx] += _DT + self._gas_max_accel[idx] = max(self._gas_max_accel[idx], a_ego) + self._gas_max_pedal[idx] = max(self._gas_max_pedal[idx], gas_val) + + # 과속방지턱 감속 작동 중이거나 40km/h 미만 저속 구간 여부 판별 (A/B안 적용) + is_speed_bump = False + if sm is not None and sm.alive.get('carrotMan', False): + try: + carrot_man = sm['carrotMan'] + is_speed_bump = (carrot_man.xSpdType == 22 or carrot_man.activeCarrot == 5) + except Exception: + pass + + # 가속 과다 학습 방지: 가속 중인데 브레이크를 밟는 경우 OR 자율 주행 중 과도한 가속 + # A/B안 적용: 과속방지턱 통과 중이거나 40km/h 미만에서는 가속 하향 패널티 수집 제외 + if apply_long and engaged and v_ego_kph < (v_cruise_kph - 3.0) and not is_speed_bump and v_ego_kph >= 40.0: + idx = _speed_band(v_ego_kph) + if brake_pressed: + if (lead_drel == 0 or lead_drel > 120.0) and not exclude_brake_learning: + self._gas_dec_acc[idx] += _DT + elif not gas_pressed and a_ego > 1.5: # 자율 주행 중 급가속 감지 + self._gas_dec_auto_acc[idx] += _DT + + # ── Phase 2: 조향 패턴 (속도 20km/h 이상, 인게이지 상태) ────────── + if apply_lat and engaged and v_ego_kph >= 20.0: + desired_angle = 0.0 + steer_err = 0.0 + if sm is not None: + try: + if sm.alive.get('carControl', False): + desired_angle = sm['carControl'].actuators.steeringAngleDeg + elif sm.alive.get('controlsState', False): + desired_angle = sm['controlsState'].steeringAngleDesired + steer_err = desired_angle - steer_deg + except Exception: + pass + + if self.is_angle_control: + # [A] 앵글 조향 차량 데이터 수집 + # 직진 구간 편차 수집 (override 없을 때만 순수 자동조향 편차) + if abs(steer_deg) < _STRAIGHT_DEG and not steer_pressed: + self._steer_acc += steer_deg + self._steer_count += 1 + # PathOffset용 차선중심 편차(m) 수집 — PathOffset이 실제로 바꾸는 신호(횡위치) + try: + if sm is not None and sm.alive.get('modelV2', False): + md = sm['modelV2'] + ll, lp = md.laneLines, md.laneLineProbs + if len(ll) >= 4 and len(lp) >= 4 and lp[1] > 0.5 and lp[2] > 0.5 \ + and len(ll[1].y) > 0 and len(ll[2].y) > 0: + self._lane_center_acc += (ll[1].y[0] + ll[2].y[0]) * 0.5 + self._lane_center_n += 1 + except Exception: + pass + + # 커브 구간 감지: 조향각이 충분히 크고 속도가 있으면 코너로 보고 "매 틱" 표본 수집. + # (구버전은 진입 순간 1틱에서 steer_pressed인 경우만 봐서, 코너 중간에 들어가는 + # 언더스티어 보정(바깥 쏠림 후 안쪽으로 꺾음)을 거의 못 잡았음 → 학습 안 됨) + if v_ego_kph >= 30.0 and abs(steer_deg) >= _CURVE_DEG: + self._curve_entries += 1 + if steer_pressed and not exclude_steer_learning: + self._curve_overrides += 1 + # 개입 방향 판정 (desired_angle * steer_err) + # desired_angle과 steer_err의 부호가 같으면 Outer(안쪽쏠림, 풀어주기), 반대면 Inner(언더스티어, 더 꺾기) + if desired_angle * steer_err > 0: + self._curve_overrides_inner_hugging += 1 + elif desired_angle * steer_err < 0: + self._curve_overrides_understeer += 1 + else: + # [B] 토크 조향 차량 데이터 수집 + # 1. 커브 구간 (조향각이 크고 속도가 있을 때) + if v_ego_kph >= 40.0 and abs(steer_deg) >= 5.0: + self._torque_curve_entries += 1 + if steer_pressed and not exclude_steer_learning: + self._torque_curve_overrides += 1 + # 개입 방향 판정 + if desired_angle * steer_err > 0: + self._torque_curve_overrides_inner_hugging += 1 + elif desired_angle * steer_err < 0: + self._torque_curve_overrides_understeer += 1 + + # 추종오차의 '크기'가 아니라 '부호 패턴'을 본다. + # 부호 반전(zero-crossing) = 진동(Kp 과다) / 부호 유지 = 정상상태 lag. + # (구버전은 |err|만 세서 진동이 만든 오차를 '게인 부족'으로 오인 → KpV 무한 상향 발산) + if abs(steer_err) >= 1.5: + self._torque_error_count += 1 + err_sign = 1 if steer_err > 0 else -1 + if self._prev_torque_err_sign != 0: + if err_sign != self._prev_torque_err_sign: + self._torque_osc_count += 1 # 부호 반전 → 진동 + else: + self._torque_steady_count += 1 # 부호 유지 → 정상상태 lag + self._prev_torque_err_sign = err_sign + + # 2. 완만한/직선 구간 (조향각이 작을 때 - Friction 용) + elif v_ego_kph >= 30.0 and abs(steer_deg) < 5.0: + self._torque_straight_entries += 1 + self._prev_torque_err_sign = 0 # 커브 종료 → 진동 부호 추적 초기화(구간 간 오검출 방지) + if steer_pressed and not exclude_steer_learning: + self._torque_straight_overrides += 1 + # 고속 직선 핑퐁(KpV 과다) 검출: 운전자 개입 없이 조향각이 + # 0 근처에서 반복 반전 → 순수 비례게인 과다 신호(소유주가 KpV로 잡던 현상). + elif v_ego_kph >= _TORQUE_PINGPONG_MIN_KPH: + s_sign = 0 + if steer_deg > _TORQUE_PINGPONG_DEG: + s_sign = 1 + elif steer_deg < -_TORQUE_PINGPONG_DEG: + s_sign = -1 + if s_sign != 0: + if self._prev_torque_straight_sign != 0 and s_sign != self._prev_torque_straight_sign: + self._torque_straight_reversal += 1 + self._prev_torque_straight_sign = s_sign + + self._prev_steer_deg = steer_deg + + # Phase 3: 수동 제동 (선행차 근접 시) ──────────────────────── + is_auto_braking = False + # A/B안 적용: 과속방지턱 통과 중이거나 40km/h 미만에서는 제동 학습(JLeadFactor) 수집 제외 + if apply_long and engaged and not gear_park and 0 < lead_drel < 100.0 and not is_speed_bump and v_ego_kph >= 40.0: + # (1) 수동 제동 트리거 + if brake_pressed and not exclude_brake_learning: + if not self._prev_brake: + self._brake_count += 1 + self._brake_max_decel = max(self._brake_max_decel, -a_ego) + v_ego_ms = v_ego_kph / 3.6 + lead_v_ms = lead_v_kph / 3.6 + v_rel_ms = lead_v_ms - v_ego_ms + if v_rel_ms < 0: + ttc = lead_drel / -v_rel_ms + self._brake_min_ttc = min(self._brake_min_ttc, ttc) + # (2) 자율 주행 중 너무 늦게 급제동 발생 -> 제동 시점 앞당기기 필요 + # 단, TTC(위급도)를 함께 본다. 잘 예측된 '여유 있는' 중간 감속(TTC 충분)은 + # 늦은 제동이 아니므로 제외 — onset jerk 완화로 생긴 정상 감속이 JLeadFactor3를 + # 끌어올려 다시 제동을 날카롭게 만드는 악순환을 차단한다. + elif not brake_pressed and a_ego < -1.7: + v_ego_ms = v_ego_kph / 3.6 + lead_v_ms = lead_v_kph / 3.6 + v_rel_ms = lead_v_ms - v_ego_ms + ttc = lead_drel / -v_rel_ms if v_rel_ms < 0 else 999.0 + # 실제 '늦은 제동' 조건: TTC가 낮아 위급(접근)하거나, 패닉 수준 급제동 + if ttc < _JLEAD_AUTO_TTC or a_ego < _JLEAD_AUTO_PANIC_DECEL: + is_auto_braking = True + self._brake_min_ttc = min(self._brake_min_ttc, ttc) + if not self._prev_auto_brake: + self._brake_auto_count += 1 + + self._prev_auto_brake = is_auto_braking + + # 제동 과다 학습 방지: 강한 제동 중 가속 페달을 밟는 경우 (불필요한 제동 억제) + # A/B안 적용: 과속방지턱 통과 중이거나 40km/h 미만에서는 가속 오버라이드 학습 수집 제외 + if apply_long and engaged and gas_pressed and a_ego < -0.8 and not is_speed_bump and v_ego_kph >= 40.0: + if 0 < lead_drel < 150.0 and not exclude_gas_learning: + self._jlead_gas_acc += _DT + + # ── Phase 5: DynamicTFollow / TFollowDecelBoost ────────────────── + # A/B안 적용: 과속방지턱 통과 중이거나 40km/h 미만에서는 DynamicTFollow 학습 수집 제외 + if apply_long and engaged and brake_pressed and not self._prev_brake and not is_speed_bump and v_ego_kph >= 40.0 and not exclude_brake_learning: + # DynamicTFollow: 앞차 급감속 중 브레이크 개입 + if lead_jlead < _DYN_JLEAD_THRESHOLD and lead_drel < 150.0: + self._dyn_brake_count += 1 + # TFollowDecelBoost: 내 차 강한 감속 중 브레이크 개입 + if a_ego < _DECEL_A_THRESHOLD: + self._decel_brake_count += 1 + + # decay 누적 시간 업데이트 + if apply_long and engaged and not gear_park and not is_speed_bump and v_ego_kph >= 40.0: + self._decel_sec += _DT + if 0.0 < lead_drel < 100.0: + self._jlead_sec += _DT + if 0.0 < lead_drel < 150.0: + self._dyn_sec += _DT + if apply_long and engaged and v_ego_kph >= 80.0: + self._speed_factor_sec += _DT + + self._prev_brake = brake_pressed + + # ── Phase 4: TFollowGap (선행차 추종 중 가속/감속 개입) ────────── + # 선행차가 잡힌 상태(0 < lead_drel < _TFOLLOW_MAX_LEAD_DREL)에서 + # 일정 속도 이상 주행 중 페달 개입을 분석합니다. + if apply_long and engaged and v_ego_kph >= _TFOLLOW_MIN_V_KPH and 0.0 < lead_drel < _TFOLLOW_MAX_LEAD_DREL: + gap_idx = self._current_gap - 1 # 0-indexed + + # (1) 가속 페달 개입 시 -> 간격 좁히기 의도로 판단 + if gas_pressed and not exclude_gas_learning: + self._tfollow_gas_acc[gap_idx] += _DT + v_ego_ms = v_ego_kph / 3.6 + if v_ego_ms > 1.0: + time_gap = lead_drel / v_ego_ms + self._tfollow_min_gap[gap_idx] = min(self._tfollow_min_gap[gap_idx], time_gap) + + # (2) 브레이크 페달 개입 시 -> 차간 거리가 가까워지거나 불안해서 감속하려는 의도로 판단 + elif brake_pressed and not exclude_brake_learning: + self._tfollow_brake_acc[gap_idx] += _DT + + # 고속 주행(80km/h 이상) 시 추가 고속 차간 거리 보정 학습 누적 + if v_ego_kph >= 80.0: + self._tfollow_speed_brake_acc += _DT + + # (3) 페달 개입이 없을 때 자율 주행 중 가감속 헌팅(Swing) 감지 + else: + if (self._prev_a_ego > 0.3 and a_ego < -0.3) or (self._prev_a_ego < -0.3 and a_ego > 0.3): + self._accel_swing_count += 1 + if self._accel_swing_count > 8: + self._tfollow_brake_auto_acc[gap_idx] += _DT * 2.0 + self._accel_swing_count = 0 + + # ── Phase 6: 가변 곡선 감속 학습 ────────────────────────────────────────── + if apply_long and engaged and sm is not None and sm.alive.get('modelV2', False) and v_ego_kph >= 20.0: + modelData = sm['modelV2'] + if len(modelData.position.x) >= 3: + x_pts = np.array(modelData.position.x) + y_pts = np.array(modelData.position.y) + n_points = len(x_pts) + + # Calculate maximum curvature along the predicted path + max_c = 0.0 + for i in range(1, n_points - 1): + x1, y1 = x_pts[i-1], y_pts[i-1] + x2, y2 = x_pts[i], y_pts[i] + x3, y3 = x_pts[i+1], y_pts[i+1] + + dx1, dy1 = x2 - x1, y2 - y1 + dx2, dy2 = x3 - x2, y3 - y2 + + a_side = math.sqrt(dx1**2 + dy1**2) + b_side = math.sqrt(dx2**2 + dy2**2) + c_side = math.sqrt((x3 - x1)**2 + (y3 - y1)**2) + + cross = dx1 * dy2 - dy1 * dx2 + if a_side * b_side * c_side > 1e-6: + curvature = (2.0 * abs(cross)) / (a_side * b_side * c_side) + max_c = max(max_c, curvature) + + # Curve detected threshold: max_c > 0.0035 (radius < ~285m) + is_curve = (max_c > 0.0035) + no_lead = (lead_drel == 0.0 or lead_drel > 80.0) # no close lead car + + if is_curve and no_lead: + self._curve_active_sec += _DT # 커브 주행 누적(개입 무관) - decay 판정용 + if gas_pressed and not exclude_gas_learning: + self._curve_override_gas_sec += _DT + elif brake_pressed and not exclude_brake_learning: + self._curve_override_brake_sec += _DT + # Track peak deceleration in curve + self._curve_max_decel = max(self._curve_max_decel, -a_ego) + # 커브 제동 시 최저 속도(하한 도달 여부 판단용) + self._curve_brake_min_v = min(self._curve_brake_min_v, v_ego_kph) + + # Detect unique brake event in curve + if not prev_brake: + self._curve_override_brake_count += 1 + + # Calculate steering tracking error (desired vs actual steering angle) + try: + desired_angle = 0.0 + if sm.alive.get('carControl', False): + desired_angle = sm['carControl'].actuators.steeringAngleDeg + elif sm.alive.get('controlsState', False): + desired_angle = sm['controlsState'].steeringAngleDesired + steer_err = desired_angle - steer_deg + if abs(steer_err) >= 1.5: + self._curve_steer_error_sec += _DT + except Exception: + pass + + # ── Phase 7: 정차/출발 데이터 수집 ─────────────────────────────── + # 자율 정차 접근(저속 + 자율 감속 + 가속페달 없음) 중 운전자 개입/정지 품질 수집 + if apply_long and engaged and not gear_park: + approaching = (v_ego_kph <= _STOP_APPROACH_V_KPH and a_ego < _STOP_DECEL_THRESHOLD + and not gas_pressed) + if approaching: + self._stop_approaching = True + + if self._stop_approaching: + # (a) 운전자 추가 제동 → 자율 정차가 약하거나 늦음 + if brake_pressed and not exclude_brake_learning: + self._stop_brake_sec += _DT + # (b) 운전자 가속 개입 → 자율 정차가 강하거나 이름 + elif gas_pressed and not exclude_gas_learning: + self._stop_gas_sec += _DT + # (c) 정차 직전 피크 저크(거친 정지) 추적 + if v_ego_kph < 8.0: + jerk = abs(a_ego - self._prev_a_ego) / _DT + self._stop_max_jerk = max(self._stop_max_jerk, jerk) + # (d) 완전 정지 전이 감지 (직전 이동 → 정지) + if v_ego_kph < _STOP_FULLSTOP_V_KPH and self._prev_v_kph >= _STOP_FULLSTOP_V_KPH: + self._stop_events += 1 + if self._stop_max_jerk >= _STOP_HARSH_JERK: + self._stop_harsh_count += 1 + if 0.0 < lead_drel < 80.0: + self._stop_lead_gap_sum += lead_drel + self._stop_lead_gap_count += 1 + self._stop_approaching = False + self._stop_max_jerk = 0.0 + else: + self._stop_approaching = False + self._stop_max_jerk = 0.0 + + # ── Phase 8: 종방향 PID 추종 데이터 수집 ───────────────────────── + # 자율 가감속(운전자 페달 미개입) 중 지령가속도 대비 실측가속도 추종 오차 분석 + if (apply_long and engaged and not gear_park and not gas_pressed and not brake_pressed + and v_ego_kph >= 20.0 and sm is not None and sm.alive.get('carControl', False)): + try: + cmd_accel = sm['carControl'].actuators.accel + # 지연 보상 비교: 출력(cmd)은 계획을 action_t(=LongActuatorDelay+DT_MDL)만큼 앞서 + # 샘플한 값이므로, 현재 실측(a_ego)은 'action_t 전의 지령'과 비교해야 공정하다. + # (구버전: cmd(t)-a_ego(t) → transient마다 선행분이 통째로 lag로 계산돼 Delay/Kf가 + # 무한 상향되는 양의 피드백(runaway 20→90) → 감지 순간 과제동 유발) + if self._long_samples % 100 == 0: + self._long_delay_units = max(0, self._params.get_int("LongActuatorDelay")) + self._cmd_hist.append(float(cmd_accel)) + n_delay = min(len(self._cmd_hist) - 1, + int((self._long_delay_units * 0.01 + 0.05) / _DT + 0.5)) + long_err = self._cmd_hist[-1 - n_delay] - a_ego + self._long_samples += 1 + self._long_err_sum += abs(long_err) + if abs(long_err) >= _LONG_ERR_THRESHOLD: + self._long_lag_count += 1 # 전체 lag(참고용) + + # 지령이 변하면 이후 잠시(HOLD)를 transient로 간주 — 실측이 따라붙는 정착 구간. + if abs(cmd_accel - self._prev_cmd_accel) >= _LONG_CMD_CHANGE: + self._long_trans_hold = _LONG_TRANS_HOLD_STEPS + self._prev_cmd_accel = cmd_accel + + # 둔감(lag)은 transient(지령 변화 추종) 구간에서만 측정한다. + # → 정속(정상상태)에서 남는 경사·게인 오프셋을 lag로 오인하지 않음. + if self._long_trans_hold > 0: + self._long_trans_samples += 1 + self._long_trans_err_sum += abs(long_err) + if abs(long_err) >= _LONG_ERR_THRESHOLD: + self._long_trans_lag += 1 + self._long_trans_hold -= 1 + + # 진동(overshoot): 오차 부호가 반전하며 진폭이 큼 → 과반응 + if long_err * self._prev_long_err < 0 and abs(long_err) >= 0.3: + self._long_overshoot_count += 1 + self._prev_long_err = long_err + except Exception: + pass + else: + # 수집 구간 단절(페달 개입/저속 등) → 이력 클리어로 stale 지연보상 비교 방지 + self._cmd_hist.clear() + self._long_trans_hold = 0 + + # ── Phase 9: 수동주행 기준분포 로거 ────────────────────────────── + # openpilot 비인게이지(=사람이 직접 운전) 주행 중, 상황별 사람의 가감속·추종거리· + # 페달상태를 통째로 누적한다. 핵심은 '무페달(코스팅)' 구간의 자연 감속을 측정해 + # 이 차/이 운전자의 회생제동 권한과 코스팅 선호를 직접 식별하는 것(역문제 없음). + if apply_long and (not engaged) and not gear_park and v_ego_kph >= _MANUAL_MIN_V_KPH: + band = _speed_band(v_ego_kph) + if gas_pressed: + # 사람이 선택한 가속(과도 가속은 학습 제외 기준 재사용) + if not extreme_acceleration: + self._manual_gas_accel_sum[band] += a_ego + self._manual_gas_n[band] += 1 + elif brake_pressed: + # 사람이 선택한 감속 크기(양수로 저장) + self._manual_brake_decel_sum[band] += max(-a_ego, 0.0) + self._manual_brake_n[band] += 1 + else: + # 무페달 = 코스팅(자연 회생제동/엔진브레이크). 이 구간의 감속률을 측정. + self._manual_coast_sec[band] += _DT + if a_ego < 0.0: + self._manual_coast_decel_sum[band] += -a_ego + self._manual_coast_decel_n[band] += 1 + # 수동 추종 차간시간(time gap) 기준 분포 (선행차 존재 시) + if 0.0 < lead_drel < _TFOLLOW_MAX_LEAD_DREL and v_ego_kph >= _TFOLLOW_MIN_V_KPH: + v_ms = v_ego_kph / 3.6 + if v_ms > 1.0: + self._manual_gap_sum += lead_drel / v_ms + self._manual_gap_n += 1 + + # 이전 프레임 상태 갱신 (Phase 4 swing 감지 + Phase 7 저크 계산 공용) + self._prev_a_ego = a_ego + self._prev_v_kph = v_ego_kph + + # ── 주행 중 팝업 타이머 업데이트 ────────────────────────────────── + if engaged and not gear_park: + self._engaged_elapsed_sec += _DT + self._check_elapsed_sec += _DT + + # 쿨다운 소모 + if self._popup_cooldown_sec > 0: + self._popup_cooldown_sec -= _DT + + # [Trigger 2-체크] 5분마다 추천사항 확인 → pending 플래그 세팅 + if self._check_elapsed_sec >= self._check_interval_sec: + self._check_elapsed_sec = 0.0 + if self._has_driven and self._popup_cooldown_sec <= 0: + recs = self._calc_recommendations() + if recs: + self._pending_popup = True + + # [Trigger 2-발동] 정차 시 (v < 3 km/h) pending 팝업 표시 + if (self._pending_popup and not gear_park + and self._has_driven and v_ego_kph < 3.0 + and self._popup_cooldown_sec <= 0): + self._fire_popup(source="stop") + self._pending_popup = False + + # [Trigger 1] 인게이지 30분 경과 → 즉시 팝업 (주행 중이라도) + if (self._engaged_elapsed_sec >= self._popup_interval_sec + and self._has_driven and self._popup_cooldown_sec <= 0): + self._fire_popup(source="timer") + self._engaged_elapsed_sec = 0.0 + self._pending_popup = False # 30분 팝업이 발동하면 pending 취소 + + # [Trigger 3] 주차 감지 (이전에 주차가 아니었고, 주행을 한 번이라도 한 경우에만 발동) + if gear_park and not self._prev_gear_park and self._has_driven: + self._on_parking() + self._has_driven = False # 팝업 후 플래그 초기화 + + self._prev_gear_park = gear_park + + # ── Phase별 누적 데이터 리셋 헬퍼 (단일 출처) ────────────────────── + # 과거 apply_recommendations()의 리셋 목록이 _clear_all_data()와 어긋나 + # understeer/inner_hugging·torque 카운터가 '적용' 후에도 남아, 분모(curve_overrides)만 + # 0이 되어 비율(understeer_ratio 등)이 오염되던 버그가 있었다. + # 모든 리셋 경로를 아래 헬퍼로 일원화해 재발을 막는다. + def _reset_phase1(self): + """가속 (CruiseMaxVals0~6)""" + self._gas_acc = [0.0] * _NUM_BANDS + self._gas_dec_acc = [0.0] * _NUM_BANDS + self._gas_dec_auto_acc = [0.0] * _NUM_BANDS + self._band_sec = [0.0] * _NUM_BANDS + self._gas_max_accel = [0.0] * _NUM_BANDS + self._gas_max_pedal = [0.0] * _NUM_BANDS + + def _reset_phase2(self): + """조향 (PathOffset/SteerActuatorDelay/SteerRatioRate + 토크 조향) — 방향 카운터 포함""" + self._steer_acc = 0.0 + self._steer_count = 0 + self._lane_center_acc = 0.0 + self._lane_center_n = 0 + self._curve_entries = 0 + self._curve_overrides = 0 + self._curve_overrides_understeer = 0 + self._curve_overrides_inner_hugging = 0 + self._torque_curve_entries = 0 + self._torque_curve_overrides = 0 + self._torque_curve_overrides_understeer = 0 + self._torque_curve_overrides_inner_hugging = 0 + self._torque_straight_entries = 0 + self._torque_straight_overrides = 0 + self._torque_error_count = 0 + self._torque_osc_count = 0 + self._torque_steady_count = 0 + self._torque_straight_reversal = 0 + self._prev_torque_err_sign = 0 + self._prev_torque_straight_sign = 0 + + def _reset_phase3(self): + """JLeadFactor3 (수동/자율 제동)""" + self._brake_count = 0 + self._brake_auto_count = 0 + self._jlead_gas_acc = 0.0 + self._jlead_sec = 0.0 + self._brake_max_decel = 0.0 + self._brake_min_ttc = 999.0 + self._prev_brake = False + self._prev_auto_brake = False + + def _reset_phase4(self): + """TFollowGap1~4 / TFollowSpeedFactor""" + self._tfollow_gas_acc = [0.0] * 4 + self._tfollow_brake_acc = [0.0] * 4 + self._tfollow_brake_auto_acc = [0.0] * 4 + self._tfollow_speed_brake_acc = 0.0 + self._speed_factor_sec = 0.0 + self._tfollow_min_gap = [999.0] * 4 + + def _reset_phase5(self): + """DynamicTFollow / TFollowDecelBoost""" + self._dyn_brake_count = 0 + self._decel_brake_count = 0 + self._dyn_sec = 0.0 + self._decel_sec = 0.0 + + def _reset_phase6(self): + """AutoCurveSpeedFactor (커브 감속)""" + self._curve_override_gas_sec = 0.0 + self._curve_override_brake_sec = 0.0 + self._curve_override_brake_count = 0 + self._curve_max_decel = 0.0 + self._curve_steer_error_sec = 0.0 + self._curve_brake_min_v = 999.0 + self._curve_active_sec = 0.0 + + def _reset_phase7(self): + """정차/출발 (StoppingAccel/VEgoStopping/StopDistanceCarrot)""" + self._stop_events = 0 + self._stop_brake_sec = 0.0 + self._stop_gas_sec = 0.0 + self._stop_harsh_count = 0 + self._stop_lead_gap_sum = 0.0 + self._stop_lead_gap_count = 0 + self._stop_approaching = False + self._stop_max_jerk = 0.0 + + def _reset_phase8(self): + """종방향 PID (LongTuningKf/LongActuatorDelay/LongTuningKpV)""" + self._long_samples = 0 + self._long_err_sum = 0.0 + self._long_lag_count = 0 + self._long_overshoot_count = 0 + self._prev_long_err = 0.0 + self._prev_cmd_accel = 0.0 + self._long_trans_hold = 0 + self._long_trans_samples = 0 + self._long_trans_lag = 0 + self._long_trans_err_sum = 0.0 + + def _reset_phase9(self): + """수동주행 기준분포 로거 (LongCoastBand)""" + self._manual_coast_sec = [0.0] * _NUM_BANDS + self._manual_coast_decel_sum = [0.0] * _NUM_BANDS + self._manual_coast_decel_n = [0] * _NUM_BANDS + self._manual_gas_accel_sum = [0.0] * _NUM_BANDS + self._manual_gas_n = [0] * _NUM_BANDS + self._manual_brake_decel_sum = [0.0] * _NUM_BANDS + self._manual_brake_n = [0] * _NUM_BANDS + self._manual_gap_sum = 0.0 + self._manual_gap_n = 0 + + def _reset_all_phases(self): + self._reset_phase1() + self._reset_phase2() + self._reset_phase3() + self._reset_phase4() + self._reset_phase5() + self._reset_phase6() + self._reset_phase7() + self._reset_phase8() + self._reset_phase9() + + def _clear_all_data(self): + """모든 누적 데이터를 0으로 초기화하고 DB에서도 삭제""" + self._reset_all_phases() + self._params.remove("CarrotLearningData") + self._params.remove("CarrotLearningRecommend") + + def _factory_reset(self): + """공장초기화: 오토튜너가 변경할 수 있는 모든 파라미터를 설치 기본값으로 + 일괄 복원하고, 누적 학습 데이터/추천도 모두 삭제한다. + (오토튜닝 결과가 마음에 들지 않을 때 처음 상태로 되돌리는 용도)""" + for key, val in _FACTORY_DEFAULTS.items(): + self._params.put_int(key, val) + self._clear_all_data() + self._params.remove("CarrotLearningRecommend") + self._params.remove("CarrotLearningHistory") + self._params.put_bool("CarrotLearningPopupReady", False) + + def is_active(self) -> bool: + return self._is_active() + + # ------------------------------------------------------------------ + # 내부 메서드 + # ------------------------------------------------------------------ + + def _is_active(self) -> bool: + return self._params.get_int("CarrotLearningActive") == 1 + + def _load(self): + """이전 세션 누적 데이터 복원""" + raw = self._params.get("CarrotLearningData") + if not raw: + return + try: + data = json.loads(raw) + # Phase 1 + loaded = data.get("gas_acc", [0.0] * _NUM_BANDS) + if len(loaded) == _NUM_BANDS: + self._gas_acc = [float(x) for x in loaded] + loaded_dec = data.get("gas_dec_acc", [0.0] * _NUM_BANDS) + if len(loaded_dec) == _NUM_BANDS: + self._gas_dec_acc = [float(x) for x in loaded_dec] + loaded_auto = data.get("gas_dec_auto_acc", [0.0] * _NUM_BANDS) + if len(loaded_auto) == _NUM_BANDS: + self._gas_dec_auto_acc = [float(x) for x in loaded_auto] + loaded_band = data.get("band_sec", [0.0] * _NUM_BANDS) + if len(loaded_band) == _NUM_BANDS: + self._band_sec = [float(x) for x in loaded_band] + # Phase 2 + lat = data.get("lateral", {}) + self._steer_acc = float(lat.get("steer_acc", 0.0)) + self._steer_count = int(lat.get("steer_count", 0)) + self._curve_entries = int(lat.get("curve_entries", 0)) + self._curve_overrides = int(lat.get("curve_overrides", 0)) + self._curve_overrides_understeer = int(lat.get("curve_overrides_understeer", 0)) + self._curve_overrides_inner_hugging = int(lat.get("curve_overrides_inner_hugging", 0)) + self._torque_curve_entries = int(lat.get("torque_curve_entries", 0)) + self._torque_curve_overrides = int(lat.get("torque_curve_overrides", 0)) + self._torque_curve_overrides_understeer = int(lat.get("torque_curve_overrides_understeer", 0)) + self._torque_curve_overrides_inner_hugging = int(lat.get("torque_curve_overrides_inner_hugging", 0)) + self._torque_straight_entries = int(lat.get("torque_straight_entries", 0)) + self._torque_straight_overrides = int(lat.get("torque_straight_overrides", 0)) + self._torque_error_count = int(lat.get("torque_error_count", 0)) + self._torque_osc_count = int(lat.get("torque_osc_count", 0)) + self._torque_steady_count = int(lat.get("torque_steady_count", 0)) + self._torque_straight_reversal = int(lat.get("torque_straight_reversal", 0)) + # Phase 3 + lon = data.get("lon", {}) + self._brake_count = int(lon.get("brake_count", 0)) + self._jlead_gas_acc = float(lon.get("jlead_gas_acc", 0.0)) + self._jlead_sec = float(lon.get("jlead_sec", 0.0)) + # Phase 4 + loaded4 = data.get("tfollow_gas_acc", [0.0] * 4) + if len(loaded4) == 4: + self._tfollow_gas_acc = [float(x) for x in loaded4] + loaded4_dec = data.get("tfollow_brake_acc", [0.0] * 4) + if len(loaded4_dec) == 4: + self._tfollow_brake_acc = [float(x) for x in loaded4_dec] + loaded4_auto = data.get("tfollow_brake_auto_acc", [0.0] * 4) + if len(loaded4_auto) == 4: + self._tfollow_brake_auto_acc = [float(x) for x in loaded4_auto] + self._tfollow_speed_brake_acc = float(data.get("tfollow_speed_brake_acc", 0.0)) + self._speed_factor_sec = float(data.get("speed_factor_sec", 0.0)) + self._brake_auto_count = int(data.get("brake_auto_count", 0)) + # Phase 5 + p5 = data.get("phase5", {}) + self._dyn_brake_count = int(p5.get("dyn_brake_count", 0)) + self._decel_brake_count = int(p5.get("decel_brake_count", 0)) + self._dyn_sec = float(p5.get("dyn_sec", 0.0)) + self._decel_sec = float(p5.get("decel_sec", 0.0)) + # Phase 6 + p6 = data.get("phase6", {}) + self._curve_override_gas_sec = float(p6.get("curve_override_gas_sec", 0.0)) + self._curve_override_brake_sec = float(p6.get("curve_override_brake_sec", 0.0)) + self._curve_override_brake_count = int(p6.get("curve_override_brake_count", 0)) + self._curve_max_decel = float(p6.get("curve_max_decel", 0.0)) + self._curve_steer_error_sec = float(p6.get("curve_steer_error_sec", 0.0)) + self._curve_brake_min_v = float(p6.get("curve_brake_min_v", 999.0)) + self._curve_active_sec = float(p6.get("curve_active_sec", 0.0)) + # Phase 7 + p7 = data.get("phase7", {}) + self._stop_events = int(p7.get("stop_events", 0)) + self._stop_brake_sec = float(p7.get("stop_brake_sec", 0.0)) + self._stop_gas_sec = float(p7.get("stop_gas_sec", 0.0)) + self._stop_harsh_count = int(p7.get("stop_harsh_count", 0)) + self._stop_lead_gap_sum = float(p7.get("stop_lead_gap_sum", 0.0)) + self._stop_lead_gap_count = int(p7.get("stop_lead_gap_count", 0)) + # Phase 8 + p8 = data.get("phase8", {}) + self._long_samples = int(p8.get("long_samples", 0)) + self._long_err_sum = float(p8.get("long_err_sum", 0.0)) + self._long_lag_count = int(p8.get("long_lag_count", 0)) + self._long_overshoot_count = int(p8.get("long_overshoot_count", 0)) + self._long_trans_samples = int(p8.get("long_trans_samples", 0)) + self._long_trans_lag = int(p8.get("long_trans_lag", 0)) + self._long_trans_err_sum = float(p8.get("long_trans_err_sum", 0.0)) + # Phase 9 (밴드별 리스트는 길이 검증 후 복원) + p9 = data.get("phase9", {}) + def _load_band_list(key, cast, default): + v = p9.get(key, None) + if isinstance(v, list) and len(v) == _NUM_BANDS: + return [cast(x) for x in v] + return [default] * _NUM_BANDS + self._manual_coast_sec = _load_band_list("manual_coast_sec", float, 0.0) + self._manual_coast_decel_sum = _load_band_list("manual_coast_decel_sum", float, 0.0) + self._manual_coast_decel_n = _load_band_list("manual_coast_decel_n", int, 0) + self._manual_gas_accel_sum = _load_band_list("manual_gas_accel_sum", float, 0.0) + self._manual_gas_n = _load_band_list("manual_gas_n", int, 0) + self._manual_brake_decel_sum = _load_band_list("manual_brake_decel_sum", float, 0.0) + self._manual_brake_n = _load_band_list("manual_brake_n", int, 0) + self._manual_gap_sum = float(p9.get("manual_gap_sum", 0.0)) + self._manual_gap_n = int(p9.get("manual_gap_n", 0)) + + # v3 Override Intensity & Dynamics Restore + override = data.get("override_dynamics", {}) + loaded_gmax_a = override.get("gas_max_accel", [0.0] * _NUM_BANDS) + if len(loaded_gmax_a) == _NUM_BANDS: + self._gas_max_accel = [float(x) for x in loaded_gmax_a] + loaded_gmax_p = override.get("gas_max_pedal", [0.0] * _NUM_BANDS) + if len(loaded_gmax_p) == _NUM_BANDS: + self._gas_max_pedal = [float(x) for x in loaded_gmax_p] + self._brake_max_decel = float(override.get("brake_max_decel", 0.0)) + self._brake_min_ttc = float(override.get("brake_min_ttc", 999.0)) + loaded_tf_min = override.get("tfollow_min_gap", [999.0] * 4) + if len(loaded_tf_min) == 4: + self._tfollow_min_gap = [float(x) for x in loaded_tf_min] + except Exception: + pass # 데이터 손상 시 기본값 유지 + + def _save(self): + """현재 누적 데이터를 Params에 저장""" + data = { + "gas_acc": self._gas_acc, + "band_sec": self._band_sec, + "gas_dec_acc": self._gas_dec_acc, + "lateral": { + "steer_acc": self._steer_acc, + "steer_count": self._steer_count, + "curve_entries": self._curve_entries, + "curve_overrides": self._curve_overrides, + "curve_overrides_understeer": self._curve_overrides_understeer, + "curve_overrides_inner_hugging": self._curve_overrides_inner_hugging, + "torque_curve_entries": self._torque_curve_entries, + "torque_curve_overrides": self._torque_curve_overrides, + "torque_curve_overrides_understeer": self._torque_curve_overrides_understeer, + "torque_curve_overrides_inner_hugging": self._torque_curve_overrides_inner_hugging, + "torque_straight_entries": self._torque_straight_entries, + "torque_straight_overrides": self._torque_straight_overrides, + "torque_error_count": self._torque_error_count, + "torque_osc_count": self._torque_osc_count, + "torque_steady_count": self._torque_steady_count, + "torque_straight_reversal": self._torque_straight_reversal, + }, + "lon": { + "brake_count": self._brake_count, + "jlead_gas_acc": self._jlead_gas_acc, + "jlead_sec": self._jlead_sec, + }, + "tfollow_gas_acc": self._tfollow_gas_acc, + "tfollow_brake_acc": self._tfollow_brake_acc, + "tfollow_brake_auto_acc": self._tfollow_brake_auto_acc, + "tfollow_speed_brake_acc": self._tfollow_speed_brake_acc, + "speed_factor_sec": self._speed_factor_sec, + "gas_dec_auto_acc": self._gas_dec_auto_acc, + "brake_auto_count": self._brake_auto_count, + "phase5": { + "dyn_brake_count": self._dyn_brake_count, + "decel_brake_count": self._decel_brake_count, + "dyn_sec": self._dyn_sec, + "decel_sec": self._decel_sec, + }, + "phase6": { + "curve_override_gas_sec": self._curve_override_gas_sec, + "curve_override_brake_sec": self._curve_override_brake_sec, + "curve_override_brake_count": self._curve_override_brake_count, + "curve_max_decel": self._curve_max_decel, + "curve_steer_error_sec": self._curve_steer_error_sec, + "curve_brake_min_v": self._curve_brake_min_v, + "curve_active_sec": self._curve_active_sec, + }, + "phase7": { + "stop_events": self._stop_events, + "stop_brake_sec": self._stop_brake_sec, + "stop_gas_sec": self._stop_gas_sec, + "stop_harsh_count": self._stop_harsh_count, + "stop_lead_gap_sum": self._stop_lead_gap_sum, + "stop_lead_gap_count": self._stop_lead_gap_count, + }, + "phase8": { + "long_samples": self._long_samples, + "long_err_sum": self._long_err_sum, + "long_lag_count": self._long_lag_count, + "long_overshoot_count": self._long_overshoot_count, + "long_trans_samples": self._long_trans_samples, + "long_trans_lag": self._long_trans_lag, + "long_trans_err_sum": self._long_trans_err_sum, + }, + "phase9": { + "manual_coast_sec": self._manual_coast_sec, + "manual_coast_decel_sum": self._manual_coast_decel_sum, + "manual_coast_decel_n": self._manual_coast_decel_n, + "manual_gas_accel_sum": self._manual_gas_accel_sum, + "manual_gas_n": self._manual_gas_n, + "manual_brake_decel_sum": self._manual_brake_decel_sum, + "manual_brake_n": self._manual_brake_n, + "manual_gap_sum": self._manual_gap_sum, + "manual_gap_n": self._manual_gap_n, + }, + "override_dynamics": { + "gas_max_accel": self._gas_max_accel, + "gas_max_pedal": self._gas_max_pedal, + "brake_max_decel": self._brake_max_decel, + "brake_min_ttc": self._brake_min_ttc, + "tfollow_min_gap": self._tfollow_min_gap, + }, + } + self._params.put("CarrotLearningData", json.dumps(data).encode('utf8')) + + def _fire_popup(self, source: str = "parking"): + """추천 계산 → Params 저장 → 팝업 신호. + source: 'parking' | 'stop' | 'timer' + """ + self._save() + recommendations = self._calc_recommendations() + if not recommendations: + return + self._params.put("CarrotLearningRecommend", json.dumps(recommendations).encode('utf8')) + self._params.put("CarrotLearningPopupSource", source) + self._params.put_bool("CarrotLearningPopupReady", True) + self._popup_cooldown_sec = 300.0 # 5분 쿨다운 (중복 팝업 방지) + + def _on_parking(self): + """주차 전환 시: _fire_popup 호출""" + self._fire_popup(source="parking") + self._engaged_elapsed_sec = 0.0 + self._pending_popup = False + + def _calc_recommendations(self) -> dict: + """Phase 1~4 추천값 계산. 추천 없으면 빈 dict 반환.""" + apply_lat = self._params.get_bool("CarrotTunerApplyLat") if self._params.get("CarrotTunerApplyLat") is not None else True + apply_long = self._params.get_bool("CarrotTunerApplyLong") if self._params.get("CarrotTunerApplyLong") is not None else True + + result = { + "가속 (Acceleration)": {}, + "조향 (Steering)": {}, + "주행 (Driving)": {}, + "거리 (Following Distance)": {}, + "동적제어 (Dynamic Control)": {}, + } + + # ── Phase 1: CruiseMaxVals ────────────────────────────────────── + drive_mode = self._params.get_int("MyDrivingMode") # 1: ECO, 2: SAFE, 3: NORMAL, 4: HIGH + if apply_long: + for i, acc_sec in enumerate(self._gas_acc): + key = _ACCEL_KEYS[i] + current_raw = self._params.get_int(key) + if current_raw <= 0: continue + + # 드라이브 모드별 동적 가속 제한 상한값 설정 (더 조밀하고 안전하게 제한) + max_limit = 200 + if key == "CruiseMaxVals0": # 0~10 km/h + max_limit = 220 + elif key == "CruiseMaxVals1": # 10~40 km/h + if drive_mode in (1, 2): # ECO, SAFE + max_limit = 170 + elif drive_mode == 3: # NORMAL + max_limit = 190 + elif drive_mode == 4: # HIGH + max_limit = 220 + elif key == "CruiseMaxVals2": # 40~60 km/h + if drive_mode in (1, 2): # ECO, SAFE + max_limit = 140 + elif drive_mode in (3, 4): # NORMAL, HIGH + max_limit = 150 + elif key == "CruiseMaxVals3": # 60~80 km/h + if drive_mode in (1, 2): # ECO, SAFE + max_limit = 110 + elif drive_mode in (3, 4): # NORMAL, HIGH + max_limit = 120 + elif key == "CruiseMaxVals4": # 80~110 km/h + max_limit = 100 + elif key == "CruiseMaxVals5": # 110~140 km/h + max_limit = 80 + elif key == "CruiseMaxVals6": # 140~ km/h + max_limit = 60 + + total_dec = self._gas_dec_acc[i] + self._gas_dec_auto_acc[i] + + # [상호 억제 로직: Accel Penalty Discount] + # 가속 중 '실제 제동'(gas_dec_acc — 선행차 추종 제동은 수집 단계에서 제외됨)이 + # 있었다면 가속이 굼떴다는 신호(gas help)를 그만큼 깎는다. + # (과거: 선행차 추종 제동까지 포함한 brake_count로 깎아, 선행차 급감속 때문에 + # 밟은 브레이크가 가속 한계를 잠식하던 신호 오귀속 버그 → gas_dec_acc[i] 기반으로 분리) + dampened_acc_sec = max(0.0, acc_sec - self._gas_dec_acc[i]) + + # 자율 가감속 요동(Auto-Surging) 방지: + # 운전자가 페달을 밟지 않았어도 자율 급가속과 자율 급감속이 동시에 잦은 경우, 가속 한계치를 최우선적으로 낮춥니다. + is_auto_surging = (self._gas_dec_auto_acc[i] >= 3.0 and self._brake_auto_count >= 3) + + # 오버라이드 중 기록된 피크 가속도 (m/s^2) + max_accel = self._gas_max_accel[i] + current_accel_limit = current_raw / 100.0 + accel_deficit = max_accel - current_accel_limit + + recommended_raw = current_raw + reason = "" + sec = 0.0 + + if current_raw > max_limit: + # 현재 설정값이 동적 상한선보다 큰 경우 강제 상한 제한으로 하향 조치 트리거 + recommended_raw = max_limit + reason = f"exceeds drive-mode limit ({max_limit})" + sec = 0.0 + elif dampened_acc_sec >= _GAS_THRESHOLD_SEC and not is_auto_surging: + # 피크 가속도 부족분에 비례하는 가변 증가율 적용 (최소 5%, 최대 25%) + if accel_deficit > 0.05: + dynamic_ratio = float(np.clip(accel_deficit / current_accel_limit * 0.8, 0.05, 0.25)) + else: + dynamic_ratio = _GAS_RECOMMEND_RATIO # 기본 10% + recommended_raw = min(max_limit, int(current_raw * (1.0 + dynamic_ratio))) + reason = f"gas help (deficit {accel_deficit:.2f}m/s^2, ratio {dynamic_ratio*100:.1f}%)" + sec = dampened_acc_sec + elif total_dec >= _GAS_REDUCE_THRESHOLD_SEC or is_auto_surging: + # 하향 신호는 가속-중-제동(gas_dec_acc, 선행차 제외) + 자율 급가속(gas_dec_auto_acc) + # + 가감속 요동(auto-surging)만 사용. 선행차 추종 제동은 더 이상 가속을 깎지 않는다. + recommended_raw = max(50, int(current_raw * (1.0 + _GAS_REDUCE_RATIO))) + recommended_raw = min(max_limit, recommended_raw) + if is_auto_surging: + reason = "excessive auto-surging penalty" + sec = self._gas_dec_auto_acc[i] + self._brake_auto_count + else: + reason = "aggressive accel (auto)" if self._gas_dec_auto_acc[i] > self._gas_dec_acc[i] else "too aggressive (manual)" + sec = total_dec + elif (i in _LOWBAND_DECAY_BANDS + and self._band_sec[i] >= _LOWBAND_DECAY_MIN_SEC + and acc_sec < _LOWBAND_DECAY_GAS_DEADBAND + and current_raw > _FACTORY_DEFAULTS.get(key, current_raw)): + # 저속 밴드(0/1) 양방향 균형: 그 속도대를 충분히 달렸는데 가속요청이 없으면 + # 기본값 쪽으로 약하게 하향(과거 단방향 상승 보완). 기본값 미만으로는 내리지 않음. + default_raw = _FACTORY_DEFAULTS[key] + recommended_raw = max(default_raw, current_raw - _LOWBAND_DECAY_STEP) + reason = f"low-speed relax (no gas request, default {default_raw})" + sec = round(self._band_sec[i], 1) + else: + continue + + if recommended_raw != current_raw: + # Max Delta Cap: 1회 적용 시 최대 변동폭을 ±15로 제한 + delta = recommended_raw - current_raw + if delta > 15: + recommended_raw = current_raw + 15 + elif delta < -15: + recommended_raw = current_raw - 15 + + result["가속 (Acceleration)"][key] = { + "current": current_raw, + "recommended": recommended_raw, + "band_kph": f"{_BP_KPH[i]}~{_BP_KPH[i+1] if i+1 < _NUM_BANDS else '∞'} km/h ({reason})", + "acc_sec": round(sec, 1), + } + + # ── Phase 2: 조향 패턴 추천 ───────────────────────────────────── + if apply_lat and self.is_angle_control: + # ── [A] 앵글 조향 차량 튜닝 ──────────────────────────────────── + # Phase 2a: PathOffset (차선중심 편차) — 신호를 조향각→차선중심 편차(m)로 교체. + # 조향각 평균은 PathOffset으로 바뀌지 않아 수렴 불가(runaway ±150, 차선 쏠림 유발). + if self._lane_center_n >= _LATERAL_MIN_SAMPLES: + # avg_lc = 차선중심의 y좌표(모델 프레임, ego=0). path.y와 같은 좌표계이므로 + # 경로를 avg_lc 방향으로 그만큼 옮기면 차가 차선중심에 수렴한다(프레임 무관). + avg_lc = self._lane_center_acc / self._lane_center_n + if abs(avg_lc) >= _PATH_OFFSET_LC_MIN_M: + current_offset = self._params.get_int("PathOffset") + delta = int(np.clip(avg_lc * 100.0 * _PATH_OFFSET_LC_GAIN, + -_PATH_OFFSET_STEP_MAX, _PATH_OFFSET_STEP_MAX)) + recommended = int(np.clip(current_offset + delta, + -_PATH_OFFSET_ABS_MAX, _PATH_OFFSET_ABS_MAX)) + if recommended != current_offset: + result["조향 (Steering)"]["PathOffset"] = { + "current": current_offset, + "recommended": recommended, + "band_kph": "차선중심 편차 보정", + "avg_m": round(avg_lc, 2), + } + + # Phase 2b: SteerActuatorDelay & SteerRatioRate + if self._curve_entries >= _LATERAL_MIN_CURVE: + override_ratio = self._curve_overrides / self._curve_entries + + # 개입 방향 비율 산출 + understeer_ratio = 0.0 + inner_hugging_ratio = 0.0 + if self._curve_overrides > 0: + understeer_ratio = self._curve_overrides_understeer / self._curve_overrides + inner_hugging_ratio = self._curve_overrides_inner_hugging / self._curve_overrides + + # (1) SteerActuatorDelay + current_delay = self._params.get_int("SteerActuatorDelay") + # 0 = liveDelay(자동, ~0.2s). 학습 조정 시 기준값으로 환산. + base_delay = current_delay if current_delay > 0 else _SAD_AUTO_BASELINE + recommended_delay = current_delay + if override_ratio >= 0.30: + if understeer_ratio >= 0.60: + recommended_delay = min(_SAD_LEARN_MAX, base_delay + _DELAY_STEP_UNIT) + elif inner_hugging_ratio >= 0.60: + recommended_delay = max(_SAD_LEARN_MIN, base_delay - _DELAY_STEP_UNIT) + # 안정 상태(개입 적음)에서는 값을 변경하지 않는다. + # (과거: max(50, current-10)로 인해 초기화 직후 안정 주행이면 무조건 50으로 튀던 버그) + + if recommended_delay != current_delay: + result["조향 (Steering)"]["SteerActuatorDelay"] = { + "current": current_delay, + "recommended": recommended_delay, + "band_kph": "커브 진입 지연 보정 (조향 지연 상향)" if (override_ratio >= 0.30 and understeer_ratio >= 0.60) else ("커브 진입 안쪽 쏠림 보정 (조향 지연 하향)" if (override_ratio >= 0.30 and inner_hugging_ratio >= 0.60) else "커브 진입 안정화 감쇄"), + "override_ratio": round(override_ratio * 100, 1), + } + + # (2) SteerRatioRate + current_sr_rate = self._params.get_int("SteerRatioRate") + if current_sr_rate <= 0: + current_sr_rate = 100 # 기본값 100% + recommended_sr = current_sr_rate + + if override_ratio >= 0.40: + if understeer_ratio >= 0.60: + recommended_sr = min(150, current_sr_rate + _SR_RATE_STEP_UNIT) + elif inner_hugging_ratio >= 0.60: + recommended_sr = max(90, current_sr_rate - _SR_RATE_STEP_UNIT) + elif override_ratio < 0.15 and current_sr_rate > 100: + # 개입이 적은 안정 상태 → 과거 '강화분'(>100)만 기본값(100) 쪽으로 소폭 환원. + # (과거: 90까지 내려 기본값보다 약한 조향=언더스티어를 유발 → 커브 바깥 쏠림 악화. + # inner_hugging 보정으로 100 미만이 된 경우는 정당한 약화이므로 건드리지 않는다.) + recommended_sr = max(100, current_sr_rate - 2) + + if recommended_sr != current_sr_rate: + result["조향 (Steering)"]["SteerRatioRate"] = { + "current": current_sr_rate, + "recommended": recommended_sr, + "band_kph": "커브 강한 개입 대응 (조향 강화)" if (override_ratio >= 0.40 and understeer_ratio >= 0.60) else ("커브 안쪽 쏠림 개입 대응 (조향 감쇄)" if (override_ratio >= 0.40 and inner_hugging_ratio >= 0.60) else "부드러운 조향 감속 보정"), + "override_ratio": round(override_ratio * 100, 1), + } + + elif apply_lat: + # ── [B] 토크 조향 차량 튜닝 ──────────────────────────────────── + # 1. SteerActuatorDelay + if self._torque_curve_entries >= 100: # 최소 샘플 수 (약 10초) + override_ratio = self._torque_curve_overrides / self._torque_curve_entries + + understeer_ratio = 0.0 + inner_hugging_ratio = 0.0 + if self._torque_curve_overrides > 0: + understeer_ratio = self._torque_curve_overrides_understeer / self._torque_curve_overrides + inner_hugging_ratio = self._torque_curve_overrides_inner_hugging / self._torque_curve_overrides + + current_delay = self._params.get_int("SteerActuatorDelay") + # 0 = liveDelay(자동, ~0.2s). 학습 조정 시 기준값으로 환산. + base_delay = current_delay if current_delay > 0 else _SAD_AUTO_BASELINE + recommended_delay = current_delay + if override_ratio >= 0.30: + if understeer_ratio >= 0.60: + recommended_delay = min(_SAD_LEARN_MAX, base_delay + _DELAY_STEP_UNIT) + elif inner_hugging_ratio >= 0.60: + recommended_delay = max(_SAD_LEARN_MIN, base_delay - _DELAY_STEP_UNIT) + # 안정 상태(개입 적음)에서는 값을 변경하지 않는다(50으로 튀던 버그 제거). + + if recommended_delay != current_delay: + result["조향 (Steering)"]["SteerActuatorDelay"] = { + "current": current_delay, + "recommended": recommended_delay, + "band_kph": "토크 커브 진입 지연 보정" if (override_ratio >= 0.30 and understeer_ratio >= 0.60) else ("토크 커브 안쪽 쏠림 보정" if (override_ratio >= 0.30 and inner_hugging_ratio >= 0.60) else "토크 커브 안정화 감쇄"), + "override_ratio": round(override_ratio * 100, 1), + } + + # 2. LateralTorqueAccelFactor & LateralTorqueKf (횡가속도 비례 피드포워드) + # ※ 방향성 주의(문서2): 토크 = 목표횡가속도 / AccelFactor + friction + # AccelFactor는 분모 → 값↑ = 토크↓ (조향 약화), 값↓ = 토크↑ (조향 강화) + # Kf는 피드포워드 게인 → 값↑ = 토크↑ (조향 강화) + # understeer(OP가 덜 꺾어 운전자가 더 꺾음) → 토크 강화: factor↓ + Kf↑ + # inner_hugging(OP가 과하게 꺾어 운전자가 풀어줌) → 토크 약화: factor↑ + Kf↓ + current_factor = self._params.get_int("LateralTorqueAccelFactor") + current_kf = self._params.get_int("LateralTorqueKf") + + recommended_factor = current_factor + recommended_kf = current_kf + steer_dir = "" + + if override_ratio >= 0.40 and understeer_ratio >= 0.60: + # 조향 부족 → 토크 강화 (factor↓, Kf↑) + recommended_factor = _clamp_spec("LateralTorqueAccelFactor", current_factor - 100) + recommended_kf = _clamp_spec("LateralTorqueKf", current_kf + 3) + steer_dir = "understeer" + elif override_ratio >= 0.40 and inner_hugging_ratio >= 0.60: + # 안쪽 쏠림 → 토크 약화 (factor↑, Kf↓) + recommended_factor = _clamp_spec("LateralTorqueAccelFactor", current_factor + 100) + recommended_kf = _clamp_spec("LateralTorqueKf", current_kf - 3) + steer_dir = "inner_hugging" + elif override_ratio < 0.15: + # 개입이 거의 없는 안정 상태 → Kf를 0까지 무한 감쇄하지 않고 + # 공장기본값(100)쪽으로만 약하게 수렴(양방향 균형). FF를 0으로 깎아 + # Kp 의존(진동 영역)으로 끌고 가던 단방향 drift 제거. + kf_default = _FACTORY_DEFAULTS["LateralTorqueKf"] + recommended_kf = _clamp_spec("LateralTorqueKf", _decay_toward(current_kf, kf_default, 1)) + steer_dir = "stable" + + if recommended_factor != current_factor: + result["조향 (Steering)"]["LateralTorqueAccelFactor"] = { + "current": current_factor, + "recommended": recommended_factor, + "band_kph": "토크 커브 조향 강화 (선회력↑)" if steer_dir == "understeer" else "토크 커브 안쪽 쏠림 완화 (선회력↓)", + "override_ratio": round(override_ratio * 100, 1), + } + if recommended_kf != current_kf: + result["조향 (Steering)"]["LateralTorqueKf"] = { + "current": current_kf, + "recommended": recommended_kf, + "band_kph": "토크 커브 피드포워드 강화" if steer_dir == "understeer" else ("토크 피드포워드 약화" if steer_dir == "inner_hugging" else "토크 피드포워드 안정화 감쇄"), + "override_ratio": round(override_ratio * 100, 1), + } + + # 3. LateralTorqueFriction (직선 미세 지연 보정) + if self._torque_straight_entries >= 200: # 직선/완만 구간 약 20초 + straight_override_ratio = self._torque_straight_overrides / self._torque_straight_entries + current_friction = self._params.get_int("LateralTorqueFriction") + recommended_friction = current_friction + + if straight_override_ratio >= 0.35: # 미세 보정 구간 개입 높음 → 마찰보상 상향 + recommended_friction = min(300, current_friction + 5) + elif straight_override_ratio < 0.08: # 개입 없음 → 마찰보상 소폭 하향 수렴 + recommended_friction = max(10, current_friction - 2) + + if recommended_friction != current_friction: + result["조향 (Steering)"]["LateralTorqueFriction"] = { + "current": current_friction, + "recommended": recommended_friction, + "band_kph": "직선 미세 불감대 해소" if straight_override_ratio >= 0.35 else "미세 조향 마찰보상 최적화", + "override_ratio": round(straight_override_ratio * 100, 1), + } + + # 4. LateralTorqueKiV / LateralTorqueKpV (피드백 게인) ─ 진동 vs 정상상태 lag 구분 + # 핵심 개선: 추종오차의 '크기'가 아니라 '부호 패턴'으로 비례게인 방향을 정한다. + # (구버전: error_count≥50이면 무조건 KpV 상향 → 진동이 만든 오차도 '게인부족'으로 + # 오인해 KpV를 계속 올림 → 고속 직선 핑퐁/커브 탈출 진동 = 발산 양의 피드백) + # - 진동(부호반전 多)·고속 직선 핑퐁 → Kp 과다 → KpV/KiV 하향(default 아래도 허용) + # - 정상상태 lag(부호 일관) → FF(Kf) 강화 우선, Kp는 Kf 충분할 때만 보조 상향 + # - 추종 양호 → 공장기본값으로 약하게 수렴(양방향 균형) + current_kiv = self._params.get_int("LateralTorqueKiV") + current_kpv = self._params.get_int("LateralTorqueKpV") + current_kf2 = self._params.get_int("LateralTorqueKf") # 블록 독립적으로 재조회(스코프 안전) + kiv_default = _FACTORY_DEFAULTS["LateralTorqueKiV"] # 10 + kpv_default = _FACTORY_DEFAULTS["LateralTorqueKpV"] # 100 + recommended_kiv = current_kiv + recommended_kpv = current_kpv + kp_dir = "" + + curve_osc_dominant = (self._torque_osc_count >= _TORQUE_OSC_MIN + and self._torque_osc_count > self._torque_steady_count) + straight_pingpong = self._torque_straight_reversal >= _TORQUE_PINGPONG_MIN_COUNT + + if straight_pingpong or curve_osc_dominant: + # 진동/핑퐁 → 비례·적분 게인 하향. default(100) 아래로도 수렴 허용(소유주 최적 KpV=70). + recommended_kpv = max(_TORQUE_KPV_MIN, current_kpv - _TORQUE_KPV_STEP) + recommended_kiv = max(_TORQUE_KIV_MIN, current_kiv - 1) + kp_dir = "oscillation" + elif self._torque_steady_count >= _TORQUE_STEADY_MIN and self._torque_osc_count < _TORQUE_OSC_MIN: + # 정상상태 lag(부호 일관): Kf(FF) 강화는 위 understeer 로직이 담당. + # Kf가 이미 충분(≥기준)할 때만 잔차 보정용으로 Kp를 소폭 상향. + if current_kf2 >= _TORQUE_KF_FF_PREF: + recommended_kpv = min(_TORQUE_KPV_MAX, current_kpv + _TORQUE_KPV_STEP) + recommended_kiv = min(_TORQUE_KIV_MAX, current_kiv + 1) + kp_dir = "steady_lag" + elif self._torque_curve_entries >= 100 and self._torque_error_count < 20: + # 충분한 커브 주행에도 추종오차가 적음(양호) → 기본값 쪽으로 약하게 수렴 + recommended_kpv = _decay_toward(current_kpv, kpv_default, 2) + recommended_kiv = _decay_toward(current_kiv, kiv_default, 1) + kp_dir = "good" + + _KP_MSG = { + "oscillation": "고속 핑퐁/진동 감지 → 비례게인 하향", + "steady_lag": "정상상태 추종지연 → 비례게인 보조 상향", + "good": "조향 추종 양호 → 비례게인 기본값 수렴", + } + _KI_MSG = { + "oscillation": "진동 억제 → 적분게인 하향", + "steady_lag": "정상상태 오차 → 적분게인 보조 상향", + "good": "조향 추종 양호 → 적분게인 기본값 수렴", + } + if recommended_kpv != current_kpv: + result["조향 (Steering)"]["LateralTorqueKpV"] = { + "current": current_kpv, + "recommended": recommended_kpv, + "band_kph": _KP_MSG.get(kp_dir, ""), + "error_ticks": self._torque_error_count, + "osc_ticks": self._torque_osc_count + self._torque_straight_reversal, + } + if recommended_kiv != current_kiv: + result["조향 (Steering)"]["LateralTorqueKiV"] = { + "current": current_kiv, + "recommended": recommended_kiv, + "band_kph": _KI_MSG.get(kp_dir, ""), + "error_ticks": self._torque_error_count, + } + + # ── Phase 3: JLeadFactor3 (수동 제동) ─────────────────────────── + jlead_candidate = None + if apply_long: + total_brake = self._brake_count + self._brake_auto_count + # '늦은 제동'으로 판단되는 경우에만 상향. (정상적인 여유 제동의 단방향 누적 방지) + # - 자율 급제동(brake_auto_count): 시스템이 늦게 스스로 급제동 = 명백한 지연 신호 + # - 수동 제동이라도 TTC가 충분히 낮을 때만(늦은 시점) 인정 + late_braking = (self._brake_auto_count >= 3) or (self._brake_min_ttc < _JLEAD_LATE_TTC) + # 선제적 '교육용' 제동: 위험할 만큼 늦지는 않지만(TTC 3.5~6s) 선행차에 접근하며 + # 운전자가 직접, 약하지 않은 감속(≥1.0m/s^2)으로 반복 제동 = '시스템 반응이 굼뜨다'를 + # 가르치는 신호. (과거: TTC<3.5의 위험한 늦은 제동만 인정해 이 교육이 무시됐음) + proactive_braking = (not late_braking + and _JLEAD_LATE_TTC <= self._brake_min_ttc < _JLEAD_PROACTIVE_TTC + and self._brake_max_decel >= _JLEAD_PROACTIVE_DECEL) + if total_brake >= _BRAKE_MIN_COUNT and (late_braking or proactive_braking): + current_jlead = self._params.get_int("JLeadFactor3") + + if late_braking: + # TTC와 감속량을 반영한 동적 증가 계산 + ttc_factor = float(np.clip((4.5 - self._brake_min_ttc) / 2.0, 0.0, 1.0)) + decel_factor = float(np.clip((self._brake_max_decel - 0.8) / 1.0, 0.0, 1.0)) + dynamic_step = int(10 + 25 * max(ttc_factor, decel_factor)) + # Max Delta Cap: 1회당 변화폭을 최대 15로 제한 + dynamic_step = min(15, dynamic_step) + reason = "late braking (auto)" if self._brake_auto_count > self._brake_count else "approaching lead (manual)" + else: + # 선제 교육 제동은 약하게(+5)만 반영 → 정상 여유 제동의 과도 누적 방지 + dynamic_step = _JLEAD_PROACTIVE_STEP + reason = "proactive braking (manual)" + + recommended = min(_JLEAD_MAX_RECO, current_jlead + dynamic_step) # 상한 80 -> 20 (runaway 방지) + if recommended != current_jlead: + jlead_candidate = { + "current": current_jlead, + "recommended": recommended, + "band_kph": f"{reason} (TTC min {self._brake_min_ttc:.1f}s, step +{dynamic_step})", + "brake_count": round(total_brake, 1), + "_signal": total_brake, + } + elif self._jlead_gas_acc >= _JLEAD_GAS_THRESHOLD_SEC: + current_jlead = self._params.get_int("JLeadFactor3") + recommended = max(0, current_jlead + _JLEAD_REDUCE_STEP) + recommended = min(_JLEAD_MAX_RECO, recommended) # 상한 20 적용 + if recommended != current_jlead: + jlead_candidate = { + "current": current_jlead, + "recommended": recommended, + "band_kph": "too aggressive (gas override)", + "gas_sec": round(self._jlead_gas_acc, 1), + "_signal": self._jlead_gas_acc, + } + elif self._jlead_sec >= 180.0 and total_brake < 2 and self._jlead_gas_acc < 1.0: + current_jlead = self._params.get_int("JLeadFactor3") + default_jlead = _FACTORY_DEFAULTS["JLeadFactor3"] # 0 + if current_jlead > default_jlead: + recommended = max(default_jlead, current_jlead - 5) + if recommended != current_jlead: + jlead_candidate = { + "current": current_jlead, + "recommended": recommended, + "band_kph": "jlead decay (no late braking, default 0)", + "sec": round(self._jlead_sec, 1), + "_signal": 0.1, + } + + # ── Phase 4: TFollowGap (선행차 추종 중 거리 좁히기 가속 개입) ── + # EnableSpeedTF<0(속도 앵커 모드)에선 GAP1~4가 '버튼 단계'가 아니라 '속도 앵커'이므로 + # 현재 버튼 gap을 학습하는 이 로직이 의미상 맞지 않는다(예: 고속 추종은 gap3/4가 지배하는데 + # 현재 버튼 gap2만 축소됨). 이 경우 자동학습을 끄고 사용자가 앵커값을 직접 설정하도록 둔다. + if apply_long and self._params.get_int("EnableSpeedTF") >= 0: + for i, gas_sec in enumerate(self._tfollow_gas_acc): + key = _TFOLLOW_KEYS[i] + name = _TFOLLOW_NAMES[i] + current_val = self._params.get_int(key) + if current_val <= 0: continue + + total_dec = self._tfollow_brake_acc[i] + self._tfollow_brake_auto_acc[i] + + recommended_val = current_val + if gas_sec >= _TFOLLOW_GAS_THRESHOLD_SEC: + # 실제 차간 거리 오차에 기반한 동적 감소 계산. + # 운전자가 현재 설정보다 실제로 더 바짝(gap_diff>MIN) 따라간 근거가 있을 때만 축소한다. + # min_gap이 설정 이상(gap_diff<=MIN)이면 '더 좁히려는' 근거가 없으므로 미조정 + # (과거: 근거 없이도 매번 -5 축소 → 120으로 재설정해도 계속 줄어드는 폭주 버그). + target_val = int(self._tfollow_min_gap[i] * 100) + gap_diff = current_val - target_val + if gap_diff <= _TFOLLOW_GAP_DIFF_MIN: + continue + dynamic_step = float(np.clip(int(gap_diff * 0.5), 5, 25)) + recommended_val = max(70, current_val - int(dynamic_step)) + reason = f"too wide (gap diff {gap_diff*0.01:.2f}s, step -{int(dynamic_step)})" + sec = gas_sec + elif total_dec >= _TFOLLOW_BRAKE_THRESHOLD_SEC: + # 제동 급박도에 비례한 차간 거리 동적 증가 + dynamic_step = int(5 + float(np.clip((4.0 - self._brake_min_ttc) * 5, 0, 10))) + recommended_val = min(200, current_val + dynamic_step) + reason = "hunting detected (auto)" if self._tfollow_brake_auto_acc[i] > self._tfollow_brake_acc[i] else f"too short (step +{dynamic_step})" + sec = total_dec + else: + continue + + if recommended_val != current_val: + result["거리 (Following Distance)"][key] = { + "current": current_val, + "recommended": recommended_val, + "band_kph": f"highway ≥{_TFOLLOW_MIN_V_KPH:.0f}km/h ({name}, {reason})", + "sec": round(sec, 1), + } + + # ── Phase 6: TFollowSpeedFactor (고속 차간 거리 보정) ─────────── + if apply_long and self._tfollow_speed_brake_acc >= _TFOLLOW_SPEED_FACTOR_THRESHOLD_SEC: + current_sf = self._params.get_int("TFollowSpeedFactor") + recommended_sf = min(100, current_sf + _TFOLLOW_SPEED_FACTOR_STEP) + if recommended_sf != current_sf: + result["거리 (Following Distance)"]["TFollowSpeedFactor"] = { + "current": current_sf, + "recommended": recommended_sf, + "band_kph": "high-speed safety (>80km/h)", + "sec": round(self._tfollow_speed_brake_acc, 1), + } + elif apply_long and self._speed_factor_sec >= 180.0 and self._tfollow_speed_brake_acc < 1.0: + current_sf = self._params.get_int("TFollowSpeedFactor") + default_sf = _FACTORY_DEFAULTS["TFollowSpeedFactor"] + if current_sf > default_sf: + recommended_sf = max(default_sf, current_sf - 5) + if recommended_sf != current_sf: + result["거리 (Following Distance)"]["TFollowSpeedFactor"] = { + "current": current_sf, + "recommended": recommended_sf, + "band_kph": "high-speed decay (no braking, default 0)", + "sec": round(self._speed_factor_sec, 1), + } + + # ── Phase 5a: DynamicTFollow (앞차 급감속 반응 민감도) ─────────── + dyn_candidate = None + if apply_long and self._dyn_brake_count >= _DYN_TFOLLOW_BRAKE_MIN: + current_dyn = self._params.get_int("DynamicTFollow") + recommended_dyn = min(_DYN_TFOLLOW_MAX, current_dyn + _DYN_TFOLLOW_STEP) + if recommended_dyn != current_dyn: + dyn_candidate = { + "current": current_dyn, + "recommended": recommended_dyn, + "band_kph": "lead decel override", + "brake_count": self._dyn_brake_count, + "_signal": self._dyn_brake_count, + } + elif apply_long and self._dyn_sec >= 180.0 and self._dyn_brake_count < 1: + current_dyn = self._params.get_int("DynamicTFollow") + default_dyn = _FACTORY_DEFAULTS["DynamicTFollow"] + if current_dyn > default_dyn: + recommended_dyn = max(default_dyn, current_dyn - 3) + if recommended_dyn != current_dyn: + dyn_candidate = { + "current": current_dyn, + "recommended": recommended_dyn, + "band_kph": "dyn decay (no lead decel, default 0)", + "brake_count": self._dyn_brake_count, + "_signal": 0.1, + } + + # ── Phase 5b: TFollowDecelBoost (내 차 감속 중 버퍼 확보) ──────── + boost_candidate = None + if apply_long and self._decel_brake_count >= _DECEL_BOOST_BRAKE_MIN: + current_boost = self._params.get_int("TFollowDecelBoost") + recommended_boost = min(_DECEL_BOOST_MAX, current_boost + _DECEL_BOOST_STEP) + if recommended_boost != current_boost: + boost_candidate = { + "current": current_boost, + "recommended": recommended_boost, + "band_kph": "decel braking", + "brake_count": self._decel_brake_count, + "_signal": self._decel_brake_count, + } + elif apply_long and self._decel_sec >= 180.0 and self._decel_brake_count < 1: + current_boost = self._params.get_int("TFollowDecelBoost") + default_boost = _FACTORY_DEFAULTS["TFollowDecelBoost"] + if current_boost > default_boost: + recommended_boost = max(default_boost, current_boost - 3) + if recommended_boost != current_boost: + boost_candidate = { + "current": current_boost, + "recommended": recommended_boost, + "band_kph": "boost decay (no decel override, default 10)", + "brake_count": self._decel_brake_count, + "_signal": 0.1, + } + + # ── 브레이크 파라미터 충돌 방지: 동시 다중 적용 시 과보정 억제 ── + # JLeadFactor3 / DynamicTFollow / TFollowDecelBoost 는 모두 + # '제동 여유 확대' 방향이므로 동시 적용 시 복합 효과로 과보수화 위험. + # → 가장 강한 시그널 1개만 이번 세션에 추천하고, 나머지는 '다음 세션' 권고. + brake_candidates = [ + ("JLeadFactor3", "주행 (Driving)", jlead_candidate), + ("DynamicTFollow", "동적제어 (Dynamic Control)", dyn_candidate), + ("TFollowDecelBoost", "동적제어 (Dynamic Control)", boost_candidate), + ] + active = [(name, group, c) for name, group, c in brake_candidates if c is not None] + + if len(active) <= 1: + # 충돌 없음: 그냥 모두 추가 + for name, group, c in active: + entry = {k: v for k, v in c.items() if k != "_signal"} + result[group][name] = entry + else: + # 2개 이상 동시 발동 → 시그널이 가장 강한 것 1개만 추천 + active_sorted = sorted(active, key=lambda x: x[2]["_signal"], reverse=True) + winner_name, winner_group, winner_c = active_sorted[0] + entry = {k: v for k, v in winner_c.items() if k != "_signal"} + # 다음 세션에 재평가하도록 안내 메시지 추가 + deferred_names = [n for n, g, c in active_sorted[1:]] + entry["band_kph"] = entry["band_kph"] + f" ※다음세션권고:{','.join(deferred_names)}" + result[winner_group][winner_name] = entry + + # ── Phase 6: Curve Speed (AutoCurveSpeedFactor) ───────────────── + # 실제 커브 감속 knob은 AutoCurveSpeedFactor (carrot_man.vturn_speed에서 사용). + # 값↑ = 곡률을 더 민감하게 인식 → 커브 목표속도↓ → 커브 시작 전 더 일찍/충분히 감속. + # (과거: 미사용 파라미터 AutoCurveSpeedAggressiveness를 조정해 실주행 효과가 없었음) + key = "AutoCurveSpeedFactor" + current_raw = self._cur_or_default(key) + + recommended_raw = current_raw + reason = "" + sec = 0.0 + + # 하한(floor) 도달 여부: 커브에서 이미 하한속도까지 내려간 상태로 제동했다면 + # AutoCurveSpeedFactor를 올려도 목표속도가 더 낮아지지 못해(=floor 클램프) 효과가 없다. + # → 이 경우 factor 상향 추천을 생략해 '헛도는 학습'(상한까지 무의미한 누적)을 방지. + lower_limit = self._params.get_int("AutoCurveSpeedLowerLimit") + if lower_limit <= 0: + lower_limit = 30 + floor_bound = (self._curve_brake_min_v <= lower_limit + 3) + + has_brake_signal = (self._curve_override_brake_count >= 3 or self._curve_override_brake_sec >= 5.0) + # (A) 조향 추적오차는 더 이상 곡선감속 상향에 쓰지 않는다. + # 커브의 조향오차는 언더스티어/횡방향 튜닝 문제(SteerActuatorDelay/SteerRatioRate가 담당)이지 + # '감속 부족'이 아니다. 과거엔 이걸 factor↑로 오인해 상한까지 런어웨이(과감속)했음. + default_factor = _PARAM_SPEC["AutoCurveSpeedFactor"]["default"] # 120 + # (B) 편안한 커브: 충분히 커브를 달렸는데 제동/가속 개입이 거의 없고 감속도도 낮음 + comfortable = (self._curve_active_sec >= 30.0 + and not has_brake_signal + and self._curve_override_gas_sec < 5.0 + and self._curve_max_decel < 1.5) + + # 커브에서 제동 개입 = 감속 부족 → Factor 상향(더 일찍 더 감속) + if apply_long and has_brake_signal and not floor_bound: + recommended_raw = _clamp_spec(key, current_raw + 10) + reason = f"brake in curve (count {self._curve_override_brake_count}, peak decel {self._curve_max_decel:.2f}m/s^2)" + sec = self._curve_override_brake_sec + # floor-bound(이미 하한속도에서 제동)면 factor 무효 → 추천 생략(헛도는 학습 방지) + # (C) 커브에서 가속 개입 = 과도한 감속 → Factor 하향(덜 감속). 임계 10s→5s로 완화. + # 단, 커브 조향 추적오차(언더스티어)가 누적된 상태(≥5s)면 '더 빠른 커브 진입'은 + # 바깥 쏠림을 악화시키므로 하향을 보류한다(횡방향 튜닝이 먼저 해결되어야 함). + elif apply_long and self._curve_override_gas_sec >= 5.0 and self._curve_steer_error_sec < 5.0: + recommended_raw = _clamp_spec(key, current_raw - 10) + reason = f"gas in curve (acc {self._curve_override_gas_sec:.1f}s)" + sec = self._curve_override_gas_sec + # (B) 개입 없이 편안히 통과 + factor가 기본값보다 높으면 → default 쪽으로 소폭 자동 회복(decay) + elif apply_long and comfortable and current_raw > default_factor: + recommended_raw = _clamp_spec(key, max(default_factor, current_raw - 5)) + reason = f"comfortable curves, decay toward default ({self._curve_active_sec:.0f}s)" + sec = self._curve_active_sec + + if recommended_raw != current_raw: + if "곡선 (Curve)" not in result: + result["곡선 (Curve)"] = {} + result["곡선 (Curve)"][key] = { + "current": current_raw, + "recommended": recommended_raw, + "reason": reason, + "band_kph": "curve deceleration", + "sec": sec, + } + + # ── Phase 7: 정차/출발 (StoppingAccel / VEgoStopping / StopDistanceCarrot) ── + if apply_long and self._stop_events >= _STOP_MIN_EVENTS: + brake_per = self._stop_brake_sec / self._stop_events + gas_per = self._stop_gas_sec / self._stop_events + harsh_ratio = self._stop_harsh_count / self._stop_events + + # (1) StoppingAccel: 정차 접근 중 제동 vs 가속 개입 균형 + # 음수일수록 일찍·강하게 제동 시작 (문서2: 0=강한제동, 음수=일찍 약하게) + cur_sa = self._params.get_int("StoppingAccel") + rec_sa = cur_sa + sa_reason = "" + if brake_per >= 0.8 and brake_per > gas_per: + rec_sa = _clamp_spec("StoppingAccel", cur_sa - _STOP_ACCEL_STEP) + sa_reason = f"weak/late stop (brake {brake_per:.1f}s/stop)" + elif gas_per >= 0.8 and gas_per > brake_per: + rec_sa = _clamp_spec("StoppingAccel", cur_sa + _STOP_ACCEL_STEP) + sa_reason = f"early/hard stop (gas {gas_per:.1f}s/stop)" + if rec_sa != cur_sa: + result["주행 (Driving)"]["StoppingAccel"] = { + "current": cur_sa, + "recommended": rec_sa, + "band_kph": f"정차 제동 시점/강도 ({sa_reason})", + "stop_events": self._stop_events, + } + + # (2) VEgoStopping: 거친 정지 비율이 높으면 정지 판정 속도 상향 → 부드럽게 멈춤 + if harsh_ratio >= 0.5: + cur_ve = self._cur_or_default("VEgoStopping") + rec_ve = _clamp_spec("VEgoStopping", cur_ve + _STOP_VEGO_STEP) + if rec_ve != cur_ve: + result["주행 (Driving)"]["VEgoStopping"] = { + "current": cur_ve, + "recommended": rec_ve, + "band_kph": f"거친 정지 완화 (harsh {harsh_ratio*100:.0f}%)", + "stop_events": self._stop_events, + } + elif harsh_ratio <= 0.2: + # 정지가 부드러움: 기본값 방향으로 약하게 감쇠(단방향 래칫 방지). + # 근본 원인(예: Delay runaway 과제동)이 고쳐진 뒤 올라간 값이 눌러앉는 것을 막는다. + cur_ve = self._cur_or_default("VEgoStopping") + default_ve = _PARAM_SPEC["VEgoStopping"]["default"] + if cur_ve > default_ve: + rec_ve = _decay_toward(cur_ve, default_ve, 2) + if rec_ve != cur_ve: + result["주행 (Driving)"]["VEgoStopping"] = { + "current": cur_ve, + "recommended": rec_ve, + "band_kph": f"정지 양호 → 기본값 감쇠 (harsh {harsh_ratio*100:.0f}%)", + "stop_events": self._stop_events, + } + + # (3) StopDistanceCarrot: 선행차 뒤 최종 정지 거리 보정 + if self._stop_lead_gap_count >= 3: + avg_gap = self._stop_lead_gap_sum / self._stop_lead_gap_count + cur_sd = self._cur_or_default("StopDistanceCarrot") + rec_sd = cur_sd + sd_reason = "" + if avg_gap >= _STOP_GAP_WIDE_M and gas_per > brake_per: + rec_sd = _clamp_spec("StopDistanceCarrot", cur_sd - _STOP_DIST_STEP) + sd_reason = f"stops too far ({avg_gap:.1f}m)" + elif avg_gap <= _STOP_GAP_NEAR_M or brake_per > gas_per: + rec_sd = _clamp_spec("StopDistanceCarrot", cur_sd + _STOP_DIST_STEP) + sd_reason = f"stops too close ({avg_gap:.1f}m)" + if rec_sd != cur_sd: + result["주행 (Driving)"]["StopDistanceCarrot"] = { + "current": cur_sd, + "recommended": rec_sd, + "band_kph": f"정지 거리 보정 ({sd_reason})", + "stop_events": self._stop_events, + } + + # ── Phase 8: 종방향 PID (LongTuningKf / LongActuatorDelay / LongTuningKpV) ── + if apply_long and self._long_samples >= _LONG_MIN_SAMPLES: + # lag(둔감)는 transient(지령 변화 추종) 구간에서만 평가한다. transient 표본이 + # 충분치 않으면 0으로 둬서 Kf/Delay를 헛상향하지 않는다. (정속 경사 오프셋 배제) + if self._long_trans_samples >= _LONG_TRANS_MIN: + lag_ratio = self._long_trans_lag / self._long_trans_samples + mean_abs_err = self._long_trans_err_sum / self._long_trans_samples + else: + lag_ratio = 0.0 + mean_abs_err = self._long_err_sum / self._long_samples + overshoot_ratio = self._long_overshoot_count / self._long_samples + + if lag_ratio >= _LONG_LAG_RATIO and lag_ratio > overshoot_ratio: + # 둔감(추종 지연) 우세 → 피드포워드/지연보정 상향 (선제 가감속) + cur_kf = self._cur_or_default("LongTuningKf") + rec_kf = _clamp_spec("LongTuningKf", cur_kf + _LONG_KF_STEP) + if rec_kf != cur_kf: + result["주행 (Driving)"]["LongTuningKf"] = { + "current": cur_kf, + "recommended": rec_kf, + "band_kph": f"가감속 둔감 보정 (lag {lag_ratio*100:.0f}%, err {mean_abs_err:.2f})", + "samples": self._long_samples, + } + cur_ld = self._cur_or_default("LongActuatorDelay") + rec_ld = _clamp_spec("LongActuatorDelay", cur_ld + _LONG_DELAY_STEP) + if rec_ld != cur_ld: + result["주행 (Driving)"]["LongActuatorDelay"] = { + "current": cur_ld, + "recommended": rec_ld, + "band_kph": f"가감속 선제 반영 (lag {lag_ratio*100:.0f}%)", + "samples": self._long_samples, + } + elif overshoot_ratio >= _LONG_OVERSHOOT_RATIO and overshoot_ratio > lag_ratio: + # 진동(과반응) 우세 → 비례게인 하향 + cur_kp = self._cur_or_default("LongTuningKpV") + rec_kp = _clamp_spec("LongTuningKpV", cur_kp - _LONG_KP_STEP) + if rec_kp != cur_kp: + result["주행 (Driving)"]["LongTuningKpV"] = { + "current": cur_kp, + "recommended": rec_kp, + "band_kph": f"가감속 진동 억제 (overshoot {overshoot_ratio*100:.0f}%)", + "samples": self._long_samples, + } + elif lag_ratio < 0.15 and overshoot_ratio < 0.15: + # 건강(추종 양호): Kf/Delay를 기본값 방향으로 약하게 감쇠 — 단방향 래칫 방지. + # (0.15~0.30 사이는 히스테리시스 데드밴드로 무조치. _clamp_spec이 규격 밖으로 + # 올라가버린 과거 값(예: Delay 90)도 상한으로 즉시 회수한다.) + for pkey, step in (("LongTuningKf", _LONG_KF_STEP), ("LongActuatorDelay", _LONG_DELAY_STEP)): + cur = self._cur_or_default(pkey) + rec = _clamp_spec(pkey, _decay_toward(cur, _PARAM_SPEC[pkey]["default"], step)) + if rec != cur: + result["주행 (Driving)"][pkey] = { + "current": cur, + "recommended": rec, + "band_kph": f"양호 → 기본값 감쇠 (lag {lag_ratio*100:.0f}%)", + "samples": self._long_samples, + } + + # ── Phase 9: 수동주행 코스팅 측정 → LongCoastBand 추천 ──────────── + # 사람이 무페달로 코스팅할 때의 자연 감속(회생제동/엔진브레이크)을 측정하여, + # 종방향 코스팅 데드밴드를 차의 코스트 권한 일부 범위 내에서 직접 보정한다. + if apply_long: + coast_n = sum(self._manual_coast_decel_n) + coast_sec = sum(self._manual_coast_sec) + if coast_n >= _MANUAL_COAST_MIN_N and coast_sec >= _MANUAL_COAST_MIN_SEC: + mean_coast_decel = sum(self._manual_coast_decel_sum) / coast_n # m/s² (양수) + # 데드밴드(m/s²) = 측정 코스팅 감속 × 게인, 안전범위로 클램프 후 cm/s² 정수화 + rec_band = _clamp_spec("LongCoastBand", + round(np.clip(mean_coast_decel * _MANUAL_COAST_GAIN, 0.15, 0.40) * 100)) + cur_band = self._params.get_int("LongCoastBand") + # 5cm/s²(=0.05 m/s²) 이상 차이날 때만 추천(미세 변동 잡음 억제) + if abs(rec_band - cur_band) >= 5: + result["주행 (Driving)"]["LongCoastBand"] = { + "current": cur_band, + "recommended": rec_band, + "band_kph": f"수동 코스팅 감속 {mean_coast_decel:.2f}m/s² 측정 → 코스팅(회생제동) 데드밴드 보정", + "samples": coast_n, + } + + return {k: v for k, v in result.items() if v} + + def apply_recommendations(self): + """UI [적용] 버튼 클릭 시 호출. 추천 적용 + 데이터 초기화.""" + raw = self._params.get("CarrotLearningRecommend") + if not raw: + return + try: + recommendations = json.loads(raw) + except Exception: + return + + apply_lat = self._params.get_bool("CarrotTunerApplyLat") if self._params.get("CarrotTunerApplyLat") is not None else True + apply_long = self._params.get_bool("CarrotTunerApplyLong") if self._params.get("CarrotTunerApplyLong") is not None else True + + lat_keys = { + "PathOffset", "SteerActuatorDelay", "SteerRatioRate", + "LateralTorqueAccelFactor", "LateralTorqueKf", "LateralTorqueFriction", + "LateralTorqueKiV", "LateralTorqueKpV" + } + + applied_changes = {} + for group in recommendations: + g_items = {} + for key in recommendations[group]: + info = recommendations[group][key] + is_lat = key in lat_keys + if is_lat and not apply_lat: + continue + if not is_lat and not apply_long: + continue + self._params.put_int(key, info["recommended"]) + g_items[key] = info + if g_items: + applied_changes[group] = g_items + + if applied_changes: + import datetime + timestamp_str = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + history_id = datetime.datetime.now().strftime("%Y%m%d%H%M%S%f") + + new_entry = { + "id": history_id, + "timestamp": timestamp_str, + "changes": applied_changes + } + + history_raw = self._params.get("CarrotLearningHistory") + history_arr = [] + if history_raw: + try: + history_arr = json.loads(history_raw) + if not isinstance(history_arr, list): + history_arr = [] + except Exception: + history_arr = [] + + history_arr.insert(0, new_entry) + history_arr = history_arr[:50] + self._params.put("CarrotLearningHistory", json.dumps(history_arr).encode('utf8')) + + + # ── 적용된 Phase의 누적치만 선택적으로 리셋 ───────────────────── + # 적용되지 않은 Phase(특히 느리게 쌓이는 조향)는 데이터를 보존해 + # 무관한 적용 때문에 학습 진척이 초기화되지 않도록 한다. + # (제동 3종 충돌 방지로 '다음 세션 권고'된 항목의 근거 데이터도 자연히 이월됨) + applied_phases = set() + for group in applied_changes: + for key in applied_changes[group]: + ph = _KEY_RESET_PHASE.get(key) + if ph is not None: + applied_phases.add(ph) + phase_reset = { + 1: self._reset_phase1, 2: self._reset_phase2, 3: self._reset_phase3, + 4: self._reset_phase4, 5: self._reset_phase5, 6: self._reset_phase6, + 7: self._reset_phase7, 8: self._reset_phase8, 9: self._reset_phase9, + } + for ph in applied_phases: + phase_reset[ph]() + + # 주행 중 팝업 타이머 리셋 (적용 후 재학습 시작) + self._engaged_elapsed_sec = 0.0 + self._check_elapsed_sec = 0.0 + self._pending_popup = False + self._popup_cooldown_sec = 0.0 + + # 보존된 Phase 데이터가 재부팅에도 살아남도록 즉시 저장(remove 대신 _save). + self._save() + self._params.remove("CarrotLearningRecommend") + self._params.remove("CarrotLearningPopupSource") + self._params.put_bool("CarrotLearningPopupReady", False) + # 주의: 적용된 추천값이 다음 학습 라운드의 새 기준점이 됩니다. + # (과거: 여기서 튜닝 파라미터를 공장초기값으로 원복하여 방금 적용한 추천을 + # 덮어쓰는 버그가 있었음 → 제거. 누적 데이터만 초기화하고 파라미터는 유지.) + + +# ══════════════════════════════════════════════════════════════════════ +# Driving Style Profiler (DSP) +# 수동 주행 데이터를 분석하여 오픈파일럿 종방향 파라미터 초기값을 추천. +# CarrotLearner(engaged 전용)와 상호 독립적으로 작동. +# ══════════════════════════════════════════════════════════════════════ + +# DSP 상수 +_DSP_MIN_ACCEL_SAMPLES = 50 # 가속 프로파일 최소 샘플 수 (~5초) +_DSP_MIN_FOLLOW_SAMPLES = 30 # 차간 거리 최소 안정 추종 샘플 수 (~3초) +_DSP_MIN_BRAKE_EVENTS = 5 # 제동 시점 최소 이벤트 수 +_DSP_MIN_DRIVE_TIME_SEC = 600.0 # 최소 수동 주행 시간 (10분) +_DSP_SAFETY_TF_MIN = 0.70 # TFollowGap 안전 하한선 (초) +_DSP_SAFETY_TF_MAX = 2.50 # TFollowGap 안전 상한선 (초) +_DSP_SAFETY_ACCEL_MAX = 250 # CruiseMaxVals 안전 상한 (2.50 m/s²) +_DSP_SAFETY_ACCEL_MIN = 50 # CruiseMaxVals 안전 하한 (0.50 m/s²) +_DSP_JLEAD_SAFETY_MULTIPLIER = 1.1 # 시스템 지연 보정 계수 (인간 0.2초 vs 시스템 0.5초) + + +class DrivingStyleProfiler: + """수동 주행 성향 프로파일러 (Driving Style Profiler). + + 오픈파일럿 미인게이지 상태(수동 운전)에서의 가속/제동/차간거리 + 패턴을 수집하여 오픈파일럿 종방향 파라미터 초기값을 추천합니다. + + CarrotLearner와 역할 분담: + - DSP: 초기값 설정 (Personalization, engaged=False) + - CarrotLearner: 지속적 미세 조정 (Error Correction, engaged=True) + """ + + def __init__(self): + self._params = Params() + self._active = False # DSP 활성화 여부 (프로파일링 미완료 시 활성) + + # ── 가속 프로파일 (속도 대역별) ── + self._accel_samples = [0] * _NUM_BANDS # 샘플 수 + self._accel_sum = [0.0] * _NUM_BANDS # aEgo 합산 + self._accel_max = [0.0] * _NUM_BANDS # 대역별 최대 가속도 + + # ── 차간 거리 프로파일 (안정 추종 구간) ── + self._follow_samples = 0 # 안정 추종 샘플 수 + self._follow_time_sum = 0.0 # 시간거리(dRel/vEgo) 합산 + self._follow_stable_streak = 0 # 연속 안정 추종 프레임 수 + self._follow_speed_time_pairs = [] # (speed_kph, time_gap) 쌍 저장 (최대 500) + + # ── 제동 프로파일 ── + self._brake_events = 0 # 수동 제동 이벤트 수 + self._brake_drel_sum = 0.0 # 제동 시점 dRel 합산 + self._brake_vrel_sum = 0.0 # 제동 시점 vRel 합산 + + # ── 세션 관리 ── + self._manual_drive_time = 0.0 # 수동 주행 누적 시간 + self._prev_brake = False + self._has_manual_driven = False # 수동 주행이 한 번이라도 있었는지 + + self._load() + + def _is_active(self) -> bool: + """DSP가 아직 프로파일링이 완료되지 않았을 때만 활성화.""" + return not self._params.get_bool("CarrotDSPComplete") + + def _load(self): + """저장된 DSP 데이터 복원.""" + raw = self._params.get("CarrotDSPData") + if not raw: + return + try: + data = json.loads(raw) + self._accel_samples = [int(x) for x in data.get("accel_samples", [0]*_NUM_BANDS)] + self._accel_sum = [float(x) for x in data.get("accel_sum", [0.0]*_NUM_BANDS)] + self._accel_max = [float(x) for x in data.get("accel_max", [0.0]*_NUM_BANDS)] + self._follow_samples = int(data.get("follow_samples", 0)) + self._follow_time_sum = float(data.get("follow_time_sum", 0.0)) + self._follow_speed_time_pairs = data.get("follow_speed_time_pairs", []) + self._brake_events = int(data.get("brake_events", 0)) + self._brake_drel_sum = float(data.get("brake_drel_sum", 0.0)) + self._brake_vrel_sum = float(data.get("brake_vrel_sum", 0.0)) + self._manual_drive_time = float(data.get("manual_drive_time", 0.0)) + except Exception: + pass + + def _save(self): + """DSP 데이터를 Params에 저장.""" + data = { + "accel_samples": self._accel_samples, + "accel_sum": self._accel_sum, + "accel_max": self._accel_max, + "follow_samples": self._follow_samples, + "follow_time_sum": self._follow_time_sum, + "follow_speed_time_pairs": self._follow_speed_time_pairs[-500:], # 최대 500개 유지 + "brake_events": self._brake_events, + "brake_drel_sum": self._brake_drel_sum, + "brake_vrel_sum": self._brake_vrel_sum, + "manual_drive_time": self._manual_drive_time, + } + self._params.put("CarrotDSPData", json.dumps(data).encode('utf8')) + + def update(self, v_ego_kph: float, engaged: bool, gear_park: bool, + a_ego: float = 0.0, brake_pressed: bool = False, + lead_drel: float = 0.0, lead_v_kph: float = 0.0): + """매 프레임 호출 (carrot_functions.py에서). + + engaged=False(수동 주행) 상태에서만 데이터를 수집합니다. + gear_park=True 시 추천을 계산합니다. + """ + if not self._is_active(): + return + + # 주차 전환 시: 저장 → 추천 계산 + if gear_park: + if self._has_manual_driven: + self._save() + self._calc_and_publish_recommendations() + self._has_manual_driven = False + return + + # ── 수동 주행 데이터만 수집 (오픈파일럿 미인게이지) ── + if engaged: + return # 인게이지 상태에서는 CarrotLearner가 담당 + + if v_ego_kph < 3.0: + self._prev_brake = brake_pressed + return # 극저속은 의미 없음 + + self._has_manual_driven = True + self._manual_drive_time += 0.1 # ~DT_MDL (100ms) + + # ── (A) 가속 프로파일 수집 ── + # 가속 페달을 밟고 있을 때(aEgo > 0.3)의 가속도를 속도 대역별로 기록 + if a_ego > 0.3: + idx = _speed_band(v_ego_kph) + self._accel_samples[idx] += 1 + self._accel_sum[idx] += a_ego + self._accel_max[idx] = max(self._accel_max[idx], a_ego) + + # ── (B) 차간 거리 프로파일 수집 ── + # 선행차가 존재하고, 안정적 추종 중(가/감속 없이 일정 거리 유지)일 때 + v_ego_ms = v_ego_kph / 3.6 + if lead_drel > 5.0 and v_ego_ms > 3.0 and abs(a_ego) < 0.5: + time_gap = lead_drel / v_ego_ms + if 0.5 < time_gap < 4.0: # 비정상적 값 필터링 + self._follow_stable_streak += 1 + if self._follow_stable_streak >= 10: # 1초 이상 안정 추종 + self._follow_samples += 1 + self._follow_time_sum += time_gap + if len(self._follow_speed_time_pairs) < 500: + self._follow_speed_time_pairs.append([round(v_ego_kph, 1), round(time_gap, 3)]) + else: + self._follow_stable_streak = 0 + else: + self._follow_stable_streak = 0 + + # ── (C) 제동 프로파일 수집 ── + # 선행차가 가까울 때 브레이크를 밟는 시점의 거리와 상대속도를 기록 + if brake_pressed and not self._prev_brake: + if lead_drel > 0 and lead_drel < 100.0: + self._brake_events += 1 + self._brake_drel_sum += lead_drel + v_rel = v_ego_kph - lead_v_kph + self._brake_vrel_sum += max(0, v_rel) + + self._prev_brake = brake_pressed + + def _calc_and_publish_recommendations(self): + """수집된 수동 주행 데이터를 분석하여 초기 파라미터 추천을 생성.""" + if self._manual_drive_time < _DSP_MIN_DRIVE_TIME_SEC: + return # 최소 주행 시간 미달 + + result = {} + + # ── (A) CruiseMaxVals 초기값 추천 ── + accel_recs = {} + for i in range(_NUM_BANDS): + if self._accel_samples[i] < _DSP_MIN_ACCEL_SAMPLES: + continue + avg_accel = self._accel_sum[i] / self._accel_samples[i] + max_accel = self._accel_max[i] + # 운전자의 평균 가속도와 최대 가속도를 모두 고려하여 크루즈 최고 가속도 한계를 설정 + # OP의 최고 한계이므로 너무 답답하지 않게 평균값보다 훨씬 높게 설정합니다. + derived_accel = max(avg_accel * 1.5, max_accel * 0.85) + recommended_raw = int(np.clip(derived_accel * 100, _DSP_SAFETY_ACCEL_MIN, _DSP_SAFETY_ACCEL_MAX)) + current_raw = self._params.get_int(_ACCEL_KEYS[i]) + if current_raw <= 0: + continue + # 현재 값과 차이가 5% 이상일 때만 추천 + if abs(recommended_raw - current_raw) >= current_raw * 0.05: + accel_recs[_ACCEL_KEYS[i]] = { + "current": current_raw, + "recommended": recommended_raw, + "band_kph": f"{_BP_KPH[i]}~{_BP_KPH[i+1] if i+1 < _NUM_BANDS else '∞'} km/h", + "avg_accel": round(avg_accel, 2), + "max_accel": round(self._accel_max[i], 2), + "samples": self._accel_samples[i], + } + if accel_recs: + result["🚀 가속 초기값 (Accel Profile)"] = accel_recs + + # ── (B) TFollowGap 초기값 추천 ── + follow_recs = {} + if self._follow_samples >= _DSP_MIN_FOLLOW_SAMPLES: + avg_time_gap = self._follow_time_sum / self._follow_samples + # 기계가 유지하는 간격은 사람이 인지하는 것보다 멀게 느껴지므로 0.9를 곱해 보정 + adjusted_time_gap = avg_time_gap * 0.9 + # 안전 하한선 적용 + safe_time_gap = max(_DSP_SAFETY_TF_MIN, min(_DSP_SAFETY_TF_MAX, adjusted_time_gap)) + recommended_raw = int(safe_time_gap * 100) + + # 현재 GAP2 (Standard) 기준으로 비교 + current_gap2 = self._params.get_int("TFollowGap2") + if current_gap2 > 0 and abs(recommended_raw - current_gap2) >= 5: + follow_recs["TFollowGap2"] = { + "current": current_gap2, + "recommended": recommended_raw, + "band_kph": "Standard GAP (수동 주행 평균 차간 시간)", + "avg_time_gap": round(avg_time_gap, 2), + "samples": self._follow_samples, + } + + # 나머지 GAP 레벨도 비례 조정 + # GAP1 = 추천값 * 0.85, GAP3 = 추천값 * 1.12, GAP4 = 추천값 * 1.23 + gap_ratios = {"TFollowGap1": 0.85, "TFollowGap3": 1.12, "TFollowGap4": 1.23} + for key, ratio in gap_ratios.items(): + derived = int(np.clip(safe_time_gap * ratio * 100, _DSP_SAFETY_TF_MIN * 100, _DSP_SAFETY_TF_MAX * 100)) + current = self._params.get_int(key) + if current > 0 and abs(derived - current) >= 5: + follow_recs[key] = { + "current": current, + "recommended": derived, + "band_kph": f"{key} (비례 조정, ratio={ratio})", + "avg_time_gap": round(safe_time_gap * ratio, 2), + "samples": self._follow_samples, + } + + if follow_recs: + result["🛣️ 차간거리 초기값 (Follow Profile)"] = follow_recs + + # ── (C) JLeadFactor3 초기값 추천 ── + if self._brake_events >= _DSP_BRAKE_EVENTS_MIN: + avg_brake_drel = self._brake_drel_sum / self._brake_events + # 운전자의 평균 제동 시작 거리 → JLeadFactor3 매핑 + # 거리가 먼 운전자 = 조기 제동 선호 = 높은 JLeadFactor3 + # 거리가 짧은 운전자 = 늦은 제동 선호 = 낮은 JLeadFactor3 + # 선형 매핑: dRel 15m → 50, dRel 60m → 120 + raw_factor = int(np.interp(avg_brake_drel, [15, 60], [50, 120])) + # 시스템 지연 보정 적용 (1.1배) + recommended_jlead = int(np.clip(raw_factor * _DSP_JLEAD_SAFETY_MULTIPLIER, 50, 200)) + current_jlead = self._params.get_int("JLeadFactor3") + if current_jlead > 0 and abs(recommended_jlead - current_jlead) >= 10: + result["🚙 제동 시점 초기값 (Brake Profile)"] = { + "JLeadFactor3": { + "current": current_jlead, + "recommended": recommended_jlead, + "band_kph": f"평균 제동 시작 거리: {avg_brake_drel:.1f}m (시스템 지연 보정 x{_DSP_JLEAD_SAFETY_MULTIPLIER})", + "avg_brake_drel": round(avg_brake_drel, 1), + "brake_events": self._brake_events, + } + } + + if not result: + return # 추천할 항목 없음 + + # 추천 결과 저장 및 팝업 트리거 + self._params.put("CarrotDSPRecommend", json.dumps(result).encode('utf8')) + self._params.put_bool("CarrotDSPPopupReady", True) + + def apply_recommendations(self): + """UI [적용] 버튼 클릭 시 호출. 추천 적용 + 프로파일링 완료 마킹.""" + raw = self._params.get("CarrotDSPRecommend") + if not raw: + return + try: + recommendations = json.loads(raw) + except Exception: + return + for group in recommendations: + for key in recommendations[group]: + info = recommendations[group][key] + self._params.put_int(key, info["recommended"]) + + # 프로파일링 완료 마킹 → 이후 DSP는 비활성화, CarrotLearner가 미세 조정 + self._params.put_bool("CarrotDSPComplete", True) + self._params.remove("CarrotDSPData") + self._params.remove("CarrotDSPRecommend") + self._params.put_bool("CarrotDSPPopupReady", False) + + def get_profile_progress(self) -> dict: + """현재 프로파일링 진행 상황을 반환 (UI 표시용).""" + total_min = self._manual_drive_time / 60.0 + accel_ready = sum(1 for s in self._accel_samples if s >= _DSP_MIN_ACCEL_SAMPLES) + return { + "drive_time_min": round(total_min, 1), + "accel_bands_ready": f"{accel_ready}/{_NUM_BANDS}", + "follow_samples": self._follow_samples, + "brake_events": self._brake_events, + "is_ready": self._manual_drive_time >= _DSP_MIN_DRIVE_TIME_SEC, + } + + +# _DSP_BRAKE_EVENTS_MIN: 별도 상수 정의 (상단의 Phase3 상수와 공유 불가) +_DSP_BRAKE_EVENTS_MIN = 5 diff --git a/selfdrive/carrot/carrot_man.py b/selfdrive/carrot/carrot_man.py index 1498463b49..68302211c9 100644 --- a/selfdrive/carrot/carrot_man.py +++ b/selfdrive/carrot/carrot_man.py @@ -1171,29 +1171,47 @@ def carrot_curve_speed(self, sm): return self.vturn_speed(sm['carState'], sm) def vturn_speed(self, CS, sm): - TARGET_LAT_A = 1.9 # m/s^2 + # 거리 인지 곡률-추종 가변 속도 제어 + # - 예측경로의 '최대 곡률 1점'만 보던 방식(단일 목표)에서, + # 각 예측점의 곡률로 안전속도를 구하고 거리·편안한 감속도를 반영해 + # '지금 허용 가능한 속도'의 최소값을 목표로 삼는 방식으로 변경. + # - 효과: 멀리 있는 급커브엔 일찍 완만히 감속을 시작하고(사전 감속), + # 정점 통과 후 곡률이 풀리면 자동으로 증속(램프 초입>중반>후반 가변). + TARGET_LAT_A = 1.9 # m/s^2 커브에서 허용할 횡가속도(높을수록 빠른 커브주행) + A_DECEL = 0.85 # m/s^2 커브 접근 시 가정하는 편안한 감속도(낮을수록 더 일찍 감속) + # 1.2→0.85: 감속을 더 일찍 시작해 정점 전에 완료(정점 이후 가속) modelData = sm['modelV2'] - v_ego = max(CS.vEgo, 0.1) - # Set the curve sensitivity - orientation_rate = np.array(modelData.orientationRate.z) * self.autoCurveSpeedFactor + + orientation_rate = np.array(modelData.orientationRate.z) velocity = np.array(modelData.velocity.x) + distances = np.array(modelData.position.x) + + n = int(min(len(orientation_rate), len(velocity), len(distances))) + if n == 0: + return 250.0 + + orientation_rate = orientation_rate[:n] + velocity = np.maximum(velocity[:n], 0.1) + distances = np.maximum(distances[:n], 0.0) + + # 진행방향(좌/우) 부호는 가장 굽은 지점 기준 + max_index = int(np.argmax(np.abs(orientation_rate))) + curv_direction = np.sign(orientation_rate[max_index]) or 1.0 - # Get the maximum lat accel from the model - max_index = np.argmax(np.abs(orientation_rate)) - curv_direction = np.sign(orientation_rate[max_index]) - max_pred_lat_acc = np.amax(np.abs(orientation_rate) * velocity) + # 각 예측점의 곡률(1/m) = |yawRate|/v, AutoCurveSpeedFactor로 민감도 스케일 + curvature = (np.abs(orientation_rate) / velocity) * self.autoCurveSpeedFactor + curvature = np.maximum(curvature, 1e-5) - # Get the maximum curve based on the current velocity - max_curve = max_pred_lat_acc / (v_ego**2) + # 각 점에서 허용 가능한 안전속도(횡가속도 한계 기준) + v_safe = np.sqrt(TARGET_LAT_A / curvature) # m/s - # Set the target lateral acceleration - adjusted_target_lat_a = TARGET_LAT_A + # 그 점까지 편안히 감속해 도달하려면 '지금' 낼 수 있는 최대 속도 + # v_now^2 = v_safe^2 + 2*a*d (거리 d가 멀수록 더 높은 현재속도 허용 → 일찍 완만히 감속) + v_allow = np.sqrt(v_safe**2 + 2.0 * A_DECEL * distances) - # Get the target velocity for the maximum curve - #turnSpeed = max(abs(adjusted_target_lat_a / max_curve)**0.5 * 3.6, self.autoCurveSpeedLowerLimit) - turnSpeed = max(abs(adjusted_target_lat_a / max_curve)**0.5 * 3.6, 5) - turnSpeed = min(turnSpeed, 250) + turnSpeed = float(np.min(v_allow)) * 3.6 # km/h + turnSpeed = min(max(turnSpeed, 5.0), 250.0) return turnSpeed * curv_direction def carrot_navi_thread(self): diff --git a/selfdrive/carrot/carrot_serv.py b/selfdrive/carrot/carrot_serv.py index 17ca5d39fe..2cff7ef45c 100644 --- a/selfdrive/carrot/carrot_serv.py +++ b/selfdrive/carrot/carrot_serv.py @@ -78,6 +78,22 @@ } import collections + +# 부드러운 재가속 auto(목표속도 slew 리미터). +# 목표속도(desiredSpeed)를 실제 도달목표(설정속도 이하)로 "완만히 상승, 즉시 하강"시킨다. +# - AUTO_RISE_KPH_S: 상승 기울기(kph/초). 사용자 체감상 초당 약 +5 상승. +# - AUTO_MAX_HEAD_KPH: 목표가 현재속도보다 앞설 수 있는 최대치(가속 권한/안티와인드업). +# 작으면 가속이 약해 저속에서 못 오르고(데드락), 크면 급가속. 5→10으로 상향해 데드락 해소. +# - AUTO_MAX_HEAD_LEAD_KPH: 선행차가 확실히 빠르게 앞서갈 때만 헤드룸을 넓혀 답답함 없이 캐치업. +AUTO_RISE_KPH_S = 5.0 +AUTO_MAX_HEAD_KPH = 10.0 +AUTO_MAX_HEAD_LEAD_KPH = 18.0 +AUTO_DT = 0.05 # update_navi 주기(20Hz) +# 선행차 캐치업 시 상승률. 기본 5kph/s(=1.39m/s²)는 정차출발 가속부스트(상한 ~2.47m/s²=8.9kph/s) +# 보다 낮아, desiredSpeed가 못 따라오면 순항목표가 현재속도에 붙어 가속을 되레 1.39로 제한한다. +# 캐치업 구간에선 상승률을 높여(부스트가 실제로 쓰이도록) 병목을 제거. 평시 완만함은 기본값 유지. +AUTO_RISE_LEAD_KPH_S = 15.0 + class CarrotServ: def __init__(self): self.params = Params() @@ -188,6 +204,7 @@ def __init__(self): self.gas_override_speed = 0 self.gas_pressed_state = False self.source_last = "none" + self.auto_speed = 0.0 # 재가속 auto slew 상태(kph) self.debugText = "" @@ -991,7 +1008,44 @@ def update_navi(self, remote_ip, sm, pm, vturn_speed, coords, distances, route_s desired_speed, source = min(speed_n_sources, key=lambda x: x[0]) + # 재가속 auto(목표속도 slew 리미터): 목표속도를 실제 도달목표(설정속도 이하)로 + # "완만히 상승, 즉시 하강"시켜 모든 상황에서 급가속을 막으면서도 목표에 반드시 도달·유지한다. + # - 상승: 초당 AUTO_RISE_KPH_S만큼, 단 현재속도+헤드룸을 넘지 않음(안티와인드업). + # - 하강(감속 소스 등장): 즉시 반영(안전). 정지/미인게이지: 현재속도에서 재시작. + # - 선행차가 확실히 빠르면 헤드룸을 넓혀 답답함 없이 캐치업(실제 거리는 추종 로직이 관리). + # 과거 "현재속도+5 고정" 방식은 목표가 실제 도달점에 못 오르고 저속에서 가속 데드락을 + # 유발했고, 선행차 예외(+15)가 목표를 설정속도 위로 부풀렸다 → slew 방식으로 두 문제 해소. if CS is not None: + v_max = CS.vCruise if 0.0 < CS.vCruise < 200.0 else 250.0 + auto_target = min(desired_speed, v_max) # 실제 도달목표(설정속도 이하) + + max_head = AUTO_MAX_HEAD_KPH + rise = AUTO_RISE_KPH_S + if sm.alive['radarState']: + lead = sm['radarState'].leadOne + if lead.status and lead.vLeadK * CV.MS_TO_KPH > v_ego_kph + 3.0: + max_head = AUTO_MAX_HEAD_LEAD_KPH # 선행차 캐치업: 헤드룸 확장 + rise = AUTO_RISE_LEAD_KPH_S # + 상승률↑ → 가속부스트/Gap1 병목 제거 + + engaged = sm.alive['selfdriveState'] and sm['selfdriveState'].enabled + if not engaged: + self.auto_speed = v_ego_kph # 미인게이지: 현재속도에서 재시작 + elif auto_target <= self.auto_speed: + self.auto_speed = auto_target # 감속: 즉시 하강 + else: + # 완만 상승. 정지 상태(v_ego≈0)에서도 상승시켜야 출발이 가능하다(현재속도+헤드룸까지). + # 목표를 0에 못박으면 desiredSpeed=0이 되어 선행차 출발/신호 출발 시 가속 데드락 발생. + self.auto_speed = min(auto_target, self.auto_speed + rise * AUTO_DT) + # 안티와인드업: 목표가 현재속도+헤드룸 이상 앞서지 않게(급가속 방지). 감속목표(<현재)엔 영향 없음. + # 정지 시엔 현재속도+헤드룸(=헤드룸)까지만 목표가 오르며, 실제 정지 유지는 downstream + # MPC(선행차 추종/e2e stop, v_cruise=0)가 담당하므로 크리프 없이 안전하게 출발 대기한다. + self.auto_speed = min(self.auto_speed, v_ego_kph + max_head) + self.auto_speed = max(self.auto_speed, min(auto_target, v_ego_kph)) # 현재속도 이하로 불필요 하강 방지 + + if self.auto_speed < desired_speed: + desired_speed = self.auto_speed + source = "auto" # UI 표시 라벨 + if source != self.source_last: self.gas_override_speed = 0 self.gas_pressed_state = CS.gasPressed @@ -1006,6 +1060,12 @@ def update_navi(self, remote_ip, sm, pm, vturn_speed, coords, distances, route_s if desired_speed < self.gas_override_speed: source = "gas" desired_speed = self.gas_override_speed + # 가스 오버라이드도 auto 램프 헤드룸(현재속도+head) 안으로 제한 → 페달을 뗀 뒤 + # 목표가 현재속도보다 크게 앞서 급가속하거나 'gas' 표시가 오래 남는 것을 방지. + # (auto_speed는 이미 설정속도·저속제한을 반영하므로 안전한 상한이다) + if self.auto_speed < desired_speed: + desired_speed = self.auto_speed + source = "auto" self.debugText += f"route={route_speed:.1f}"#f"desired={desired_speed:.1f},{source},g={self.gas_override_speed:.0f}" @@ -1169,6 +1229,9 @@ def set_time(self, epoch_time, timezone): try: subprocess.run(["sudo", "ln", "-s", zoneinfo_path, localtime_path], check=True) print(f"Timezone successfully set to: {timezone}") + # 앱이 보낸 타임존은 최고 우선순위로 기록 -> timed.py의 wifi/gps 추정이 덮어쓰지 않음 + self.params.put("TimezoneName", timezone) + self.params.put("TimezoneSource", "app") except subprocess.CalledProcessError as e: print(f"Failed to set timezone to {timezone}: {e}") diff --git a/selfdrive/carrot/server/services/params.py b/selfdrive/carrot/server/services/params.py index e05b5f8348..c794d68a66 100644 --- a/selfdrive/carrot/server/services/params.py +++ b/selfdrive/carrot/server/services/params.py @@ -252,8 +252,13 @@ def put_typed(params: "Params", key: str, value: Any, p: Optional[Dict[str, Any] obj = json.loads(value) if isinstance(value, str) else value params.put(key, obj) else: - # BYTES or anything unmapped → best-effort string write. - params.put(key, str(value)) + # BYTES or anything unmapped → bytes write. params_pyx 타입표는 (bytes, BYTES)만 + # 허용하므로 str을 그대로 넘기면 TypeError("Type mismatch") → UI "Save failed". + # (예: Clear All Logs의 CarrotLearningHistory="[]" 저장 실패 버그) + if isinstance(value, (bytes, bytearray, memoryview)): + params.put(key, bytes(value)) + else: + params.put(key, str(value).encode("utf-8")) def set_param_value(name: str, value: Any, p: Optional[Dict[str, Any]] = None) -> None: diff --git a/selfdrive/carrot/web/css/pages/tuner.css b/selfdrive/carrot/web/css/pages/tuner.css new file mode 100644 index 0000000000..425f137bc1 --- /dev/null +++ b/selfdrive/carrot/web/css/pages/tuner.css @@ -0,0 +1,288 @@ +/* Auto-Tuner page — portrait-first layout for the C4 web app. + Mirrors the on-device AutoTunerHistoryPanel but stacks the (landscape) two-column + list+graph vertically so the trend graph gets full width on a tall screen. */ + +.page--tuner { + overflow-y: auto; + -webkit-overflow-scrolling: touch; +} + +.tuner-wrap { + max-width: 720px; + margin: 0 auto; + padding: 12px 14px calc(28px + env(safe-area-inset-bottom)); + display: flex; + flex-direction: column; + gap: 12px; + color: var(--md-on-surface); +} + +.tuner-header { + display: flex; + align-items: center; + justify-content: space-between; +} +.tuner-title { + margin: 0; + font-size: 22px; + font-weight: 700; +} +.tuner-icon-btn { + width: 40px; + height: 40px; + border: none; + border-radius: 10px; + background: var(--md-surface-cont-h); + color: var(--md-on-surface); + font-size: 20px; + cursor: pointer; +} +.tuner-icon-btn:active { background: var(--md-surface-cont-hh); } + +/* status chips */ +.tuner-chips { + display: flex; + flex-wrap: wrap; + gap: 8px; +} +.tuner-chip { + flex: 1 1 28%; + min-width: 96px; + height: 44px; + border: none; + border-radius: 10px; + font-size: 14px; + font-weight: 700; + color: #fff; + cursor: pointer; +} +.tuner-chip.is-on { background: var(--md-success, #178644); } +.tuner-chip.is-off { background: #45505f; } + +/* destructive action buttons (Parameter Init. Reset / Clear All Logs) */ +.tuner-actions { + display: flex; + gap: 8px; +} +.tuner-action-btn { + flex: 1 1 0; + height: 42px; + border: none; + border-radius: 10px; + font-size: 14px; + font-weight: 700; + color: #fff; + cursor: pointer; +} +.tuner-action-btn--danger { background: #8a1d1d; } +.tuner-action-btn--danger:active { background: #6f1717; } +.tuner-action-btn--warn { background: #b58709; } +.tuner-action-btn--warn:active { background: #946f07; } + +/* summary */ +.tuner-summary { + display: flex; + flex-direction: column; + gap: 2px; +} +.tuner-summary-line { + font-size: 14px; + color: var(--md-on-surface-var); +} +.tuner-summary-latest { + font-size: 13px; + color: var(--md-surface-bright); +} + +/* cards */ +.tuner-card { + background: var(--md-surface-cont); + border: 1px solid color-mix(in srgb, var(--md-surface-bright) 20%, transparent); + border-radius: 14px; + padding: 12px; +} +.tuner-section-title { + font-size: 15px; + font-weight: 700; + color: var(--md-on-surface); + margin-bottom: 8px; +} + +/* graph */ +.tuner-graph-card { padding: 12px 10px 8px; } +.tuner-canvas-wrap { width: 100%; } +.tuner-canvas { + display: block; + width: 100%; + height: 300px; + border-radius: 8px; +} +.tuner-axis { + display: flex; + justify-content: space-between; + padding: 4px 6px 0 32px; + font-size: 11px; + color: var(--md-surface-bright); +} +.tuner-snapshot { + margin-top: 8px; + padding: 8px 10px; + background: var(--md-surface-cont-h); + border-radius: 10px; + font-size: 13px; +} +.tuner-snap-title { font-weight: 700; margin-bottom: 4px; } +.tuner-snap-row { + display: flex; + justify-content: space-between; + gap: 12px; + padding: 1px 0; + color: var(--md-on-surface-var); +} +.tuner-snap-k { font-family: var(--font-mono); } +.tuner-snap-v { font-variant-numeric: tabular-nums; } + +/* filters + legend */ +.tuner-filters, .tuner-legend { + display: flex; + flex-wrap: wrap; + gap: 6px; +} +.tuner-filter-chip { + height: 34px; + padding: 0 14px; + border: 1px solid color-mix(in srgb, var(--md-surface-bright) 30%, transparent); + border-radius: 17px; + background: transparent; + color: var(--md-on-surface-var); + font-size: 13px; + cursor: pointer; +} +.tuner-filter-chip.is-active { + background: var(--md-primary-state, #1d4ed8); + border-color: var(--md-primary, #4f86f7); + color: #fff; +} +.tuner-legend-chip { + display: inline-flex; + align-items: center; + gap: 6px; + height: 30px; + padding: 0 10px; + border: 1px solid transparent; + border-radius: 15px; + background: var(--md-surface-cont-h); + color: var(--md-on-surface-var); + font-size: 12px; + font-family: var(--font-mono); + cursor: pointer; +} +.tuner-legend-chip.is-solo { + border-color: var(--md-primary, #4f86f7); + background: var(--md-surface-cont-hh); + color: #fff; +} +.tuner-dot { + width: 10px; height: 10px; + border-radius: 50%; + flex: 0 0 auto; +} + +/* diagnostics */ +.tuner-diag { display: flex; flex-direction: column; gap: 8px; } +.tuner-diag-section { + background: var(--md-surface-cont); + border: 1px solid color-mix(in srgb, var(--md-surface-bright) 18%, transparent); + border-radius: 12px; + padding: 8px 12px; +} +.tuner-diag-section > summary { + cursor: pointer; + font-size: 14px; + font-weight: 600; + list-style: none; +} +.tuner-diag-section > summary::-webkit-details-marker { display: none; } +.tuner-diag-rows { margin-top: 8px; display: flex; flex-direction: column; gap: 6px; } +.tuner-diag-row { + display: grid; + grid-template-columns: 1fr auto; + align-items: center; + gap: 6px 10px; + font-size: 13px; +} +.tuner-diag-k { color: var(--md-on-surface-var); } +.tuner-diag-v { font-variant-numeric: tabular-nums; color: var(--md-on-surface); } +.tuner-diag-bar { + grid-column: 1 / -1; + height: 5px; + border-radius: 3px; + background: var(--md-surface-cont-hh); + overflow: hidden; +} +.tuner-diag-bar > i { + display: block; + height: 100%; + background: var(--md-warning, #f0a020); +} + +/* history cards */ +.tuner-cards { display: flex; flex-direction: column; gap: 10px; } +.tuner-hist-card { padding: 12px; } +.tuner-hist-top { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 8px; +} +.tuner-hist-ts { font-size: 13px; color: var(--md-surface-bright); } +.tuner-hist-cat { + font-size: 12px; + padding: 2px 10px; + border-radius: 12px; + background: var(--md-surface-cont-hh); + color: var(--md-on-surface-var); +} +.tuner-hist-change { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 10px; + padding: 3px 0; +} +.tuner-hist-key { font-family: var(--font-mono); font-size: 13px; } +.tuner-hist-delta { + font-variant-numeric: tabular-nums; + font-size: 14px; + font-weight: 600; +} +.tuner-hist-delta > i { color: var(--md-surface-bright); font-style: normal; } +.tuner-hist-reason { + font-size: 12px; + color: var(--md-surface-bright); + padding: 0 0 4px; +} +.tuner-hist-actions { display: flex; justify-content: flex-end; margin-top: 6px; } +.tuner-restore-btn { + height: 36px; + padding: 0 16px; + border: 1px solid color-mix(in srgb, var(--md-surface-bright) 32%, transparent); + border-radius: 9px; + background: transparent; + color: var(--md-on-surface); + font-size: 13px; + cursor: pointer; +} +.tuner-restore-btn:active { background: var(--md-surface-cont-h); } + +.tuner-empty { + padding: 24px; + text-align: center; + color: var(--md-surface-bright); + font-size: 14px; +} + +/* landscape: give the graph a roomier height */ +@media (orientation: landscape) and (min-width: 900px) { + .tuner-canvas { height: 360px; } +} diff --git a/selfdrive/carrot/web/index.html b/selfdrive/carrot/web/index.html index ed811bb590..35c19fcf12 100644 --- a/selfdrive/carrot/web/index.html +++ b/selfdrive/carrot/web/index.html @@ -104,6 +104,7 @@ + @@ -507,6 +508,9 @@

Home

+ + +
@@ -643,6 +647,12 @@

Home

Logs + +
+
+
+ + +
+
+
+
${tT("tuner_trend", "Parameter trend (last 30)")}
+
+ +
+
+ +
+
+
+
+
${tT("tuner_history", "Change history")}
+
+ `; + + const refreshBtn = page.querySelector("#tunerRefreshBtn"); + if (refreshBtn) refreshBtn.addEventListener("click", () => tunerRefresh()); + + const canvas = page.querySelector("#tunerCanvas"); + if (canvas) canvas.addEventListener("click", (ev) => tunerOnCanvasClick(ev)); + + const resetBtn = page.querySelector("#tunerParamResetBtn"); + if (resetBtn) resetBtn.addEventListener("click", () => tunerParamReset()); + const clearBtn = page.querySelector("#tunerClearLogsBtn"); + if (clearBtn) clearBtn.addEventListener("click", () => tunerClearLogs()); + + window.addEventListener("resize", () => { + if (!document.getElementById("pageTuner")?.hidden) tunerRenderGraph(); + }); +} + +// ── Data load ───────────────────────────────────────────────────────────── +async function tunerRefresh() { + if (typeof bulkGet !== "function") return; + tunerState.loading = true; + let values = {}; + try { + values = await bulkGet(TUNER_PARAM_KEYS); + } catch (e) { + values = {}; + } + tunerState.loading = false; + + const hist = tunerSafeParse(values.CarrotLearningHistory, []); + tunerState.history = Array.isArray(hist) ? hist : []; + tunerState.data = tunerSafeParse(values.CarrotLearningData, null); + tunerState.active = tunerTruthy(values.CarrotLearningActive); + tunerState.applyLat = tunerTruthy(values.CarrotTunerApplyLat); + tunerState.applyLong = tunerTruthy(values.CarrotTunerApplyLong); + + tunerComputeSeries(); + tunerRenderChips(); + tunerRenderSummary(); + tunerRenderCategories(); + tunerRenderLegend(); + tunerRenderGraph(); + tunerRenderDiagnostics(); + tunerRenderCards(); +} + +// Port of refreshHistory()'s timeline build: forward-fill each param's recommended +// value across the (oldest→newest) timeline, seeding pre-first points with 'current'. +function tunerComputeSeries() { + const hist = tunerState.history; + const nPoints = Math.min(hist.length, TUNER_CHART_LIMIT); + + const timestamps = []; + const entries = []; + for (let i = nPoints - 1; i >= 0; i--) { + const item = hist[i] || {}; + timestamps.push(item.timestamp || ""); + entries.push(item); + } + + // Gather all params + remember each param's category (group label). + const categories = {}; + const paramSet = new Set(); + for (const entry of entries) { + const changes = entry.changes || {}; + for (const group of Object.keys(changes)) { + const gItems = changes[group] || {}; + for (const key of Object.keys(gItems)) { + paramSet.add(key); + categories[key] = group; + } + } + } + + const series = {}; + const lastValues = {}; + for (const p of paramSet) series[p] = []; + + for (let t = 0; t < nPoints; t++) { + const changes = entries[t].changes || {}; + const currentChanges = {}; + for (const group of Object.keys(changes)) { + const gItems = changes[group] || {}; + for (const key of Object.keys(gItems)) { + const rec = Number(gItems[key]?.recommended); + if (Number.isFinite(rec)) currentChanges[key] = rec; + } + } + for (const param of paramSet) { + if (param in currentChanges) { + lastValues[param] = currentChanges[param]; + series[param].push(currentChanges[param]); + } else if (param in lastValues) { + series[param].push(lastValues[param]); + } else { + // Seed with the 'current' of the first future occurrence. + let initial = 0.0; + for (let ft = t; ft < nPoints; ft++) { + const fChanges = entries[ft].changes || {}; + let found = false; + for (const group of Object.keys(fChanges)) { + const gItems = fChanges[group] || {}; + if (param in gItems) { + const cur = Number(gItems[param]?.current); + if (Number.isFinite(cur)) initial = cur; + found = true; + break; + } + } + if (found) break; + } + lastValues[param] = initial; + series[param].push(initial); + } + } + } + + // Assign stable colors by sorted key. + const colors = {}; + const sortedKeys = Array.from(paramSet).sort(); + sortedKeys.forEach((p, idx) => { colors[p] = TUNER_COLORS[idx % TUNER_COLORS.length]; }); + + tunerState.timestamps = timestamps; + tunerState.series = series; + tunerState.colors = colors; + tunerState.categories = categories; + if (tunerState.selectedParam && !paramSet.has(tunerState.selectedParam)) tunerState.selectedParam = null; + if (tunerState.selectedIndex >= nPoints) tunerState.selectedIndex = -1; +} + +// ── Status chips (Active / Apply LAT / Apply LONG) ────────────────────────── +function tunerRenderChips() { + const host = document.getElementById("tunerChips"); + if (!host) return; + host.innerHTML = ""; + + const chips = [ + { key: "CarrotLearningActive", on: tunerState.active, kind: "active", + onLabel: tT("tuner_learn_on", "Learning ON"), offLabel: tT("tuner_learn_off", "Learning OFF") }, + { key: "CarrotTunerApplyLat", on: tunerState.applyLat, kind: "lat", + onLabel: tT("tuner_apply_lat_on", "LAT ON"), offLabel: tT("tuner_apply_lat_off", "LAT OFF") }, + { key: "CarrotTunerApplyLong", on: tunerState.applyLong, kind: "long", + onLabel: tT("tuner_apply_long_on", "LONG ON"), offLabel: tT("tuner_apply_long_off", "LONG OFF") }, + ]; + + for (const c of chips) { + const btn = document.createElement("button"); + btn.type = "button"; + btn.className = "tuner-chip" + (c.on ? " is-on" : " is-off"); + btn.textContent = c.on ? c.onLabel : c.offLabel; + btn.addEventListener("click", () => tunerToggleParam(c.key, c.on)); + host.appendChild(btn); + } +} + +// Learning(master) ↔ LAT/LONG 연동 규칙 (C4 등 단말 메뉴가 없는 차량 지원): +// • Learning ON → LAT ON, LONG ON 자동 동반 +// • Learning OFF → LAT OFF, LONG OFF 자동 동반 +// • Learning OFF 상태에서 LAT 또는 LONG 중 하나라도 ON → Learning ON +// • LAT/LONG 모두 OFF가 되면 → Learning OFF +async function tunerToggleParam(key, currentlyOn) { + if (typeof setParam !== "function") return; + const next = currentlyOn ? 0 : 1; + const onFlag = !currentlyOn; + try { + if (key === "CarrotLearningActive") { + // 마스터 스위치: LAT/LONG을 함께 켜고/끈다 + await setParam("CarrotLearningActive", next); + await setParam("CarrotTunerApplyLat", next); + await setParam("CarrotTunerApplyLong", next); + tunerState.active = onFlag; + tunerState.applyLat = onFlag; + tunerState.applyLong = onFlag; + } else { + // 개별 LAT/LONG 토글 + await setParam(key, next); + if (key === "CarrotTunerApplyLat") tunerState.applyLat = onFlag; + else if (key === "CarrotTunerApplyLong") tunerState.applyLong = onFlag; + // Learning은 LAT/LONG 상태를 따라감: 하나라도 ON이면 ON, 모두 OFF면 OFF + const anyOn = tunerState.applyLat || tunerState.applyLong; + if (anyOn !== tunerState.active) { + await setParam("CarrotLearningActive", anyOn ? 1 : 0); + tunerState.active = anyOn; + } + } + tunerRenderChips(); + } catch (e) { + if (typeof showAppToast === "function") showAppToast(tT("tuner_save_failed", "Save failed"), { kind: "error" }); + } +} + +// Parameter Init. Reset — mirrors the on-device "Factory Reset": sets the +// CarrotTunerFactoryReset flag; the carrot_learning process restores every tuner +// param to its install default and deletes learning data/history. +async function tunerParamReset() { + if (typeof setParam !== "function") return; + const confirmFn = (typeof appConfirm === "function") ? appConfirm : async () => window.confirm("Reset?"); + const ok = await confirmFn( + tT("tuner_reset_confirm", "Restore all auto-tuned parameters to defaults and delete learning data/history?"), + { title: tT("tuner_param_reset", "Parameter Init. Reset") }, + ); + if (!ok) return; + try { + await setParam("CarrotTunerFactoryReset", 1); + if (typeof showAppToast === "function") showAppToast(tT("tuner_reset_done", "Parameters restored to defaults")); + setTimeout(() => tunerRefresh(), 900); + } catch (e) { + if (typeof showAppToast === "function") showAppToast(tT("tuner_save_failed", "Save failed"), { kind: "error" }); + } +} + +// Clear All Logs — clears accumulated learning data and the change history. +async function tunerClearLogs() { + if (typeof setParam !== "function") return; + const confirmFn = (typeof appConfirm === "function") ? appConfirm : async () => window.confirm("Clear?"); + const ok = await confirmFn( + tT("tuner_clear_confirm", "Delete all learning data and change history?"), + { title: tT("tuner_clear_logs", "Clear All Logs") }, + ); + if (!ok) return; + try { + await setParam("CarrotLearningClear", 1); + await setParam("CarrotLearningHistory", "[]"); + if (typeof showAppToast === "function") showAppToast(tT("tuner_clear_done", "Logs cleared")); + setTimeout(() => tunerRefresh(), 500); + } catch (e) { + if (typeof showAppToast === "function") showAppToast(tT("tuner_save_failed", "Save failed"), { kind: "error" }); + } +} + +// ── Summary line ──────────────────────────────────────────────────────────── +function tunerRenderSummary() { + const host = document.getElementById("tunerSummary"); + if (!host) return; + const changeCount = tunerState.history.length; + + let drivenSec = 0; + const d = tunerState.data; + if (d && Array.isArray(d.band_sec)) drivenSec = d.band_sec.reduce((a, b) => a + (Number(b) || 0), 0); + + let latestText = ""; + const latest = tunerState.history[0]; + if (latest && latest.changes) { + const parts = []; + for (const group of Object.keys(latest.changes)) { + const gItems = latest.changes[group] || {}; + for (const key of Object.keys(gItems)) { + const it = gItems[key] || {}; + parts.push(`${key} ${it.current ?? "?"}→${it.recommended ?? "?"}`); + } + } + if (parts.length) latestText = parts.slice(0, 2).join(", "); + } + + const driveLabel = tT("tuner_driven", "Driven"); + const changesLabel = tT("tuner_changes", "changes"); + const latestLabel = tT("tuner_latest", "Latest"); + host.innerHTML = ` +
${driveLabel} ${tunerFmtDuration(drivenSec)} · ${changeCount} ${changesLabel}
+ ${latestText ? `
${latestLabel} ▸ ${tunerEsc(latestText)}
` : ""}`; +} + +function tunerFmtDuration(sec) { + sec = Math.round(sec || 0); + if (sec < 60) return `${sec}s`; + const m = Math.floor(sec / 60); + const s = sec % 60; + if (m < 60) return `${m}m ${s}s`; + const h = Math.floor(m / 60); + return `${h}h ${m % 60}m`; +} + +// ── Category filter chips ─────────────────────────────────────────────────── +function tunerRenderCategories() { + const host = document.getElementById("tunerCategories"); + if (!host) return; + host.innerHTML = ""; + + const cats = []; + const seen = new Set(); + for (const p of Object.keys(tunerState.categories)) { + const c = tunerState.categories[p]; + if (!seen.has(c)) { seen.add(c); cats.push(c); } + } + if (cats.length <= 1) return; // nothing to filter + + const makeChip = (label, value) => { + const btn = document.createElement("button"); + btn.type = "button"; + btn.className = "tuner-filter-chip" + (tunerState.activeCategory === value ? " is-active" : ""); + btn.textContent = label; + btn.addEventListener("click", () => { + tunerState.activeCategory = (tunerState.activeCategory === value) ? null : value; + tunerState.selectedParam = null; + tunerRenderCategories(); + tunerRenderLegend(); + tunerRenderGraph(); + }); + return btn; + }; + + host.appendChild(makeChip(tT("tuner_all", "All"), null)); + for (const c of cats) host.appendChild(makeChip(tunerCatShort(c), c)); +} + +// "가속 (Acceleration)" → "가속" / "Acceleration" depending on lang heuristic. +function tunerCatShort(c) { + if (!c) return c; + const m = c.match(/^\s*([^(]+?)\s*\(([^)]+)\)\s*$/); + if (!m) return c; + return (typeof LANG !== "undefined" && LANG === "ko") ? m[1].trim() : m[2].trim(); +} + +// ── Legend chips (one per visible param; tap to solo) ─────────────────────── +function tunerVisibleParams() { + let params = Object.keys(tunerState.series); + if (tunerState.activeCategory) { + params = params.filter((p) => tunerState.categories[p] === tunerState.activeCategory); + } + return params.sort(); +} + +function tunerRenderLegend() { + const host = document.getElementById("tunerLegend"); + if (!host) return; + host.innerHTML = ""; + for (const p of tunerVisibleParams()) { + const btn = document.createElement("button"); + btn.type = "button"; + const soloed = tunerState.selectedParam === p; + btn.className = "tuner-legend-chip" + (soloed ? " is-solo" : ""); + btn.innerHTML = `${tunerEsc(p)}`; + btn.addEventListener("click", () => { + tunerState.selectedParam = soloed ? null : p; + tunerRenderLegend(); + tunerRenderGraph(); + }); + host.appendChild(btn); + } +} + +// ── Canvas graph (port of AutoTunerGraphWidget::paintEvent) ────────────────── +function tunerGraphLayout(canvas) { + const wrap = canvas.parentElement; + const cssW = Math.max(240, wrap ? wrap.clientWidth : 320); + const cssH = 300; + const dpr = window.devicePixelRatio || 1; + canvas.style.width = cssW + "px"; + canvas.style.height = cssH + "px"; + canvas.width = Math.round(cssW * dpr); + canvas.height = Math.round(cssH * dpr); + const ctx = canvas.getContext("2d"); + ctx.setTransform(dpr, 0, 0, dpr, 0, 0); + return { ctx, cssW, cssH }; +} + +function tunerRenderGraph() { + const canvas = document.getElementById("tunerCanvas"); + if (!canvas) return; + const { ctx, cssW, cssH } = tunerGraphLayout(canvas); + + ctx.fillStyle = "#11161d"; + ctx.fillRect(0, 0, cssW, cssH); + + const ts = tunerState.timestamps; + const axis = document.getElementById("tunerAxis"); + const snap = document.getElementById("tunerSnapshot"); + + if (!ts.length) { + ctx.fillStyle = "#7a8694"; + ctx.font = "16px " + tunerFont(); + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + ctx.fillText(tT("tuner_no_data", "No historical data to display"), cssW / 2, cssH / 2); + if (axis) axis.textContent = ""; + if (snap) snap.hidden = true; + return; + } + + const mL = 32, mR = 12, mT = 16, mB = 22; + const gx = mL, gy = mT, gw = cssW - mL - mR, gh = cssH - mT - mB; + let stepsX = ts.length - 1; + if (stepsX < 1) stepsX = 1; + + const params = tunerVisibleParams(); + const isLargeScale = (p) => tunerState.series[p].some((v) => Math.abs(v) > TUNER_LARGE_SCALE); + const excluded = (p) => isLargeScale(p) && p !== tunerState.selectedParam; + const drawn = params.filter((p) => !(tunerState.selectedParam ? p !== tunerState.selectedParam : excluded(p))); + + // global min/max over drawn params + let gmin = 0, gmax = 0, first = true; + for (const p of drawn) { + for (const v of tunerState.series[p]) { + if (first) { gmin = gmax = v; first = false; } + else { if (v < gmin) gmin = v; if (v > gmax) gmax = v; } + } + } + if (first) { gmin = 0; gmax = 1; } + if (gmin === gmax) { gmin -= 1; gmax += 1; } + const pad = (gmax - gmin) * 0.12; + gmin -= pad; gmax += pad; + + const yOf = (v) => gy + gh - ((v - gmin) / (gmax - gmin)) * gh; + const xOf = (i) => gx + (i * gw) / stepsX; + + // grid + ctx.strokeStyle = "#222a34"; + ctx.lineWidth = 1; + for (let i = 0; i <= stepsX; i++) { + const x = xOf(i); + ctx.beginPath(); ctx.moveTo(x, gy); ctx.lineTo(x, gy + gh); ctx.stroke(); + } + const stepsY = 4; + ctx.fillStyle = "#6b7785"; + ctx.font = "11px " + tunerFont(); + ctx.textAlign = "right"; + ctx.textBaseline = "middle"; + for (let i = 0; i <= stepsY; i++) { + const y = gy + (i * gh) / stepsY; + ctx.beginPath(); ctx.moveTo(gx, y); ctx.lineTo(gx + gw, y); ctx.stroke(); + const val = gmax - (i * (gmax - gmin)) / stepsY; + ctx.fillText(tunerFmtNum(val), gx - 4, y); + } + + // selected vertical cursor + if (tunerState.selectedIndex >= 0 && tunerState.selectedIndex < ts.length) { + const x = xOf(tunerState.selectedIndex); + ctx.strokeStyle = "rgba(255,255,255,0.5)"; + ctx.setLineDash([4, 4]); + ctx.beginPath(); ctx.moveTo(x, gy); ctx.lineTo(x, gy + gh); ctx.stroke(); + ctx.setLineDash([]); + } + + // series lines + nodes + for (const p of drawn) { + const vals = tunerState.series[p]; + ctx.strokeStyle = tunerState.colors[p]; + ctx.lineWidth = (p === tunerState.selectedParam) ? 3 : 2; + ctx.beginPath(); + vals.forEach((v, i) => { + const x = xOf(i), y = yOf(v); + if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); + }); + ctx.stroke(); + if (tunerState.selectedIndex >= 0) { + const i = tunerState.selectedIndex; + ctx.fillStyle = tunerState.colors[p]; + ctx.beginPath(); ctx.arc(xOf(i), yOf(vals[i]), 3.5, 0, Math.PI * 2); ctx.fill(); + } + } + + // x-axis labels (first / last timestamp) + if (axis) { + const a = tunerShortTs(ts[0]); + const b = tunerShortTs(ts[ts.length - 1]); + axis.innerHTML = `${tunerEsc(a)}${tunerEsc(b)}`; + } + + tunerRenderSnapshot(); +} + +function tunerRenderSnapshot() { + const snap = document.getElementById("tunerSnapshot"); + if (!snap) return; + const i = tunerState.selectedIndex; + if (i < 0 || i >= tunerState.timestamps.length) { snap.hidden = true; snap.innerHTML = ""; return; } + + // The entry that produced this timeline point (timeline is oldest→newest). + const nPoints = Math.min(tunerState.history.length, TUNER_CHART_LIMIT); + const entry = tunerState.history[nPoints - 1 - i]; + const rows = []; + if (entry && entry.changes) { + for (const group of Object.keys(entry.changes)) { + const gItems = entry.changes[group] || {}; + for (const key of Object.keys(gItems)) { + const it = gItems[key] || {}; + rows.push(`
${tunerEsc(key)}${it.current ?? "?"} → ${it.recommended ?? "?"}
`); + } + } + } + snap.hidden = false; + snap.innerHTML = + `
▣ ${tunerEsc(tunerState.timestamps[i])}
` + + (rows.length ? rows.join("") : `
${tT("tuner_no_change", "no change")}
`); +} + +function tunerOnCanvasClick(ev) { + const ts = tunerState.timestamps; + if (!ts.length) return; + const canvas = ev.currentTarget; + const rect = canvas.getBoundingClientRect(); + const cssW = rect.width; + const mL = 32, mR = 12; + const gx = mL, gw = cssW - mL - mR; + let stepsX = ts.length - 1; + if (stepsX < 1) stepsX = 1; + const clickX = ev.clientX - rect.left; + + let closest = 0, minDist = Infinity; + for (let i = 0; i < ts.length; i++) { + const nodeX = gx + (i * gw) / stepsX; + const dist = Math.abs(clickX - nodeX); + if (dist < minDist) { minDist = dist; closest = i; } + } + tunerState.selectedIndex = (minDist < 40) ? ((tunerState.selectedIndex === closest) ? -1 : closest) : -1; + tunerRenderGraph(); +} + +// ── Diagnostics (CarrotLearningData → collapsible) ────────────────────────── +function tunerRenderDiagnostics() { + const host = document.getElementById("tunerDiag"); + if (!host) return; + const d = tunerState.data; + if (!d) { host.innerHTML = ""; return; } + + const sections = []; + + const p8 = d.phase8; + if (p8 && p8.long_samples > 0) { + const lag = p8.long_lag_count / p8.long_samples; + const over = p8.long_overshoot_count / p8.long_samples; + const meanErr = p8.long_err_sum / p8.long_samples; + sections.push({ + title: tT("tuner_diag_long", "Longitudinal (phase8)"), + rows: [ + [tT("tuner_diag_err", "Mean track error"), `${meanErr.toFixed(2)} m/s²`, null], + [tT("tuner_diag_lag", "Lag"), `${p8.long_lag_count}/${p8.long_samples} (${Math.round(lag * 100)}%)`, lag], + [tT("tuner_diag_over", "Overshoot"), `${p8.long_overshoot_count}/${p8.long_samples} (${Math.round(over * 100)}%)`, over], + ], + }); + } + + const p7 = d.phase7; + if (p7 && p7.stop_events > 0) { + sections.push({ + title: tT("tuner_diag_stop", "Stops (phase7)"), + rows: [ + [tT("tuner_diag_stop_events", "Stop events"), `${p7.stop_events}`, null], + [tT("tuner_diag_harsh", "Harsh stops"), `${p7.stop_harsh_count || 0}`, null], + ], + }); + } + + if (!sections.length) { host.innerHTML = ""; return; } + + host.innerHTML = sections.map((sec) => ` +
+ ${tunerEsc(sec.title)} +
+ ${sec.rows.map(([k, v, ratio]) => ` +
+ ${tunerEsc(k)} + ${tunerEsc(v)} + ${ratio == null ? "" : ``} +
`).join("")} +
+
`).join(""); +} + +// ── History cards (+ restore) ─────────────────────────────────────────────── +function tunerRenderCards() { + const host = document.getElementById("tunerCards"); + if (!host) return; + host.innerHTML = ""; + + if (!tunerState.history.length) { + host.innerHTML = `
${tT("tuner_no_data", "No historical data to display")}
`; + return; + } + + for (const entry of tunerState.history) { + const card = document.createElement("div"); + card.className = "tuner-card tuner-hist-card"; + + let catLabel = ""; + const changeRows = []; + for (const group of Object.keys(entry.changes || {})) { + if (!catLabel) catLabel = tunerCatShort(group); + const gItems = entry.changes[group] || {}; + for (const key of Object.keys(gItems)) { + const it = gItems[key] || {}; + const reason = it.band_kph ? `
${tunerEsc(String(it.band_kph))}
` : ""; + changeRows.push(` +
+ ${tunerEsc(key)} + ${it.current ?? "?"} ${it.recommended ?? "?"} +
${reason}`); + } + } + + card.innerHTML = ` +
+ ${tunerEsc(entry.timestamp || "")} + ${catLabel ? `${tunerEsc(catLabel)}` : ""} +
+ ${changeRows.join("")} +
+ +
`; + + const btn = card.querySelector(".tuner-restore-btn"); + if (btn) btn.addEventListener("click", () => tunerRestore(entry.id)); + host.appendChild(card); + } +} + +// Port of restoreItem(): write each changed param's 'current', then drop the entry. +async function tunerRestore(id) { + if (!id || typeof setParam !== "function") return; + const confirmFn = (typeof appConfirm === "function") ? appConfirm : async () => window.confirm("Restore?"); + const ok = await confirmFn( + tT("tuner_restore_confirm", "Restore the parameters to this state?"), + { title: tT("tuner_restore", "Restore") }, + ); + if (!ok) return; + + const entry = tunerState.history.find((e) => e.id === id); + if (!entry) return; + + try { + for (const group of Object.keys(entry.changes || {})) { + const gItems = entry.changes[group] || {}; + for (const key of Object.keys(gItems)) { + const prev = Number(gItems[key]?.current); + if (Number.isFinite(prev)) await setParam(key, Math.round(prev)); + } + } + const remaining = tunerState.history.filter((e) => e.id !== id); + await setParam("CarrotLearningHistory", JSON.stringify(remaining)); + if (typeof showAppToast === "function") showAppToast(tT("tuner_restored", "Restored to previous values")); + await tunerRefresh(); + } catch (e) { + if (typeof showAppToast === "function") showAppToast(tT("tuner_save_failed", "Save failed"), { kind: "error" }); + } +} + +// ── small utils ───────────────────────────────────────────────────────────── +function tunerEsc(s) { + return String(s == null ? "" : s) + .replace(/&/g, "&").replace(//g, ">") + .replace(/"/g, """).replace(/'/g, "'"); +} +function tunerFmtNum(v) { + if (Math.abs(v) >= 1000) return Math.round(v).toString(); + if (Math.abs(v) >= 100) return v.toFixed(0); + return v.toFixed(Math.abs(v) < 10 ? 1 : 0); +} +function tunerShortTs(ts) { + if (!ts) return ""; + // "2026-06-25 17:47" → "06-25 17:47" + const m = String(ts).match(/(\d{2})-(\d{2})\s+(\d{2}:\d{2})/); + return m ? `${m[1]}-${m[2]} ${m[3]}` : String(ts); +} +function tunerFont() { + return 'Roboto, system-ui, -apple-system, sans-serif'; +} diff --git a/selfdrive/carrot/web/js/shared/constants.js b/selfdrive/carrot/web/js/shared/constants.js index a4f374e33d..bc76c9d85b 100644 --- a/selfdrive/carrot/web/js/shared/constants.js +++ b/selfdrive/carrot/web/js/shared/constants.js @@ -26,7 +26,7 @@ const SWIPE_COMMIT_RATIO = 0.22; const SWIPE_VELOCITY_THRESHOLD = 0.45; const SWIPE_EDGE_RESISTANCE = 0.18; -const SWIPE_PAGES = ["carrot", "setting", "tools", "logs", "terminal"]; +const SWIPE_PAGES = ["carrot", "setting", "tools", "logs", "tuner", "terminal"]; const SETTING_BACK_EDGE_WIDTH = 32; const QUICK_LINK_FIXED_URL = "https://man.carrotpilot.app/"; diff --git a/selfdrive/carrot/web/js/shared/dom.js b/selfdrive/carrot/web/js/shared/dom.js index 50f9e125a8..2556a1de65 100644 --- a/selfdrive/carrot/web/js/shared/dom.js +++ b/selfdrive/carrot/web/js/shared/dom.js @@ -8,6 +8,7 @@ const btnHome = document.getElementById("btnHome"); const btnSetting = document.getElementById("btnSetting"); const btnLogs = document.getElementById("btnLogs"); +const btnTuner = document.getElementById("btnTuner"); const btnTerminal = document.getElementById("btnTerminal"); const btnTools = document.getElementById("btnTools"); const btnRecordToggle = document.getElementById("btnRecordToggle"); @@ -70,6 +71,7 @@ const PAGE_ELEMENTS = { car: document.getElementById("pageCar"), tools: document.getElementById("pageTools"), logs: document.getElementById("pageLogs"), + tuner: document.getElementById("pageTuner"), terminal: document.getElementById("pageTerminal"), branch: document.getElementById("pageBranch"), carrot: document.getElementById("pageCarrot"), diff --git a/selfdrive/carrot/web/js/shared/i18n.js b/selfdrive/carrot/web/js/shared/i18n.js index b2d0375201..e940f788c6 100644 --- a/selfdrive/carrot/web/js/shared/i18n.js +++ b/selfdrive/carrot/web/js/shared/i18n.js @@ -227,6 +227,7 @@ function renderUIText() { setNavText("btnSetting", s.setting); setNavText("btnTools", s.tools); setNavText("btnLogs", s.logs); + setNavText("btnTuner", s.tuner || "Tuner"); setNavText("btnTerminal", s.terminal); setText("btnQuickLinkWeb", "CarrotMan"); diff --git a/selfdrive/carrot/web/js/shared/ui/navigation.js b/selfdrive/carrot/web/js/shared/ui/navigation.js index 5b97a4ff30..d0ecb3a5af 100644 --- a/selfdrive/carrot/web/js/shared/ui/navigation.js +++ b/selfdrive/carrot/web/js/shared/ui/navigation.js @@ -245,6 +245,7 @@ function syncNavActivePage(page) { setElementClass(btnSetting, "active", page === "setting"); setElementClass(btnTools, "active", page === "tools"); setElementClass(btnLogs, "active", page === "logs"); + setElementClass(btnTuner, "active", page === "tuner"); setElementClass(btnTerminal, "active", page === "terminal"); } @@ -389,6 +390,10 @@ function runPageEnter(page, prevPage, pushHistory) { initLogsPage(); } + if (page === "tuner" && typeof initTunerPage === "function") { + initTunerPage(); + } + if (page === "terminal" && typeof initTerminalPage === "function") { initTerminalPage(); } @@ -820,6 +825,7 @@ btnSetting.onclick = () => { showPage("setting", true, getSwipeTransition(CURRENT_PAGE, "setting")); }; if (btnLogs) btnLogs.onclick = () => showPage("logs", true, getSwipeTransition(CURRENT_PAGE, "logs")); +if (btnTuner) btnTuner.onclick = () => showPage("tuner", true, getSwipeTransition(CURRENT_PAGE, "tuner")); btnTerminal.onclick = () => showPage("terminal", true, getSwipeTransition(CURRENT_PAGE, "terminal")); if (settingCarRow) { diff --git a/selfdrive/carrot/web/js/translations/en.js b/selfdrive/carrot/web/js/translations/en.js index 64c7487c20..9f6b0830e3 100644 --- a/selfdrive/carrot/web/js/translations/en.js +++ b/selfdrive/carrot/web/js/translations/en.js @@ -13,6 +13,40 @@ window.CarrotTranslations.register("en", { logs: "Logs", terminal: "Terminal", carrot: "Carrot", + tuner: "Tuner", + tuner_title: "Auto-Tuner", + tuner_refresh: "Refresh", + tuner_trend: "Parameter trend (last 50)", + tuner_history: "Change history", + tuner_param_reset: "Parameter Init. Reset", + tuner_clear_logs: "Clear All Logs", + tuner_reset_confirm: "Restore all auto-tuned parameters to defaults and delete learning data/history?", + tuner_reset_done: "Parameters restored to defaults", + tuner_clear_confirm: "Delete all learning data and change history?", + tuner_clear_done: "Logs cleared", + tuner_no_data: "No historical data to display", + tuner_no_change: "no change", + tuner_learn_on: "Learning ON", + tuner_learn_off: "Learning OFF", + tuner_apply_lat_on: "LAT ON", + tuner_apply_lat_off: "LAT OFF", + tuner_apply_long_on: "LONG ON", + tuner_apply_long_off: "LONG OFF", + tuner_driven: "Driven", + tuner_changes: "changes", + tuner_latest: "Latest", + tuner_all: "All", + tuner_restore: "Restore", + tuner_restore_confirm: "Restore the parameters to this state?", + tuner_restored: "Restored to previous values", + tuner_save_failed: "Save failed", + tuner_diag_long: "Longitudinal (phase8)", + tuner_diag_err: "Mean track error", + tuner_diag_lag: "Lag", + tuner_diag_over: "Overshoot", + tuner_diag_stop: "Stops (phase7)", + tuner_diag_stop_events: "Stop events", + tuner_diag_harsh: "Harsh stops", lang: "Lang", language: "Language", current_language: "Current language", diff --git a/selfdrive/carrot/web/js/translations/ko.js b/selfdrive/carrot/web/js/translations/ko.js index 024cbce7fa..e55708a6e0 100644 --- a/selfdrive/carrot/web/js/translations/ko.js +++ b/selfdrive/carrot/web/js/translations/ko.js @@ -13,6 +13,40 @@ window.CarrotTranslations.register("ko", { logs: "로그", terminal: "터미널", carrot: "당근", + tuner: "튜너", + tuner_title: "오토튜너", + tuner_refresh: "새로고침", + tuner_trend: "파라미터 추이 (최근 50회)", + tuner_history: "변경 이력", + tuner_param_reset: "파라미터 초기화", + tuner_clear_logs: "전체 로그 삭제", + tuner_reset_confirm: "오토튜닝된 모든 파라미터를 기본값으로 복원하고 학습 데이터/이력을 삭제할까요?", + tuner_reset_done: "파라미터를 기본값으로 복원했습니다", + tuner_clear_confirm: "모든 학습 데이터와 변경 이력을 삭제할까요?", + tuner_clear_done: "로그를 삭제했습니다", + tuner_no_data: "표시할 이력 데이터가 없습니다", + tuner_no_change: "변경 없음", + tuner_learn_on: "학습 ON", + tuner_learn_off: "학습 OFF", + tuner_apply_lat_on: "조향 적용 ON", + tuner_apply_lat_off: "조향 적용 OFF", + tuner_apply_long_on: "가감속 적용 ON", + tuner_apply_long_off: "가감속 적용 OFF", + tuner_driven: "누적주행", + tuner_changes: "건 변경", + tuner_latest: "최근", + tuner_all: "전체", + tuner_restore: "되돌리기", + tuner_restore_confirm: "이 시점의 파라미터로 되돌릴까요?", + tuner_restored: "이전 값으로 되돌렸습니다", + tuner_save_failed: "저장 실패", + tuner_diag_long: "종방향 진단 (phase8)", + tuner_diag_err: "추종오차 평균", + tuner_diag_lag: "지연(lag)", + tuner_diag_over: "진동(overshoot)", + tuner_diag_stop: "정지 (phase7)", + tuner_diag_stop_events: "정지 이벤트", + tuner_diag_harsh: "거친 정지", lang: "Lang", language: "Language", current_language: "현재 언어", diff --git a/selfdrive/carrot/web/js/translations/zh.js b/selfdrive/carrot/web/js/translations/zh.js index 1ffd015343..166f014902 100644 --- a/selfdrive/carrot/web/js/translations/zh.js +++ b/selfdrive/carrot/web/js/translations/zh.js @@ -13,6 +13,40 @@ window.CarrotTranslations.register("zh", { logs: "日志", terminal: "终端", carrot: "胡萝卜", + tuner: "调校", + tuner_title: "自动调校", + tuner_refresh: "刷新", + tuner_trend: "参数趋势 (最近50次)", + tuner_history: "变更历史", + tuner_param_reset: "参数初始化", + tuner_clear_logs: "清除所有日志", + tuner_reset_confirm: "将所有自动调校参数恢复为默认值并删除学习数据/历史?", + tuner_reset_done: "参数已恢复为默认值", + tuner_clear_confirm: "删除所有学习数据和变更历史?", + tuner_clear_done: "日志已清除", + tuner_no_data: "没有可显示的历史数据", + tuner_no_change: "无变更", + tuner_learn_on: "学习 ON", + tuner_learn_off: "学习 OFF", + tuner_apply_lat_on: "横向 ON", + tuner_apply_lat_off: "横向 OFF", + tuner_apply_long_on: "纵向 ON", + tuner_apply_long_off: "纵向 OFF", + tuner_driven: "累计行驶", + tuner_changes: "次变更", + tuner_latest: "最近", + tuner_all: "全部", + tuner_restore: "还原", + tuner_restore_confirm: "还原到此时的参数?", + tuner_restored: "已还原到先前的值", + tuner_save_failed: "保存失败", + tuner_diag_long: "纵向诊断 (phase8)", + tuner_diag_err: "跟踪误差均值", + tuner_diag_lag: "滞后(lag)", + tuner_diag_over: "超调(overshoot)", + tuner_diag_stop: "停车 (phase7)", + tuner_diag_stop_events: "停车事件", + tuner_diag_harsh: "急停", lang: "Lang", language: "Language", current_language: "当前语言", diff --git a/selfdrive/controls/lib/longcontrol.py b/selfdrive/controls/lib/longcontrol.py index 5a966f96bc..319d77f506 100644 --- a/selfdrive/controls/lib/longcontrol.py +++ b/selfdrive/controls/lib/longcontrol.py @@ -66,12 +66,45 @@ def __init__(self, CP): self.stopping_accel = 0 self.j_lead = 0.0 + # ── Coasting deadband + hysteresis ────────────────────────────── + # 사람 주행의 "발 떼고 코스팅(회생제동)" 구간을 흉내내, 계획 가속도가 0 근처에서 + # 미세하게 +/- 진동(울렁거림)할 때 출력 가감속을 0으로 부드럽게 수렴시킨다. + # LongCoastBand: 코스팅 진입 임계 가속도(0.01 m/s² 단위, 0=비활성). + # 진입 임계의 2배를 벗어나야 코스팅을 빠져나가는 히스테리시스로 채터링을 방지. + self.coast_band = 0.0 # m/s² (0=off) + self.coasting = False + self.COAST_EXIT_JERK = 1.2 # m/s³, 코스팅 진입 시 출력→0 수렴 속도 + self.use_accel_pid = False if CP.brand == "toyota": self.use_accel_pid = True def reset(self): self.pid.reset() + self.coasting = False + + def _update_coast_state(self, a_target): + """계획 가속도(a_target) 기준으로 코스팅 진입/이탈을 히스테리시스로 판정.""" + if self.coast_band <= 0.0: + self.coasting = False + return + if self.coasting: + # 진입 임계의 2배(이탈 임계)를 넘어서는 분명한 가감속 요구가 있을 때만 이탈 + if abs(a_target) > self.coast_band * 2.0: + self.coasting = False + else: + if abs(a_target) < self.coast_band: + self.coasting = True + + def _coast_output(self): + """코스팅 중 출력 가감속을 0(무가감속=자연 회생제동)으로 부드럽게 램프.""" + step = self.COAST_EXIT_JERK * DT_CTRL + oa = self.last_output_accel + if oa > step: + return oa - step + if oa < -step: + return oa + step + return 0.0 def update(self, active, CS, long_plan, accel_limits, t_since_plan, radarState): @@ -85,6 +118,7 @@ def update(self, active, CS, long_plan, accel_limits, t_since_plan, radarState): if self.readParamCount >= 100: self.readParamCount = 0 self.stopping_accel = self.params.get_float("StoppingAccel") * 0.01 + self.coast_band = self.params.get_float("LongCoastBand") * 0.01 elif self.readParamCount == 10: if len(self.CP.longitudinalTuning.kpBP) == 1 and len(self.CP.longitudinalTuning.kiBP)==1: longitudinalTuningKpV = self.params.get_float("LongTuningKpV") * 0.01 @@ -116,8 +150,13 @@ def update(self, active, CS, long_plan, accel_limits, t_since_plan, radarState): stopAccel = self.stopping_accel if self.stopping_accel < 0.0 else self.CP.stopAccel if output_accel > stopAccel: + # 저속일수록 감속 rate를 줄여서 정차 직전 꿀렁임 방지 (속도 비례 감속 한계 조절) + speed_factor = float(np.interp(CS.vEgo, [0.0, 0.2, 0.5, 1.5], [0.05, 0.1, 0.3, 1.0])) + # Brake Cushion: stopAccel에 가까워질수록 감속 rate를 더 줄여서 부드럽게 안착 + accel_margin = max(output_accel - stopAccel, 0.01) + cushion_factor = float(np.interp(accel_margin, [0.0, 0.3, 1.0], [0.15, 0.5, 1.0])) output_accel = min(output_accel, 0.0) - output_accel -= self.CP.stoppingDecelRate * DT_CTRL + output_accel -= self.CP.stoppingDecelRate * speed_factor * cushion_factor * DT_CTRL self.reset() elif self.long_control_state == LongCtrlState.starting: @@ -129,8 +168,14 @@ def update(self, active, CS, long_plan, accel_limits, t_since_plan, radarState): error = a_target_ff - CS.aEgo else: error = v_target_now - CS.vEgo + # 코스팅 판정은 '계획 가속도(a_target_ff)'의 미세 진동을 기준으로 한다. + self._update_coast_state(a_target_ff) output_accel = self.pid.update(error, speed=CS.vEgo, - feedforward=a_target_ff) + feedforward=a_target_ff, + freeze_integrator=self.coasting) + if self.coasting: + # 코스팅 중에는 출력을 0으로 부드럽게 수렴 → 자연 회생제동/엔진브레이크 활용 + output_accel = self._coast_output() self.last_output_accel = np.clip(output_accel, accel_limits[0], accel_limits[1]) return self.last_output_accel, a_target_ff, j_target_now diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index 9cf3ee3fab..67c90b84aa 100644 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -59,6 +59,33 @@ COMFORT_BRAKE = 2.5 STOP_DISTANCE = 6.0 +# 고속 + 선행차 접근 시 제동 명령을 앞당김 — 고속 늦은 감지로 인한 충돌 우려 대응. +# 정속 추종·저속에서는 무동작(고속·접근·TTC 삼중 게이트)이라 평상시 부드러움은 유지. +HIGH_SPEED_BRAKE_KPH = 70.0 # 이 속도(km/h) 이상에서만 선제 제동 강화 적용 +HIGH_SPEED_BRAKE_TTC = 7.0 # 접근 TTC(초)가 이 값 미만이면 활성 +HIGH_SPEED_TF_BOOST = 0.45 # Lever A: 추종거리(t_follow) 최대 선제 확대량(초) +HIGH_SPEED_JLF_GAIN_BP = [60.0, 100.0] # Lever C: JLeadFactor3 속도연동 보간 속도(km/h) +HIGH_SPEED_JLF_GAIN_V = [1.0, 1.8] # 위 속도에서의 j_lead_factor 배율 + +# 선행차 status 깜빡임(레이더/비전이 0.x초 놓침→재포착) 보정: 잠깐 끊겨도 직전 lead를 +# 짧게 유지(외삽)해 '없음→가속→재포착→복귀' 떨림을 막는다. 짧고(가까운 lead만) 안전하게. +_LEAD_HOLD_FRAMES = 6 # 유지 프레임수 (6*DT_MDL=0.3s) +_LEAD_HOLD_MAX_DREL = 80.0 # 이 거리(m) 이내의 선행차만 유지 + + +class _HeldLead: + """status가 잠깐 끊긴 선행차를 외삽해 잠시 대체하는 경량 객체(process_lead 호환).""" + __slots__ = ('status', 'dRel', 'vLead', 'aLeadK', 'aLeadTau', 'vRel', 'jLead') + + def __init__(self, dRel, vLead, aLeadK, aLeadTau, vRel): + self.status = True + self.dRel = dRel + self.vLead = vLead + self.aLeadK = aLeadK + self.aLeadTau = aLeadTau + self.vRel = vRel + self.jLead = 0.0 # 유지 중엔 jerk 예측 없음(떨림 방지) + def get_jerk_factor(personality=log.LongitudinalPersonality.standard): if personality==log.LongitudinalPersonality.moreRelaxed: return 1.0 @@ -240,6 +267,8 @@ def __init__(self, mode='acc', dt=DT_MDL): self.a_change_cost = A_CHANGE_COST self.j_lead = 0.0 + self._lead_held = None # 선행차 status 깜빡임 시 유지할 직전 lead + self._lead_hold_count = 0 self.reset() self.source = SOURCES[2] @@ -355,6 +384,21 @@ def process_lead(self, lead, j_lead): lead_xv = self.extrapolate_lead(x_lead, v_lead, a_lead, a_lead_tau, j_lead) return lead_xv, v_lead + def lead_with_hold(self, lead): + """선행차 status가 잠깐 끊기면(깜빡임) 직전 lead를 짧게 외삽해 대체한다. + 가까운 선행차(≤_LEAD_HOLD_MAX_DREL)에만, 최대 _LEAD_HOLD_FRAMES 동안만 유지.""" + if lead.status: + self._lead_hold_count = _LEAD_HOLD_FRAMES + self._lead_held = _HeldLead(lead.dRel, lead.vLead, lead.aLeadK, lead.aLeadTau, lead.vRel) + return lead + if self._lead_hold_count > 0 and self._lead_held is not None and self._lead_held.dRel < _LEAD_HOLD_MAX_DREL: + self._lead_hold_count -= 1 + h = self._lead_held + h.dRel = max(0.0, h.dRel + h.vRel * DT_MDL) # 위치 외삽 + h.vLead = max(0.0, h.vLead + h.aLeadK * DT_MDL) # 속도 외삽(직전 가속도로) + return h + return lead # 유지 만료 → 실제 상태(없음) + def set_accel_limits(self, min_a, max_a): # TODO this sets a max accel limit, but the minimum limit is only for cruise decel # needs refactor @@ -365,15 +409,20 @@ def update(self, carrot, reset_state, radarstate, v_cruise, x, v, a, j, personal v_ego = self.x0[1] a_ego = self.x0[2] t_follow = carrot.get_T_FOLLOW(personality, v_ego, a_ego) - self.status = radarstate.leadOne.status or radarstate.leadTwo.status + # 선행차 status 깜빡임 시 직전 lead를 짧게 유지(가감속 떨림 방지) + lead_one = self.lead_with_hold(radarstate.leadOne) + self.status = lead_one.status or radarstate.leadTwo.status - if radarstate.leadOne.status: - j_lead = radarstate.leadOne.jLead + if lead_one.status: + j_lead = lead_one.jLead self.j_lead = j_lead * 0.1 + self.j_lead * 0.9 else: self.j_lead = 0.0 - lead_xv_0, lead_v_0 = self.process_lead(radarstate.leadOne, np.clip(self.j_lead * carrot.j_lead_factor, -1.0, 1.0)) + # Lever C: 고속에서 선행차 감속 예측(JLeadFactor3=j_lead_factor)을 증폭 → 제동을 앞당김. + # (선행차가 '감속 중'일 때 효과. 정속 선행차는 Lever A가 담당) + jlf = carrot.j_lead_factor * float(np.interp(v_ego * 3.6, HIGH_SPEED_JLF_GAIN_BP, HIGH_SPEED_JLF_GAIN_V)) + lead_xv_0, lead_v_0 = self.process_lead(lead_one, np.clip(self.j_lead * jlf, -1.0, 1.0)) lead_xv_1, _ = self.process_lead(radarstate.leadTwo, 0.0) mode = self.mode @@ -385,7 +434,15 @@ def update(self, carrot, reset_state, radarstate, v_cruise, x, v, a, j, personal else: v_cruise, stop_x, mode = carrot.v_cruise, carrot.stop_dist, carrot.mode desired_distance = desired_follow_distance(v_ego, lead_v_0, comfort_brake, stop_distance, t_follow) - t_follow = carrot.dynamic_t_follow(t_follow, radarstate.leadOne, desired_distance, self.prev_a) + t_follow = carrot.dynamic_t_follow(t_follow, lead_one, desired_distance, self.prev_a) + # Lever A: 고속 + 선행차 접근(TTC 낮음) → 추종거리를 선제 확대해 제동 명령을 앞당김. + # (vRel<0 & TTC<임계 & 고속 삼중 게이트 → 정속 추종·저속에선 무동작) + _lead = lead_one + if _lead.status and v_ego * 3.6 >= HIGH_SPEED_BRAKE_KPH and _lead.dRel > 0.0 and _lead.vRel < 0.0: + _ttc = _lead.dRel / -_lead.vRel + _tf_boost = float(np.interp(_ttc, [3.0, HIGH_SPEED_BRAKE_TTC], [HIGH_SPEED_TF_BOOST, 0.0])) + _tf_boost *= float(np.interp(v_ego * 3.6, [HIGH_SPEED_BRAKE_KPH, 110.0], [0.5, 1.0])) + t_follow += _tf_boost # To estimate a safe distance from a moving lead, we calculate how much stopping # distance that lead needs as a minimum. We can add that to the current distance @@ -412,7 +469,9 @@ def update(self, carrot, reset_state, radarstate, v_cruise, x, v, a, j, personal v_upper) cruise_obstacle = np.cumsum(T_DIFFS * v_cruise_clipped) + get_safe_obstacle_distance(v_cruise_clipped, t_follow, comfort_brake, stop_distance) - adjust_dist = carrot.trafficStopDistanceAdjust if v_ego > 0.1 else -2.0 + # 최종 정차(v<=0.1)는 파라미터 대신 하드코딩 보정 사용. 0에 가까울수록 정지점에 더 바짝 정차. + # 정지차(모델 인식)도 이 경로를 타므로 안전여유 확보 필요 → -1.5 (과거 -2.0은 너무 멀고, -1.0은 추돌위험). + adjust_dist = carrot.trafficStopDistanceAdjust if v_ego > 0.1 else -1.5 if 50 < stop_x + adjust_dist < cruise_obstacle[0]: stop_x = cruise_obstacle[0] - adjust_dist x2 = stop_x * np.ones(N+1) + adjust_dist @@ -428,7 +487,7 @@ def update(self, carrot, reset_state, radarstate, v_cruise, x, v, a, j, personal # These are not used in ACC mode x[:], v[:], a[:], j[:] = 0.0, 0.0, 0.0, 0.0 - if radarstate.leadOne.status: + if lead_one.status: self.a_change_cost = np.interp(abs(self.j_lead), [0.3, 2.0], [A_CHANGE_COST, 20]) else: self.a_change_cost = A_CHANGE_COST diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index be66b65567..9db6241f03 100644 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -26,6 +26,22 @@ MIN_ALLOW_THROTTLE_SPEED = 2.5 RESET_DECEL_RAMP_TIME = 2.0 +# ── Jerk ease-in (A안): 가감속 onset에서 jerk를 점증시켜 S-curve로 만든다 ── +# 일정 jerk 상한은 onset에서 jerk가 0→상한으로 '계단'처럼 튀어(=jounce 스파이크) +# 시작 jolt를 남긴다. 시작 직후 jerk를 점증시키면 가속도가 S자로 부드럽게 붙는다. +JERK_EASE_TIME = 0.4 # 새 maneuver 시작 후 jerk를 100%로 키우는 시간(s) +JERK_EASE_FLOOR = 0.3 # 시작 시 jerk 비율 하한(감속 onset 등) +# 가속은 선행차 추종 재가속(거리 좁히기)이 느리지 않게 시작 jerk를 더 높게 둔다. +# (감속보다 높은 floor → 초중반 가속력↑, 단 0에서 시작하는 ease 자체는 유지해 급가속감 방지) +JERK_EASE_FLOOR_ACCEL = 0.6 +# 가속 ease-out: 가속을 마무리하며(현재 가속 중, 양의 가속을 0 근처로 줄이는 구간) 목표 +# 차간거리에 살며시 도달하도록 부드러운 jerk를 쓴다(사용자 요청: 끝부분은 더 부드럽게). +ACCEL_EASEOUT_JERK = 1.2 +# 고속 제동 안전 우회: 고속에서 선행차 접근 중이면 ease를 풀어(=즉응) 감지 초기부터 +# 충분한 제동이 미리 들어가게 한다(고속 늦은 감지로 인한 충돌 우려 대응). +HIGH_SPEED_BRAKE_KPH = 70.0 +HIGH_SPEED_BRAKE_TTC = 8.0 # 이 TTC(초) 이내로 접근 중이면 제동 ease 해제 + # Lookup table for turns _A_TOTAL_MAX_V = [2.4, 4.8] #[1.7, 3.2] _A_TOTAL_MAX_BP = [20., 40.] @@ -96,6 +112,8 @@ def __init__(self, CP, init_v=0.0, init_a=0.0, dt=DT_MDL): self.allow_throttle = True self.a_desired = init_a + self._jerk_ramp_t = 0.0 # jerk ease-in 경과시간(새 가감속 시작부터) + self._jerk_dir = 0 # 직전 가감속 방향(+1 가속 / -1 감속 / 0) self.v_desired_filter = FirstOrderFilter(init_v, 2.0, self.dt) self.prev_accel_clip = [ACCEL_MIN, ACCEL_MAX] self.output_a_target = 0.0 @@ -136,6 +154,77 @@ def parse_model(model_msg): throttle_prob = 1.0 return x, v, a, j, throttle_prob + @staticmethod + def _is_stopping(sm, carrot): + """정지 의도(신호등 정지 xState==3 또는 모델 shouldStop) 여부.""" + try: + return bool(carrot.xState.value == 3 or sm['modelV2'].action.shouldStop) + except Exception: + return False + + + def _positive_jerk_limit(self, a_prev, v_ego_kph): + """가속 방향(a_target>a_prev) jerk 상한.""" + if a_prev < 0.0: + # 감속 해제(음수 가속도를 0으로 푸는 구간)는 크게 허용 — 브레이크는 신속히 뗀다. + return 3.0 + # 가속 build-up: 저중속 재가속(추종 거리 좁히기)이 느리지 않게 jerk를 속도비례로 키우고, + # 가속 전용 ease floor로 초중반 가속력을 확보(급가속감은 ease-in ramp로 방지). + jerk_speed = float(np.interp(v_ego_kph, [0.0, 30.0, 80.0], [0.95, 1.5, 2.0])) + jerk_accel = float(np.interp(a_prev, [0.0, 1.0], [1.0, 0.7])) + ease_acc = float(np.clip(self._jerk_ramp_t / JERK_EASE_TIME, JERK_EASE_FLOOR_ACCEL, 1.0)) + return jerk_speed * jerk_accel * ease_acc + + def _negative_jerk_limit(self, a_target, a_prev, v_ego_kph, sm, carrot): + """감속 방향(a_target 0.1 and a_target > -0.4: + # 가속 ease-out: 가속 중 목표 간격에 근접해 가속을 거두는 구간(실제 제동 아님)은 + # 부드러운 jerk로 살며시 마무리(끝부분 부드럽게 — 사용자 요청). + return ACCEL_EASEOUT_JERK + # 제동 build-up: 목표 감속이 깊을수록 한도를 키워(≈무제한) 안전 확보. + # -1.2(완만) 2.0 / -2.5 5.0 / -4.0(긴급) 12.0 [m/s^3] + max_negative_jerk = float(np.interp(a_target, [-4.0, -2.5, -1.2], [12.0, 5.0, 2.0])) + # 기본 ease(전환 후 점증)에서 시작, 아래 상황에선 ease를 풀어(→1.0) 즉응 제동. + ease_dec = float(np.clip(self._jerk_ramp_t / JERK_EASE_TIME, JERK_EASE_FLOOR, 1.0)) + # (1) 깊은 감속은 '고속에서만' ease 해제 — 저속(≤25km/h)은 급브레이킹 완화 위해 ease 유지. + deep = float(np.interp(a_target, [-3.0, -1.5], [1.0, 0.0])) * float(np.interp(v_ego_kph, [25.0, 50.0], [0.0, 1.0])) + ease_dec = max(ease_dec, deep) + # (2) 고속 + 선행차 접근(TTC<임계): 감지 초기부터 충분 제동(고속 늦은 감지 충돌 우려 대응). + if v_ego_kph >= HIGH_SPEED_BRAKE_KPH: + try: + lead = sm['radarState'].leadOne + if lead.status and lead.dRel > 0.0 and lead.vRel < 0.0 and (lead.dRel / -lead.vRel) < HIGH_SPEED_BRAKE_TTC: + ease_dec = 1.0 + except Exception: + pass + # (3) 정지(신호등/모델) 접근: 선행차 없어도 즉응 제동으로 정지선 초과 방지. + if self._is_stopping(sm, carrot): + ease_dec = 1.0 + return max_negative_jerk * ease_dec + + def _apply_jerk_limits(self, a_target, a_prev, v_ego_kph, sm, carrot): + """가감속 명령의 jerk(가속도 변화율)를 제한해 onset jolt를 줄인다(S-curve). + + maneuver phase(가속 +1 / 감속 -1)를 a_target 부호 + 데드밴드(±0.15)로 판정하고, + 가속↔감속 '전환'에서만 ease ramp를 재시작한다(지속 가감속에선 jerk가 100%까지 자람). + """ + if a_target > 0.15: + phase = 1 + elif a_target < -0.15: + phase = -1 + else: + phase = self._jerk_dir # 데드밴드: 직전 phase 유지 + if phase != self._jerk_dir: + self._jerk_ramp_t = 0.0 + self._jerk_dir = phase + self._jerk_ramp_t += self.dt + + if a_target > a_prev: + a_target = min(a_target, a_prev + self._positive_jerk_limit(a_prev, v_ego_kph) * self.dt) + elif a_target < a_prev: + a_target = max(a_target, a_prev - self._negative_jerk_limit(a_target, a_prev, v_ego_kph, sm, carrot) * self.dt) + return a_target + def update(self, sm, carrot): self.mpc.mode = 'blended' if sm['selfdriveState'].experimentalMode else 'acc' @@ -237,7 +326,14 @@ def update(self, sm, carrot): # Interpolate 0.05 seconds and save as starting point for next iteration a_prev = self.a_desired - self.a_desired = float(np.interp(self.dt, CONTROL_N_T_IDX, self.a_desired_trajectory)) + a_target = float(np.interp(self.dt, CONTROL_N_T_IDX, self.a_desired_trajectory)) + v_ego_kph = v_ego * CV.MS_TO_KPH + + # jerk(가속도 변화율) 제한으로 a_target을 다듬는다. + # (내리막 중력보상은 제거 — 내리막 크롤링 추종/재출발에서 MPC와 충돌해 과감속·stuck을 + # 반복 유발. 평지와 동일한 openpilot 기본 동작 유지. git 히스토리에 보존.) + a_target = self._apply_jerk_limits(a_target, a_prev, v_ego_kph, sm, carrot) + self.a_desired = a_target self.v_desired_filter.x = self.v_desired_filter.x + self.dt * (self.a_desired + a_prev) / 2.0 longitudinalActuatorDelay = self.params.get_float("LongActuatorDelay")*0.01 diff --git a/selfdrive/ui/qt/home.cc b/selfdrive/ui/qt/home.cc index f027f477a6..e6d2cf1fb7 100644 --- a/selfdrive/ui/qt/home.cc +++ b/selfdrive/ui/qt/home.cc @@ -4,15 +4,287 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include #include "selfdrive/ui/qt/offroad/experimental_mode.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/widgets/prime.h" +#include "selfdrive/ui/qt/widgets/input.h" +#include "common/params.h" +#include "selfdrive/ui/qt/qt_window.h" #ifdef ENABLE_MAPS #include "selfdrive/ui/qt/maps/map_settings.h" #endif +// AutoTunerGuideDialog +AutoTunerGuideDialog::AutoTunerGuideDialog(const QString &html_content, QWidget *parent) : DialogBase(parent) { + setWindowFlags(Qt::Popup | Qt::FramelessWindowHint); + setAttribute(Qt::WA_TranslucentBackground); + setStyleSheet(R"( + DialogBase { background: transparent; } + #container { background-color: #1b1b1b; border-radius: 20px; } + QLabel { color: #dddddd; font-size: 45px; margin: 20px; } + QPushButton { padding: 20px; height: 100px; font-size: 45px; border-radius: 10px; color: white; background-color: #465BEA; } + QPushButton:pressed { background-color: #3049F4; } + )"); + + QVBoxLayout *outer_layout = new QVBoxLayout(this); + outer_layout->setContentsMargins(40, 40, 40, 40); + + QFrame *container = new QFrame(this); + container->setObjectName("container"); + QVBoxLayout *main_layout = new QVBoxLayout(container); + main_layout->setContentsMargins(20, 20, 20, 20); + + QLabel *text = new QLabel(html_content); + text->setWordWrap(true); + text->setAlignment(Qt::AlignTop | Qt::AlignLeft); + + QScrollArea *scroll = new QScrollArea(); + scroll->setWidgetResizable(true); + scroll->setFrameShape(QFrame::NoFrame); + scroll->setStyleSheet("QScrollArea { background: transparent; } QWidget { background: transparent; }"); + QScroller::grabGesture(scroll->viewport(), QScroller::LeftMouseButtonGesture); + scroll->setWidget(text); + + main_layout->addWidget(scroll, 1); + + QPushButton *btn_ok = new QPushButton(tr("확인")); + btn_ok->setFixedWidth(400); + main_layout->addWidget(btn_ok, 0, Qt::AlignCenter); + + outer_layout->addWidget(container); + + connect(btn_ok, &QPushButton::clicked, this, &QDialog::accept); +} + +void AutoTunerGuideDialog::showEvent(QShowEvent *event) { + setMainWindow(this); + QDialog::showEvent(event); +} + +// AutoTunerDialog +AutoTunerDialog::AutoTunerDialog(const QString &title_text, const QJsonObject &recs, QWidget *parent) : DialogBase(parent), recommendations(recs) { + setWindowFlags(Qt::Popup | Qt::FramelessWindowHint); + setAttribute(Qt::WA_TranslucentBackground); + setStyleSheet(R"( + DialogBase { background: transparent; } + #container { background-color: #2b2b2b; border-radius: 30px; border: 2px solid #555555; } + QLabel { color: white; } + QCheckBox { font-size: 45px; color: white; spacing: 20px; } + QCheckBox::indicator { width: 50px; height: 50px; } + QPushButton { padding: 25px; font-size: 45px; font-weight: 500; border-radius: 10px; color: white; background-color: #444444; } + QPushButton:pressed { background-color: #333333; } + )"); + + QVBoxLayout *outer_layout = new QVBoxLayout(this); + outer_layout->setContentsMargins(200, 40, 200, 40); + + QFrame *container = new QFrame(this); + container->setObjectName("container"); + QVBoxLayout *main_layout = new QVBoxLayout(container); + main_layout->setContentsMargins(40, 30, 40, 30); + main_layout->setSpacing(15); + + QLabel *title = new QLabel(title_text); + title->setStyleSheet("font-size: 55px; font-weight: bold; margin-bottom: 10px;"); + title->setAlignment(Qt::AlignCenter); + main_layout->addWidget(title); + + QScrollArea *scroll = new QScrollArea(); + scroll->setWidgetResizable(true); + scroll->setFrameShape(QFrame::NoFrame); + scroll->setStyleSheet("QScrollArea { background: transparent; } QWidget { background: transparent; }"); + QScroller::grabGesture(scroll->viewport(), QScroller::LeftMouseButtonGesture); + + QWidget *scroll_widget = new QWidget(); + QVBoxLayout *scroll_layout = new QVBoxLayout(scroll_widget); + scroll_layout->setContentsMargins(0, 0, 0, 0); + scroll_layout->setSpacing(15); + + for (const QString& group : recommendations.keys()) { + QJsonObject group_items = recommendations[group].toObject(); + + QString short_group; + bool is_en = (QString::fromStdString(Params().get("LanguageSetting")) != "main_ko"); + if (is_en && group.contains("(")) { + short_group = group.split("(").last().replace(")", ""); + } else { + short_group = group.split(" ").first(); + } + + for (const QString& key : group_items.keys()) { + QJsonObject info = group_items[key].toObject(); + QString item_text = QString("[%1] %2 [%3]  :  %4 ➔ %5") + .arg(short_group) + .arg(key) + .arg(info["band_kph"].toString()) + .arg(info["current"].toInt()) + .arg(info["recommended"].toInt()); + + QHBoxLayout *item_layout = new QHBoxLayout(); + item_layout->setContentsMargins(0, 0, 0, 15); + item_layout->setSpacing(20); + + QCheckBox *item_cb = new QCheckBox(); + item_cb->setChecked(true); + item_cb->setStyleSheet("QCheckBox::indicator { width: 50px; height: 50px; }"); + + QLabel *item_label = new QLabel(item_text); + item_label->setStyleSheet("font-size: 45px; color: white;"); + item_label->setWordWrap(true); + + item_layout->addWidget(item_cb); + item_layout->addWidget(item_label, 1); + + scroll_layout->addLayout(item_layout); + item_checkboxes[key] = item_cb; + } + } + scroll_layout->addStretch(); + scroll->setWidget(scroll_widget); + main_layout->addWidget(scroll, 1); + + QHBoxLayout *btn_layout = new QHBoxLayout(); + + QPushButton *btn_guide = new QPushButton(tr("사용 안내 (Guide)")); + btn_guide->setStyleSheet("background-color: #3b5998;"); + + QPushButton *btn_later = new QPushButton(tr("나중에 (Later)")); + btn_later->setStyleSheet("background-color: #555555;"); + + QPushButton *btn_clear = new QPushButton(tr("학습 초기화 (Clear)")); + btn_clear->setStyleSheet("background-color: #8a1d1d;"); + + QPushButton *btn_apply = new QPushButton(tr("선택 적용 (Apply Selected)")); + btn_apply->setStyleSheet("background-color: #178644;"); + + btn_layout->addWidget(btn_guide); + btn_layout->addWidget(btn_later); + btn_layout->addWidget(btn_clear); + btn_layout->addWidget(btn_apply); + main_layout->addLayout(btn_layout); + + outer_layout->addWidget(container); + + connect(btn_guide, &QPushButton::clicked, this, [=]() { + QString guide_html; + if (QString::fromStdString(Params().get("LanguageSetting")) != "main_ko") { + guide_html = R"( +
+
🥕 CarrotPilot Auto-Tuner Guide

+
📊 Data Collection & Application
+
    +
  • Data Collection: Records driving data in the background, focusing on overrides (gas pedal) and interventions (brake pedal).
  • +
  • Pattern Analysis: Analyzes the gap between current settings and driving behavior to calculate ideal parameters.
  • +
  • Recommendation & Apply: Shows a popup when parking (P). Click [Apply Selected] to apply. (Manage in Tuning History)
  • +

+
⚙️ Group Parameter Details
+ 🚀 [Acceleration]
+ Adjusts cruise control acceleration capabilities.
+ - CruiseMaxVals0~6: Increases accel limits per speed band to fix sluggish starts and acceleration.
+ 🚙 [Driving]
+ Adjusts longitudinal (brake) control.
+ - JLeadFactor3: If the driver brakes frequently, it tunes the system to start braking earlier and smoother.
+ 🛣️ [Following Distance]
+ Optimizes highway following distance. If gas is pressed often while following a lead car at speed, it means the gap is too wide. Recommends decreasing TFollowGap for that gap level.
+ - TFollowGap1~4: Per-GAP-level time-gap setting (seconds x100). Lower = closer.
+ 🎛️ [Dynamic Control]
+ If multiple brake params trigger at once, only the strongest signal is recommended this session to avoid over-correction. Others deferred to next session.
+ - DynamicTFollow: When lead car decelerates suddenly, driver may brake to compensate. More events = system widens gap faster when lead decelerates. (0=off)
+ - TFollowDecelBoost: During strong ego deceleration, gap shrinks naturally. More events = system maintains extra buffer during braking. (default 10, range 0~100)
+ 🔄 [Steering]
+ Adjusts handling responsiveness.
+ - PathOffset: Compensates for lane drifts.
+ - SteerActuatorDelay: Reduces cornering delay.
+
+
🧠 Autonomous Pattern Detection
+ Even without your pedal input, the system monitors its own control quality:
+ - (auto) Late Braking: If the system brakes too hard at the last moment, it recommends increasing JLeadFactor3 to start braking earlier.
+ - (auto) Aggressive Accel: If the system accelerates too harshly relative to the lead car, it recommends lowering CruiseMaxVals.
+ - (auto) Hunting: If the system oscillates between accel/decel while following, it recommends widening the gap for stability. +
+ )"; + } else { + guide_html = R"( +
+
🥕 CarrotPilot Auto-Tuner 사용 안내

+
📊 데이터 수집 및 적용 방식
+
    +
  • 주행 중 데이터 수집: 백그라운드에서 차량의 주행 데이터를 실시간으로 기록하며, 특히 오버라이드(가속 페달 밟음)개입(직접 브레이크) 순간을 중점 수집합니다.
  • +
  • 패턴 분석: 현재 설정값과 운전자 주행 성향의 차이를 분석하여 이상적인 파라미터를 계산합니다.
  • +
  • 추천 및 적용: 주차(P) 시 팝업으로 추천값을 안내하며, [선택 적용]을 누르면 즉시 반영됩니다. (설정의 튜닝 이력에서 이력 확인/삭제 가능)
  • +

+
⚙️ 그룹별 튜닝 설정 안내
+ 🚀 [가속] (Acceleration)
+ 오픈파일럿의 크루즈 가속 능력을 조정합니다.
+ - CruiseMaxVals0~6: 속도 대역별 가속 한계치를 높여 굼뜬 출발과 답답한 가속을 개선합니다.
+ 🚙 [주행] (Driving)
+ 종방향(브레이크) 제어 능력을 조정합니다.
+ - JLeadFactor3: 운전자가 브레이크를 자주 밟을 경우, 앞차가 가까워질 때 조금 더 일찍 감속을 시작하도록 유도합니다.
+ 🛣️ [거리] (Following Distance)
+ 고속도 주행 중 선행차 추종 거리를 최적화합니다. 선행차가 있는데도 가속 페달을 자주 밟는다면, 시스템이 지나치게 거리를 넓게 유지하는 것으로 판단하여 해당 GAP의 TFollowGap 값을 줄이는 방향으로 추천합니다.
+ - TFollowGap1~4: GAP 단계별 추종 거리 시간(x0.01초). 낮을수록 가까워집니다. 최소 0.70초 보장.
+ 🎛️ [동적제어] (Dynamic Control)
+ 브레이크 관련 파라미터가 동시 여러 개 발동되면 합산 과보정 위험이 있어, 이벤트가 가장 많은 1개만 먼저 추천합니다. 나머지는 다음 세션 재평가.
+ - DynamicTFollow: 앞차가 급감속할 때 브레이크 개입이 쌓이면, 시스템이 앞차 감속에 더 빠르게 반응하도록 조정합니다. (0=사용 안 함)
+ - TFollowDecelBoost: 내 차 강감속 중 브레이크 개입이 쌓이면, 감속이 강할수록 자동으로 더 넉넉한 간격을 유지하도록 보완합니다. (기본값 10, 범위 0~100)
+ 🔄 [조향] (Steering)
+ 핸들링 반응성을 조정합니다.
+ - PathOffset / SteerActuatorDelay: 조향 편차 및 지연 보정.
+
+
🧠 자율 주행 패턴 자동 감지
+ 운전자가 페달을 밟지 않아도, 시스템은 스스로의 제어 품질을 감시합니다:
+ - (auto) 늦은 제동: 시스템이 뒤늦게 급제동을 거는 패턴이 감지되면, 더 일찍 부드럽게 감속하도록 제동 시점 상향을 추천합니다.
+ - (auto) 과도한 가속: 선행차 흐름에 비해 시스템이 너무 거칠게 가속하면, 가속 한계치를 낮추도록 추천합니다.
+ - (auto) 주행 요동(Hunting): 가속과 감속을 반복하며 거리를 불안정하게 맞추는 패턴이 감지되면, 제어의 여유를 위해 차간 거리를 넓히도록 추천합니다. +
+ )"; + } + AutoTunerGuideDialog *d = new AutoTunerGuideDialog(guide_html, this); + d->exec(); + d->deleteLater(); + }); + + connect(btn_later, &QPushButton::clicked, this, &QDialog::reject); + + connect(btn_clear, &QPushButton::clicked, [=]() { + if (ConfirmationDialog::confirm(tr("적용하지 않고 현재까지의 모든 학습 데이터를 삭제하시겠습니까?"), tr("초기화"), this)) { + Params().putBool("CarrotLearningClear", true); + this->reject(); + } + }); + + connect(btn_apply, &QPushButton::clicked, this, &QDialog::accept); +} + +QJsonObject AutoTunerDialog::getSelectedItems() { + QJsonObject selected; + for (const QString& group : recommendations.keys()) { + QJsonObject group_items = recommendations[group].toObject(); + QJsonObject selected_group_items; + + for (const QString& key : group_items.keys()) { + if (item_checkboxes.contains(key) && item_checkboxes[key]->isChecked()) { + selected_group_items[key] = group_items[key]; + } + } + + if (!selected_group_items.isEmpty()) { + selected[group] = selected_group_items; + } + } + return selected; +} + // HomeWindow: the container for the offroad and onroad UIs HomeWindow::HomeWindow(QWidget* parent) : QWidget(parent) { @@ -80,6 +352,108 @@ void HomeWindow::updateState(const UIState &s) { break; } + // Auto-Tuner: 주행 중 주차(P단) 전환 시 즉시 팝업 표시 또는 자동 적용 (1초 주기로 체크) + static int carrot_tuner_frame = 0; + if (carrot_tuner_frame++ % 20 == 0) { + Params params; + if (params.getBool("CarrotLearningPopupReady")) { + // 중복 팝업/자동 적용 처리를 방지하기 위해 즉시 플래그 해제 + params.putBool("CarrotLearningPopupReady", false); + + QString raw = QString::fromStdString(params.get("CarrotLearningRecommend")); + QJsonDocument doc = QJsonDocument::fromJson(raw.toUtf8()); + if (!raw.isEmpty() && doc.isObject()) { + QJsonObject obj = doc.object(); + bool auto_apply = params.getBool("CarrotLearningAutoApply"); + + if (auto_apply) { + // ── [A] 자동 적용 (Auto Apply) ────────────────────────────────────── + // 추천사항의 모든 파라미터 적용 + for (const QString& group : obj.keys()) { + QJsonObject group_items = obj[group].toObject(); + for (const QString& key : group_items.keys()) { + QJsonObject info = group_items[key].toObject(); + // 엔진이 이미 안전 범위로 클램프함. StoppingAccel 등 음수/0 추천도 + // 적용되도록 양수 가드 대신 키 존재 여부로 판정. + if (info.contains("recommended")) { + int recommended = info["recommended"].toInt(0); + params.put(key.toStdString(), std::to_string(recommended)); + } + } + } + + // history_array에 이력 저장 + QJsonArray history_array; + QString history_raw = QString::fromStdString(params.get("CarrotLearningHistory")); + if (!history_raw.isEmpty()) { + QJsonDocument h_doc = QJsonDocument::fromJson(history_raw.toUtf8()); + if (h_doc.isArray()) history_array = h_doc.array(); + } + + QJsonObject history_entry; + history_entry["timestamp"] = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm"); + history_entry["changes"] = obj; + history_entry["id"] = QString::number(QDateTime::currentMSecsSinceEpoch()); + + history_array.prepend(history_entry); + while (history_array.size() > 50) history_array.removeLast(); + + params.put("CarrotLearningHistory", QJsonDocument(history_array).toJson(QJsonDocument::Compact).toStdString()); + + // 학습 데이터 클리어 신호 + params.putBool("CarrotLearningClear", true); + } else { + // ── [B] 수동 적용 (Manual Apply) ───────────────────────────────────── + QString msg = tr("Auto-Tuner: Driving pattern learned!"); + AutoTunerDialog *dialog = new AutoTunerDialog(msg, obj, this); + connect(dialog, &QDialog::accepted, [=]() { + Params p; + QJsonObject selected = dialog->getSelectedItems(); + if (!selected.isEmpty()) { + QJsonArray history_array; + QString history_raw = QString::fromStdString(p.get("CarrotLearningHistory")); + if (!history_raw.isEmpty()) { + QJsonDocument h_doc = QJsonDocument::fromJson(history_raw.toUtf8()); + if (h_doc.isArray()) history_array = h_doc.array(); + } + + QJsonObject history_entry; + history_entry["timestamp"] = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm"); + history_entry["changes"] = selected; + history_entry["id"] = QString::number(QDateTime::currentMSecsSinceEpoch()); + + history_array.prepend(history_entry); + while (history_array.size() > 50) history_array.removeLast(); + + p.put("CarrotLearningHistory", QJsonDocument(history_array).toJson(QJsonDocument::Compact).toStdString()); + + for (const QString& group : selected.keys()) { + QJsonObject group_items = selected[group].toObject(); + for (const QString& key : group_items.keys()) { + QJsonObject info = group_items[key].toObject(); + // 엔진이 이미 안전 범위로 클램프함. 음수/0 추천(StoppingAccel 등)도 + // 적용되도록 양수 가드 대신 키 존재 여부로 판정. + if (info.contains("recommended")) { + int recommended = info["recommended"].toInt(0); + p.put(key.toStdString(), std::to_string(recommended)); + } + } + } + } + Params().putBool("CarrotLearningClear", true); + dialog->deleteLater(); + }); + + connect(dialog, &QDialog::rejected, [=]() { + dialog->deleteLater(); + }); + + setMainWindow(dialog); + } + } + } + } + } void HomeWindow::offroadTransition(bool offroad) { diff --git a/selfdrive/ui/qt/home.h b/selfdrive/ui/qt/home.h index f60b80b21a..1b9bcf9bdf 100644 --- a/selfdrive/ui/qt/home.h +++ b/selfdrive/ui/qt/home.h @@ -14,7 +14,30 @@ #include "selfdrive/ui/qt/sidebar.h" #include "selfdrive/ui/qt/widgets/controls.h" #include "selfdrive/ui/qt/widgets/offroad_alerts.h" +#include "selfdrive/ui/qt/widgets/input.h" #include "selfdrive/ui/ui.h" +#include +#include +#include + +class AutoTunerGuideDialog : public DialogBase { + Q_OBJECT + +public: + explicit AutoTunerGuideDialog(const QString &html_content, QWidget *parent = nullptr); + void showEvent(QShowEvent *event) override; +}; + +class AutoTunerDialog : public DialogBase { + Q_OBJECT + +public: + QMap item_checkboxes; + QJsonObject recommendations; + + explicit AutoTunerDialog(const QString &title_text, const QJsonObject &recs, QWidget *parent = nullptr); + QJsonObject getSelectedItems(); +}; class OffroadHome : public QFrame { Q_OBJECT diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index be74bee579..62aeb471c8 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -1,3 +1,4 @@ +#include #include #include #include @@ -7,6 +8,13 @@ #include #include +#include +#include +#include +#include +#include +#include +#include #include "common/watchdog.h" #include "common/util.h" @@ -460,6 +468,1066 @@ void SettingsWindow::setCurrentPanel(int index, const QString ¶m) { nav_btns->buttons()[index]->setChecked(true); } +AutoTunerGraphWidget::AutoTunerGraphWidget(QWidget *parent) : QWidget(parent) { + setAttribute(Qt::WA_OpaquePaintEvent, false); +} + +void AutoTunerGraphWidget::setData(const QList &ts, const QMap> &histories, const QMap &cols) { + timestamps = ts; + param_histories = histories; + colors = cols; + selected_index = -1; + update(); +} + +void AutoTunerGraphWidget::setSelectedParam(const QString ¶m) { + selected_param = param; + update(); +} + +void AutoTunerGraphWidget::setHiddenParams(const QSet ¶ms) { + hidden_params = params; + update(); +} + +void AutoTunerGraphWidget::mousePressEvent(QMouseEvent *event) { + if (timestamps.isEmpty()) return; + + int margin_left = 80; + int margin_right = 40; + QRect graph_rect = rect().adjusted(margin_left, 80, -margin_right, -40); + int steps_x = timestamps.size() - 1; + if (steps_x < 1) steps_x = 1; + + int click_x = event->x(); + int closest_idx = 0; + int min_dist = 999999; + + for (int i = 0; i < timestamps.size(); i++) { + int node_x = graph_rect.left() + i * graph_rect.width() / steps_x; + int dist = std::abs(click_x - node_x); + if (dist < min_dist) { + min_dist = dist; + closest_idx = i; + } + } + + if (min_dist < 60) { + selected_index = closest_idx; + } else { + selected_index = -1; + } + update(); +} + +void AutoTunerGraphWidget::paintEvent(QPaintEvent *event) { + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + // Background + painter.fillRect(rect(), QColor("#1f1f1f")); + + if (timestamps.isEmpty() || param_histories.isEmpty()) { + painter.setPen(QColor("#888888")); + painter.setFont(QFont("Arial", 40)); + painter.drawText(rect(), Qt::AlignCenter, tr("No historical data to display")); + return; + } + + // Margin - bottom margin reduced to 40 since overlapping labels are removed + int margin_left = 80; + int margin_right = 40; + int margin_top = 80; + int margin_bottom = 40; + + QRect graph_rect = rect().adjusted(margin_left, margin_top, -margin_right, -margin_bottom); + + int steps_x = timestamps.size() - 1; + if (steps_x < 1) steps_x = 1; + + // Draw Grid Lines (Vertical & Horizontal) + painter.setPen(QPen(QColor("#2d2d2d"), 2, Qt::SolidLine)); + + // X grid lines + for (int i = 0; i <= steps_x; i++) { + int x = graph_rect.left() + i * graph_rect.width() / steps_x; + painter.drawLine(x, graph_rect.top(), x, graph_rect.bottom()); + } + + // Y grid lines + int steps_y = 4; + for (int i = 0; i <= steps_y; i++) { + int y = graph_rect.top() + i * graph_rect.height() / steps_y; + painter.drawLine(graph_rect.left(), y, graph_rect.right(), y); + } + + // 큰 수치(예: StopDistanceCarrot, 500↑) 파라미터는 작은 수치(≤200) 파라미터와 + // 스케일 차이가 커서 전체보기 가독성을 해친다. 따라서 '큰 수치' 파라미터는 + // 좌측에서 직접 선택(상세보기)했을 때만 그래프에 표시하고, 그 외(전체보기 포함) + // 에서는 축 범위 계산과 그리기 모두에서 제외한다. + const double LARGE_SCALE_THRESHOLD = 300.0; + auto isLargeScale = [&](const QString ¶m) { + for (double v : param_histories[param]) { + if (std::abs(v) > LARGE_SCALE_THRESHOLD) return true; + } + return false; + }; + auto excludedFromView = [&](const QString ¶m) { + // 그룹이 접혀(숨김) 있는 파라미터는 그래프에서 제외 + if (hidden_params.contains(param)) return true; + // 큰 수치 파라미터는 자신이 선택된 경우에만 표시 + return isLargeScale(param) && param != selected_param; + }; + + double global_min = 0.0; + double global_max = 0.0; + bool first_val = true; + + // Compute global min/max bounds (large-scale params excluded unless selected) + for (const QString ¶m : param_histories.keys()) { + QList values = param_histories[param]; + if (values.size() != timestamps.size()) continue; + if (excludedFromView(param)) continue; + for (double val : values) { + if (first_val) { + global_min = val; + global_max = val; + first_val = false; + } else { + if (val < global_min) global_min = val; + if (val > global_max) global_max = val; + } + } + } + + // Draw Line Paths + painter.setBrush(Qt::NoBrush); + for (const QString ¶m : param_histories.keys()) { + QList values = param_histories[param]; + if (values.size() != timestamps.size()) continue; + if (excludedFromView(param)) continue; // 큰 수치 파라미터는 선택 시에만 그림 + + // Always use global min/max bounds to maintain consistent scaling across all variables + double min_val = global_min; + double max_val = global_max; + double diff = max_val - min_val; + + bool is_highlighted = selected_param.isEmpty() || (selected_param == param); + int opacity = 255; + int line_width = 4; + QColor color; + + if (!selected_param.isEmpty()) { + if (selected_param == param) { + color = colors.value(param, QColor(Qt::white)); + opacity = 255; + line_width = 8; + } else { + color = QColor("#444444"); // Dark gray for non-selected parameters + opacity = 80; + line_width = 2; + } + } else { + color = colors.value(param, QColor(Qt::white)); + opacity = 255; + line_width = 4; + } + color.setAlpha(opacity); + + bool dimmed = (!selected_param.isEmpty() && selected_param != param); + + // 노드 좌표 미리 계산 + QList pts; + for (int i = 0; i < values.size(); i++) { + int x = graph_rect.left() + i * graph_rect.width() / steps_x; + int y; + if (diff < 1e-5) { + y = graph_rect.top() + graph_rect.height() / 2; + } else { + y = graph_rect.bottom() - (values[i] - min_val) / diff * graph_rect.height(); + } + pts.append(QPoint(x, y)); + } + + painter.setBrush(Qt::NoBrush); + if (dimmed) { + // 비선택 파라미터: 전체 점선으로 흐리게 표시 + QPen pen(color, line_width); + pen.setStyle(Qt::DotLine); + painter.setPen(pen); + QPainterPath path; + for (int i = 0; i < pts.size(); i++) { + if (i == 0) path.moveTo(pts[i]); + else path.lineTo(pts[i]); + } + painter.drawPath(path); + } else { + // 강조 표시: 이전 값과 달라진 구간은 실선, 동일한 구간은 점선(가는선)으로 + // 자연스럽게 이어지도록 세그먼트 단위로 그린다. + // 전체보기(선택 없음)에서는 점선이 촘촘해 거의 실선처럼 보이므로 + // 실선은 조금 더 굵게, 점선은 간격을 더 넓게 한다. + // 상세보기(특정 파라미터 선택)는 현재 굵기/간격이 적당하므로 그대로 둔다. + bool all_view = selected_param.isEmpty(); + int solid_width = all_view ? (line_width + 2) : line_width; + int dot_width = std::max(1, line_width / 2); // 점선은 더 가늘게 + for (int i = 1; i < pts.size(); i++) { + bool changed = qAbs(values[i] - values[i - 1]) > 1e-6; + if (changed) { + QPen segpen(color, solid_width); + segpen.setStyle(Qt::SolidLine); + painter.setPen(segpen); + } else { + QPen segpen(color, dot_width); + if (all_view) { + segpen.setStyle(Qt::CustomDashLine); + segpen.setDashPattern({1, 4}); // 점(1) : 간격(4) — 기본 DotLine보다 넓게 + } else { + segpen.setStyle(Qt::DotLine); + } + painter.setPen(segpen); + } + painter.drawLine(pts[i - 1], pts[i]); + } + } + + // Draw Nodes and Value Labels + for (int i = 0; i < pts.size(); i++) { + int x = pts[i].x(); + int y = pts[i].y(); + + // 변경된 지점에만 노드(점)를 표시 (시작점 포함). 동일 구간은 점선만 이어짐. + bool changed = (i == 0) || qAbs(values[i] - values[i - 1]) > 1e-6; + + if (changed) { + painter.setBrush(color); + painter.setPen(Qt::NoPen); + int dot_size = (selected_param == param) ? 16 : 10; + painter.drawEllipse(QPoint(x, y), dot_size / 2, dot_size / 2); + } + + // 수치는 시작점(i==0) 또는 이전 값과 달라진 경우만 표시 + // 여러 파라미터가 겹쳐 그려져 가독성이 떨어지므로 폰트를 작게 유지하고, + // 폰트 색상은 해당 파라미터 선 색상과 동일하게 한다. + if (is_highlighted && changed) { + QColor label_color = colors.value(param, QColor(Qt::white)); + painter.setPen(label_color); + painter.setFont(QFont("Arial", (selected_param == param) ? 22 : 16, QFont::Bold)); + QString val_str = QString::number(values[i], 'g', 4); + // 선에 너무 붙지 않도록 약간 위로 올려 표시 + painter.drawText(QRect(x - 80, y - 46, 160, 28), Qt::AlignCenter, val_str); + } + } + } + + // Draw Vertical Guide Line and Time Tooltip on touch/click + if (selected_index >= 0 && selected_index < timestamps.size()) { + int x = graph_rect.left() + selected_index * graph_rect.width() / steps_x; + + // Vertical Guide + painter.setPen(QPen(QColor("#ffaa00"), 2.5, Qt::DashLine)); + painter.drawLine(x, graph_rect.top(), x, graph_rect.bottom()); + + // Tooltip Background & Text + QString date_str = timestamps[selected_index]; + painter.setFont(QFont("Arial", 28, QFont::Bold)); + + QFontMetrics fm = painter.fontMetrics(); + int txt_w = fm.horizontalAdvance(date_str) + 30; + int txt_h = 50; + + QRect tooltip_rect(x - txt_w / 2, margin_top - 65, txt_w, txt_h); + + // Boundary check + if (tooltip_rect.left() < 10) tooltip_rect.moveLeft(10); + if (tooltip_rect.right() > width() - 10) tooltip_rect.moveRight(width() - 10); + + painter.setBrush(QColor("#2d2d2d")); + painter.setPen(QPen(QColor("#ffaa00"), 2)); + painter.drawRoundedRect(tooltip_rect, 10, 10); + + painter.setPen(QColor("#ffffff")); + painter.drawText(tooltip_rect, Qt::AlignCenter, date_str); + } +} + +AutoTunerHistoryPanel::AutoTunerHistoryPanel(QWidget* parent) : QFrame(parent) { + QHBoxLayout *main_layout = new QHBoxLayout(this); + main_layout->setContentsMargins(20, 20, 20, 20); + main_layout->setSpacing(20); + + // Left Column: Parameters List + QVBoxLayout *left_layout = new QVBoxLayout(); + left_layout->setSpacing(15); + + QLabel *list_title = new QLabel(tr("Parameters")); + list_title->setStyleSheet("font-size: 42px; font-weight: bold; color: white;"); + left_layout->addWidget(list_title); + + QScrollArea *scroll = new QScrollArea(); + scroll->setWidgetResizable(true); + scroll->setFrameShape(QFrame::NoFrame); + scroll->setFixedWidth(340); + scroll->setStyleSheet("QScrollArea { background: transparent; } QWidget { background: transparent; }"); + QScroller::grabGesture(scroll->viewport(), QScroller::LeftMouseButtonGesture); + + QWidget *scroll_widget = new QWidget(); + param_list_layout = new QVBoxLayout(scroll_widget); + param_list_layout->setContentsMargins(0, 0, 0, 0); + param_list_layout->setSpacing(8); + scroll->setWidget(scroll_widget); + left_layout->addWidget(scroll, 1); + + QVBoxLayout *toggles_layout = new QVBoxLayout(); + toggles_layout->setSpacing(10); + toggles_layout->setContentsMargins(0, 10, 0, 0); + + // 터치 면적을 키우기 위해 가로로 길던 버튼을 정사각형에 가깝게 바꾸고 + // 두 개를 나란히(좌우) 배치한다. 글씨는 3줄로 표시. + QPushButton *lat_toggle = new QPushButton(this); + lat_toggle->setFixedHeight(150); + + QPushButton *long_toggle = new QPushButton(this); + long_toggle->setFixedHeight(150); + + auto updateToggles = [=]() { + bool apply_lat = Params().getBool("CarrotTunerApplyLat"); + bool apply_long = Params().getBool("CarrotTunerApplyLong"); + // 활성(ON)=초록, 비활성(OFF)=회색. 3줄 텍스트가 박스를 넘지 않도록 폰트 축소. + QString on_style = "background-color: #178644; font-size: 22px; font-weight: bold; border-radius: 10px; color: white;"; + QString off_style = "background-color: #4a5568; font-size: 22px; font-weight: bold; border-radius: 10px; color: white;"; + lat_toggle->setText(tr("Apply LAT\n(Steering)\n%1").arg(apply_lat ? "ON" : "OFF")); + lat_toggle->setStyleSheet(apply_lat ? on_style : off_style); + long_toggle->setText(tr("Apply LONG\n(Accel/Brake)\n%1").arg(apply_long ? "ON" : "OFF")); + long_toggle->setStyleSheet(apply_long ? on_style : off_style); + }; + + updateToggles(); + + connect(lat_toggle, &QPushButton::clicked, this, [=]() { + bool current = Params().getBool("CarrotTunerApplyLat"); + Params().putBool("CarrotTunerApplyLat", !current); + updateToggles(); + }); + + connect(long_toggle, &QPushButton::clicked, this, [=]() { + bool current = Params().getBool("CarrotTunerApplyLong"); + Params().putBool("CarrotTunerApplyLong", !current); + updateToggles(); + }); + + // 두 토글을 좌우로 나란히 배치 (각각 정사각형에 가까운 형태) + QHBoxLayout *apply_row = new QHBoxLayout(); + apply_row->setSpacing(10); + apply_row->addWidget(lat_toggle); + apply_row->addWidget(long_toggle); + toggles_layout->addLayout(apply_row); + + // 공장초기화: 좌측 하단(Apply LONG 아래)에 배치. 모든 튜너 파라미터를 + // params_keys.h 기본값으로 복원하고 학습 데이터/이력을 삭제한다. + QPushButton *factoryResetBtn = new QPushButton(tr("Parameter Init. Reset"), this); + factoryResetBtn->setFixedHeight(75); + factoryResetBtn->setStyleSheet("background-color: #8a1d1d; font-size: 26px; font-weight: bold; border-radius: 10px; color: white;"); + connect(factoryResetBtn, &QPushButton::clicked, this, [=]() { + if (ConfirmationDialog::confirm( + tr("Reset all auto-tuned parameters to factory defaults and delete learning data/history?"), + tr("Parameter Init. Reset"), this)) { + Params p; + static const std::vector tunerKeys = { + "CruiseMaxVals0", "CruiseMaxVals1", "CruiseMaxVals2", "CruiseMaxVals3", + "CruiseMaxVals4", "CruiseMaxVals5", "CruiseMaxVals6", + "JLeadFactor3", "TFollowGap1", "TFollowGap2", "TFollowGap3", "TFollowGap4", + "TFollowSpeedFactor", "DynamicTFollow", "TFollowDecelBoost", + "PathOffset", "SteerActuatorDelay", "SteerRatioRate", + "LateralTorqueAccelFactor", "LateralTorqueKf", "LateralTorqueFriction", + "LateralTorqueKiV", "LateralTorqueKpV", + "AutoCurveSpeedFactor", "AutoCurveSpeedAggressiveness", + "StoppingAccel", "VEgoStopping", "StopDistanceCarrot", + "LongTuningKf", "LongTuningKpV", "LongActuatorDelay", + }; + for (const auto &key : tunerKeys) { + auto def = p.getKeyDefaultValue(key); + if (def.has_value()) p.put(key, *def); + } + p.remove("CarrotLearningData"); + p.remove("CarrotLearningRecommend"); + p.remove("CarrotLearningHistory"); + p.putBool("CarrotLearningPopupReady", false); + p.putBool("CarrotTunerFactoryReset", true); + ConfirmationDialog::alert(tr("Factory reset applied. Tuning parameters restored to defaults."), this); + } + }); + toggles_layout->addWidget(factoryResetBtn); + left_layout->addLayout(toggles_layout); + + // Right Column: Chart + Controls + QVBoxLayout *right_layout = new QVBoxLayout(); + right_layout->setSpacing(20); + + QHBoxLayout *header_layout = new QHBoxLayout(); + header_layout->addStretch(); + + QPushButton *btn_card_list = new QPushButton(tr("View Card Type")); + btn_card_list->setStyleSheet("background-color: #10b981; font-size: 40px; border-radius: 10px; color: white; font-weight: bold; padding: 0px 50px;"); + btn_card_list->setFixedHeight(110); + connect(btn_card_list, &QPushButton::clicked, this, [=]() { + AutoTunerCardListDialog dlg(this); + dlg.exec(); + }); + header_layout->addWidget(btn_card_list); + + QPushButton *btn_all = new QPushButton(tr("Show All Parameters")); + btn_all->setStyleSheet("background-color: #0ea5e9; font-size: 40px; border-radius: 10px; color: white; font-weight: bold; padding: 0px 50px;"); + btn_all->setFixedHeight(110); + connect(btn_all, &QPushButton::clicked, this, [=]() { + if (graph_widget) graph_widget->setSelectedParam(""); + selected_param = ""; + updateLabelColors(); + }); + header_layout->addWidget(btn_all); + + QPushButton *btn_clear = new QPushButton(tr("Clear All Logs")); + btn_clear->setStyleSheet("background-color: #eab308; font-size: 40px; border-radius: 10px; color: white; font-weight: bold; padding: 0px 50px;"); + btn_clear->setFixedHeight(110); + connect(btn_clear, &QPushButton::clicked, this, &AutoTunerHistoryPanel::clearAll); + header_layout->addWidget(btn_clear); + + QPushButton *close_btn = new QPushButton(tr("Close")); + close_btn->setStyleSheet("background-color: #bb3333; font-size: 40px; border-radius: 10px; color: white; font-weight: bold; padding: 0px 50px;"); + close_btn->setFixedHeight(110); + connect(close_btn, &QPushButton::clicked, this, [=]() { + QWidget* w = this->window(); + if (w) { + QDialog* dlg = qobject_cast(w); + if (dlg) dlg->reject(); + else w->close(); + } + }); + header_layout->addWidget(close_btn); + + right_layout->addLayout(header_layout); + + graph_widget = new AutoTunerGraphWidget(this); + graph_widget->setMinimumHeight(750); + right_layout->addWidget(graph_widget, 1); + + main_layout->addLayout(left_layout); + main_layout->addLayout(right_layout, 1); + + // Palette initialization + param_colors.clear(); + QList palette = { + QColor("#3b82f6"), // Blue (파랑) + QColor("#10b981"), // Mint (민트) + QColor("#fbbf24"), // Light Yellow (밝은 노랑) + QColor("#8b5cf6"), // Violet (보라) + QColor("#ec4899"), // Pink (분홍) + QColor("#06b6d4"), // Cyan (시안/하늘) + QColor("#84cc16"), // Lime (연두) + QColor("#f43f5e"), // Rose (장미) + QColor("#14b8a6"), // Teal (청록) + QColor("#ffffff"), // White (흰색) + QColor("#f97316"), // Orange (주황) + QColor("#a855f7"), // Purple (자주) + QColor("#60a5fa"), // Light Blue (연파랑) + QColor("#34d399"), // Light Green (연초록) + QColor("#e879f9") // Light Magenta (연자주) + }; + // Pre-seed common params to keep consistent colors + param_colors["CruiseMaxVals0"] = QColor("#3b82f6"); // Blue + param_colors["CruiseMaxVals1"] = QColor("#60a5fa"); // Light Blue + param_colors["CruiseMaxVals2"] = QColor("#10b981"); // Mint Green + param_colors["CruiseMaxVals3"] = QColor("#84cc16"); // Lime + param_colors["CruiseMaxVals4"] = QColor("#fbbf24"); // Yellow + param_colors["CruiseMaxVals5"] = QColor("#f97316"); // Orange + param_colors["CruiseMaxVals6"] = QColor("#ec4899"); // Pink + param_colors["JLeadFactor3"] = QColor("#8b5cf6"); // Violet + param_colors["TFollowGap1"] = QColor("#06b6d4"); // Cyan + param_colors["TFollowGap2"] = QColor("#14b8a6"); // Teal + param_colors["TFollowGap3"] = QColor("#ffffff"); // White + param_colors["TFollowGap4"] = QColor("#a855f7"); // Purple + param_colors["PathOffset"] = QColor("#e879f9"); // Light Magenta + param_colors["SteerActuatorDelay"] = QColor("#f43f5e"); // Rose + param_colors["AutoCurveSpeedAggressiveness"] = QColor("#ff5722"); // Highlight curve learning with unique red-orange + + refreshHistory(); +} + +void AutoTunerHistoryPanel::showEvent(QShowEvent *event) { + refreshHistory(); + QFrame::showEvent(event); +} + +void AutoTunerHistoryPanel::refreshHistory() { + // Clear parameter list layout + QLayoutItem *child; + while ((child = param_list_layout->takeAt(0)) != nullptr) { + if (child->widget()) delete child->widget(); + delete child; + } + param_labels.clear(); + + QString raw = QString::fromStdString(Params().get("CarrotLearningHistory")); + if (raw.isEmpty()) { + latest_id = ""; + if (graph_widget) { + graph_widget->setData(QList(), QMap>(), QMap()); + } + return; + } + + QJsonArray arr = QJsonDocument::fromJson(raw.toUtf8()).array(); + + // Set latest entry info + QJsonObject latest_item = arr[0].toObject(); + latest_id = latest_item["id"].toString(); + + // Re-build historical timeline (max 30 points) + int chart_limit = 50; + int n_points = std::min(arr.size(), chart_limit); + + QList timestamps; + QList entries; + for (int i = n_points - 1; i >= 0; i--) { + QJsonObject item = arr[i].toObject(); + timestamps.append(item["timestamp"].toString()); + entries.append(item); + } + + // 1. Gather all parameters present in the timeline, remembering each + // parameter's group label (가속/조향/거리/주행 등)으로 좌측 리스트를 묶기 위함. + QSet param_set; + group_params.clear(); + for (const auto& entry : entries) { + QJsonObject changes = entry["changes"].toObject(); + for (const QString& group : changes.keys()) { + QJsonObject g_items = changes[group].toObject(); + for (const QString& key : g_items.keys()) { + param_set.insert(key); + if (!group_params[group].contains(key)) { + group_params[group].append(key); + } + } + } + } + + // 그룹 표시 순서: 가속 → 조향 → 곡선 → 거리 → 주행 → (초기값 프로파일 등 기타) + auto groupRank = [](const QString &g) -> int { + if (g.contains("Acceleration") && !g.contains("Profile")) return 0; + if (g.contains("Steering")) return 1; + if (g.contains("Curve")) return 2; + if (g.contains("Following Distance") && !g.contains("Profile")) return 3; + if (g.contains("Driving")) return 4; + return 10; // 초기값 프로파일 및 기타 그룹은 뒤에 + }; + group_order = group_params.keys(); + std::stable_sort(group_order.begin(), group_order.end(), + [&](const QString &a, const QString &b) { + int ra = groupRank(a), rb = groupRank(b); + if (ra != rb) return ra < rb; + return a.compare(b, Qt::CaseInsensitive) < 0; + }); + + // 2. Extrapolate timeline values for each parameter + QMap> param_histories; + QMap last_values; + + for (int t = 0; t < n_points; t++) { + QJsonObject changes = entries[t]["changes"].toObject(); + QMap current_changes; + for (const QString& group : changes.keys()) { + QJsonObject g_items = changes[group].toObject(); + for (const QString& key : g_items.keys()) { + current_changes[key] = g_items[key].toObject()["recommended"].toDouble(); + } + } + + for (const QString& param : param_set) { + if (current_changes.contains(param)) { + double val = current_changes[param]; + last_values[param] = val; + param_histories[param].append(val); + } else { + if (last_values.contains(param)) { + param_histories[param].append(last_values[param]); + } else { + // Find the first occurrence in the future to extract 'current' initial value + double initial_val = 0.0; + for (int future_t = t; future_t < n_points; future_t++) { + QJsonObject f_changes = entries[future_t]["changes"].toObject(); + bool found = false; + for (const QString& group : f_changes.keys()) { + QJsonObject fg_items = f_changes[group].toObject(); + if (fg_items.contains(param)) { + initial_val = fg_items[param].toObject()["current"].toDouble(); + found = true; + break; + } + } + if (found) break; + } + last_values[param] = initial_val; + param_histories[param].append(initial_val); + } + } + } + } + + // Assign colors dynamically for new parameters + QList palette = { + QColor("#3b82f6"), QColor("#10b981"), QColor("#f59e0b"), QColor("#8b5cf6"), + QColor("#ec4899"), QColor("#06b6d4"), QColor("#84cc16"), QColor("#f43f5e"), + QColor("#14b8a6"), QColor("#a855f7") + }; + int color_idx = 0; + for (const QString ¶m : param_set) { + if (!param_colors.contains(param)) { + param_colors[param] = palette[color_idx % palette.size()]; + color_idx++; + } + } + + // 각 그룹 내 파라미터는 알파벳순 정렬 + for (const QString &group : group_order) { + group_params[group].sort(Qt::CaseInsensitive); + } + + // 더 이상 존재하지 않는 그룹은 collapsed 상태에서 제거 (이력 변경 대응) + for (const QString &g : collapsed_groups.values()) { + if (!group_params.contains(g)) collapsed_groups.remove(g); + } + + // 좌측 리스트를 그룹 단위로 렌더링 + rebuildParamList(); + + if (graph_widget) { + graph_widget->setData(timestamps, param_histories, param_colors); + } + + // Ensure selected_param is still valid + if (!param_set.contains(selected_param)) { + selected_param = ""; + if (graph_widget) graph_widget->setSelectedParam(""); + } + applyHiddenParams(); + updateLabelColors(); +} + +// 좌측 파라미터 리스트를 그룹 헤더 + (펼쳐진 경우) 소속 파라미터 버튼으로 다시 그린다. +// 그룹 헤더를 누르면 toggleGroup()으로 접기/펴기 + 그래프 표시가 토글된다. +void AutoTunerHistoryPanel::rebuildParamList() { + // 기존 항목 제거 + QLayoutItem *child; + while ((child = param_list_layout->takeAt(0)) != nullptr) { + if (child->widget()) delete child->widget(); + delete child; + } + param_labels.clear(); + + // 그룹 라벨에서 표시용 짧은 이름 추출: "거리 (Following Distance)" → "Following Distance" + // 단, 이력 데이터의 그룹 키(=정렬/매칭 기준)는 그대로 두고 표시명만 바꾼다. + auto groupShortName = [](const QString &group) -> QString { + QString name; + int open = group.indexOf('('); + int close = group.lastIndexOf(')'); + if (open >= 0 && close > open) { + name = group.mid(open + 1, close - open - 1).trimmed(); + } else { + name = group.trimmed(); + } + // 표시명만 "Following Distance" → "Following Gap" 으로 치환 + if (name == "Following Distance") name = "Following Gap"; + return name; + }; + + for (const QString &group : group_order) { + bool collapsed = collapsed_groups.contains(group); + + // ── 그룹 헤더 (클릭 시 접기/펴기) ── + QPushButton *header_btn = new QPushButton(); + // 헤더 바는 파라미터 항목(#252525)과 확실히 구분되도록 더 밝은 회색으로 + header_btn->setStyleSheet("text-align: left; padding: 0px 12px; border-radius: 10px; background-color: #5a5f6b; color: white; font-size: 26px;"); + header_btn->setFixedHeight(60); + + QHBoxLayout *header_layout = new QHBoxLayout(header_btn); + header_layout->setContentsMargins(12, 0, 12, 0); + header_layout->setSpacing(12); + + // 접힘/펼침 표시 화살표 + QLabel *arrow = new QLabel(collapsed ? "▸" : "▾"); + arrow->setStyleSheet(QString("color: %1; font-size: 26px; font-weight: bold; background: transparent;") + .arg(collapsed ? "#777777" : "#ffffff")); + header_layout->addWidget(arrow); + + QLabel *header_lbl = new QLabel(groupShortName(group)); + header_lbl->setStyleSheet(QString("color: %1; font-size: 26px; font-weight: bold; background: transparent;") + .arg(collapsed ? "#777777" : "#ffffff")); + header_layout->addWidget(header_lbl, 1); + + connect(header_btn, &QPushButton::clicked, this, [=]() { toggleGroup(group); }); + param_list_layout->addWidget(header_btn); + + // ── 소속 파라미터들 (접혀있으면 그리지 않음) ── + if (collapsed) continue; + for (const QString ¶m : group_params[group]) { + QColor color = param_colors.value(param, QColor(Qt::white)); + + QPushButton *btn = new QPushButton(); + btn->setStyleSheet("text-align: left; padding: 0px 15px; border-radius: 10px; background-color: #252525; color: white; font-size: 28px;"); + btn->setFixedHeight(55); + + QHBoxLayout *btn_layout = new QHBoxLayout(btn); + btn_layout->setContentsMargins(22, 0, 10, 0); // 그룹 소속 표시를 위해 약간 들여쓰기 + btn_layout->setSpacing(15); + + QLabel *dot = new QLabel(); + dot->setFixedSize(20, 20); + dot->setStyleSheet(QString("background-color: %1; border-radius: 10px;").arg(color.name())); + btn_layout->addWidget(dot); + + QLabel *lbl = new QLabel(param); + lbl->setStyleSheet("color: white; font-size: 28px; font-weight: bold; background: transparent;"); + btn_layout->addWidget(lbl, 1); + param_labels[param] = lbl; + + connect(btn, &QPushButton::clicked, this, [=]() { + if (graph_widget) graph_widget->setSelectedParam(param); + selected_param = param; + updateLabelColors(); + }); + + param_list_layout->addWidget(btn); + } + } + param_list_layout->addStretch(); +} + +// 접혀있는 그룹들의 소속 파라미터를 모아 그래프에서 숨긴다. +void AutoTunerHistoryPanel::applyHiddenParams() { + QSet hidden; + for (const QString &group : collapsed_groups.values()) { + for (const QString ¶m : group_params.value(group)) { + hidden.insert(param); + } + } + if (graph_widget) graph_widget->setHiddenParams(hidden); +} + +// 그룹 헤더 클릭 핸들러: 접기/펴기 상태를 토글하고 리스트·그래프를 갱신한다. +void AutoTunerHistoryPanel::toggleGroup(const QString &group) { + if (collapsed_groups.contains(group)) { + collapsed_groups.remove(group); + } else { + collapsed_groups.insert(group); + // 접는 그룹에 현재 선택된 파라미터가 있으면 선택 해제 (숨겨지므로) + if (!selected_param.isEmpty() && group_params.value(group).contains(selected_param)) { + selected_param = ""; + if (graph_widget) graph_widget->setSelectedParam(""); + } + } + rebuildParamList(); + applyHiddenParams(); + updateLabelColors(); +} + +void AutoTunerHistoryPanel::restoreItem(const QString& id) { + if (ConfirmationDialog::confirm(tr("Are you sure you want to restore the parameters to this state?"), tr("Restore"), this)) { + QString raw = QString::fromStdString(Params().get("CarrotLearningHistory")); + QJsonArray arr = QJsonDocument::fromJson(raw.toUtf8()).array(); + QJsonArray new_arr; + + for (int i = 0; i < arr.size(); i++) { + QJsonObject entry = arr[i].toObject(); + if (entry["id"].toString() == id) { + QJsonObject changes = entry["changes"].toObject(); + for (const QString& group : changes.keys()) { + QJsonObject g_items = changes[group].toObject(); + for (const QString& key : g_items.keys()) { + int prev_val = g_items[key].toObject()["current"].toInt(); + Params().putInt(key.toStdString(), prev_val); + } + } + } else { + new_arr.append(entry); + } + } + + if (new_arr.isEmpty()) { + Params().remove("CarrotLearningHistory"); + } else { + Params().put("CarrotLearningHistory", QJsonDocument(new_arr).toJson(QJsonDocument::Compact).toStdString()); + } + refreshHistory(); + ConfirmationDialog::alert(tr("Restored to previous values successfully."), this); + } +} + +void AutoTunerHistoryPanel::deleteItem(const QString& id) { + if (ConfirmationDialog::confirm(tr("Are you sure you want to delete this item?"), tr("Delete"), this)) { + QString raw = QString::fromStdString(Params().get("CarrotLearningHistory")); + QJsonArray arr = QJsonDocument::fromJson(raw.toUtf8()).array(); + QJsonArray new_arr; + for (int i = 0; i < arr.size(); i++) { + if (arr[i].toObject()["id"].toString() != id) { + new_arr.append(arr[i]); + } + } + if (new_arr.isEmpty()) { + Params().remove("CarrotLearningHistory"); + } else { + Params().put("CarrotLearningHistory", QJsonDocument(new_arr).toJson(QJsonDocument::Compact).toStdString()); + } + refreshHistory(); + } +} + +void AutoTunerHistoryPanel::clearAll() { + if (ConfirmationDialog::confirm(tr("Are you sure you want to delete all history and restore parameters to their factory default values?"), tr("Clear All"), this)) { + Params params; + std::map defaults = { + {"CruiseMaxVals0", "160"}, + {"CruiseMaxVals1", "200"}, + {"CruiseMaxVals2", "160"}, + {"CruiseMaxVals3", "130"}, + {"CruiseMaxVals4", "110"}, + {"CruiseMaxVals5", "95"}, + {"CruiseMaxVals6", "80"}, + {"JLeadFactor3", "0"}, + {"TFollowGap1", "110"}, + {"TFollowGap2", "120"}, + {"TFollowGap3", "140"}, + {"TFollowGap4", "160"}, + {"DynamicTFollow", "0"}, + {"TFollowDecelBoost", "10"}, + {"PathOffset", "0"}, + {"SteerActuatorDelay", "0"} + }; + for (const auto& [key, val] : defaults) { + params.put(key, val); + } + params.remove("CarrotLearningHistory"); + refreshHistory(); + } +} + +void AutoTunerHistoryPanel::updateLabelColors() { + for (auto k : param_labels.keys()) { + if (!param_labels[k]) continue; + if (selected_param.isEmpty()) { + param_labels[k]->setStyleSheet("color: white; font-size: 28px; font-weight: bold; background: transparent;"); + } else if (k == selected_param) { + param_labels[k]->setStyleSheet("color: red; font-size: 28px; font-weight: bold; background: transparent;"); + } else { + param_labels[k]->setStyleSheet("color: #777777; font-size: 28px; font-weight: bold; background: transparent;"); + } + } +} + +// AutoTunerHistoryDialog implementation +AutoTunerHistoryDialog::AutoTunerHistoryDialog(QWidget *parent) : DialogBase(parent) { + QFrame *container = new QFrame(this); + container->setStyleSheet("QFrame { background-color: #1B1B1B; border-radius: 20px; }"); + QVBoxLayout *main_layout = new QVBoxLayout(container); + main_layout->setContentsMargins(20, 20, 20, 20); + main_layout->setSpacing(20); + + AutoTunerHistoryPanel *panel = new AutoTunerHistoryPanel(this); + main_layout->addWidget(panel, 1); + + QVBoxLayout *outer_layout = new QVBoxLayout(this); + outer_layout->setContentsMargins(30, 30, 30, 30); + outer_layout->addWidget(container); +} + +// AutoTunerCardListDialog implementation +AutoTunerCardListDialog::AutoTunerCardListDialog(QWidget *parent) : DialogBase(parent) { + QFrame *container = new QFrame(this); + container->setStyleSheet("QFrame { background-color: #1B1B1B; border-radius: 20px; }"); + QVBoxLayout *main_layout = new QVBoxLayout(container); + main_layout->setContentsMargins(50, 50, 50, 50); + main_layout->setSpacing(30); + + // Header layout: Title and Close button + QHBoxLayout *header_layout = new QHBoxLayout(); + QLabel *title = new QLabel(tr("Tuning History Card List"), this); + title->setStyleSheet("font-size: 60px; font-weight: bold; color: white;"); + header_layout->addWidget(title); + header_layout->addStretch(); + + QPushButton *close_btn = new QPushButton(tr("Close"), this); + close_btn->setFixedSize(250, 100); + close_btn->setStyleSheet("background-color: #bb3333; font-size: 40px; border-radius: 10px; color: white;"); + connect(close_btn, &QPushButton::clicked, this, &AutoTunerCardListDialog::reject); + header_layout->addWidget(close_btn); + main_layout->addLayout(header_layout); + + // Scroll Area + QScrollArea *scroll = new QScrollArea(this); + scroll->setWidgetResizable(true); + scroll->setFrameShape(QFrame::NoFrame); + scroll->setStyleSheet("QScrollArea { background: transparent; } QWidget { background: transparent; }"); + QScroller::grabGesture(scroll->viewport(), QScroller::LeftMouseButtonGesture); + + QWidget *scroll_widget = new QWidget(); + list_layout = new QVBoxLayout(scroll_widget); + list_layout->setContentsMargins(0, 0, 0, 0); + list_layout->setSpacing(10); + scroll->setWidget(scroll_widget); + main_layout->addWidget(scroll, 1); + + QVBoxLayout *outer_layout = new QVBoxLayout(this); + outer_layout->setContentsMargins(100, 100, 100, 100); + outer_layout->addWidget(container); + + refreshHistory(); +} + +void AutoTunerCardListDialog::refreshHistory() { + // Clear parameter list layout + QLayoutItem *child; + while ((child = list_layout->takeAt(0)) != nullptr) { + if (child->widget()) delete child->widget(); + delete child; + } + + QString raw = QString::fromStdString(Params().get("CarrotLearningHistory")); + if (raw.isEmpty()) { + QLabel *lbl = new QLabel(tr("No historical data to display"), this); + lbl->setStyleSheet("font-size: 45px; color: #888888;"); + lbl->setAlignment(Qt::AlignCenter); + list_layout->addWidget(lbl); + list_layout->addStretch(); + return; + } + + QJsonArray arr = QJsonDocument::fromJson(raw.toUtf8()).array(); + for (int i = 0; i < arr.size(); i++) { + QJsonObject item = arr[i].toObject(); + QString id = item["id"].toString(); + QString time_str = item["timestamp"].toString(); + QJsonObject changes = item["changes"].toObject(); + + QFrame *row = new QFrame(); + row->setStyleSheet("background-color: #2b2b2b; border-radius: 15px; padding: 5px 25px;"); + QHBoxLayout *row_layout = new QHBoxLayout(row); + row_layout->setContentsMargins(25, 5, 25, 5); + + QString text = QString("%1
").arg(tr("[%1 Applied]").arg(time_str)); + for (const QString& group : changes.keys()) { + QJsonObject g_items = changes[group].toObject(); + QString short_group; + bool is_ko = (QString::fromStdString(Params().get("LanguageSetting")) == "main_ko"); + if (!is_ko && group.contains("(")) { + short_group = group.split("(").last().replace(")", ""); + } else { + short_group = group.split(" ").first(); + } + for (const QString& key : g_items.keys()) { + QJsonObject info = g_items[key].toObject(); + text += QString("[%1] %2 [%3]  :  %4 ➔ %5
") + .arg(short_group) + .arg(key) + .arg(info["band_kph"].toString()) + .arg(info["current"].toInt()) + .arg(info["recommended"].toInt()); + } + } + + QLabel *lbl = new QLabel(text); + lbl->setWordWrap(true); + row_layout->addWidget(lbl, 1); + + bool is_latest = (i == 0); + + QPushButton *btn_restore = new QPushButton(tr("Restore")); + if (is_latest) { + btn_restore->setStyleSheet("background-color: #178644; font-size: 40px; padding: 20px; border-radius: 10px; color: white; font-weight: bold;"); + } else { + btn_restore->setStyleSheet("background-color: #333333; font-size: 40px; padding: 20px; border-radius: 10px; color: #666666;"); + } + btn_restore->setEnabled(is_latest); + btn_restore->setFixedSize(220, 110); + connect(btn_restore, &QPushButton::clicked, this, [=]() { restoreItem(id); }); + row_layout->addWidget(btn_restore); + + QPushButton *btn_del = new QPushButton(tr("Delete")); + if (is_latest) { + btn_del->setStyleSheet("background-color: #555555; font-size: 40px; padding: 20px; border-radius: 10px; color: white; font-weight: bold;"); + } else { + btn_del->setStyleSheet("background-color: #333333; font-size: 40px; padding: 20px; border-radius: 10px; color: #666666;"); + } + btn_del->setEnabled(is_latest); + btn_del->setFixedSize(220, 110); + connect(btn_del, &QPushButton::clicked, this, [=]() { deleteItem(id); }); + row_layout->addWidget(btn_del); + + list_layout->addWidget(row); + } + list_layout->addStretch(); +} + +void AutoTunerCardListDialog::restoreItem(const QString& id) { + if (ConfirmationDialog::confirm(tr("Are you sure you want to restore the parameters to this state?"), tr("Restore"), this)) { + QString raw = QString::fromStdString(Params().get("CarrotLearningHistory")); + QJsonArray arr = QJsonDocument::fromJson(raw.toUtf8()).array(); + QJsonArray new_arr; + + for (int i = 0; i < arr.size(); i++) { + QJsonObject entry = arr[i].toObject(); + if (entry["id"].toString() == id) { + QJsonObject changes = entry["changes"].toObject(); + for (const QString& group : changes.keys()) { + QJsonObject g_items = changes[group].toObject(); + for (const QString& key : g_items.keys()) { + int prev_val = g_items[key].toObject()["current"].toInt(); + Params().putInt(key.toStdString(), prev_val); + } + } + } else { + new_arr.append(entry); + } + } + + if (new_arr.isEmpty()) { + Params().remove("CarrotLearningHistory"); + } else { + Params().put("CarrotLearningHistory", QJsonDocument(new_arr).toJson(QJsonDocument::Compact).toStdString()); + } + refreshHistory(); + + // Trigger refresh of parent panel + AutoTunerHistoryPanel *p = qobject_cast(parent()); + if (p) { + p->refreshHistory(); + } + ConfirmationDialog::alert(tr("Restored to previous values successfully."), this); + } +} + +void AutoTunerCardListDialog::deleteItem(const QString& id) { + if (ConfirmationDialog::confirm(tr("Are you sure you want to delete this item?"), tr("Delete"), this)) { + QString raw = QString::fromStdString(Params().get("CarrotLearningHistory")); + QJsonArray arr = QJsonDocument::fromJson(raw.toUtf8()).array(); + QJsonArray new_arr; + for (int i = 0; i < arr.size(); i++) { + if (arr[i].toObject()["id"].toString() != id) { + new_arr.append(arr[i]); + } + } + if (new_arr.isEmpty()) { + Params().remove("CarrotLearningHistory"); + } else { + Params().put("CarrotLearningHistory", QJsonDocument(new_arr).toJson(QJsonDocument::Compact).toStdString()); + } + refreshHistory(); + + // Trigger refresh of parent panel + AutoTunerHistoryPanel *p = qobject_cast(parent()); + if (p) { + p->refreshHistory(); + } + } +} + SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { // setup two main layouts @@ -649,7 +1717,6 @@ CarrotPanel::CarrotPanel(QWidget* parent) : QWidget(parent) { updateButtonStyles(); }); - updateButtonStyles(); select_layout->addWidget(start_btn); @@ -692,6 +1759,32 @@ CarrotPanel::CarrotPanel(QWidget* parent) : QWidget(parent) { //cruiseToggles->addItem(new CValueControl("MyHighModeFactor", "DRIVEMODE: HIGH ratio(100%)", "AccelRatio control ratio", 100, 300, 10)); latLongToggles = new ListWidget(this); + + QPushButton* viewHistoryBtn = new QPushButton(tr("View Tuning History")); + viewHistoryBtn->setObjectName("viewHistoryBtn"); + viewHistoryBtn->setStyleSheet(R"( + QPushButton { + margin-top: 10px; margin-bottom: 20px; padding: 10px; height: 120px; border-radius: 15px; + color: #FFFFFF; background-color: #2C2CE2; + font-size: 50px; font-weight: 400; + } + QPushButton:pressed { + background-color: #2424FF; + } + )"); + connect(viewHistoryBtn, &QPushButton::clicked, this, [=]() { + AutoTunerHistoryDialog dlg(this); + dlg.exec(); + }); + latLongToggles->addItem(viewHistoryBtn); + // 공장초기화(Factory Reset) 버튼은 튜닝 이력 그래프 화면 좌측 하단으로 이동함. + + CValueControl* learningActiveCtrl = new CValueControl("CarrotLearningActive", tr("Auto-Tuner: Driving-Based Learning"), tr("Learn from driver interventions (gas/brake) and recommend parameter adjustments when parking. 0=Off, 1=On"), 0, 1, 1); + connect(learningActiveCtrl, &CValueControl::valueChanged, this, [=](int val) { + viewHistoryBtn->setVisible(val == 1); + }); + viewHistoryBtn->setVisible(Params().getBool("CarrotLearningActive")); + latLongToggles->addItem(learningActiveCtrl); latLongToggles->addItem(new CValueControl("UseLaneLineSpeed", tr("Laneline mode speed(0)"), tr("Laneline mode, lat_mpc control used"), 0, 200, 5)); latLongToggles->addItem(new CValueControl("UseLaneLineCurveSpeed", tr("Laneline mode curve speed(0)"), tr("Laneline mode, high speed only"), 0, 200, 5)); latLongToggles->addItem(new CValueControl("AdjustLaneOffset", tr("AdjustLaneOffset(0)cm"), "", 0, 500, 5)); @@ -883,7 +1976,11 @@ CarrotPanel::CarrotPanel(QWidget* parent) : QWidget(parent) { toggles_layout->addWidget(startToggles); toggles_layout->addWidget(speedToggles); ScrollView* toggles_view = new ScrollView(toggles, this); - carrotLayout->addWidget(toggles_view, 1); + + content_stack = new QStackedWidget(this); + content_stack->addWidget(toggles_view); + + carrotLayout->addWidget(content_stack, 1); homeScreen->setLayout(carrotLayout); main_layout->addWidget(homeScreen); @@ -893,6 +1990,7 @@ CarrotPanel::CarrotPanel(QWidget* parent) : QWidget(parent) { } void CarrotPanel::togglesCarrot(int widgetIndex) { + content_stack->setCurrentIndex(0); startToggles->setVisible(widgetIndex == 0); cruiseToggles->setVisible(widgetIndex == 1); speedToggles->setVisible(widgetIndex == 2); @@ -903,8 +2001,11 @@ void CarrotPanel::togglesCarrot(int widgetIndex) { void CarrotPanel::updateButtonStyles() { QString styleSheet = R"( - #start_btn, #cruise_btn, #speed_btn, #latLong_btn ,#disp_btn, #path_btn { - height: 120px; border-radius: 15px; background-color: #393939; + #start_btn, #cruise_btn, #speed_btn, #latLong_btn, #disp_btn, #path_btn { + height: 120px; + border-radius: 15px; + background-color: #393939; + color: #E4E4E4; } #start_btn:pressed, #cruise_btn:pressed, #speed_btn:pressed, #latLong_btn:pressed, #disp_btn:pressed, #path_btn:pressed { background-color: #4a4a4a; @@ -978,7 +2079,9 @@ void CValueControl::showEvent(QShowEvent* event) { } void CValueControl::refresh() { - label.setText(QString::fromStdString(Params().get(m_params.toStdString()))); + QString strVal = QString::fromStdString(Params().get(m_params.toStdString())); + label.setText(strVal); + emit valueChanged(strVal.toInt()); } void CValueControl::adjustValue(int delta) { @@ -986,6 +2089,7 @@ void CValueControl::adjustValue(int delta) { value = qBound(m_min, value + delta, m_max); Params().putInt(m_params.toStdString(), value); refresh(); + emit valueChanged(value); } void CValueControl::increaseValue() { diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h index dd60ede091..03c51a166d 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -115,6 +115,8 @@ class CarrotPanel : public QWidget { QWidget* homeWidget; QVBoxLayout* carrotLayout; + QStackedWidget* content_stack = nullptr; + ListWidget* cruiseToggles; ListWidget* latLongToggles; ListWidget* pathToggles; @@ -135,6 +137,9 @@ class CValueControl : public AbstractControl { public: CValueControl(const QString& params, const QString& title, const QString& desc, int min, int max, int unit = 1); +signals: + void valueChanged(int val); + private slots: void increaseValue(); void decreaseValue(); @@ -153,3 +158,78 @@ private slots: int m_max; int m_unit; }; + +#include +#include +#include +#include +#include + +class AutoTunerGraphWidget : public QWidget { + Q_OBJECT +public: + explicit AutoTunerGraphWidget(QWidget *parent = nullptr); + void setData(const QList ×tamps, const QMap> ¶m_histories, const QMap &colors); + void setSelectedParam(const QString ¶m); + void setHiddenParams(const QSet ¶ms); + +protected: + void paintEvent(QPaintEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + +private: + QList timestamps; + QMap> param_histories; + QMap colors; + QString selected_param; + QSet hidden_params; + int selected_index = -1; +}; + +class AutoTunerCardListDialog : public DialogBase { + Q_OBJECT +public: + explicit AutoTunerCardListDialog(QWidget *parent = nullptr); +private slots: + void refreshHistory(); + void deleteItem(const QString& id); + void restoreItem(const QString& id); +private: + QVBoxLayout *list_layout; +}; + +class AutoTunerHistoryDialog : public DialogBase { + Q_OBJECT +public: + explicit AutoTunerHistoryDialog(QWidget *parent = nullptr); +}; + +class AutoTunerHistoryPanel : public QFrame { + Q_OBJECT +public: + explicit AutoTunerHistoryPanel(QWidget* parent = nullptr); +public slots: + void refreshHistory(); + void updateLabelColors(); +private slots: + void deleteItem(const QString& id); + void restoreItem(const QString& id); + void clearAll(); +private: + void rebuildParamList(); + void toggleGroup(const QString &group); + void applyHiddenParams(); + AutoTunerGraphWidget *graph_widget; + QVBoxLayout *param_list_layout; + QMap param_labels; + QString selected_param; + QString latest_id; + QMap param_colors; + // 좌측 파라미터 리스트를 그룹(가속/조향/거리/주행 등)으로 묶어 + // 그룹 헤더 클릭 시 접기/펴기 + 그래프 표시 토글을 지원하기 위한 상태 + QStringList group_order; // 표시 순서대로 정렬된 그룹 라벨 + QMap group_params; // 그룹 라벨 → 소속 파라미터들 + QSet collapsed_groups; // 접혀있는(그래프 숨김) 그룹 라벨 +protected: + void showEvent(QShowEvent *event) override; +}; diff --git a/selfdrive/ui/translations/main_ar.ts b/selfdrive/ui/translations/main_ar.ts index 6471159616..420d86c027 100644 --- a/selfdrive/ui/translations/main_ar.ts +++ b/selfdrive/ui/translations/main_ar.ts @@ -87,6 +87,876 @@ من أجل "%1" + + AutoTunerCardListDialog + + Tuning History Card List + + + + Close + إغلاق + + + No historical data to display + + + + [%1 Applied] + + + + Restore + + + + Delete + + + + Are you sure you want to restore the parameters to this state? + + + + Restored to previous values successfully. + + + + Are you sure you want to delete this item? + + + + + AutoTunerDialog + + 사용 안내 (Guide) + + + + 나중에 (Later) + + + + 학습 초기화 (Clear) + + + + 선택 적용 (Apply Selected) + + + + 적용하지 않고 현재까지의 모든 학습 데이터를 삭제하시겠습니까? + + + + 초기화 + + + + + AutoTunerGraphWidget + + No historical data to display + + + + + AutoTunerGuideDialog + + 확인 + + + + + AutoTunerHistoryPanel + + Parameters + + + + Apply LAT (Steering): ON + + + + Apply LAT (Steering): OFF + + + + Apply LONG (Accel): ON + + + + Apply LONG (Accel): OFF + + + + View Card Type + + + + Show All Parameters + + + + Clear All Logs + + + + Close + إغلاق + + + Are you sure you want to restore the parameters to this state? + + + + Restore + + + + Restored to previous values successfully. + + + + Are you sure you want to delete this item? + + + + Delete + + + + Clear All + + + + Are you sure you want to delete all history and restore parameters to their factory default values? + + + + + CarrotPanel + + Start + + + + Cruise + + + + Speed + + + + Tuning + + + + Disp + + + + Path + + + + Button: Cruise Button Mode + + + + 0:Normal,1:User1,2:User2 + + + + Button: Cancel Button Mode + + + + 0:Long,1:Long+Lat + + + + Button: LFA Button Mode + + + + 0:Normal,1:Decel&Stop&LeadCarReady + + + + Button: Cruise Speed Unit(Basic) + + + + Button: Cruise Speed Unit(Extra) + + + + CRUISE: Eco control(4km/h) + + + + Temporarily increasing the set speed to improve fuel efficiency. + + + + CRUISE: Auto speed up (0%) + + + + Auto speed up based on the lead car up to RoadSpeedLimit. + + + + GAP1: Apply TFollow (110)x0.01s + + + + GAP2: Apply TFollow (120)x0.01s + + + + GAP3: Apply TFollow (160)x0.01s + + + + GAP4: Apply TFollow (180)x0.01s + + + + Dynamic GAP control + + + + Dynamic GAP control (LaneChange) + + + + DRIVEMODE: Select + + + + 1:ECO,2:SAFE,3:NORMAL,4:HIGH + + + + DRIVEMODE: Auto + + + + NORMAL mode only + + + + TrafficLight DetectMode + + + + 0:None, 1:Stopping only, 2: Stop & Go + + + + AChangeCostStarting + + + + TrafficStopDistanceAdjust + + + + View Tuning History + + + + Auto-Tuner: Driving-Based Learning + + + + Learn from driver interventions (gas/brake) and recommend parameter adjustments when parking. 0=Off, 1=On + + + + Laneline mode speed(0) + + + + Laneline mode, lat_mpc control used + + + + Laneline mode curve speed(0) + + + + Laneline mode, high speed only + + + + AdjustLaneOffset(0)cm + + + + LaneChange need torque + + + + -1:Disable lanechange, 0: no need torque, 1:need torque + + + + LaneChange delay + + + + x0.1sec + + + + LaneChange Bsd + + + + -1:ignore bsd, 0:BSD detect, 1: block steer torque + + + + LaneChange LineCheck + + + + 0:Color+Type, 1:Type only, 2:Type+torque override solid + + + + LAT: SteerRatiox0.1(0) + + + + Custom SteerRatio + + + + LAT: SteerRatioRatex0.01(100) + + + + SteerRatio apply rate + + + + LAT: PathOffset + + + + (-)left, (+)right + + + + LAT:SteerActuatorDelay(30) + + + + x0.01, 0:LiveDelay + + + + LAT:LatSmoothSec(13) + + + + x0.01 + + + + LAT: TorqueCustom(0) + + + + LAT: TorqueAccelFactor(2500) + + + + LAT: TorqueFriction(100) + + + + LAT: CustomSteerMax(0) + + + + LAT: CustomSteerDeltaUp(0) + + + + LAT: CustomSteerDeltaDown(0) + + + + LONG: P Gain(100) + + + + LONG: I Gain(0) + + + + LONG: FF Gain(100) + + + + LONG: ActuatorDelay(20) + + + + LONG: VEgoStopping(50) + + + + Stopping factor + + + + LONG: Radar reaction factor(100) + + + + LONG: StoppingStartAccelx0.01(-40) + + + + LONG: StopDistance (600)cm + + + + LONG: Jerk Lead Factor (0) + + + + ACCEL:0km/h(160) + + + + Acceleration needed at specified speed.(x0.01m/s^2) + + + + ACCEL:10km/h(160) + + + + ACCEL:40km/h(120) + + + + ACCEL:60km/h(100) + + + + ACCEL:80km/h(80) + + + + ACCEL:110km/h(70) + + + + ACCEL:140km/h(60) + + + + MaxAngleFrames(89) + + + + 89:Basic, steering instrument panel error 85~87 + + + + Debug Info + + + + Tpms Info + + + + Time Info + + + + 0:None,1:Time/Date,2:Time,3:Date + + + + Path End + + + + 0:None,1:Display + + + + Device State + + + + Lane Info + + + + -1:None, 0:Path, 1:Path+Lane, 2: Path+Lane+RoadEdge + + + + Radar Info + + + + 0:None,1:Display,2:RelPos,3:Stopped Car + + + + Route Info + + + + Debug plot + + + + Brightness ratio + + + + Tire Trajectory + + + + Display tire paths with a gradient effect on the lane markers. + + + + Path Color: Cruise OFF + + + + (+10:Stroke)0:Red,1:Orange,2:Yellow,3:Green,4:Blue,5:Indigo,6:Violet,7:Brown,8:White,9:Black + + + + Path Mode: Laneless + + + + 0:Normal,1,2:Rec,3,4:^^,5,6:Rec,7,8:^^,9,10,11,12:Smooth^^ + + + + Path Color: Laneless + + + + Path Mode: LaneMode + + + + Path Color: LaneMode + + + + Path Width ratio(100%) + + + + SELECT YOUR CAR + + + + Select Manufacturer + + + + Select your car + + + + HYUNDAI: CAMERA SCC + + + + 1:Connect the SCC's CAN line to CAM, 2:Sync Cruise state, 3:StockLong + + + + CANFD: HDA2 mode + + + + 1:HDA2,2:HDA2+BSM + + + + Enable Radar Track + + + + 1:Enable RadarTrack, -1,2:Disable use HKG SCC radar at all times + + + + Auto Cruise control + + + + Softhold, Auto Cruise ON/OFF control + + + + CRUISE: Auto ON distance(0cm) + + + + When GAS/Brake is OFF, Cruise ON when the lead car gets closer. + + + + Auto Engage control on start + + + + 1:SteerEnable, 2:Steer/Cruise Engage + + + + Auto AccelTok speed + + + + Gas(Accel)Tok enable speed + + + + Read Cruise Speed from PCM + + + + Toyota must set to 1, Honda 3 + + + + Sound Volume(100%) + + + + Sound Volume, Engage(10%) + + + + Power off time (min) + + + + EnableConnect + + + + Your device may be banned by Comma + + + + Mapbox Style(0) + + + + Record Road camera(0) + + + + 1:RoadCam, 2:RoadCam+WideRoadCam + + + + Use HDP(CCNC)(0) + + + + 1:While Using APN, 2:Always + + + + NNFF + + + + Twilsonco's NNFF(Reboot required) + + + + NNFFLite + + + + Twilsonco's NNFF-Lite(Reboot required) + + + + Auto update Cruise speed + + + + Disable Min.SteerSpeed + + + + Disable DM + + + + Hotspot enabled on boot + + + + Enable Software Menu + + + + IsLdwsCar + + + + Hardware is C3x Lite + + + + Share Data + + + + 0:None, 1:TCP JSON Data(Reboot required) + + + + CURVE: Lower limit speed(30) + + + + When you approach a curve, reduce your speed. Minimum speed + + + + CURVE: Auto Control ratio(100%) + + + + RoadSpeedLimitOffset(-1) + + + + -1:NotUsed,RoadLimitSpeed+Offset + + + + Auto Roadlimit Speed adjust (50%) + + + + SpeedCamDecelEnd(6s) + + + + Sets the deceleration completion point. A larger value completes deceleration farther away from the camera. + + + + NaviSpeedControlMode(2) + + + + 0:No slowdown, 1: speed camera, 2: + accident prevention bump, 3: + mobile camera + + + + SpeedCamDecelRatex0.01m/s^2(80) + + + + Lower number, slows down from a greater distance + + + + SpeedCamSafetyFactor(105%) + + + + SpeedBumpTimeDistance(1s) + + + + SpeedBumpSpeed(35Km/h) + + + + NaviCountDown mode(2) + + + + 0: off, 1:tbt+camera, 2:tbt+camera+bump + + + + Turn Speed control mode(1) + + + + 0: off, 1:vision, 2:vision+route, 3: route + + + + Smart Speed Control(0) + + + + 0: off, 1:accel, 2:decel, 3: all + + + + Map TurnSpeed Factor(100) + + + + Model TurnSpeed Factor(0) + + + + ATC: Auto turn control(0) + + + + 0:None, 1: lane change, 2: lane change + speed, 3: speed + + + + ATC: Turn Speed (20) + + + + 0:None, turn speed + + + + ATC: Turn CtrlDistTime (6) + + + + dist=speed*time + + + + ATC Auto Map Change(0) + + + ConfirmationDialog @@ -113,6 +983,33 @@ رفض، إلغاء التثبيت %1 + + DestinationWidget + + Home + + + + Work + + + + No destination set + + + + home + + + + work + + + + No %1 location set + + + DeveloperPanel @@ -172,11 +1069,11 @@ Reset Calibration - إعادة ضبط المعايرة + إعادة ضبط المعايرة RESET - إعادة الضبط + إعادة الضبط Are you sure you want to reset calibration? @@ -268,7 +1165,7 @@ Reset - إعادة الضبط + إعادة الضبط Review @@ -286,6 +1183,81 @@ PAIR إقران + + ReCalibration + + + + Git Pull & Reboot + + + + Git pull & Reboot? + + + + Yes + + + + Failed to start update process. + + + + Update process started. Device will reboot if updates are applied. + + + + Set default + + + + Set to default? + + + + Remove MapboxKey + + + + Remove Mapbox key? + + + + Calibration Status + + + + SHOW + + + + Reboot & Disengage to Calibration + + + + + DrawCarrot + + ECO + + + + SAFE + + + + NORM + + + + FAST + + + + ERRM + + DriverViewWindow @@ -338,6 +1310,13 @@ Firehose Mode allows you to maximize your training data uploads to improve openp + + HomeWindow + + Auto-Tuner: Driving pattern learned! + + + HudRenderer @@ -375,7 +1354,52 @@ Firehose Mode allows you to maximize your training data uploads to improve openp Installer Installing... - جارٍ التثبيت... + جارٍ التثبيت... + + + + MapETA + + eta + + + + min + + + + hr + + + + + MapSettings + + NAVIGATION + + + + Manage at %1 + + + + Manage at connect.comma.ai + + + + + MapWindow + + Map Loading + + + + Waiting for GPS(APN) + + + + Waiting for route + @@ -456,6 +1480,14 @@ Firehose Mode allows you to maximize your training data uploads to improve openp openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. لقد اكتشف openpilot تغييراً في موقع تركيب الجهاز. تأكد من تثبيت الجهاز بشكل كامل في موقعه وتثبيته بإحكام على الزجاج الأمامي. + + Poor visibility detected for driver monitoring. Ensure the device has a clear view of the driver. This can be checked in the device settings. Extreme lighting conditions and/or unconventional mounting positions may also trigger this alert. + + + + Excessive %1 actuation detected on your last drive. Please contact support at https://comma.ai/support and share your device's Dongle ID for troubleshooting. + + OffroadHome @@ -529,6 +1561,21 @@ Firehose Mode allows you to maximize your training data uploads to improve openp إلغاء + + PathEndDrawer + + Signal Error + + + + Signal Ready + + + + Signal slowing + + + PrimeAdWidget @@ -583,7 +1630,7 @@ Firehose Mode allows you to maximize your training data uploads to improve openp openpilot - openpilot + openpilot %n minute(s) ago @@ -622,6 +1669,26 @@ Firehose Mode allows you to maximize your training data uploads to improve openp now الآن + + km + + + + m + + + + mi + + + + ft + + + + carrotpilot + + Reset @@ -694,6 +1761,10 @@ This may take up to a minute. Firehose + + CarrotPilot + + Setup @@ -1130,6 +2201,18 @@ This may take up to a minute. Enable driver monitoring even when openpilot is not engaged. تمكين مراقبة السائق حتى عندما لا يكون نظام OpenPilot مُفعّلاً. + + Record and Upload Microphone Audio + + + + Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect. + + + + MoreRelaxed + + Updater diff --git a/selfdrive/ui/translations/main_de.ts b/selfdrive/ui/translations/main_de.ts index bdda0484d4..79548e90ad 100644 --- a/selfdrive/ui/translations/main_de.ts +++ b/selfdrive/ui/translations/main_de.ts @@ -87,6 +87,876 @@ für "%1" + + AutoTunerCardListDialog + + Tuning History Card List + + + + Close + Schließen + + + No historical data to display + + + + [%1 Applied] + + + + Restore + + + + Delete + + + + Are you sure you want to restore the parameters to this state? + + + + Restored to previous values successfully. + + + + Are you sure you want to delete this item? + + + + + AutoTunerDialog + + 사용 안내 (Guide) + + + + 나중에 (Later) + + + + 학습 초기화 (Clear) + + + + 선택 적용 (Apply Selected) + + + + 적용하지 않고 현재까지의 모든 학습 데이터를 삭제하시겠습니까? + + + + 초기화 + + + + + AutoTunerGraphWidget + + No historical data to display + + + + + AutoTunerGuideDialog + + 확인 + + + + + AutoTunerHistoryPanel + + Parameters + + + + Apply LAT (Steering): ON + + + + Apply LAT (Steering): OFF + + + + Apply LONG (Accel): ON + + + + Apply LONG (Accel): OFF + + + + View Card Type + + + + Show All Parameters + + + + Clear All Logs + + + + Close + Schließen + + + Are you sure you want to restore the parameters to this state? + + + + Restore + + + + Restored to previous values successfully. + + + + Are you sure you want to delete this item? + + + + Delete + + + + Clear All + + + + Are you sure you want to delete all history and restore parameters to their factory default values? + + + + + CarrotPanel + + Start + + + + Cruise + + + + Speed + + + + Tuning + + + + Disp + + + + Path + + + + Button: Cruise Button Mode + + + + 0:Normal,1:User1,2:User2 + + + + Button: Cancel Button Mode + + + + 0:Long,1:Long+Lat + + + + Button: LFA Button Mode + + + + 0:Normal,1:Decel&Stop&LeadCarReady + + + + Button: Cruise Speed Unit(Basic) + + + + Button: Cruise Speed Unit(Extra) + + + + CRUISE: Eco control(4km/h) + + + + Temporarily increasing the set speed to improve fuel efficiency. + + + + CRUISE: Auto speed up (0%) + + + + Auto speed up based on the lead car up to RoadSpeedLimit. + + + + GAP1: Apply TFollow (110)x0.01s + + + + GAP2: Apply TFollow (120)x0.01s + + + + GAP3: Apply TFollow (160)x0.01s + + + + GAP4: Apply TFollow (180)x0.01s + + + + Dynamic GAP control + + + + Dynamic GAP control (LaneChange) + + + + DRIVEMODE: Select + + + + 1:ECO,2:SAFE,3:NORMAL,4:HIGH + + + + DRIVEMODE: Auto + + + + NORMAL mode only + + + + TrafficLight DetectMode + + + + 0:None, 1:Stopping only, 2: Stop & Go + + + + AChangeCostStarting + + + + TrafficStopDistanceAdjust + + + + View Tuning History + + + + Auto-Tuner: Driving-Based Learning + + + + Learn from driver interventions (gas/brake) and recommend parameter adjustments when parking. 0=Off, 1=On + + + + Laneline mode speed(0) + + + + Laneline mode, lat_mpc control used + + + + Laneline mode curve speed(0) + + + + Laneline mode, high speed only + + + + AdjustLaneOffset(0)cm + + + + LaneChange need torque + + + + -1:Disable lanechange, 0: no need torque, 1:need torque + + + + LaneChange delay + + + + x0.1sec + + + + LaneChange Bsd + + + + -1:ignore bsd, 0:BSD detect, 1: block steer torque + + + + LaneChange LineCheck + + + + 0:Color+Type, 1:Type only, 2:Type+torque override solid + + + + LAT: SteerRatiox0.1(0) + + + + Custom SteerRatio + + + + LAT: SteerRatioRatex0.01(100) + + + + SteerRatio apply rate + + + + LAT: PathOffset + + + + (-)left, (+)right + + + + LAT:SteerActuatorDelay(30) + + + + x0.01, 0:LiveDelay + + + + LAT:LatSmoothSec(13) + + + + x0.01 + + + + LAT: TorqueCustom(0) + + + + LAT: TorqueAccelFactor(2500) + + + + LAT: TorqueFriction(100) + + + + LAT: CustomSteerMax(0) + + + + LAT: CustomSteerDeltaUp(0) + + + + LAT: CustomSteerDeltaDown(0) + + + + LONG: P Gain(100) + + + + LONG: I Gain(0) + + + + LONG: FF Gain(100) + + + + LONG: ActuatorDelay(20) + + + + LONG: VEgoStopping(50) + + + + Stopping factor + + + + LONG: Radar reaction factor(100) + + + + LONG: StoppingStartAccelx0.01(-40) + + + + LONG: StopDistance (600)cm + + + + LONG: Jerk Lead Factor (0) + + + + ACCEL:0km/h(160) + + + + Acceleration needed at specified speed.(x0.01m/s^2) + + + + ACCEL:10km/h(160) + + + + ACCEL:40km/h(120) + + + + ACCEL:60km/h(100) + + + + ACCEL:80km/h(80) + + + + ACCEL:110km/h(70) + + + + ACCEL:140km/h(60) + + + + MaxAngleFrames(89) + + + + 89:Basic, steering instrument panel error 85~87 + + + + Debug Info + + + + Tpms Info + + + + Time Info + + + + 0:None,1:Time/Date,2:Time,3:Date + + + + Path End + + + + 0:None,1:Display + + + + Device State + + + + Lane Info + + + + -1:None, 0:Path, 1:Path+Lane, 2: Path+Lane+RoadEdge + + + + Radar Info + + + + 0:None,1:Display,2:RelPos,3:Stopped Car + + + + Route Info + + + + Debug plot + + + + Brightness ratio + + + + Tire Trajectory + + + + Display tire paths with a gradient effect on the lane markers. + + + + Path Color: Cruise OFF + + + + (+10:Stroke)0:Red,1:Orange,2:Yellow,3:Green,4:Blue,5:Indigo,6:Violet,7:Brown,8:White,9:Black + + + + Path Mode: Laneless + + + + 0:Normal,1,2:Rec,3,4:^^,5,6:Rec,7,8:^^,9,10,11,12:Smooth^^ + + + + Path Color: Laneless + + + + Path Mode: LaneMode + + + + Path Color: LaneMode + + + + Path Width ratio(100%) + + + + SELECT YOUR CAR + + + + Select Manufacturer + + + + Select your car + + + + HYUNDAI: CAMERA SCC + + + + 1:Connect the SCC's CAN line to CAM, 2:Sync Cruise state, 3:StockLong + + + + CANFD: HDA2 mode + + + + 1:HDA2,2:HDA2+BSM + + + + Enable Radar Track + + + + 1:Enable RadarTrack, -1,2:Disable use HKG SCC radar at all times + + + + Auto Cruise control + + + + Softhold, Auto Cruise ON/OFF control + + + + CRUISE: Auto ON distance(0cm) + + + + When GAS/Brake is OFF, Cruise ON when the lead car gets closer. + + + + Auto Engage control on start + + + + 1:SteerEnable, 2:Steer/Cruise Engage + + + + Auto AccelTok speed + + + + Gas(Accel)Tok enable speed + + + + Read Cruise Speed from PCM + + + + Toyota must set to 1, Honda 3 + + + + Sound Volume(100%) + + + + Sound Volume, Engage(10%) + + + + Power off time (min) + + + + EnableConnect + + + + Your device may be banned by Comma + + + + Mapbox Style(0) + + + + Record Road camera(0) + + + + 1:RoadCam, 2:RoadCam+WideRoadCam + + + + Use HDP(CCNC)(0) + + + + 1:While Using APN, 2:Always + + + + NNFF + + + + Twilsonco's NNFF(Reboot required) + + + + NNFFLite + + + + Twilsonco's NNFF-Lite(Reboot required) + + + + Auto update Cruise speed + + + + Disable Min.SteerSpeed + + + + Disable DM + + + + Hotspot enabled on boot + + + + Enable Software Menu + + + + IsLdwsCar + + + + Hardware is C3x Lite + + + + Share Data + + + + 0:None, 1:TCP JSON Data(Reboot required) + + + + CURVE: Lower limit speed(30) + + + + When you approach a curve, reduce your speed. Minimum speed + + + + CURVE: Auto Control ratio(100%) + + + + RoadSpeedLimitOffset(-1) + + + + -1:NotUsed,RoadLimitSpeed+Offset + + + + Auto Roadlimit Speed adjust (50%) + + + + SpeedCamDecelEnd(6s) + + + + Sets the deceleration completion point. A larger value completes deceleration farther away from the camera. + + + + NaviSpeedControlMode(2) + + + + 0:No slowdown, 1: speed camera, 2: + accident prevention bump, 3: + mobile camera + + + + SpeedCamDecelRatex0.01m/s^2(80) + + + + Lower number, slows down from a greater distance + + + + SpeedCamSafetyFactor(105%) + + + + SpeedBumpTimeDistance(1s) + + + + SpeedBumpSpeed(35Km/h) + + + + NaviCountDown mode(2) + + + + 0: off, 1:tbt+camera, 2:tbt+camera+bump + + + + Turn Speed control mode(1) + + + + 0: off, 1:vision, 2:vision+route, 3: route + + + + Smart Speed Control(0) + + + + 0: off, 1:accel, 2:decel, 3: all + + + + Map TurnSpeed Factor(100) + + + + Model TurnSpeed Factor(0) + + + + ATC: Auto turn control(0) + + + + 0:None, 1: lane change, 2: lane change + speed, 3: speed + + + + ATC: Turn Speed (20) + + + + 0:None, turn speed + + + + ATC: Turn CtrlDistTime (6) + + + + dist=speed*time + + + + ATC Auto Map Change(0) + + + ConfirmationDialog @@ -113,6 +983,33 @@ Ablehnen, deinstallieren %1 + + DestinationWidget + + Home + + + + Work + + + + No destination set + + + + home + + + + work + + + + No %1 location set + + + DeveloperPanel @@ -172,11 +1069,11 @@ Reset Calibration - Neu kalibrieren + Neu kalibrieren RESET - RESET + RESET Are you sure you want to reset calibration? @@ -268,7 +1165,7 @@ Reset - Zurücksetzen + Zurücksetzen Review @@ -286,6 +1183,81 @@ PAIR + + ReCalibration + + + + Git Pull & Reboot + + + + Git pull & Reboot? + + + + Yes + + + + Failed to start update process. + + + + Update process started. Device will reboot if updates are applied. + + + + Set default + + + + Set to default? + + + + Remove MapboxKey + + + + Remove Mapbox key? + + + + Calibration Status + + + + SHOW + + + + Reboot & Disengage to Calibration + + + + + DrawCarrot + + ECO + + + + SAFE + + + + NORM + + + + FAST + + + + ERRM + + DriverViewWindow @@ -338,6 +1310,13 @@ Firehose Mode allows you to maximize your training data uploads to improve openp + + HomeWindow + + Auto-Tuner: Driving pattern learned! + + + HudRenderer @@ -371,7 +1350,52 @@ Firehose Mode allows you to maximize your training data uploads to improve openp Installer Installing... - Installiere... + Installiere... + + + + MapETA + + eta + + + + min + + + + hr + + + + + MapSettings + + NAVIGATION + + + + Manage at %1 + + + + Manage at connect.comma.ai + + + + + MapWindow + + Map Loading + + + + Waiting for GPS(APN) + + + + Waiting for route + @@ -451,6 +1475,14 @@ Firehose Mode allows you to maximize your training data uploads to improve openp Device temperature too high. System cooling down before starting. Current internal component temperature: %1 + + Poor visibility detected for driver monitoring. Ensure the device has a clear view of the driver. This can be checked in the device settings. Extreme lighting conditions and/or unconventional mounting positions may also trigger this alert. + + + + Excessive %1 actuation detected on your last drive. Please contact support at https://comma.ai/support and share your device's Dongle ID for troubleshooting. + + OffroadHome @@ -524,6 +1556,21 @@ Firehose Mode allows you to maximize your training data uploads to improve openp Aktivieren + + PathEndDrawer + + Signal Error + + + + Signal Ready + + + + Signal slowing + + + PrimeAdWidget @@ -578,7 +1625,7 @@ Firehose Mode allows you to maximize your training data uploads to improve openp openpilot - openpilot + openpilot %n minute(s) ago @@ -605,6 +1652,26 @@ Firehose Mode allows you to maximize your training data uploads to improve openp now + + km + + + + m + + + + mi + + + + ft + + + + carrotpilot + + Reset @@ -676,6 +1743,10 @@ This may take up to a minute. Firehose + + CarrotPilot + + Setup @@ -1114,6 +2185,18 @@ This may take up to a minute. Enable driver monitoring even when openpilot is not engaged. + + Record and Upload Microphone Audio + + + + Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect. + + + + MoreRelaxed + + Updater diff --git a/selfdrive/ui/translations/main_es.ts b/selfdrive/ui/translations/main_es.ts index 8168e126e3..8a9bd04d94 100644 --- a/selfdrive/ui/translations/main_es.ts +++ b/selfdrive/ui/translations/main_es.ts @@ -87,6 +87,876 @@ para "%1" + + AutoTunerCardListDialog + + Tuning History Card List + + + + Close + Cerrar + + + No historical data to display + + + + [%1 Applied] + + + + Restore + + + + Delete + + + + Are you sure you want to restore the parameters to this state? + + + + Restored to previous values successfully. + + + + Are you sure you want to delete this item? + + + + + AutoTunerDialog + + 사용 안내 (Guide) + + + + 나중에 (Later) + + + + 학습 초기화 (Clear) + + + + 선택 적용 (Apply Selected) + + + + 적용하지 않고 현재까지의 모든 학습 데이터를 삭제하시겠습니까? + + + + 초기화 + + + + + AutoTunerGraphWidget + + No historical data to display + + + + + AutoTunerGuideDialog + + 확인 + + + + + AutoTunerHistoryPanel + + Parameters + + + + Apply LAT (Steering): ON + + + + Apply LAT (Steering): OFF + + + + Apply LONG (Accel): ON + + + + Apply LONG (Accel): OFF + + + + View Card Type + + + + Show All Parameters + + + + Clear All Logs + + + + Close + Cerrar + + + Are you sure you want to restore the parameters to this state? + + + + Restore + + + + Restored to previous values successfully. + + + + Are you sure you want to delete this item? + + + + Delete + + + + Clear All + + + + Are you sure you want to delete all history and restore parameters to their factory default values? + + + + + CarrotPanel + + Start + + + + Cruise + + + + Speed + + + + Tuning + + + + Disp + + + + Path + + + + Button: Cruise Button Mode + + + + 0:Normal,1:User1,2:User2 + + + + Button: Cancel Button Mode + + + + 0:Long,1:Long+Lat + + + + Button: LFA Button Mode + + + + 0:Normal,1:Decel&Stop&LeadCarReady + + + + Button: Cruise Speed Unit(Basic) + + + + Button: Cruise Speed Unit(Extra) + + + + CRUISE: Eco control(4km/h) + + + + Temporarily increasing the set speed to improve fuel efficiency. + + + + CRUISE: Auto speed up (0%) + + + + Auto speed up based on the lead car up to RoadSpeedLimit. + + + + GAP1: Apply TFollow (110)x0.01s + + + + GAP2: Apply TFollow (120)x0.01s + + + + GAP3: Apply TFollow (160)x0.01s + + + + GAP4: Apply TFollow (180)x0.01s + + + + Dynamic GAP control + + + + Dynamic GAP control (LaneChange) + + + + DRIVEMODE: Select + + + + 1:ECO,2:SAFE,3:NORMAL,4:HIGH + + + + DRIVEMODE: Auto + + + + NORMAL mode only + + + + TrafficLight DetectMode + + + + 0:None, 1:Stopping only, 2: Stop & Go + + + + AChangeCostStarting + + + + TrafficStopDistanceAdjust + + + + View Tuning History + + + + Auto-Tuner: Driving-Based Learning + + + + Learn from driver interventions (gas/brake) and recommend parameter adjustments when parking. 0=Off, 1=On + + + + Laneline mode speed(0) + + + + Laneline mode, lat_mpc control used + + + + Laneline mode curve speed(0) + + + + Laneline mode, high speed only + + + + AdjustLaneOffset(0)cm + + + + LaneChange need torque + + + + -1:Disable lanechange, 0: no need torque, 1:need torque + + + + LaneChange delay + + + + x0.1sec + + + + LaneChange Bsd + + + + -1:ignore bsd, 0:BSD detect, 1: block steer torque + + + + LaneChange LineCheck + + + + 0:Color+Type, 1:Type only, 2:Type+torque override solid + + + + LAT: SteerRatiox0.1(0) + + + + Custom SteerRatio + + + + LAT: SteerRatioRatex0.01(100) + + + + SteerRatio apply rate + + + + LAT: PathOffset + + + + (-)left, (+)right + + + + LAT:SteerActuatorDelay(30) + + + + x0.01, 0:LiveDelay + + + + LAT:LatSmoothSec(13) + + + + x0.01 + + + + LAT: TorqueCustom(0) + + + + LAT: TorqueAccelFactor(2500) + + + + LAT: TorqueFriction(100) + + + + LAT: CustomSteerMax(0) + + + + LAT: CustomSteerDeltaUp(0) + + + + LAT: CustomSteerDeltaDown(0) + + + + LONG: P Gain(100) + + + + LONG: I Gain(0) + + + + LONG: FF Gain(100) + + + + LONG: ActuatorDelay(20) + + + + LONG: VEgoStopping(50) + + + + Stopping factor + + + + LONG: Radar reaction factor(100) + + + + LONG: StoppingStartAccelx0.01(-40) + + + + LONG: StopDistance (600)cm + + + + LONG: Jerk Lead Factor (0) + + + + ACCEL:0km/h(160) + + + + Acceleration needed at specified speed.(x0.01m/s^2) + + + + ACCEL:10km/h(160) + + + + ACCEL:40km/h(120) + + + + ACCEL:60km/h(100) + + + + ACCEL:80km/h(80) + + + + ACCEL:110km/h(70) + + + + ACCEL:140km/h(60) + + + + MaxAngleFrames(89) + + + + 89:Basic, steering instrument panel error 85~87 + + + + Debug Info + + + + Tpms Info + + + + Time Info + + + + 0:None,1:Time/Date,2:Time,3:Date + + + + Path End + + + + 0:None,1:Display + + + + Device State + + + + Lane Info + + + + -1:None, 0:Path, 1:Path+Lane, 2: Path+Lane+RoadEdge + + + + Radar Info + + + + 0:None,1:Display,2:RelPos,3:Stopped Car + + + + Route Info + + + + Debug plot + + + + Brightness ratio + + + + Tire Trajectory + + + + Display tire paths with a gradient effect on the lane markers. + + + + Path Color: Cruise OFF + + + + (+10:Stroke)0:Red,1:Orange,2:Yellow,3:Green,4:Blue,5:Indigo,6:Violet,7:Brown,8:White,9:Black + + + + Path Mode: Laneless + + + + 0:Normal,1,2:Rec,3,4:^^,5,6:Rec,7,8:^^,9,10,11,12:Smooth^^ + + + + Path Color: Laneless + + + + Path Mode: LaneMode + + + + Path Color: LaneMode + + + + Path Width ratio(100%) + + + + SELECT YOUR CAR + + + + Select Manufacturer + + + + Select your car + + + + HYUNDAI: CAMERA SCC + + + + 1:Connect the SCC's CAN line to CAM, 2:Sync Cruise state, 3:StockLong + + + + CANFD: HDA2 mode + + + + 1:HDA2,2:HDA2+BSM + + + + Enable Radar Track + + + + 1:Enable RadarTrack, -1,2:Disable use HKG SCC radar at all times + + + + Auto Cruise control + + + + Softhold, Auto Cruise ON/OFF control + + + + CRUISE: Auto ON distance(0cm) + + + + When GAS/Brake is OFF, Cruise ON when the lead car gets closer. + + + + Auto Engage control on start + + + + 1:SteerEnable, 2:Steer/Cruise Engage + + + + Auto AccelTok speed + + + + Gas(Accel)Tok enable speed + + + + Read Cruise Speed from PCM + + + + Toyota must set to 1, Honda 3 + + + + Sound Volume(100%) + + + + Sound Volume, Engage(10%) + + + + Power off time (min) + + + + EnableConnect + + + + Your device may be banned by Comma + + + + Mapbox Style(0) + + + + Record Road camera(0) + + + + 1:RoadCam, 2:RoadCam+WideRoadCam + + + + Use HDP(CCNC)(0) + + + + 1:While Using APN, 2:Always + + + + NNFF + + + + Twilsonco's NNFF(Reboot required) + + + + NNFFLite + + + + Twilsonco's NNFF-Lite(Reboot required) + + + + Auto update Cruise speed + + + + Disable Min.SteerSpeed + + + + Disable DM + + + + Hotspot enabled on boot + + + + Enable Software Menu + + + + IsLdwsCar + + + + Hardware is C3x Lite + + + + Share Data + + + + 0:None, 1:TCP JSON Data(Reboot required) + + + + CURVE: Lower limit speed(30) + + + + When you approach a curve, reduce your speed. Minimum speed + + + + CURVE: Auto Control ratio(100%) + + + + RoadSpeedLimitOffset(-1) + + + + -1:NotUsed,RoadLimitSpeed+Offset + + + + Auto Roadlimit Speed adjust (50%) + + + + SpeedCamDecelEnd(6s) + + + + Sets the deceleration completion point. A larger value completes deceleration farther away from the camera. + + + + NaviSpeedControlMode(2) + + + + 0:No slowdown, 1: speed camera, 2: + accident prevention bump, 3: + mobile camera + + + + SpeedCamDecelRatex0.01m/s^2(80) + + + + Lower number, slows down from a greater distance + + + + SpeedCamSafetyFactor(105%) + + + + SpeedBumpTimeDistance(1s) + + + + SpeedBumpSpeed(35Km/h) + + + + NaviCountDown mode(2) + + + + 0: off, 1:tbt+camera, 2:tbt+camera+bump + + + + Turn Speed control mode(1) + + + + 0: off, 1:vision, 2:vision+route, 3: route + + + + Smart Speed Control(0) + + + + 0: off, 1:accel, 2:decel, 3: all + + + + Map TurnSpeed Factor(100) + + + + Model TurnSpeed Factor(0) + + + + ATC: Auto turn control(0) + + + + 0:None, 1: lane change, 2: lane change + speed, 3: speed + + + + ATC: Turn Speed (20) + + + + 0:None, turn speed + + + + ATC: Turn CtrlDistTime (6) + + + + dist=speed*time + + + + ATC Auto Map Change(0) + + + ConfirmationDialog @@ -113,6 +983,33 @@ Rechazar, desinstalar %1 + + DestinationWidget + + Home + + + + Work + + + + No destination set + + + + home + + + + work + + + + No %1 location set + + + DeveloperPanel @@ -184,11 +1081,11 @@ Reset Calibration - Formatear Calibración + Formatear Calibración RESET - REINICIAR + REINICIAR Are you sure you want to reset calibration? @@ -196,7 +1093,7 @@ Reset - Formatear + Formatear Review Training Guide @@ -286,6 +1183,81 @@ Disengage to Power Off Desactivar para apagar + + ReCalibration + + + + Git Pull & Reboot + + + + Git pull & Reboot? + + + + Yes + + + + Failed to start update process. + + + + Update process started. Device will reboot if updates are applied. + + + + Set default + + + + Set to default? + + + + Remove MapboxKey + + + + Remove Mapbox key? + + + + Calibration Status + + + + SHOW + + + + Reboot & Disengage to Calibration + + + + + DrawCarrot + + ECO + + + + SAFE + + + + NORM + + + + FAST + + + + ERRM + + DriverViewWindow @@ -338,6 +1310,13 @@ Firehose Mode allows you to maximize your training data uploads to improve openp + + HomeWindow + + Auto-Tuner: Driving pattern learned! + + + HudRenderer @@ -371,7 +1350,52 @@ Firehose Mode allows you to maximize your training data uploads to improve openp Installer Installing... - Instalando... + Instalando... + + + + MapETA + + eta + + + + min + + + + hr + + + + + MapSettings + + NAVIGATION + + + + Manage at %1 + + + + Manage at connect.comma.ai + + + + + MapWindow + + Map Loading + + + + Waiting for GPS(APN) + + + + Waiting for route + @@ -452,6 +1476,14 @@ Firehose Mode allows you to maximize your training data uploads to improve openp openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. openpilot detectó un cambio en la posición de montaje del dispositivo. Asegúrese de que el dispositivo esté completamente asentado en el soporte y que el soporte esté firmemente asegurado al parabrisas. + + Poor visibility detected for driver monitoring. Ensure the device has a clear view of the driver. This can be checked in the device settings. Extreme lighting conditions and/or unconventional mounting positions may also trigger this alert. + + + + Excessive %1 actuation detected on your last drive. Please contact support at https://comma.ai/support and share your device's Dongle ID for troubleshooting. + + OffroadHome @@ -525,6 +1557,21 @@ Firehose Mode allows you to maximize your training data uploads to improve openp Cancelar + + PathEndDrawer + + Signal Error + + + + Signal Ready + + + + Signal slowing + + + PrimeAdWidget @@ -579,7 +1626,7 @@ Firehose Mode allows you to maximize your training data uploads to improve openp openpilot - openpilot + openpilot now @@ -606,6 +1653,26 @@ Firehose Mode allows you to maximize your training data uploads to improve openp hace %n días + + km + + + + m + + + + mi + + + + ft + + + + carrotpilot + + Reset @@ -678,6 +1745,10 @@ Esto puede tardar un minuto. Firehose + + CarrotPilot + + Setup @@ -1114,6 +2185,18 @@ Esto puede tardar un minuto. Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode. Activar el control longitudinal (fase experimental) para permitir el modo Experimental. + + Record and Upload Microphone Audio + + + + Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect. + + + + MoreRelaxed + + Updater diff --git a/selfdrive/ui/translations/main_fr.ts b/selfdrive/ui/translations/main_fr.ts index 3fe002e03d..2d34ed659b 100644 --- a/selfdrive/ui/translations/main_fr.ts +++ b/selfdrive/ui/translations/main_fr.ts @@ -87,6 +87,876 @@ pour "%1" + + AutoTunerCardListDialog + + Tuning History Card List + + + + Close + Fermer + + + No historical data to display + + + + [%1 Applied] + + + + Restore + + + + Delete + + + + Are you sure you want to restore the parameters to this state? + + + + Restored to previous values successfully. + + + + Are you sure you want to delete this item? + + + + + AutoTunerDialog + + 사용 안내 (Guide) + + + + 나중에 (Later) + + + + 학습 초기화 (Clear) + + + + 선택 적용 (Apply Selected) + + + + 적용하지 않고 현재까지의 모든 학습 데이터를 삭제하시겠습니까? + + + + 초기화 + + + + + AutoTunerGraphWidget + + No historical data to display + + + + + AutoTunerGuideDialog + + 확인 + + + + + AutoTunerHistoryPanel + + Parameters + + + + Apply LAT (Steering): ON + + + + Apply LAT (Steering): OFF + + + + Apply LONG (Accel): ON + + + + Apply LONG (Accel): OFF + + + + View Card Type + + + + Show All Parameters + + + + Clear All Logs + + + + Close + Fermer + + + Are you sure you want to restore the parameters to this state? + + + + Restore + + + + Restored to previous values successfully. + + + + Are you sure you want to delete this item? + + + + Delete + + + + Clear All + + + + Are you sure you want to delete all history and restore parameters to their factory default values? + + + + + CarrotPanel + + Start + + + + Cruise + + + + Speed + + + + Tuning + + + + Disp + + + + Path + + + + Button: Cruise Button Mode + + + + 0:Normal,1:User1,2:User2 + + + + Button: Cancel Button Mode + + + + 0:Long,1:Long+Lat + + + + Button: LFA Button Mode + + + + 0:Normal,1:Decel&Stop&LeadCarReady + + + + Button: Cruise Speed Unit(Basic) + + + + Button: Cruise Speed Unit(Extra) + + + + CRUISE: Eco control(4km/h) + + + + Temporarily increasing the set speed to improve fuel efficiency. + + + + CRUISE: Auto speed up (0%) + + + + Auto speed up based on the lead car up to RoadSpeedLimit. + + + + GAP1: Apply TFollow (110)x0.01s + + + + GAP2: Apply TFollow (120)x0.01s + + + + GAP3: Apply TFollow (160)x0.01s + + + + GAP4: Apply TFollow (180)x0.01s + + + + Dynamic GAP control + + + + Dynamic GAP control (LaneChange) + + + + DRIVEMODE: Select + + + + 1:ECO,2:SAFE,3:NORMAL,4:HIGH + + + + DRIVEMODE: Auto + + + + NORMAL mode only + + + + TrafficLight DetectMode + + + + 0:None, 1:Stopping only, 2: Stop & Go + + + + AChangeCostStarting + + + + TrafficStopDistanceAdjust + + + + View Tuning History + + + + Auto-Tuner: Driving-Based Learning + + + + Learn from driver interventions (gas/brake) and recommend parameter adjustments when parking. 0=Off, 1=On + + + + Laneline mode speed(0) + + + + Laneline mode, lat_mpc control used + + + + Laneline mode curve speed(0) + + + + Laneline mode, high speed only + + + + AdjustLaneOffset(0)cm + + + + LaneChange need torque + + + + -1:Disable lanechange, 0: no need torque, 1:need torque + + + + LaneChange delay + + + + x0.1sec + + + + LaneChange Bsd + + + + -1:ignore bsd, 0:BSD detect, 1: block steer torque + + + + LaneChange LineCheck + + + + 0:Color+Type, 1:Type only, 2:Type+torque override solid + + + + LAT: SteerRatiox0.1(0) + + + + Custom SteerRatio + + + + LAT: SteerRatioRatex0.01(100) + + + + SteerRatio apply rate + + + + LAT: PathOffset + + + + (-)left, (+)right + + + + LAT:SteerActuatorDelay(30) + + + + x0.01, 0:LiveDelay + + + + LAT:LatSmoothSec(13) + + + + x0.01 + + + + LAT: TorqueCustom(0) + + + + LAT: TorqueAccelFactor(2500) + + + + LAT: TorqueFriction(100) + + + + LAT: CustomSteerMax(0) + + + + LAT: CustomSteerDeltaUp(0) + + + + LAT: CustomSteerDeltaDown(0) + + + + LONG: P Gain(100) + + + + LONG: I Gain(0) + + + + LONG: FF Gain(100) + + + + LONG: ActuatorDelay(20) + + + + LONG: VEgoStopping(50) + + + + Stopping factor + + + + LONG: Radar reaction factor(100) + + + + LONG: StoppingStartAccelx0.01(-40) + + + + LONG: StopDistance (600)cm + + + + LONG: Jerk Lead Factor (0) + + + + ACCEL:0km/h(160) + + + + Acceleration needed at specified speed.(x0.01m/s^2) + + + + ACCEL:10km/h(160) + + + + ACCEL:40km/h(120) + + + + ACCEL:60km/h(100) + + + + ACCEL:80km/h(80) + + + + ACCEL:110km/h(70) + + + + ACCEL:140km/h(60) + + + + MaxAngleFrames(89) + + + + 89:Basic, steering instrument panel error 85~87 + + + + Debug Info + + + + Tpms Info + + + + Time Info + + + + 0:None,1:Time/Date,2:Time,3:Date + + + + Path End + + + + 0:None,1:Display + + + + Device State + + + + Lane Info + + + + -1:None, 0:Path, 1:Path+Lane, 2: Path+Lane+RoadEdge + + + + Radar Info + + + + 0:None,1:Display,2:RelPos,3:Stopped Car + + + + Route Info + + + + Debug plot + + + + Brightness ratio + + + + Tire Trajectory + + + + Display tire paths with a gradient effect on the lane markers. + + + + Path Color: Cruise OFF + + + + (+10:Stroke)0:Red,1:Orange,2:Yellow,3:Green,4:Blue,5:Indigo,6:Violet,7:Brown,8:White,9:Black + + + + Path Mode: Laneless + + + + 0:Normal,1,2:Rec,3,4:^^,5,6:Rec,7,8:^^,9,10,11,12:Smooth^^ + + + + Path Color: Laneless + + + + Path Mode: LaneMode + + + + Path Color: LaneMode + + + + Path Width ratio(100%) + + + + SELECT YOUR CAR + + + + Select Manufacturer + + + + Select your car + + + + HYUNDAI: CAMERA SCC + + + + 1:Connect the SCC's CAN line to CAM, 2:Sync Cruise state, 3:StockLong + + + + CANFD: HDA2 mode + + + + 1:HDA2,2:HDA2+BSM + + + + Enable Radar Track + + + + 1:Enable RadarTrack, -1,2:Disable use HKG SCC radar at all times + + + + Auto Cruise control + + + + Softhold, Auto Cruise ON/OFF control + + + + CRUISE: Auto ON distance(0cm) + + + + When GAS/Brake is OFF, Cruise ON when the lead car gets closer. + + + + Auto Engage control on start + + + + 1:SteerEnable, 2:Steer/Cruise Engage + + + + Auto AccelTok speed + + + + Gas(Accel)Tok enable speed + + + + Read Cruise Speed from PCM + + + + Toyota must set to 1, Honda 3 + + + + Sound Volume(100%) + + + + Sound Volume, Engage(10%) + + + + Power off time (min) + + + + EnableConnect + + + + Your device may be banned by Comma + + + + Mapbox Style(0) + + + + Record Road camera(0) + + + + 1:RoadCam, 2:RoadCam+WideRoadCam + + + + Use HDP(CCNC)(0) + + + + 1:While Using APN, 2:Always + + + + NNFF + + + + Twilsonco's NNFF(Reboot required) + + + + NNFFLite + + + + Twilsonco's NNFF-Lite(Reboot required) + + + + Auto update Cruise speed + + + + Disable Min.SteerSpeed + + + + Disable DM + + + + Hotspot enabled on boot + + + + Enable Software Menu + + + + IsLdwsCar + + + + Hardware is C3x Lite + + + + Share Data + + + + 0:None, 1:TCP JSON Data(Reboot required) + + + + CURVE: Lower limit speed(30) + + + + When you approach a curve, reduce your speed. Minimum speed + + + + CURVE: Auto Control ratio(100%) + + + + RoadSpeedLimitOffset(-1) + + + + -1:NotUsed,RoadLimitSpeed+Offset + + + + Auto Roadlimit Speed adjust (50%) + + + + SpeedCamDecelEnd(6s) + + + + Sets the deceleration completion point. A larger value completes deceleration farther away from the camera. + + + + NaviSpeedControlMode(2) + + + + 0:No slowdown, 1: speed camera, 2: + accident prevention bump, 3: + mobile camera + + + + SpeedCamDecelRatex0.01m/s^2(80) + + + + Lower number, slows down from a greater distance + + + + SpeedCamSafetyFactor(105%) + + + + SpeedBumpTimeDistance(1s) + + + + SpeedBumpSpeed(35Km/h) + + + + NaviCountDown mode(2) + + + + 0: off, 1:tbt+camera, 2:tbt+camera+bump + + + + Turn Speed control mode(1) + + + + 0: off, 1:vision, 2:vision+route, 3: route + + + + Smart Speed Control(0) + + + + 0: off, 1:accel, 2:decel, 3: all + + + + Map TurnSpeed Factor(100) + + + + Model TurnSpeed Factor(0) + + + + ATC: Auto turn control(0) + + + + 0:None, 1: lane change, 2: lane change + speed, 3: speed + + + + ATC: Turn Speed (20) + + + + 0:None, turn speed + + + + ATC: Turn CtrlDistTime (6) + + + + dist=speed*time + + + + ATC Auto Map Change(0) + + + ConfirmationDialog @@ -113,6 +983,33 @@ Refuser, désinstaller %1 + + DestinationWidget + + Home + + + + Work + + + + No destination set + + + + home + + + + work + + + + No %1 location set + + + DeveloperPanel @@ -172,11 +1069,11 @@ Reset Calibration - Réinitialiser la calibration + Réinitialiser la calibration RESET - RÉINITIALISER + RÉINITIALISER Are you sure you want to reset calibration? @@ -184,7 +1081,7 @@ Reset - Réinitialiser + Réinitialiser Review Training Guide @@ -286,6 +1183,81 @@ PAIR ASSOCIER + + ReCalibration + + + + Git Pull & Reboot + + + + Git pull & Reboot? + + + + Yes + + + + Failed to start update process. + + + + Update process started. Device will reboot if updates are applied. + + + + Set default + + + + Set to default? + + + + Remove MapboxKey + + + + Remove Mapbox key? + + + + Calibration Status + + + + SHOW + + + + Reboot & Disengage to Calibration + + + + + DrawCarrot + + ECO + + + + SAFE + + + + NORM + + + + FAST + + + + ERRM + + DriverViewWindow @@ -338,6 +1310,13 @@ Firehose Mode allows you to maximize your training data uploads to improve openp + + HomeWindow + + Auto-Tuner: Driving pattern learned! + + + HudRenderer @@ -371,7 +1350,52 @@ Firehose Mode allows you to maximize your training data uploads to improve openp Installer Installing... - Installation... + Installation... + + + + MapETA + + eta + + + + min + + + + hr + + + + + MapSettings + + NAVIGATION + + + + Manage at %1 + + + + Manage at connect.comma.ai + + + + + MapWindow + + Map Loading + + + + Waiting for GPS(APN) + + + + Waiting for route + @@ -452,6 +1476,14 @@ Firehose Mode allows you to maximize your training data uploads to improve openp openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. openpilot a détecté un changement dans la position de montage de l'appareil. Assurez-vous que l'appareil est totalement inséré dans le support et que le support est fermement fixé au pare-brise. + + Poor visibility detected for driver monitoring. Ensure the device has a clear view of the driver. This can be checked in the device settings. Extreme lighting conditions and/or unconventional mounting positions may also trigger this alert. + + + + Excessive %1 actuation detected on your last drive. Please contact support at https://comma.ai/support and share your device's Dongle ID for troubleshooting. + + OffroadHome @@ -525,6 +1557,21 @@ Firehose Mode allows you to maximize your training data uploads to improve openp Annuler + + PathEndDrawer + + Signal Error + + + + Signal Ready + + + + Signal slowing + + + PrimeAdWidget @@ -579,7 +1626,7 @@ Firehose Mode allows you to maximize your training data uploads to improve openp openpilot - openpilot + openpilot %n minute(s) ago @@ -606,6 +1653,26 @@ Firehose Mode allows you to maximize your training data uploads to improve openp now maintenant + + km + + + + m + + + + mi + + + + ft + + + + carrotpilot + + Reset @@ -678,6 +1745,10 @@ Cela peut prendre jusqu'à une minute. Firehose + + CarrotPilot + + Setup @@ -1114,6 +2185,18 @@ Cela peut prendre jusqu'à une minute. Enable driver monitoring even when openpilot is not engaged. Activer la surveillance conducteur lorsque openpilot n'est pas actif. + + Record and Upload Microphone Audio + + + + Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect. + + + + MoreRelaxed + + Updater diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index d242875937..7d5cd9dc81 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -87,6 +87,876 @@ [%1] + + AutoTunerCardListDialog + + Tuning History Card List + + + + Close + 閉じる + + + No historical data to display + + + + [%1 Applied] + + + + Restore + + + + Delete + + + + Are you sure you want to restore the parameters to this state? + + + + Restored to previous values successfully. + + + + Are you sure you want to delete this item? + + + + + AutoTunerDialog + + 사용 안내 (Guide) + + + + 나중에 (Later) + + + + 학습 초기화 (Clear) + + + + 선택 적용 (Apply Selected) + + + + 적용하지 않고 현재까지의 모든 학습 데이터를 삭제하시겠습니까? + + + + 초기화 + + + + + AutoTunerGraphWidget + + No historical data to display + + + + + AutoTunerGuideDialog + + 확인 + + + + + AutoTunerHistoryPanel + + Parameters + + + + Apply LAT (Steering): ON + + + + Apply LAT (Steering): OFF + + + + Apply LONG (Accel): ON + + + + Apply LONG (Accel): OFF + + + + View Card Type + + + + Show All Parameters + + + + Clear All Logs + + + + Close + 閉じる + + + Are you sure you want to restore the parameters to this state? + + + + Restore + + + + Restored to previous values successfully. + + + + Are you sure you want to delete this item? + + + + Delete + + + + Clear All + + + + Are you sure you want to delete all history and restore parameters to their factory default values? + + + + + CarrotPanel + + Start + + + + Cruise + + + + Speed + + + + Tuning + + + + Disp + + + + Path + + + + Button: Cruise Button Mode + + + + 0:Normal,1:User1,2:User2 + + + + Button: Cancel Button Mode + + + + 0:Long,1:Long+Lat + + + + Button: LFA Button Mode + + + + 0:Normal,1:Decel&Stop&LeadCarReady + + + + Button: Cruise Speed Unit(Basic) + + + + Button: Cruise Speed Unit(Extra) + + + + CRUISE: Eco control(4km/h) + + + + Temporarily increasing the set speed to improve fuel efficiency. + + + + CRUISE: Auto speed up (0%) + + + + Auto speed up based on the lead car up to RoadSpeedLimit. + + + + GAP1: Apply TFollow (110)x0.01s + + + + GAP2: Apply TFollow (120)x0.01s + + + + GAP3: Apply TFollow (160)x0.01s + + + + GAP4: Apply TFollow (180)x0.01s + + + + Dynamic GAP control + + + + Dynamic GAP control (LaneChange) + + + + DRIVEMODE: Select + + + + 1:ECO,2:SAFE,3:NORMAL,4:HIGH + + + + DRIVEMODE: Auto + + + + NORMAL mode only + + + + TrafficLight DetectMode + + + + 0:None, 1:Stopping only, 2: Stop & Go + + + + AChangeCostStarting + + + + TrafficStopDistanceAdjust + + + + View Tuning History + + + + Auto-Tuner: Driving-Based Learning + + + + Learn from driver interventions (gas/brake) and recommend parameter adjustments when parking. 0=Off, 1=On + + + + Laneline mode speed(0) + + + + Laneline mode, lat_mpc control used + + + + Laneline mode curve speed(0) + + + + Laneline mode, high speed only + + + + AdjustLaneOffset(0)cm + + + + LaneChange need torque + + + + -1:Disable lanechange, 0: no need torque, 1:need torque + + + + LaneChange delay + + + + x0.1sec + + + + LaneChange Bsd + + + + -1:ignore bsd, 0:BSD detect, 1: block steer torque + + + + LaneChange LineCheck + + + + 0:Color+Type, 1:Type only, 2:Type+torque override solid + + + + LAT: SteerRatiox0.1(0) + + + + Custom SteerRatio + + + + LAT: SteerRatioRatex0.01(100) + + + + SteerRatio apply rate + + + + LAT: PathOffset + + + + (-)left, (+)right + + + + LAT:SteerActuatorDelay(30) + + + + x0.01, 0:LiveDelay + + + + LAT:LatSmoothSec(13) + + + + x0.01 + + + + LAT: TorqueCustom(0) + + + + LAT: TorqueAccelFactor(2500) + + + + LAT: TorqueFriction(100) + + + + LAT: CustomSteerMax(0) + + + + LAT: CustomSteerDeltaUp(0) + + + + LAT: CustomSteerDeltaDown(0) + + + + LONG: P Gain(100) + + + + LONG: I Gain(0) + + + + LONG: FF Gain(100) + + + + LONG: ActuatorDelay(20) + + + + LONG: VEgoStopping(50) + + + + Stopping factor + + + + LONG: Radar reaction factor(100) + + + + LONG: StoppingStartAccelx0.01(-40) + + + + LONG: StopDistance (600)cm + + + + LONG: Jerk Lead Factor (0) + + + + ACCEL:0km/h(160) + + + + Acceleration needed at specified speed.(x0.01m/s^2) + + + + ACCEL:10km/h(160) + + + + ACCEL:40km/h(120) + + + + ACCEL:60km/h(100) + + + + ACCEL:80km/h(80) + + + + ACCEL:110km/h(70) + + + + ACCEL:140km/h(60) + + + + MaxAngleFrames(89) + + + + 89:Basic, steering instrument panel error 85~87 + + + + Debug Info + + + + Tpms Info + + + + Time Info + + + + 0:None,1:Time/Date,2:Time,3:Date + + + + Path End + + + + 0:None,1:Display + + + + Device State + + + + Lane Info + + + + -1:None, 0:Path, 1:Path+Lane, 2: Path+Lane+RoadEdge + + + + Radar Info + + + + 0:None,1:Display,2:RelPos,3:Stopped Car + + + + Route Info + + + + Debug plot + + + + Brightness ratio + + + + Tire Trajectory + + + + Display tire paths with a gradient effect on the lane markers. + + + + Path Color: Cruise OFF + + + + (+10:Stroke)0:Red,1:Orange,2:Yellow,3:Green,4:Blue,5:Indigo,6:Violet,7:Brown,8:White,9:Black + + + + Path Mode: Laneless + + + + 0:Normal,1,2:Rec,3,4:^^,5,6:Rec,7,8:^^,9,10,11,12:Smooth^^ + + + + Path Color: Laneless + + + + Path Mode: LaneMode + + + + Path Color: LaneMode + + + + Path Width ratio(100%) + + + + SELECT YOUR CAR + + + + Select Manufacturer + + + + Select your car + + + + HYUNDAI: CAMERA SCC + + + + 1:Connect the SCC's CAN line to CAM, 2:Sync Cruise state, 3:StockLong + + + + CANFD: HDA2 mode + + + + 1:HDA2,2:HDA2+BSM + + + + Enable Radar Track + + + + 1:Enable RadarTrack, -1,2:Disable use HKG SCC radar at all times + + + + Auto Cruise control + + + + Softhold, Auto Cruise ON/OFF control + + + + CRUISE: Auto ON distance(0cm) + + + + When GAS/Brake is OFF, Cruise ON when the lead car gets closer. + + + + Auto Engage control on start + + + + 1:SteerEnable, 2:Steer/Cruise Engage + + + + Auto AccelTok speed + + + + Gas(Accel)Tok enable speed + + + + Read Cruise Speed from PCM + + + + Toyota must set to 1, Honda 3 + + + + Sound Volume(100%) + + + + Sound Volume, Engage(10%) + + + + Power off time (min) + + + + EnableConnect + + + + Your device may be banned by Comma + + + + Mapbox Style(0) + + + + Record Road camera(0) + + + + 1:RoadCam, 2:RoadCam+WideRoadCam + + + + Use HDP(CCNC)(0) + + + + 1:While Using APN, 2:Always + + + + NNFF + + + + Twilsonco's NNFF(Reboot required) + + + + NNFFLite + + + + Twilsonco's NNFF-Lite(Reboot required) + + + + Auto update Cruise speed + + + + Disable Min.SteerSpeed + + + + Disable DM + + + + Hotspot enabled on boot + + + + Enable Software Menu + + + + IsLdwsCar + + + + Hardware is C3x Lite + + + + Share Data + + + + 0:None, 1:TCP JSON Data(Reboot required) + + + + CURVE: Lower limit speed(30) + + + + When you approach a curve, reduce your speed. Minimum speed + + + + CURVE: Auto Control ratio(100%) + + + + RoadSpeedLimitOffset(-1) + + + + -1:NotUsed,RoadLimitSpeed+Offset + + + + Auto Roadlimit Speed adjust (50%) + + + + SpeedCamDecelEnd(6s) + + + + Sets the deceleration completion point. A larger value completes deceleration farther away from the camera. + + + + NaviSpeedControlMode(2) + + + + 0:No slowdown, 1: speed camera, 2: + accident prevention bump, 3: + mobile camera + + + + SpeedCamDecelRatex0.01m/s^2(80) + + + + Lower number, slows down from a greater distance + + + + SpeedCamSafetyFactor(105%) + + + + SpeedBumpTimeDistance(1s) + + + + SpeedBumpSpeed(35Km/h) + + + + NaviCountDown mode(2) + + + + 0: off, 1:tbt+camera, 2:tbt+camera+bump + + + + Turn Speed control mode(1) + + + + 0: off, 1:vision, 2:vision+route, 3: route + + + + Smart Speed Control(0) + + + + 0: off, 1:accel, 2:decel, 3: all + + + + Map TurnSpeed Factor(100) + + + + Model TurnSpeed Factor(0) + + + + ATC: Auto turn control(0) + + + + 0:None, 1: lane change, 2: lane change + speed, 3: speed + + + + ATC: Turn Speed (20) + + + + 0:None, turn speed + + + + ATC: Turn CtrlDistTime (6) + + + + dist=speed*time + + + + ATC Auto Map Change(0) + + + ConfirmationDialog @@ -113,6 +983,33 @@ 同意しない(%1をアンインストール) + + DestinationWidget + + Home + + + + Work + + + + No destination set + + + + home + + + + work + + + + No %1 location set + + + DeveloperPanel @@ -172,11 +1069,11 @@ Reset Calibration - キャリブレーションリセット + キャリブレーションリセット RESET - リセット + リセット Are you sure you want to reset calibration? @@ -268,7 +1165,7 @@ Reset - リセット + リセット Review @@ -286,6 +1183,81 @@ PAIR OK + + ReCalibration + + + + Git Pull & Reboot + + + + Git pull & Reboot? + + + + Yes + + + + Failed to start update process. + + + + Update process started. Device will reboot if updates are applied. + + + + Set default + + + + Set to default? + + + + Remove MapboxKey + + + + Remove Mapbox key? + + + + Calibration Status + + + + SHOW + + + + Reboot & Disengage to Calibration + + + + + DrawCarrot + + ECO + + + + SAFE + + + + NORM + + + + FAST + + + + ERRM + + DriverViewWindow @@ -338,6 +1310,13 @@ Firehose Mode allows you to maximize your training data uploads to improve openp + + HomeWindow + + Auto-Tuner: Driving pattern learned! + + + HudRenderer @@ -370,7 +1349,52 @@ Firehose Mode allows you to maximize your training data uploads to improve openp Installer Installing... - インストール中... + インストール中... + + + + MapETA + + eta + + + + min + + + + hr + + + + + MapSettings + + NAVIGATION + + + + Manage at %1 + + + + Manage at connect.comma.ai + + + + + MapWindow + + Map Loading + + + + Waiting for GPS(APN) + + + + Waiting for route + @@ -451,6 +1475,14 @@ Firehose Mode allows you to maximize your training data uploads to improve openp Device temperature too high. System cooling down before starting. Current internal component temperature: %1 デバイスの温度が高すぎるためシステム起動前の冷却中です。現在のデバイス内部温度: %1 + + Poor visibility detected for driver monitoring. Ensure the device has a clear view of the driver. This can be checked in the device settings. Extreme lighting conditions and/or unconventional mounting positions may also trigger this alert. + + + + Excessive %1 actuation detected on your last drive. Please contact support at https://comma.ai/support and share your device's Dongle ID for troubleshooting. + + OffroadHome @@ -524,6 +1556,21 @@ Firehose Mode allows you to maximize your training data uploads to improve openp 有効にする + + PathEndDrawer + + Signal Error + + + + Signal Ready + + + + Signal slowing + + + PrimeAdWidget @@ -578,7 +1625,7 @@ Firehose Mode allows you to maximize your training data uploads to improve openp openpilot - openpilot + openpilot %n minute(s) ago @@ -602,6 +1649,26 @@ Firehose Mode allows you to maximize your training data uploads to improve openp now たった今 + + km + + + + m + + + + mi + + + + ft + + + + carrotpilot + + Reset @@ -674,6 +1741,10 @@ This may take up to a minute. Firehose データ学習 + + CarrotPilot + + Setup @@ -1110,6 +2181,18 @@ This may take up to a minute. Enable driver monitoring even when openpilot is not engaged. openpilotが作動していない場合でも運転者モニタリングを有効にする。 + + Record and Upload Microphone Audio + + + + Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect. + + + + MoreRelaxed + + Updater diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index a78ffed84a..0a77098e56 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -87,35 +87,874 @@ "%1"에 접속하려면 비밀번호가 필요합니다 + + AutoTunerCardListDialog + + Tuning History Card List + + + + Close + 닫기 + + + No historical data to display + + + + [%1 Applied] + + + + Restore + + + + Delete + + + + Are you sure you want to restore the parameters to this state? + + + + Restored to previous values successfully. + + + + Are you sure you want to delete this item? + + + + + AutoTunerDialog + + 사용 안내 (Guide) + + + + 나중에 (Later) + + + + 학습 초기화 (Clear) + + + + 선택 적용 (Apply Selected) + + + + 적용하지 않고 현재까지의 모든 학습 데이터를 삭제하시겠습니까? + + + + 초기화 + + + + + AutoTunerGraphWidget + + No historical data to display + + + + + AutoTunerGuideDialog + + 확인 + + + + + AutoTunerHistoryPanel + + Parameters + + + + Apply LAT (Steering): ON + + + + Apply LAT (Steering): OFF + + + + Apply LONG (Accel): ON + + + + Apply LONG (Accel): OFF + + + + View Card Type + + + + Show All Parameters + + + + Clear All Logs + + + + Close + 닫기 + + + Are you sure you want to restore the parameters to this state? + + + + Restore + + + + Restored to previous values successfully. + + + + Are you sure you want to delete this item? + + + + Delete + + + + Clear All + + + + Are you sure you want to delete all history and restore parameters to their factory default values? + + + CarrotPanel - Start - 시작 + Start + 시작 + + + Cruise + 크루즈 + + + Speed + 속도 + + + Tuning + 튜닝 + + + Disp + 화면 + + + Path + 패쓰 + + + SELECT YOUR CAR + 차량선택 + + + Button: Cruise Button Mode + + + + 0:Normal,1:User1,2:User2 + + + + Button: Cancel Button Mode + + + + 0:Long,1:Long+Lat + + + + Button: LFA Button Mode + + + + 0:Normal,1:Decel&Stop&LeadCarReady + + + + Button: Cruise Speed Unit(Basic) + + + + Button: Cruise Speed Unit(Extra) + + + + CRUISE: Eco control(4km/h) + + + + Temporarily increasing the set speed to improve fuel efficiency. + + + + CRUISE: Auto speed up (0%) + + + + Auto speed up based on the lead car up to RoadSpeedLimit. + + + + GAP1: Apply TFollow (110)x0.01s + + + + GAP2: Apply TFollow (120)x0.01s + + + + GAP3: Apply TFollow (160)x0.01s + + + + GAP4: Apply TFollow (180)x0.01s + + + + Dynamic GAP control + + + + Dynamic GAP control (LaneChange) + + + + DRIVEMODE: Select + + + + 1:ECO,2:SAFE,3:NORMAL,4:HIGH + + + + DRIVEMODE: Auto + + + + NORMAL mode only + + + + TrafficLight DetectMode + + + + 0:None, 1:Stopping only, 2: Stop & Go + + + + AChangeCostStarting + + + + TrafficStopDistanceAdjust + + + + View Tuning History + + + + Auto-Tuner: Driving-Based Learning + + + + Learn from driver interventions (gas/brake) and recommend parameter adjustments when parking. 0=Off, 1=On + + + + Laneline mode speed(0) + + + + Laneline mode, lat_mpc control used + + + + Laneline mode curve speed(0) + + + + Laneline mode, high speed only + + + + AdjustLaneOffset(0)cm + + + + LaneChange need torque + + + + -1:Disable lanechange, 0: no need torque, 1:need torque + + + + LaneChange delay + + + + x0.1sec + + + + LaneChange Bsd + + + + -1:ignore bsd, 0:BSD detect, 1: block steer torque + + + + LaneChange LineCheck + + + + 0:Color+Type, 1:Type only, 2:Type+torque override solid + + + + LAT: SteerRatiox0.1(0) + + + + Custom SteerRatio + + + + LAT: SteerRatioRatex0.01(100) + + + + SteerRatio apply rate + + + + LAT: PathOffset + + + + (-)left, (+)right + + + + LAT:SteerActuatorDelay(30) + + + + x0.01, 0:LiveDelay + + + + LAT:LatSmoothSec(13) + + + + x0.01 + + + + LAT: TorqueCustom(0) + + + + LAT: TorqueAccelFactor(2500) + + + + LAT: TorqueFriction(100) + + + + LAT: CustomSteerMax(0) + + + + LAT: CustomSteerDeltaUp(0) + + + + LAT: CustomSteerDeltaDown(0) + + + + LONG: P Gain(100) + + + + LONG: I Gain(0) + + + + LONG: FF Gain(100) + + + + LONG: ActuatorDelay(20) + + + + LONG: VEgoStopping(50) + + + + Stopping factor + + + + LONG: Radar reaction factor(100) + + + + LONG: StoppingStartAccelx0.01(-40) + + + + LONG: StopDistance (600)cm + + + + LONG: Jerk Lead Factor (0) + + + + ACCEL:0km/h(160) + + + + Acceleration needed at specified speed.(x0.01m/s^2) + + + + ACCEL:10km/h(160) + + + + ACCEL:40km/h(120) + + + + ACCEL:60km/h(100) + + + + ACCEL:80km/h(80) + + + + ACCEL:110km/h(70) + + + + ACCEL:140km/h(60) + + + + MaxAngleFrames(89) + + + + 89:Basic, steering instrument panel error 85~87 + + + + Debug Info + + + + Tpms Info + + + + Time Info + + + + 0:None,1:Time/Date,2:Time,3:Date + + + + Path End + + + + 0:None,1:Display + + + + Device State + + + + Lane Info + + + + -1:None, 0:Path, 1:Path+Lane, 2: Path+Lane+RoadEdge + + + + Radar Info + + + + 0:None,1:Display,2:RelPos,3:Stopped Car + + + + Route Info + + + + Debug plot + + + + Brightness ratio + + + + Tire Trajectory + + + + Display tire paths with a gradient effect on the lane markers. + + + + Path Color: Cruise OFF + + + + (+10:Stroke)0:Red,1:Orange,2:Yellow,3:Green,4:Blue,5:Indigo,6:Violet,7:Brown,8:White,9:Black + + + + Path Mode: Laneless + + + + 0:Normal,1,2:Rec,3,4:^^,5,6:Rec,7,8:^^,9,10,11,12:Smooth^^ + + + + Path Color: Laneless + + + + Path Mode: LaneMode + + + + Path Color: LaneMode + - Cruise - 크루즈 + Path Width ratio(100%) + - Speed - 속도 + Select Manufacturer + - Tuning - 튜닝 + Select your car + - Disp - 화면 + HYUNDAI: CAMERA SCC + - Path - 패쓰 + 1:Connect the SCC's CAN line to CAM, 2:Sync Cruise state, 3:StockLong + - SELECT YOUR CAR - 차량선택 + CANFD: HDA2 mode + + + + 1:HDA2,2:HDA2+BSM + + + + Enable Radar Track + + + + 1:Enable RadarTrack, -1,2:Disable use HKG SCC radar at all times + + + + Auto Cruise control + + + + Softhold, Auto Cruise ON/OFF control + + + + CRUISE: Auto ON distance(0cm) + + + + When GAS/Brake is OFF, Cruise ON when the lead car gets closer. + + + + Auto Engage control on start + + + + 1:SteerEnable, 2:Steer/Cruise Engage + + + + Auto AccelTok speed + + + + Gas(Accel)Tok enable speed + + + + Read Cruise Speed from PCM + + + + Toyota must set to 1, Honda 3 + + + + Sound Volume(100%) + + + + Sound Volume, Engage(10%) + + + + Power off time (min) + + + + EnableConnect + + + + Your device may be banned by Comma + + + + Mapbox Style(0) + + + + Record Road camera(0) + + + + 1:RoadCam, 2:RoadCam+WideRoadCam + + + + Use HDP(CCNC)(0) + + + + 1:While Using APN, 2:Always + + + + NNFF + + + + Twilsonco's NNFF(Reboot required) + + + + NNFFLite + + + + Twilsonco's NNFF-Lite(Reboot required) + + + + Auto update Cruise speed + + + + Disable Min.SteerSpeed + + + + Disable DM + + + + Hotspot enabled on boot + + + + Enable Software Menu + + + + IsLdwsCar + + + + Hardware is C3x Lite + + + + Share Data + + + + 0:None, 1:TCP JSON Data(Reboot required) + + + + CURVE: Lower limit speed(30) + + + + When you approach a curve, reduce your speed. Minimum speed + + + + CURVE: Auto Control ratio(100%) + + + + RoadSpeedLimitOffset(-1) + + + + -1:NotUsed,RoadLimitSpeed+Offset + + + + Auto Roadlimit Speed adjust (50%) + + + + SpeedCamDecelEnd(6s) + + + + Sets the deceleration completion point. A larger value completes deceleration farther away from the camera. + + + + NaviSpeedControlMode(2) + + + + 0:No slowdown, 1: speed camera, 2: + accident prevention bump, 3: + mobile camera + + + + SpeedCamDecelRatex0.01m/s^2(80) + + + + Lower number, slows down from a greater distance + + + + SpeedCamSafetyFactor(105%) + + + + SpeedBumpTimeDistance(1s) + + + + SpeedBumpSpeed(35Km/h) + + + + NaviCountDown mode(2) + + + + 0: off, 1:tbt+camera, 2:tbt+camera+bump + + + + Turn Speed control mode(1) + + + + 0: off, 1:vision, 2:vision+route, 3: route + + + + Smart Speed Control(0) + + + + 0: off, 1:accel, 2:decel, 3: all + + + + Map TurnSpeed Factor(100) + + + + Model TurnSpeed Factor(0) + + + + ATC: Auto turn control(0) + + + + 0:None, 1: lane change, 2: lane change + speed, 3: speed + + + + ATC: Turn Speed (20) + + + + 0:None, turn speed + + + + ATC: Turn CtrlDistTime (6) + + + + dist=speed*time + + + + ATC Auto Map Change(0) + @@ -318,7 +1157,7 @@ Reset - 초기화 + 초기화 Review @@ -384,28 +1223,32 @@ Reboot & Disengage to Calibration + + Git pull & Reboot? + + DrawCarrot MANUAL - 수동운전 + 수동운전 CRUISE - 정속주행 + 정속주행 E2ECRUISE - E2E주행 + E2E주행 CRUISE READY - 크루즈준비 + 크루즈준비 SIGN DETECTED - 신호감지 + 신호감지 ECO @@ -513,6 +1356,13 @@ Firehose Mode allows you to maximize your training data uploads to improve openp + + HomeWindow + + Auto-Tuner: Driving pattern learned! + + + HudRenderer @@ -545,7 +1395,7 @@ Firehose Mode allows you to maximize your training data uploads to improve openp Installer Installing... - 설치 중... + 설치 중... @@ -671,6 +1521,14 @@ Firehose Mode allows you to maximize your training data uploads to improve openp Device temperature too high. System cooling down before starting. Current internal component temperature: %1 장치 온도가 너무 높습니다. 시작하기 전에 시스템을 냉각하고 있습니다. 현재 내부 구성 요소 온도: %1 + + Poor visibility detected for driver monitoring. Ensure the device has a clear view of the driver. This can be checked in the device settings. Extreme lighting conditions and/or unconventional mounting positions may also trigger this alert. + + + + Excessive %1 actuation detected on your last drive. Please contact support at https://comma.ai/support and share your device's Dongle ID for troubleshooting. + + OffroadHome @@ -759,17 +1617,6 @@ Firehose Mode allows you to maximize your training data uploads to improve openp 신호대기 - - TurnInfoDrawer - - ETA - 도착 - - - MIN - - - PrimeAdWidget @@ -824,7 +1671,7 @@ Firehose Mode allows you to maximize your training data uploads to improve openp openpilot - 오픈파일럿 + 오픈파일럿 %n minute(s) ago @@ -942,7 +1789,11 @@ This may take up to a minute. Carrot - 당근설정 + 당근설정 + + + CarrotPilot + @@ -1393,6 +2244,17 @@ This may take up to a minute. 운전 중에 마이크 오디오를 녹음하고 저장하십시오. 오디오는 comma connect의 대시캠 비디오에 포함됩니다. + + TurnInfoDrawer + + ETA + 도착 + + + MIN + + + Updater diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index 16edf605ad..5674b4885f 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -87,6 +87,876 @@ para "%1" + + AutoTunerCardListDialog + + Tuning History Card List + + + + Close + Fechar + + + No historical data to display + + + + [%1 Applied] + + + + Restore + + + + Delete + + + + Are you sure you want to restore the parameters to this state? + + + + Restored to previous values successfully. + + + + Are you sure you want to delete this item? + + + + + AutoTunerDialog + + 사용 안내 (Guide) + + + + 나중에 (Later) + + + + 학습 초기화 (Clear) + + + + 선택 적용 (Apply Selected) + + + + 적용하지 않고 현재까지의 모든 학습 데이터를 삭제하시겠습니까? + + + + 초기화 + + + + + AutoTunerGraphWidget + + No historical data to display + + + + + AutoTunerGuideDialog + + 확인 + + + + + AutoTunerHistoryPanel + + Parameters + + + + Apply LAT (Steering): ON + + + + Apply LAT (Steering): OFF + + + + Apply LONG (Accel): ON + + + + Apply LONG (Accel): OFF + + + + View Card Type + + + + Show All Parameters + + + + Clear All Logs + + + + Close + Fechar + + + Are you sure you want to restore the parameters to this state? + + + + Restore + + + + Restored to previous values successfully. + + + + Are you sure you want to delete this item? + + + + Delete + + + + Clear All + + + + Are you sure you want to delete all history and restore parameters to their factory default values? + + + + + CarrotPanel + + Start + + + + Cruise + + + + Speed + + + + Tuning + + + + Disp + + + + Path + + + + Button: Cruise Button Mode + + + + 0:Normal,1:User1,2:User2 + + + + Button: Cancel Button Mode + + + + 0:Long,1:Long+Lat + + + + Button: LFA Button Mode + + + + 0:Normal,1:Decel&Stop&LeadCarReady + + + + Button: Cruise Speed Unit(Basic) + + + + Button: Cruise Speed Unit(Extra) + + + + CRUISE: Eco control(4km/h) + + + + Temporarily increasing the set speed to improve fuel efficiency. + + + + CRUISE: Auto speed up (0%) + + + + Auto speed up based on the lead car up to RoadSpeedLimit. + + + + GAP1: Apply TFollow (110)x0.01s + + + + GAP2: Apply TFollow (120)x0.01s + + + + GAP3: Apply TFollow (160)x0.01s + + + + GAP4: Apply TFollow (180)x0.01s + + + + Dynamic GAP control + + + + Dynamic GAP control (LaneChange) + + + + DRIVEMODE: Select + + + + 1:ECO,2:SAFE,3:NORMAL,4:HIGH + + + + DRIVEMODE: Auto + + + + NORMAL mode only + + + + TrafficLight DetectMode + + + + 0:None, 1:Stopping only, 2: Stop & Go + + + + AChangeCostStarting + + + + TrafficStopDistanceAdjust + + + + View Tuning History + + + + Auto-Tuner: Driving-Based Learning + + + + Learn from driver interventions (gas/brake) and recommend parameter adjustments when parking. 0=Off, 1=On + + + + Laneline mode speed(0) + + + + Laneline mode, lat_mpc control used + + + + Laneline mode curve speed(0) + + + + Laneline mode, high speed only + + + + AdjustLaneOffset(0)cm + + + + LaneChange need torque + + + + -1:Disable lanechange, 0: no need torque, 1:need torque + + + + LaneChange delay + + + + x0.1sec + + + + LaneChange Bsd + + + + -1:ignore bsd, 0:BSD detect, 1: block steer torque + + + + LaneChange LineCheck + + + + 0:Color+Type, 1:Type only, 2:Type+torque override solid + + + + LAT: SteerRatiox0.1(0) + + + + Custom SteerRatio + + + + LAT: SteerRatioRatex0.01(100) + + + + SteerRatio apply rate + + + + LAT: PathOffset + + + + (-)left, (+)right + + + + LAT:SteerActuatorDelay(30) + + + + x0.01, 0:LiveDelay + + + + LAT:LatSmoothSec(13) + + + + x0.01 + + + + LAT: TorqueCustom(0) + + + + LAT: TorqueAccelFactor(2500) + + + + LAT: TorqueFriction(100) + + + + LAT: CustomSteerMax(0) + + + + LAT: CustomSteerDeltaUp(0) + + + + LAT: CustomSteerDeltaDown(0) + + + + LONG: P Gain(100) + + + + LONG: I Gain(0) + + + + LONG: FF Gain(100) + + + + LONG: ActuatorDelay(20) + + + + LONG: VEgoStopping(50) + + + + Stopping factor + + + + LONG: Radar reaction factor(100) + + + + LONG: StoppingStartAccelx0.01(-40) + + + + LONG: StopDistance (600)cm + + + + LONG: Jerk Lead Factor (0) + + + + ACCEL:0km/h(160) + + + + Acceleration needed at specified speed.(x0.01m/s^2) + + + + ACCEL:10km/h(160) + + + + ACCEL:40km/h(120) + + + + ACCEL:60km/h(100) + + + + ACCEL:80km/h(80) + + + + ACCEL:110km/h(70) + + + + ACCEL:140km/h(60) + + + + MaxAngleFrames(89) + + + + 89:Basic, steering instrument panel error 85~87 + + + + Debug Info + + + + Tpms Info + + + + Time Info + + + + 0:None,1:Time/Date,2:Time,3:Date + + + + Path End + + + + 0:None,1:Display + + + + Device State + + + + Lane Info + + + + -1:None, 0:Path, 1:Path+Lane, 2: Path+Lane+RoadEdge + + + + Radar Info + + + + 0:None,1:Display,2:RelPos,3:Stopped Car + + + + Route Info + + + + Debug plot + + + + Brightness ratio + + + + Tire Trajectory + + + + Display tire paths with a gradient effect on the lane markers. + + + + Path Color: Cruise OFF + + + + (+10:Stroke)0:Red,1:Orange,2:Yellow,3:Green,4:Blue,5:Indigo,6:Violet,7:Brown,8:White,9:Black + + + + Path Mode: Laneless + + + + 0:Normal,1,2:Rec,3,4:^^,5,6:Rec,7,8:^^,9,10,11,12:Smooth^^ + + + + Path Color: Laneless + + + + Path Mode: LaneMode + + + + Path Color: LaneMode + + + + Path Width ratio(100%) + + + + SELECT YOUR CAR + + + + Select Manufacturer + + + + Select your car + + + + HYUNDAI: CAMERA SCC + + + + 1:Connect the SCC's CAN line to CAM, 2:Sync Cruise state, 3:StockLong + + + + CANFD: HDA2 mode + + + + 1:HDA2,2:HDA2+BSM + + + + Enable Radar Track + + + + 1:Enable RadarTrack, -1,2:Disable use HKG SCC radar at all times + + + + Auto Cruise control + + + + Softhold, Auto Cruise ON/OFF control + + + + CRUISE: Auto ON distance(0cm) + + + + When GAS/Brake is OFF, Cruise ON when the lead car gets closer. + + + + Auto Engage control on start + + + + 1:SteerEnable, 2:Steer/Cruise Engage + + + + Auto AccelTok speed + + + + Gas(Accel)Tok enable speed + + + + Read Cruise Speed from PCM + + + + Toyota must set to 1, Honda 3 + + + + Sound Volume(100%) + + + + Sound Volume, Engage(10%) + + + + Power off time (min) + + + + EnableConnect + + + + Your device may be banned by Comma + + + + Mapbox Style(0) + + + + Record Road camera(0) + + + + 1:RoadCam, 2:RoadCam+WideRoadCam + + + + Use HDP(CCNC)(0) + + + + 1:While Using APN, 2:Always + + + + NNFF + + + + Twilsonco's NNFF(Reboot required) + + + + NNFFLite + + + + Twilsonco's NNFF-Lite(Reboot required) + + + + Auto update Cruise speed + + + + Disable Min.SteerSpeed + + + + Disable DM + + + + Hotspot enabled on boot + + + + Enable Software Menu + + + + IsLdwsCar + + + + Hardware is C3x Lite + + + + Share Data + + + + 0:None, 1:TCP JSON Data(Reboot required) + + + + CURVE: Lower limit speed(30) + + + + When you approach a curve, reduce your speed. Minimum speed + + + + CURVE: Auto Control ratio(100%) + + + + RoadSpeedLimitOffset(-1) + + + + -1:NotUsed,RoadLimitSpeed+Offset + + + + Auto Roadlimit Speed adjust (50%) + + + + SpeedCamDecelEnd(6s) + + + + Sets the deceleration completion point. A larger value completes deceleration farther away from the camera. + + + + NaviSpeedControlMode(2) + + + + 0:No slowdown, 1: speed camera, 2: + accident prevention bump, 3: + mobile camera + + + + SpeedCamDecelRatex0.01m/s^2(80) + + + + Lower number, slows down from a greater distance + + + + SpeedCamSafetyFactor(105%) + + + + SpeedBumpTimeDistance(1s) + + + + SpeedBumpSpeed(35Km/h) + + + + NaviCountDown mode(2) + + + + 0: off, 1:tbt+camera, 2:tbt+camera+bump + + + + Turn Speed control mode(1) + + + + 0: off, 1:vision, 2:vision+route, 3: route + + + + Smart Speed Control(0) + + + + 0: off, 1:accel, 2:decel, 3: all + + + + Map TurnSpeed Factor(100) + + + + Model TurnSpeed Factor(0) + + + + ATC: Auto turn control(0) + + + + 0:None, 1: lane change, 2: lane change + speed, 3: speed + + + + ATC: Turn Speed (20) + + + + 0:None, turn speed + + + + ATC: Turn CtrlDistTime (6) + + + + dist=speed*time + + + + ATC Auto Map Change(0) + + + ConfirmationDialog @@ -113,6 +983,33 @@ Rejeitar, desintalar %1 + + DestinationWidget + + Home + + + + Work + + + + No destination set + + + + home + + + + work + + + + No %1 location set + + + DeveloperPanel @@ -172,11 +1069,11 @@ Reset Calibration - Reinicializar Calibragem + Reinicializar Calibragem RESET - RESET + RESET Are you sure you want to reset calibration? @@ -268,7 +1165,7 @@ Reset - Resetar + Resetar Review @@ -286,6 +1183,81 @@ PAIR PAREAR + + ReCalibration + + + + Git Pull & Reboot + + + + Git pull & Reboot? + + + + Yes + + + + Failed to start update process. + + + + Update process started. Device will reboot if updates are applied. + + + + Set default + + + + Set to default? + + + + Remove MapboxKey + + + + Remove Mapbox key? + + + + Calibration Status + + + + SHOW + + + + Reboot & Disengage to Calibration + + + + + DrawCarrot + + ECO + + + + SAFE + + + + NORM + + + + FAST + + + + ERRM + + DriverViewWindow @@ -338,6 +1310,13 @@ Firehose Mode allows you to maximize your training data uploads to improve openp + + HomeWindow + + Auto-Tuner: Driving pattern learned! + + + HudRenderer @@ -371,7 +1350,52 @@ Firehose Mode allows you to maximize your training data uploads to improve openp Installer Installing... - Instalando... + Instalando... + + + + MapETA + + eta + + + + min + + + + hr + + + + + MapSettings + + NAVIGATION + + + + Manage at %1 + + + + Manage at connect.comma.ai + + + + + MapWindow + + Map Loading + + + + Waiting for GPS(APN) + + + + Waiting for route + @@ -452,6 +1476,14 @@ Firehose Mode allows you to maximize your training data uploads to improve openp Device temperature too high. System cooling down before starting. Current internal component temperature: %1 Temperatura do dispositivo muito alta. O sistema está sendo resfriado antes de iniciar. A temperatura atual do componente interno é: %1 + + Poor visibility detected for driver monitoring. Ensure the device has a clear view of the driver. This can be checked in the device settings. Extreme lighting conditions and/or unconventional mounting positions may also trigger this alert. + + + + Excessive %1 actuation detected on your last drive. Please contact support at https://comma.ai/support and share your device's Dongle ID for troubleshooting. + + OffroadHome @@ -525,6 +1557,21 @@ Firehose Mode allows you to maximize your training data uploads to improve openp Ativar + + PathEndDrawer + + Signal Error + + + + Signal Ready + + + + Signal slowing + + + PrimeAdWidget @@ -579,7 +1626,7 @@ Firehose Mode allows you to maximize your training data uploads to improve openp openpilot - openpilot + openpilot %n minute(s) ago @@ -606,6 +1653,26 @@ Firehose Mode allows you to maximize your training data uploads to improve openp now agora + + km + + + + m + + + + mi + + + + ft + + + + carrotpilot + + Reset @@ -678,6 +1745,10 @@ Isso pode levar até um minuto. Firehose Firehose + + CarrotPilot + + Setup @@ -1114,6 +2185,18 @@ Isso pode levar até um minuto. Enable driver monitoring even when openpilot is not engaged. Habilite o monitoramento do motorista mesmo quando o openpilot não estiver acionado. + + Record and Upload Microphone Audio + + + + Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect. + + + + MoreRelaxed + + Updater diff --git a/selfdrive/ui/translations/main_th.ts b/selfdrive/ui/translations/main_th.ts index fc5e690ce0..b71c7f566b 100644 --- a/selfdrive/ui/translations/main_th.ts +++ b/selfdrive/ui/translations/main_th.ts @@ -87,6 +87,876 @@ สำหรับ "%1" + + AutoTunerCardListDialog + + Tuning History Card List + + + + Close + ปิด + + + No historical data to display + + + + [%1 Applied] + + + + Restore + + + + Delete + + + + Are you sure you want to restore the parameters to this state? + + + + Restored to previous values successfully. + + + + Are you sure you want to delete this item? + + + + + AutoTunerDialog + + 사용 안내 (Guide) + + + + 나중에 (Later) + + + + 학습 초기화 (Clear) + + + + 선택 적용 (Apply Selected) + + + + 적용하지 않고 현재까지의 모든 학습 데이터를 삭제하시겠습니까? + + + + 초기화 + + + + + AutoTunerGraphWidget + + No historical data to display + + + + + AutoTunerGuideDialog + + 확인 + + + + + AutoTunerHistoryPanel + + Parameters + + + + Apply LAT (Steering): ON + + + + Apply LAT (Steering): OFF + + + + Apply LONG (Accel): ON + + + + Apply LONG (Accel): OFF + + + + View Card Type + + + + Show All Parameters + + + + Clear All Logs + + + + Close + ปิด + + + Are you sure you want to restore the parameters to this state? + + + + Restore + + + + Restored to previous values successfully. + + + + Are you sure you want to delete this item? + + + + Delete + + + + Clear All + + + + Are you sure you want to delete all history and restore parameters to their factory default values? + + + + + CarrotPanel + + Start + + + + Cruise + + + + Speed + + + + Tuning + + + + Disp + + + + Path + + + + Button: Cruise Button Mode + + + + 0:Normal,1:User1,2:User2 + + + + Button: Cancel Button Mode + + + + 0:Long,1:Long+Lat + + + + Button: LFA Button Mode + + + + 0:Normal,1:Decel&Stop&LeadCarReady + + + + Button: Cruise Speed Unit(Basic) + + + + Button: Cruise Speed Unit(Extra) + + + + CRUISE: Eco control(4km/h) + + + + Temporarily increasing the set speed to improve fuel efficiency. + + + + CRUISE: Auto speed up (0%) + + + + Auto speed up based on the lead car up to RoadSpeedLimit. + + + + GAP1: Apply TFollow (110)x0.01s + + + + GAP2: Apply TFollow (120)x0.01s + + + + GAP3: Apply TFollow (160)x0.01s + + + + GAP4: Apply TFollow (180)x0.01s + + + + Dynamic GAP control + + + + Dynamic GAP control (LaneChange) + + + + DRIVEMODE: Select + + + + 1:ECO,2:SAFE,3:NORMAL,4:HIGH + + + + DRIVEMODE: Auto + + + + NORMAL mode only + + + + TrafficLight DetectMode + + + + 0:None, 1:Stopping only, 2: Stop & Go + + + + AChangeCostStarting + + + + TrafficStopDistanceAdjust + + + + View Tuning History + + + + Auto-Tuner: Driving-Based Learning + + + + Learn from driver interventions (gas/brake) and recommend parameter adjustments when parking. 0=Off, 1=On + + + + Laneline mode speed(0) + + + + Laneline mode, lat_mpc control used + + + + Laneline mode curve speed(0) + + + + Laneline mode, high speed only + + + + AdjustLaneOffset(0)cm + + + + LaneChange need torque + + + + -1:Disable lanechange, 0: no need torque, 1:need torque + + + + LaneChange delay + + + + x0.1sec + + + + LaneChange Bsd + + + + -1:ignore bsd, 0:BSD detect, 1: block steer torque + + + + LaneChange LineCheck + + + + 0:Color+Type, 1:Type only, 2:Type+torque override solid + + + + LAT: SteerRatiox0.1(0) + + + + Custom SteerRatio + + + + LAT: SteerRatioRatex0.01(100) + + + + SteerRatio apply rate + + + + LAT: PathOffset + + + + (-)left, (+)right + + + + LAT:SteerActuatorDelay(30) + + + + x0.01, 0:LiveDelay + + + + LAT:LatSmoothSec(13) + + + + x0.01 + + + + LAT: TorqueCustom(0) + + + + LAT: TorqueAccelFactor(2500) + + + + LAT: TorqueFriction(100) + + + + LAT: CustomSteerMax(0) + + + + LAT: CustomSteerDeltaUp(0) + + + + LAT: CustomSteerDeltaDown(0) + + + + LONG: P Gain(100) + + + + LONG: I Gain(0) + + + + LONG: FF Gain(100) + + + + LONG: ActuatorDelay(20) + + + + LONG: VEgoStopping(50) + + + + Stopping factor + + + + LONG: Radar reaction factor(100) + + + + LONG: StoppingStartAccelx0.01(-40) + + + + LONG: StopDistance (600)cm + + + + LONG: Jerk Lead Factor (0) + + + + ACCEL:0km/h(160) + + + + Acceleration needed at specified speed.(x0.01m/s^2) + + + + ACCEL:10km/h(160) + + + + ACCEL:40km/h(120) + + + + ACCEL:60km/h(100) + + + + ACCEL:80km/h(80) + + + + ACCEL:110km/h(70) + + + + ACCEL:140km/h(60) + + + + MaxAngleFrames(89) + + + + 89:Basic, steering instrument panel error 85~87 + + + + Debug Info + + + + Tpms Info + + + + Time Info + + + + 0:None,1:Time/Date,2:Time,3:Date + + + + Path End + + + + 0:None,1:Display + + + + Device State + + + + Lane Info + + + + -1:None, 0:Path, 1:Path+Lane, 2: Path+Lane+RoadEdge + + + + Radar Info + + + + 0:None,1:Display,2:RelPos,3:Stopped Car + + + + Route Info + + + + Debug plot + + + + Brightness ratio + + + + Tire Trajectory + + + + Display tire paths with a gradient effect on the lane markers. + + + + Path Color: Cruise OFF + + + + (+10:Stroke)0:Red,1:Orange,2:Yellow,3:Green,4:Blue,5:Indigo,6:Violet,7:Brown,8:White,9:Black + + + + Path Mode: Laneless + + + + 0:Normal,1,2:Rec,3,4:^^,5,6:Rec,7,8:^^,9,10,11,12:Smooth^^ + + + + Path Color: Laneless + + + + Path Mode: LaneMode + + + + Path Color: LaneMode + + + + Path Width ratio(100%) + + + + SELECT YOUR CAR + + + + Select Manufacturer + + + + Select your car + + + + HYUNDAI: CAMERA SCC + + + + 1:Connect the SCC's CAN line to CAM, 2:Sync Cruise state, 3:StockLong + + + + CANFD: HDA2 mode + + + + 1:HDA2,2:HDA2+BSM + + + + Enable Radar Track + + + + 1:Enable RadarTrack, -1,2:Disable use HKG SCC radar at all times + + + + Auto Cruise control + + + + Softhold, Auto Cruise ON/OFF control + + + + CRUISE: Auto ON distance(0cm) + + + + When GAS/Brake is OFF, Cruise ON when the lead car gets closer. + + + + Auto Engage control on start + + + + 1:SteerEnable, 2:Steer/Cruise Engage + + + + Auto AccelTok speed + + + + Gas(Accel)Tok enable speed + + + + Read Cruise Speed from PCM + + + + Toyota must set to 1, Honda 3 + + + + Sound Volume(100%) + + + + Sound Volume, Engage(10%) + + + + Power off time (min) + + + + EnableConnect + + + + Your device may be banned by Comma + + + + Mapbox Style(0) + + + + Record Road camera(0) + + + + 1:RoadCam, 2:RoadCam+WideRoadCam + + + + Use HDP(CCNC)(0) + + + + 1:While Using APN, 2:Always + + + + NNFF + + + + Twilsonco's NNFF(Reboot required) + + + + NNFFLite + + + + Twilsonco's NNFF-Lite(Reboot required) + + + + Auto update Cruise speed + + + + Disable Min.SteerSpeed + + + + Disable DM + + + + Hotspot enabled on boot + + + + Enable Software Menu + + + + IsLdwsCar + + + + Hardware is C3x Lite + + + + Share Data + + + + 0:None, 1:TCP JSON Data(Reboot required) + + + + CURVE: Lower limit speed(30) + + + + When you approach a curve, reduce your speed. Minimum speed + + + + CURVE: Auto Control ratio(100%) + + + + RoadSpeedLimitOffset(-1) + + + + -1:NotUsed,RoadLimitSpeed+Offset + + + + Auto Roadlimit Speed adjust (50%) + + + + SpeedCamDecelEnd(6s) + + + + Sets the deceleration completion point. A larger value completes deceleration farther away from the camera. + + + + NaviSpeedControlMode(2) + + + + 0:No slowdown, 1: speed camera, 2: + accident prevention bump, 3: + mobile camera + + + + SpeedCamDecelRatex0.01m/s^2(80) + + + + Lower number, slows down from a greater distance + + + + SpeedCamSafetyFactor(105%) + + + + SpeedBumpTimeDistance(1s) + + + + SpeedBumpSpeed(35Km/h) + + + + NaviCountDown mode(2) + + + + 0: off, 1:tbt+camera, 2:tbt+camera+bump + + + + Turn Speed control mode(1) + + + + 0: off, 1:vision, 2:vision+route, 3: route + + + + Smart Speed Control(0) + + + + 0: off, 1:accel, 2:decel, 3: all + + + + Map TurnSpeed Factor(100) + + + + Model TurnSpeed Factor(0) + + + + ATC: Auto turn control(0) + + + + 0:None, 1: lane change, 2: lane change + speed, 3: speed + + + + ATC: Turn Speed (20) + + + + 0:None, turn speed + + + + ATC: Turn CtrlDistTime (6) + + + + dist=speed*time + + + + ATC Auto Map Change(0) + + + ConfirmationDialog @@ -113,6 +983,33 @@ ปฏิเสธ และถอนการติดตั้ง %1 + + DestinationWidget + + Home + + + + Work + + + + No destination set + + + + home + + + + work + + + + No %1 location set + + + DeveloperPanel @@ -172,11 +1069,11 @@ Reset Calibration - รีเซ็ตการคาลิเบรท + รีเซ็ตการคาลิเบรท RESET - รีเซ็ต + รีเซ็ต Are you sure you want to reset calibration? @@ -268,7 +1165,7 @@ Reset - รีเซ็ต + รีเซ็ต Review @@ -286,6 +1183,81 @@ PAIR จับคู่ + + ReCalibration + + + + Git Pull & Reboot + + + + Git pull & Reboot? + + + + Yes + + + + Failed to start update process. + + + + Update process started. Device will reboot if updates are applied. + + + + Set default + + + + Set to default? + + + + Remove MapboxKey + + + + Remove Mapbox key? + + + + Calibration Status + + + + SHOW + + + + Reboot & Disengage to Calibration + + + + + DrawCarrot + + ECO + + + + SAFE + + + + NORM + + + + FAST + + + + ERRM + + DriverViewWindow @@ -338,6 +1310,13 @@ Firehose Mode allows you to maximize your training data uploads to improve openp + + HomeWindow + + Auto-Tuner: Driving pattern learned! + + + HudRenderer @@ -370,7 +1349,52 @@ Firehose Mode allows you to maximize your training data uploads to improve openp Installer Installing... - กำลังติดตั้ง... + กำลังติดตั้ง... + + + + MapETA + + eta + + + + min + + + + hr + + + + + MapSettings + + NAVIGATION + + + + Manage at %1 + + + + Manage at connect.comma.ai + + + + + MapWindow + + Map Loading + + + + Waiting for GPS(APN) + + + + Waiting for route + @@ -451,6 +1475,14 @@ Firehose Mode allows you to maximize your training data uploads to improve openp openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. openpilot ตรวจพบการเปลี่ยนแปลงของตำแหน่งที่ติดตั้ง กรุณาตรวจสอบว่าได้เลื่อนอุปกรณ์เข้ากับจุดติดตั้งจนสุดแล้ว และจุดติดตั้งได้ยึดติดกับกระจกหน้าอย่างแน่นหนา + + Poor visibility detected for driver monitoring. Ensure the device has a clear view of the driver. This can be checked in the device settings. Extreme lighting conditions and/or unconventional mounting positions may also trigger this alert. + + + + Excessive %1 actuation detected on your last drive. Please contact support at https://comma.ai/support and share your device's Dongle ID for troubleshooting. + + OffroadHome @@ -524,6 +1556,21 @@ Firehose Mode allows you to maximize your training data uploads to improve openp ยกเลิก + + PathEndDrawer + + Signal Error + + + + Signal Ready + + + + Signal slowing + + + PrimeAdWidget @@ -578,7 +1625,7 @@ Firehose Mode allows you to maximize your training data uploads to improve openp openpilot - openpilot + openpilot %n minute(s) ago @@ -602,6 +1649,26 @@ Firehose Mode allows you to maximize your training data uploads to improve openp now ตอนนี้ + + km + + + + m + + + + mi + + + + ft + + + + carrotpilot + + Reset @@ -674,6 +1741,10 @@ This may take up to a minute. Firehose + + CarrotPilot + + Setup @@ -1110,6 +2181,18 @@ This may take up to a minute. Enable driver monitoring even when openpilot is not engaged. + + Record and Upload Microphone Audio + + + + Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect. + + + + MoreRelaxed + + Updater diff --git a/selfdrive/ui/translations/main_tr.ts b/selfdrive/ui/translations/main_tr.ts index 8484f23160..2f7e6c8f8d 100644 --- a/selfdrive/ui/translations/main_tr.ts +++ b/selfdrive/ui/translations/main_tr.ts @@ -87,6 +87,876 @@ için "%1" + + AutoTunerCardListDialog + + Tuning History Card List + + + + Close + Kapat + + + No historical data to display + + + + [%1 Applied] + + + + Restore + + + + Delete + + + + Are you sure you want to restore the parameters to this state? + + + + Restored to previous values successfully. + + + + Are you sure you want to delete this item? + + + + + AutoTunerDialog + + 사용 안내 (Guide) + + + + 나중에 (Later) + + + + 학습 초기화 (Clear) + + + + 선택 적용 (Apply Selected) + + + + 적용하지 않고 현재까지의 모든 학습 데이터를 삭제하시겠습니까? + + + + 초기화 + + + + + AutoTunerGraphWidget + + No historical data to display + + + + + AutoTunerGuideDialog + + 확인 + + + + + AutoTunerHistoryPanel + + Parameters + + + + Apply LAT (Steering): ON + + + + Apply LAT (Steering): OFF + + + + Apply LONG (Accel): ON + + + + Apply LONG (Accel): OFF + + + + View Card Type + + + + Show All Parameters + + + + Clear All Logs + + + + Close + Kapat + + + Are you sure you want to restore the parameters to this state? + + + + Restore + + + + Restored to previous values successfully. + + + + Are you sure you want to delete this item? + + + + Delete + + + + Clear All + + + + Are you sure you want to delete all history and restore parameters to their factory default values? + + + + + CarrotPanel + + Start + + + + Cruise + + + + Speed + + + + Tuning + + + + Disp + + + + Path + + + + Button: Cruise Button Mode + + + + 0:Normal,1:User1,2:User2 + + + + Button: Cancel Button Mode + + + + 0:Long,1:Long+Lat + + + + Button: LFA Button Mode + + + + 0:Normal,1:Decel&Stop&LeadCarReady + + + + Button: Cruise Speed Unit(Basic) + + + + Button: Cruise Speed Unit(Extra) + + + + CRUISE: Eco control(4km/h) + + + + Temporarily increasing the set speed to improve fuel efficiency. + + + + CRUISE: Auto speed up (0%) + + + + Auto speed up based on the lead car up to RoadSpeedLimit. + + + + GAP1: Apply TFollow (110)x0.01s + + + + GAP2: Apply TFollow (120)x0.01s + + + + GAP3: Apply TFollow (160)x0.01s + + + + GAP4: Apply TFollow (180)x0.01s + + + + Dynamic GAP control + + + + Dynamic GAP control (LaneChange) + + + + DRIVEMODE: Select + + + + 1:ECO,2:SAFE,3:NORMAL,4:HIGH + + + + DRIVEMODE: Auto + + + + NORMAL mode only + + + + TrafficLight DetectMode + + + + 0:None, 1:Stopping only, 2: Stop & Go + + + + AChangeCostStarting + + + + TrafficStopDistanceAdjust + + + + View Tuning History + + + + Auto-Tuner: Driving-Based Learning + + + + Learn from driver interventions (gas/brake) and recommend parameter adjustments when parking. 0=Off, 1=On + + + + Laneline mode speed(0) + + + + Laneline mode, lat_mpc control used + + + + Laneline mode curve speed(0) + + + + Laneline mode, high speed only + + + + AdjustLaneOffset(0)cm + + + + LaneChange need torque + + + + -1:Disable lanechange, 0: no need torque, 1:need torque + + + + LaneChange delay + + + + x0.1sec + + + + LaneChange Bsd + + + + -1:ignore bsd, 0:BSD detect, 1: block steer torque + + + + LaneChange LineCheck + + + + 0:Color+Type, 1:Type only, 2:Type+torque override solid + + + + LAT: SteerRatiox0.1(0) + + + + Custom SteerRatio + + + + LAT: SteerRatioRatex0.01(100) + + + + SteerRatio apply rate + + + + LAT: PathOffset + + + + (-)left, (+)right + + + + LAT:SteerActuatorDelay(30) + + + + x0.01, 0:LiveDelay + + + + LAT:LatSmoothSec(13) + + + + x0.01 + + + + LAT: TorqueCustom(0) + + + + LAT: TorqueAccelFactor(2500) + + + + LAT: TorqueFriction(100) + + + + LAT: CustomSteerMax(0) + + + + LAT: CustomSteerDeltaUp(0) + + + + LAT: CustomSteerDeltaDown(0) + + + + LONG: P Gain(100) + + + + LONG: I Gain(0) + + + + LONG: FF Gain(100) + + + + LONG: ActuatorDelay(20) + + + + LONG: VEgoStopping(50) + + + + Stopping factor + + + + LONG: Radar reaction factor(100) + + + + LONG: StoppingStartAccelx0.01(-40) + + + + LONG: StopDistance (600)cm + + + + LONG: Jerk Lead Factor (0) + + + + ACCEL:0km/h(160) + + + + Acceleration needed at specified speed.(x0.01m/s^2) + + + + ACCEL:10km/h(160) + + + + ACCEL:40km/h(120) + + + + ACCEL:60km/h(100) + + + + ACCEL:80km/h(80) + + + + ACCEL:110km/h(70) + + + + ACCEL:140km/h(60) + + + + MaxAngleFrames(89) + + + + 89:Basic, steering instrument panel error 85~87 + + + + Debug Info + + + + Tpms Info + + + + Time Info + + + + 0:None,1:Time/Date,2:Time,3:Date + + + + Path End + + + + 0:None,1:Display + + + + Device State + + + + Lane Info + + + + -1:None, 0:Path, 1:Path+Lane, 2: Path+Lane+RoadEdge + + + + Radar Info + + + + 0:None,1:Display,2:RelPos,3:Stopped Car + + + + Route Info + + + + Debug plot + + + + Brightness ratio + + + + Tire Trajectory + + + + Display tire paths with a gradient effect on the lane markers. + + + + Path Color: Cruise OFF + + + + (+10:Stroke)0:Red,1:Orange,2:Yellow,3:Green,4:Blue,5:Indigo,6:Violet,7:Brown,8:White,9:Black + + + + Path Mode: Laneless + + + + 0:Normal,1,2:Rec,3,4:^^,5,6:Rec,7,8:^^,9,10,11,12:Smooth^^ + + + + Path Color: Laneless + + + + Path Mode: LaneMode + + + + Path Color: LaneMode + + + + Path Width ratio(100%) + + + + SELECT YOUR CAR + + + + Select Manufacturer + + + + Select your car + + + + HYUNDAI: CAMERA SCC + + + + 1:Connect the SCC's CAN line to CAM, 2:Sync Cruise state, 3:StockLong + + + + CANFD: HDA2 mode + + + + 1:HDA2,2:HDA2+BSM + + + + Enable Radar Track + + + + 1:Enable RadarTrack, -1,2:Disable use HKG SCC radar at all times + + + + Auto Cruise control + + + + Softhold, Auto Cruise ON/OFF control + + + + CRUISE: Auto ON distance(0cm) + + + + When GAS/Brake is OFF, Cruise ON when the lead car gets closer. + + + + Auto Engage control on start + + + + 1:SteerEnable, 2:Steer/Cruise Engage + + + + Auto AccelTok speed + + + + Gas(Accel)Tok enable speed + + + + Read Cruise Speed from PCM + + + + Toyota must set to 1, Honda 3 + + + + Sound Volume(100%) + + + + Sound Volume, Engage(10%) + + + + Power off time (min) + + + + EnableConnect + + + + Your device may be banned by Comma + + + + Mapbox Style(0) + + + + Record Road camera(0) + + + + 1:RoadCam, 2:RoadCam+WideRoadCam + + + + Use HDP(CCNC)(0) + + + + 1:While Using APN, 2:Always + + + + NNFF + + + + Twilsonco's NNFF(Reboot required) + + + + NNFFLite + + + + Twilsonco's NNFF-Lite(Reboot required) + + + + Auto update Cruise speed + + + + Disable Min.SteerSpeed + + + + Disable DM + + + + Hotspot enabled on boot + + + + Enable Software Menu + + + + IsLdwsCar + + + + Hardware is C3x Lite + + + + Share Data + + + + 0:None, 1:TCP JSON Data(Reboot required) + + + + CURVE: Lower limit speed(30) + + + + When you approach a curve, reduce your speed. Minimum speed + + + + CURVE: Auto Control ratio(100%) + + + + RoadSpeedLimitOffset(-1) + + + + -1:NotUsed,RoadLimitSpeed+Offset + + + + Auto Roadlimit Speed adjust (50%) + + + + SpeedCamDecelEnd(6s) + + + + Sets the deceleration completion point. A larger value completes deceleration farther away from the camera. + + + + NaviSpeedControlMode(2) + + + + 0:No slowdown, 1: speed camera, 2: + accident prevention bump, 3: + mobile camera + + + + SpeedCamDecelRatex0.01m/s^2(80) + + + + Lower number, slows down from a greater distance + + + + SpeedCamSafetyFactor(105%) + + + + SpeedBumpTimeDistance(1s) + + + + SpeedBumpSpeed(35Km/h) + + + + NaviCountDown mode(2) + + + + 0: off, 1:tbt+camera, 2:tbt+camera+bump + + + + Turn Speed control mode(1) + + + + 0: off, 1:vision, 2:vision+route, 3: route + + + + Smart Speed Control(0) + + + + 0: off, 1:accel, 2:decel, 3: all + + + + Map TurnSpeed Factor(100) + + + + Model TurnSpeed Factor(0) + + + + ATC: Auto turn control(0) + + + + 0:None, 1: lane change, 2: lane change + speed, 3: speed + + + + ATC: Turn Speed (20) + + + + 0:None, turn speed + + + + ATC: Turn CtrlDistTime (6) + + + + dist=speed*time + + + + ATC Auto Map Change(0) + + + ConfirmationDialog @@ -113,6 +983,33 @@ Reddet, Kurulumu kaldır. %1 + + DestinationWidget + + Home + + + + Work + + + + No destination set + + + + home + + + + work + + + + No %1 location set + + + DeveloperPanel @@ -172,11 +1069,11 @@ Reset Calibration - Kalibrasyonu sıfırla + Kalibrasyonu sıfırla RESET - SIFIRLA + SIFIRLA Are you sure you want to reset calibration? @@ -266,10 +1163,6 @@ Disengage to Power Off Bağlantıyı kes ve Cihazı kapat - - Reset - - Review @@ -286,6 +1179,81 @@ PAIR + + ReCalibration + + + + Git Pull & Reboot + + + + Git pull & Reboot? + + + + Yes + + + + Failed to start update process. + + + + Update process started. Device will reboot if updates are applied. + + + + Set default + + + + Set to default? + + + + Remove MapboxKey + + + + Remove Mapbox key? + + + + Calibration Status + + + + SHOW + + + + Reboot & Disengage to Calibration + + + + + DrawCarrot + + ECO + + + + SAFE + + + + NORM + + + + FAST + + + + ERRM + + DriverViewWindow @@ -338,6 +1306,13 @@ Firehose Mode allows you to maximize your training data uploads to improve openp + + HomeWindow + + Auto-Tuner: Driving pattern learned! + + + HudRenderer @@ -370,7 +1345,52 @@ Firehose Mode allows you to maximize your training data uploads to improve openp Installer Installing... - Yükleniyor... + Yükleniyor... + + + + MapETA + + eta + + + + min + + + + hr + + + + + MapSettings + + NAVIGATION + + + + Manage at %1 + + + + Manage at connect.comma.ai + + + + + MapWindow + + Map Loading + + + + Waiting for GPS(APN) + + + + Waiting for route + @@ -450,6 +1470,14 @@ Firehose Mode allows you to maximize your training data uploads to improve openp openpilot detected a change in the device's mounting position. Ensure the device is fully seated in the mount and the mount is firmly secured to the windshield. + + Poor visibility detected for driver monitoring. Ensure the device has a clear view of the driver. This can be checked in the device settings. Extreme lighting conditions and/or unconventional mounting positions may also trigger this alert. + + + + Excessive %1 actuation detected on your last drive. Please contact support at https://comma.ai/support and share your device's Dongle ID for troubleshooting. + + OffroadHome @@ -523,6 +1551,21 @@ Firehose Mode allows you to maximize your training data uploads to improve openp + + PathEndDrawer + + Signal Error + + + + Signal Ready + + + + Signal slowing + + + PrimeAdWidget @@ -577,7 +1620,7 @@ Firehose Mode allows you to maximize your training data uploads to improve openp openpilot - openpilot + openpilot %n minute(s) ago @@ -601,6 +1644,26 @@ Firehose Mode allows you to maximize your training data uploads to improve openp now + + km + + + + m + + + + mi + + + + ft + + + + carrotpilot + + Reset @@ -672,6 +1735,10 @@ This may take up to a minute. Firehose + + CarrotPilot + + Setup @@ -1108,6 +2175,18 @@ This may take up to a minute. Enable driver monitoring even when openpilot is not engaged. + + Record and Upload Microphone Audio + + + + Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect. + + + + MoreRelaxed + + Updater diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index 9dfc6d12d3..cd7ff6fc0b 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -87,41 +87,188 @@ 网络名称:"%1" + + AutoTunerCardListDialog + + Tuning History Card List + + + + Close + 关闭 + + + No historical data to display + + + + [%1 Applied] + + + + Restore + + + + Delete + + + + Are you sure you want to restore the parameters to this state? + + + + Restored to previous values successfully. + + + + Are you sure you want to delete this item? + + + + + AutoTunerDialog + + 사용 안내 (Guide) + + + + 나중에 (Later) + + + + 학습 초기화 (Clear) + + + + 선택 적용 (Apply Selected) + + + + 적용하지 않고 현재까지의 모든 학습 데이터를 삭제하시겠습니까? + + + + 초기화 + + + + + AutoTunerGraphWidget + + No historical data to display + + + + + AutoTunerGuideDialog + + 확인 + + + + + AutoTunerHistoryPanel + + Parameters + + + + Apply LAT (Steering): ON + + + + Apply LAT (Steering): OFF + + + + Apply LONG (Accel): ON + + + + Apply LONG (Accel): OFF + + + + View Card Type + + + + Show All Parameters + + + + Clear All Logs + + + + Close + 关闭 + + + Are you sure you want to restore the parameters to this state? + + + + Restore + + + + Restored to previous values successfully. + + + + Are you sure you want to delete this item? + + + + Delete + + + + Clear All + + + + Are you sure you want to delete all history and restore parameters to their factory default values? + + + CarrotPanel Reboot - 重启 + 重启 Exit - 退出 + 退出 openpilot - openpilot + openpilot %n minute(s) ago - + %n 分钟前 %n hour(s) ago - + %n 小时前 %n day(s) ago - + %n 天前 now - 现在 + 现在 Start @@ -153,15 +300,15 @@ Button: Cruise Button Mode - 按键:巡航按钮模式 + 按键:巡航按钮模式 0:Normal,1:User1,2:User2 - 0:默认, 1:用户1, 2:用户2 + 0:默认, 1:用户1, 2:用户2 Button: Cancel Button Mode - 按键:取消按钮模式 + 按键:取消按钮模式 0:Long,1:Long+Lat @@ -169,23 +316,23 @@ Button: LFA Button Mode - 按键:LFA 按钮模式 + 按键:LFA 按钮模式 0:Normal,1:Decel&Stop&LeadCarReady - 0:默认, 1:减速&停车&前车就绪 + 0:默认, 1:减速&停车&前车就绪 Button: Cruise Speed Unit(Basic) - 按键:巡航步进(基础) + 按键:巡航步进(基础) Button: Cruise Speed Unit(Extra) - 按键:巡航步进(扩展) + 按键:巡航步进(扩展) CRUISE: Eco control(4km/h) - 巡航:经济控制(4km/h) + 巡航:经济控制(4km/h) Temporarily increasing the set speed to improve fuel efficiency. @@ -217,15 +364,15 @@ Dynamic GAP control - 动态间距控制 + 动态间距控制 Dynamic GAP control (LaneChange) - 动态间距控制(变道) + 动态间距控制(变道) DRIVEMODE: Select - 驾驶模式:选择 + 驾驶模式:选择 1:ECO,2:SAFE,3:NORMAL,4:HIGH @@ -233,15 +380,15 @@ DRIVEMODE: Auto - 驾驶模式:自动 + 驾驶模式:自动 NORMAL mode only - 仅在标准模式下生效 + 仅在标准模式下生效 TrafficLight DetectMode - 红绿灯检测模式 + 红绿灯检测模式 0:None, 1:Stopping only, 2: Stop & Go @@ -257,15 +404,15 @@ Laneline mode speed(0) - 车道线模式速度(0) + 车道线模式速度(0) Laneline mode, lat_mpc control used - 车道线模式,使用横向 MPC 控制 + 车道线模式,使用横向 MPC 控制 Laneline mode curve speed(0) - 车道线模式弯道速度(0) + 车道线模式弯道速度(0) Laneline mode, high speed only @@ -273,7 +420,7 @@ AdjustLaneOffset(0)cm - 车道偏移调整(0)cm + 车道偏移调整(0)cm LaneChange need torque @@ -285,7 +432,7 @@ LaneChange delay - 变道延迟 + 变道延迟 x0.1sec @@ -300,31 +447,31 @@ -1:忽略BSD, 0:启用BSD检测, 1:限制转向扭矩 - LAT: SteerRatiox0.1(0) + LAT: SteerRatiox0.1(0) 横向:转向比x0.1(0) - Custom SteerRatio + Custom SteerRatio 自定义转向比 - LAT: SteerRatioRatex0.01(100) + LAT: SteerRatioRatex0.01(100) 横向:转向比变化率x0.01(100) - SteerRatio apply rate + SteerRatio apply rate 转向比应用速率 - LAT: PathOffset + LAT: PathOffset 横向:路径偏移 - (-)left, (+)right + (-)left, (+)right (-)左移, (+)右移 - LAT:SteerActuatorDelay(30) + LAT:SteerActuatorDelay(30) 横向:转向执行器延迟(30) @@ -332,75 +479,75 @@ 单位 0.01,0 表示实时延迟 - LAT: TorqueCustom(0) + LAT: TorqueCustom(0) 横向:自定义扭矩(0) - LAT: TorqueAccelFactor(2500) + LAT: TorqueAccelFactor(2500) 横向:扭矩加速度系数(2500) - LAT: TorqueFriction(100) + LAT: TorqueFriction(100) 横向:扭矩摩擦(100) - LAT: CustomSteerMax(0) + LAT: CustomSteerMax(0) 横向:自定义最大转向(0) - LAT: CustomSteerDeltaUp(0) + LAT: CustomSteerDeltaUp(0) 横向:自定义转向上升速率(0) - LAT: CustomSteerDeltaDown(0) + LAT: CustomSteerDeltaDown(0) 横向:自定义转向下降速率(0) - LONG: P Gain(100) + LONG: P Gain(100) 纵向:P增益(100) - LONG: I Gain(0) + LONG: I Gain(0) 纵向:I增益(0) - LONG: FF Gain(100) + LONG: FF Gain(100) 纵向:前馈增益(100) - LONG: ActuatorDelay(20) + LONG: ActuatorDelay(20) 纵向:执行器延迟(20) - LONG: VEgoStopping(50) + LONG: VEgoStopping(50) 纵向:自车停止因子(50) - Stopping factor + Stopping factor 停止因子 - LONG: Radar reaction factor(100) + LONG: Radar reaction factor(100) 纵向:雷达反应系数(100) - LONG: StoppingStartAccelx0.01(-40) + LONG: StoppingStartAccelx0.01(-40) 纵向:停止开始加速度x0.01(-40) - LONG: StopDistance (600)cm + LONG: StopDistance (600)cm 纵向:停止距离(600)cm - LONG: Jerk Lead Factor (0) + LONG: Jerk Lead Factor (0) 纵向:前车冲击因子(0) - x0.01 + x0.01 x0.01 - ACCEL:0km/h(160) + ACCEL:0km/h(160) 加速:0km/h(160) @@ -408,27 +555,27 @@ 指定速度所需加速度(x0.01 m/s^2)。 - ACCEL:10km/h(160) + ACCEL:10km/h(160) 加速:10km/h(160) - ACCEL:40km/h(120) + ACCEL:40km/h(120) 加速:40km/h(120) - ACCEL:60km/h(100) + ACCEL:60km/h(100) 加速:60km/h(100) - ACCEL:80km/h(80) + ACCEL:80km/h(80) 加速:80km/h(80) - ACCEL:110km/h(70) + ACCEL:110km/h(70) 加速:110km/h(70) - ACCEL:140km/h(60) + ACCEL:140km/h(60) 加速:140km/h(60) @@ -440,7 +587,7 @@ 89:基础;仪表盘转向报错 85~87 - Debug Info + Debug Info 调试信息 @@ -448,43 +595,43 @@ 胎压监测信息 - Time Info + Time Info 时间信息 - 0:None,1:Time/Date,2:Time,3:Date + 0:None,1:Time/Date,2:Time,3:Date 0:无, 1:时间/日期, 2:时间, 3:日期 - Path End + Path End 路径终点 - 0:None,1:Display + 0:None,1:Display 0:不显示, 1:显示 - Device State + Device State 设备状态 - Lane Info + Lane Info 车道信息 - -1:None, 0:Path, 1:Path+Lane, 2: Path+Lane+RoadEdge + -1:None, 0:Path, 1:Path+Lane, 2: Path+Lane+RoadEdge -1:无, 0:路径, 1:路径+车道, 2:路径+车道+路缘 - Radar Info + Radar Info 雷达信息 - 0:None,1:Display,2:RelPos,3:Stopped Car + 0:None,1:Display,2:RelPos,3:Stopped Car 0:不显示, 1:显示, 2:相对位置, 3:静止车辆 - Route Info + Route Info 路线信息 @@ -492,39 +639,39 @@ 调试图表 - Brightness ratio + Brightness ratio 亮度比例 - Path Color: Cruise OFF + Path Color: Cruise OFF 路径颜色:未巡航 - (+10:Stroke)0:Red,1:Orange,2:Yellow,3:Green,4:Blue,5:Indigo,6:Violet,7:Brown,8:White,9:Black + (+10:Stroke)0:Red,1:Orange,2:Yellow,3:Green,4:Blue,5:Indigo,6:Violet,7:Brown,8:White,9:Black (+10:描边)0:红,1:橙,2:黄,3:绿,4:蓝,5:靛,6:紫,7:棕,8:白,9:黑 - Path Mode: Laneless + Path Mode: Laneless 路径模式:无车道线 - 0:Normal,1,2:Rec,3,4:^^,5,6:Rec,7,8:^^,9,10,11,12:Smooth^^ + 0:Normal,1,2:Rec,3,4:^^,5,6:Rec,7,8:^^,9,10,11,12:Smooth^^ 0:普通,1,2:推荐,3,4:^^,5,6:推荐,7,8:^^,9~12:平滑^^ - Path Color: Laneless + Path Color: Laneless 路径颜色:无车道线 - Path Mode: LaneMode + Path Mode: LaneMode 路径模式:有车道线 - Path Color: LaneMode + Path Color: LaneMode 路径颜色:有车道线 - Path Width ratio(100%) + Path Width ratio(100%) 路径宽度比例(100%) @@ -685,7 +832,7 @@ CURVE: Aggressiveness (100%) - 弯道:激进程度(100%) + 弯道:激进程度(100%) RoadSpeedLimitOffset(-1) @@ -807,25 +954,49 @@ Model TurnSpeed Factor(0) 模型转弯速度系数(0) - - Enable Software Menu - 启用软件菜单 - Select your car 选择你的车型 Wait for list... - 等待列表... + 等待列表... Select Manufacturer 选择厂商 - SELECT YOUR CAR - 选择你的车型 + View Tuning History + + + + Auto-Tuner: Driving-Based Learning + + + + Learn from driver interventions (gas/brake) and recommend parameter adjustments when parking. 0=Off, 1=On + + + + LaneChange LineCheck + + + + 0:Color+Type, 1:Type only, 2:Type+torque override solid + + + + LAT:LatSmoothSec(13) + + + + Tire Trajectory + + + + Display tire paths with a gradient effect on the lane markers. + @@ -858,27 +1029,27 @@ DestinationWidget Home - + Work - 公司 + 公司 No destination set - 未设置目的地 + 未设置目的地 home - + work - 公司 + 公司 No %1 location set - 未设置 %1 位置 + 未设置 %1 位置 @@ -940,11 +1111,11 @@ Reset Calibration - 重置设备校准 + 重置设备校准 RESET - 重置 + 重置 Are you sure you want to reset calibration? @@ -1036,7 +1207,7 @@ Reset - 重置 + 重置 Review @@ -1183,6 +1354,13 @@ Firehose Mode allows you to maximize your training data uploads to improve openp <span stylesheet='font-size: 60px; font-weight: bold; color: #e74c3c;'>未启用</span>: 连接到未计量网络 + + HomeWindow + + Auto-Tuner: Driving pattern learned! + + + HudRenderer @@ -1215,7 +1393,7 @@ Firehose Mode allows you to maximize your training data uploads to improve openp Installer Installing... - 正在安装…… + 正在安装…… @@ -1341,6 +1519,14 @@ Firehose Mode allows you to maximize your training data uploads to improve openp Device temperature too high. System cooling down before starting. Current internal component temperature: %1 设备温度过高。系统正在冷却中,等冷却完毕后才会启动。目前内部组件温度:%1 + + Poor visibility detected for driver monitoring. Ensure the device has a clear view of the driver. This can be checked in the device settings. Extreme lighting conditions and/or unconventional mounting positions may also trigger this alert. + + + + Excessive %1 actuation detected on your last drive. Please contact support at https://comma.ai/support and share your device's Dongle ID for troubleshooting. + + OffroadHome @@ -1475,371 +1661,371 @@ Firehose Mode allows you to maximize your training data uploads to improve openp QObject Button: Cruise Button Mode - 按键:巡航按钮模式 + 按键:巡航按钮模式 0:Normal,1:User1,2:User2 - 0:默认, 1:用户1, 2:用户2 + 0:默认, 1:用户1, 2:用户2 Button: Cancel Button Mode - 按键:取消按钮模式 + 按键:取消按钮模式 0:Long,1:Long+Lat - 0:纵向, 1:纵向+横向 + 0:纵向, 1:纵向+横向 Button: LFA Button Mode - 按键:LFA 按钮模式 + 按键:LFA 按钮模式 0:Normal,1:Decel&Stop&LeadCarReady - 0:默认, 1:减速&停车&前车就绪 + 0:默认, 1:减速&停车&前车就绪 Button: Cruise Speed Unit(Basic) - 按键:巡航步进(基础) + 按键:巡航步进(基础) Button: Cruise Speed Unit(Extra) - 按键:巡航步进(扩展) + 按键:巡航步进(扩展) CRUISE: Eco control(4km/h) - 巡航:经济控制(4km/h) + 巡航:经济控制(4km/h) Temporarily increasing the set speed to improve fuel efficiency. - 短暂提高设定车速以提升燃油效率。 + 短暂提高设定车速以提升燃油效率。 CRUISE: Auto speed up (0%) - 巡航:自动提速 (0%) + 巡航:自动提速 (0%) Auto speed up based on the lead car up to RoadSpeedLimit. - 基于前车自动提速,最高不超过道路限速。 + 基于前车自动提速,最高不超过道路限速。 GAP1: Apply TFollow (110)x0.01s - 间距1:应用时距(110)x0.01秒 + 间距1:应用时距(110)x0.01秒 GAP2: Apply TFollow (120)x0.01s - 间距2:应用时距(120)x0.01秒 + 间距2:应用时距(120)x0.01秒 GAP3: Apply TFollow (160)x0.01s - 间距3:应用时距(160)x0.01秒 + 间距3:应用时距(160)x0.01秒 GAP4: Apply TFollow (180)x0.01s - 间距4:应用时距(180)x0.01秒 + 间距4:应用时距(180)x0.01秒 Dynamic GAP control - 动态间距控制 + 动态间距控制 Dynamic GAP control (LaneChange) - 动态间距控制(变道) + 动态间距控制(变道) DRIVEMODE: Select - 驾驶模式:选择 + 驾驶模式:选择 1:ECO,2:SAFE,3:NORMAL,4:HIGH - 1:节能, 2:安全, 3:标准, 4:高速 + 1:节能, 2:安全, 3:标准, 4:高速 DRIVEMODE: Auto - 驾驶模式:自动 + 驾驶模式:自动 NORMAL mode only - 仅在标准模式下生效 + 仅在标准模式下生效 TrafficLight DetectMode - 红绿灯检测模式 + 红绿灯检测模式 0:None, 1:Stopping only, 2: Stop & Go - 0:无, 1:仅停止, 2:停走 + 0:无, 1:仅停止, 2:停走 Laneline mode speed(0) - 车道线模式速度(0) + 车道线模式速度(0) Laneline mode, lat_mpc control used - 车道线模式,使用横向 MPC 控制 + 车道线模式,使用横向 MPC 控制 Laneline mode curve speed(0) - 车道线模式弯道速度(0) + 车道线模式弯道速度(0) Laneline mode, high speed only - 车道线模式,仅在高速下使用 + 车道线模式,仅在高速下使用 AdjustLaneOffset(0)cm - 车道偏移调整(0)cm + 车道偏移调整(0)cm LaneChange need torque - 变道需要扭矩 + 变道需要扭矩 -1:Disable lanechange, 0: no need torque, 1:need torque - -1:禁用变道, 0:无需扭矩, 1:需要扭矩 + -1:禁用变道, 0:无需扭矩, 1:需要扭矩 LaneChange delay - 变道延迟 + 变道延迟 x0.1sec - 单位0.1秒 + 单位0.1秒 LaneChange Bsd - 变道盲区检测 + 变道盲区检测 -1:ignore bsd, 0:BSD detect, 1: block steer torque - -1:忽略BSD, 0:检测BSD, 1:限制转向扭矩 + -1:忽略BSD, 0:检测BSD, 1:限制转向扭矩 LAT: SteerRatiox0.1(0) - 横向:转向比x0.1(0) + 横向:转向比x0.1(0) Custom SteerRatio - 自定义转向比 + 自定义转向比 LAT: SteerRatioRatex0.01(100) - 横向:转向比变化率x0.01(100) + 横向:转向比变化率x0.01(100) SteerRatio apply rate - 转向比应用速率 + 转向比应用速率 LAT: PathOffset - 横向:路径偏移 + 横向:路径偏移 (-)left, (+)right - (-)左移, (+)右移 + (-)左移, (+)右移 LAT:SteerActuatorDelay(30) - 横向:转向执行器延迟(30) + 横向:转向执行器延迟(30) x0.01, 0:LiveDelay - 单位0.01,0为实时延迟 + 单位0.01,0为实时延迟 LAT: TorqueCustom(0) - 横向:自定义扭矩(0) + 横向:自定义扭矩(0) LAT: TorqueAccelFactor(2500) - 横向:扭矩加速度系数(2500) + 横向:扭矩加速度系数(2500) LAT: TorqueFriction(100) - 横向:扭矩摩擦(100) + 横向:扭矩摩擦(100) LAT: CustomSteerMax(0) - 横向:自定义最大转向(0) + 横向:自定义最大转向(0) LAT: CustomSteerDeltaUp(0) - 横向:自定义转向上升速率(0) + 横向:自定义转向上升速率(0) LAT: CustomSteerDeltaDown(0) - 横向:自定义转向下降速率(0) + 横向:自定义转向下降速率(0) LONG: P Gain(100) - 纵向:P增益(100) + 纵向:P增益(100) LONG: I Gain(0) - 纵向:I增益(0) + 纵向:I增益(0) LONG: FF Gain(100) - 纵向:前馈增益(100) + 纵向:前馈增益(100) LONG: ActuatorDelay(20) - 纵向:执行器延迟(20) + 纵向:执行器延迟(20) LONG: VEgoStopping(50) - 纵向:自车停止因子(50) + 纵向:自车停止因子(50) Stopping factor - 停止因子 + 停止因子 LONG: Radar reaction factor(100) - 纵向:雷达反应系数(100) + 纵向:雷达反应系数(100) LONG: StoppingStartAccelx0.01(-40) - 纵向:停止开始加速度x0.01(-40) + 纵向:停止开始加速度x0.01(-40) LONG: StopDistance (600)cm - 纵向:停止距离(600)cm + 纵向:停止距离(600)cm LONG: Jerk Lead Factor (0) - 纵向:前车冲击因子(0) + 纵向:前车冲击因子(0) x0.01 - x0.01 + x0.01 ACCEL:0km/h(160) - 加速:0km/h(160) + 加速:0km/h(160) Acceleration needed at specified speed.(x0.01m/s^2) - 指定速度所需加速度(x0.01m/s^2)。 + 指定速度所需加速度(x0.01m/s^2)。 ACCEL:10km/h(160) - 加速:10km/h(160) + 加速:10km/h(160) ACCEL:40km/h(120) - 加速:40km/h(120) + 加速:40km/h(120) ACCEL:60km/h(100) - 加速:60km/h(100) + 加速:60km/h(100) ACCEL:80km/h(80) - 加速:80km/h(80) + 加速:80km/h(80) ACCEL:110km/h(70) - 加速:110km/h(70) + 加速:110km/h(70) ACCEL:140km/h(60) - 加速:140km/h(60) + 加速:140km/h(60) MaxAngleFrames(89) - 最大角度帧(89) + 最大角度帧(89) 89:Basic, steering instrument panel error 85~87 - 89:基础;仪表转向错误85~87 + 89:基础;仪表转向错误85~87 Debug Info - 调试信息 + 调试信息 Tpms Info - 胎压信息 + 胎压信息 Time Info - 时间信息 + 时间信息 0:None,1:Time/Date,2:Time,3:Date - 0:无, 1:时间/日期, 2:时间, 3:日期 + 0:无, 1:时间/日期, 2:时间, 3:日期 Path End - 路径终点 + 路径终点 0:None,1:Display - 0:不显示, 1:显示 + 0:不显示, 1:显示 Device State - 设备状态 + 设备状态 Lane Info - 车道信息 + 车道信息 -1:None, 0:Path, 1:Path+Lane, 2: Path+Lane+RoadEdge - -1:无, 0:路径, 1:路径+车道, 2:路径+车道+路缘 + -1:无, 0:路径, 1:路径+车道, 2:路径+车道+路缘 Radar Info - 雷达信息 + 雷达信息 0:None,1:Display,2:RelPos,3:Stopped Car - 0:不显示, 1:显示, 2:相对位置, 3:静止车辆 + 0:不显示, 1:显示, 2:相对位置, 3:静止车辆 Route Info - 路线信息 + 路线信息 Debug plot - 调试曲线 + 调试曲线 Brightness ratio - 亮度比例 + 亮度比例 Path Color: Cruise OFF - 路径颜色:未巡航 + 路径颜色:未巡航 (+10:Stroke)0:Red,1:Orange,2:Yellow,3:Green,4:Blue,5:Indigo,6:Violet,7:Brown,8:White,9:Black - (+10:描边)0:红,1:橙,2:黄,3:绿,4:蓝,5:靛,6:紫,7:棕,8:白,9:黑 + (+10:描边)0:红,1:橙,2:黄,3:绿,4:蓝,5:靛,6:紫,7:棕,8:白,9:黑 Path Mode: Laneless - 路径模式:无车道线 + 路径模式:无车道线 0:Normal,1,2:Rec,3,4:^^,5,6:Rec,7,8:^^,9,10,11,12:Smooth^^ - 0:普通,1,2:推荐,3,4:^^,5,6:推荐,7,8:^^,9~12:平滑^^ + 0:普通,1,2:推荐,3,4:^^,5,6:推荐,7,8:^^,9~12:平滑^^ Path Color: Laneless - 路径颜色:无车道线 + 路径颜色:无车道线 Path Mode: LaneMode - 路径模式:有车道线 + 路径模式:有车道线 Path Color: LaneMode - 路径颜色:有车道线 + 路径颜色:有车道线 Path Width ratio(100%) - 路径宽度比例(100%) + 路径宽度比例(100%) km @@ -1965,119 +2151,123 @@ This may take up to a minute. Carrot - Carrot + Carrot Share Data - 分享数据 + 分享数据 0:None, 1:TCP JSON Data(Reboot required) - 0:无, 1:TCP JSON数据(需重启) + 0:无, 1:TCP JSON数据(需重启) Hardware is C3x Lite - 硬件为 C3x Lite + 硬件为 C3x Lite Hardware is C3x - 硬件为 C3x + 硬件为 C3x Hardware is C3 - 硬件为 C3 + 硬件为 C3 Hardware is TICI - 硬件为 TICI + 硬件为 TICI Hardware is EON - 硬件为 EON + 硬件为 EON Hardware is Unknown - 硬件未知 + 硬件未知 Open SSH - 开启 SSH + 开启 SSH Record UI - 录制 UI + 录制 UI Stop Recording UI - 停止录制 UI + 停止录制 UI Reset UI - 重置 UI + 重置 UI Developer Menu - 开发者菜单 + 开发者菜单 Reset Calibration - 重置校准 + 重置校准 Are you sure you want to reset calibration? - 您确定要重置校准吗? + 您确定要重置校准吗? Review Training Guide - 查看训练指南 + 查看训练指南 Regulatory - 监管信息 + 监管信息 Change Language - 修改语言 + 修改语言 Are you sure you want to reset all settings? - 您确定要重置所有设置吗? + 您确定要重置所有设置吗? Reset - 重置 + 重置 Disengaged - 控制取消 + 控制取消 Engaged - 控制激活 + 控制激活 Warning - 警告 + 警告 Critical - 严重 + 严重 openpilot Longitudinal Control (Alpha) - openpilot 纵向控制 (Alpha) + openpilot 纵向控制 (Alpha) WARNING: openpilot longitudinal control is in alpha for this car and will take over the gas and stop buttons. Look for once it has been tested and verified. - 警告:此车辆的 openpilot 纵向控制处于 alpha 阶段,将接管加速和停止按钮。请等待测试和验证完成。 + 警告:此车辆的 openpilot 纵向控制处于 alpha 阶段,将接管加速和停止按钮。请等待测试和验证完成。 Show Debug UI - 显示调试 UI + 显示调试 UI Display debug UI elements. - 显示调试 UI 元素。 + 显示调试 UI 元素。 + + + CarrotPilot + CarrotPilot @@ -2340,33 +2530,6 @@ This may take up to a minute. 从未更新 - - SettingsWindow - - CarrotPilot - CarrotPilot - - - Device - 设备 - - - Network - 网络 - - - Toggles - 设置 - - - Software - 软件 - - - Developer - 开发者 - - SshControl diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index 6d50bf92b8..1a026b891c 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -87,6 +87,876 @@ 給 "%1" + + AutoTunerCardListDialog + + Tuning History Card List + + + + Close + 關閉 + + + No historical data to display + + + + [%1 Applied] + + + + Restore + + + + Delete + + + + Are you sure you want to restore the parameters to this state? + + + + Restored to previous values successfully. + + + + Are you sure you want to delete this item? + + + + + AutoTunerDialog + + 사용 안내 (Guide) + + + + 나중에 (Later) + + + + 학습 초기화 (Clear) + + + + 선택 적용 (Apply Selected) + + + + 적용하지 않고 현재까지의 모든 학습 데이터를 삭제하시겠습니까? + + + + 초기화 + + + + + AutoTunerGraphWidget + + No historical data to display + + + + + AutoTunerGuideDialog + + 확인 + + + + + AutoTunerHistoryPanel + + Parameters + + + + Apply LAT (Steering): ON + + + + Apply LAT (Steering): OFF + + + + Apply LONG (Accel): ON + + + + Apply LONG (Accel): OFF + + + + View Card Type + + + + Show All Parameters + + + + Clear All Logs + + + + Close + 關閉 + + + Are you sure you want to restore the parameters to this state? + + + + Restore + + + + Restored to previous values successfully. + + + + Are you sure you want to delete this item? + + + + Delete + + + + Clear All + + + + Are you sure you want to delete all history and restore parameters to their factory default values? + + + + + CarrotPanel + + Start + + + + Cruise + + + + Speed + + + + Tuning + + + + Disp + + + + Path + + + + Button: Cruise Button Mode + + + + 0:Normal,1:User1,2:User2 + + + + Button: Cancel Button Mode + + + + 0:Long,1:Long+Lat + + + + Button: LFA Button Mode + + + + 0:Normal,1:Decel&Stop&LeadCarReady + + + + Button: Cruise Speed Unit(Basic) + + + + Button: Cruise Speed Unit(Extra) + + + + CRUISE: Eco control(4km/h) + + + + Temporarily increasing the set speed to improve fuel efficiency. + + + + CRUISE: Auto speed up (0%) + + + + Auto speed up based on the lead car up to RoadSpeedLimit. + + + + GAP1: Apply TFollow (110)x0.01s + + + + GAP2: Apply TFollow (120)x0.01s + + + + GAP3: Apply TFollow (160)x0.01s + + + + GAP4: Apply TFollow (180)x0.01s + + + + Dynamic GAP control + + + + Dynamic GAP control (LaneChange) + + + + DRIVEMODE: Select + + + + 1:ECO,2:SAFE,3:NORMAL,4:HIGH + + + + DRIVEMODE: Auto + + + + NORMAL mode only + + + + TrafficLight DetectMode + + + + 0:None, 1:Stopping only, 2: Stop & Go + + + + AChangeCostStarting + + + + TrafficStopDistanceAdjust + + + + View Tuning History + + + + Auto-Tuner: Driving-Based Learning + + + + Learn from driver interventions (gas/brake) and recommend parameter adjustments when parking. 0=Off, 1=On + + + + Laneline mode speed(0) + + + + Laneline mode, lat_mpc control used + + + + Laneline mode curve speed(0) + + + + Laneline mode, high speed only + + + + AdjustLaneOffset(0)cm + + + + LaneChange need torque + + + + -1:Disable lanechange, 0: no need torque, 1:need torque + + + + LaneChange delay + + + + x0.1sec + + + + LaneChange Bsd + + + + -1:ignore bsd, 0:BSD detect, 1: block steer torque + + + + LaneChange LineCheck + + + + 0:Color+Type, 1:Type only, 2:Type+torque override solid + + + + LAT: SteerRatiox0.1(0) + + + + Custom SteerRatio + + + + LAT: SteerRatioRatex0.01(100) + + + + SteerRatio apply rate + + + + LAT: PathOffset + + + + (-)left, (+)right + + + + LAT:SteerActuatorDelay(30) + + + + x0.01, 0:LiveDelay + + + + LAT:LatSmoothSec(13) + + + + x0.01 + + + + LAT: TorqueCustom(0) + + + + LAT: TorqueAccelFactor(2500) + + + + LAT: TorqueFriction(100) + + + + LAT: CustomSteerMax(0) + + + + LAT: CustomSteerDeltaUp(0) + + + + LAT: CustomSteerDeltaDown(0) + + + + LONG: P Gain(100) + + + + LONG: I Gain(0) + + + + LONG: FF Gain(100) + + + + LONG: ActuatorDelay(20) + + + + LONG: VEgoStopping(50) + + + + Stopping factor + + + + LONG: Radar reaction factor(100) + + + + LONG: StoppingStartAccelx0.01(-40) + + + + LONG: StopDistance (600)cm + + + + LONG: Jerk Lead Factor (0) + + + + ACCEL:0km/h(160) + + + + Acceleration needed at specified speed.(x0.01m/s^2) + + + + ACCEL:10km/h(160) + + + + ACCEL:40km/h(120) + + + + ACCEL:60km/h(100) + + + + ACCEL:80km/h(80) + + + + ACCEL:110km/h(70) + + + + ACCEL:140km/h(60) + + + + MaxAngleFrames(89) + + + + 89:Basic, steering instrument panel error 85~87 + + + + Debug Info + + + + Tpms Info + + + + Time Info + + + + 0:None,1:Time/Date,2:Time,3:Date + + + + Path End + + + + 0:None,1:Display + + + + Device State + + + + Lane Info + + + + -1:None, 0:Path, 1:Path+Lane, 2: Path+Lane+RoadEdge + + + + Radar Info + + + + 0:None,1:Display,2:RelPos,3:Stopped Car + + + + Route Info + + + + Debug plot + + + + Brightness ratio + + + + Tire Trajectory + + + + Display tire paths with a gradient effect on the lane markers. + + + + Path Color: Cruise OFF + + + + (+10:Stroke)0:Red,1:Orange,2:Yellow,3:Green,4:Blue,5:Indigo,6:Violet,7:Brown,8:White,9:Black + + + + Path Mode: Laneless + + + + 0:Normal,1,2:Rec,3,4:^^,5,6:Rec,7,8:^^,9,10,11,12:Smooth^^ + + + + Path Color: Laneless + + + + Path Mode: LaneMode + + + + Path Color: LaneMode + + + + Path Width ratio(100%) + + + + SELECT YOUR CAR + + + + Select Manufacturer + + + + Select your car + + + + HYUNDAI: CAMERA SCC + + + + 1:Connect the SCC's CAN line to CAM, 2:Sync Cruise state, 3:StockLong + + + + CANFD: HDA2 mode + + + + 1:HDA2,2:HDA2+BSM + + + + Enable Radar Track + + + + 1:Enable RadarTrack, -1,2:Disable use HKG SCC radar at all times + + + + Auto Cruise control + + + + Softhold, Auto Cruise ON/OFF control + + + + CRUISE: Auto ON distance(0cm) + + + + When GAS/Brake is OFF, Cruise ON when the lead car gets closer. + + + + Auto Engage control on start + + + + 1:SteerEnable, 2:Steer/Cruise Engage + + + + Auto AccelTok speed + + + + Gas(Accel)Tok enable speed + + + + Read Cruise Speed from PCM + + + + Toyota must set to 1, Honda 3 + + + + Sound Volume(100%) + + + + Sound Volume, Engage(10%) + + + + Power off time (min) + + + + EnableConnect + + + + Your device may be banned by Comma + + + + Mapbox Style(0) + + + + Record Road camera(0) + + + + 1:RoadCam, 2:RoadCam+WideRoadCam + + + + Use HDP(CCNC)(0) + + + + 1:While Using APN, 2:Always + + + + NNFF + + + + Twilsonco's NNFF(Reboot required) + + + + NNFFLite + + + + Twilsonco's NNFF-Lite(Reboot required) + + + + Auto update Cruise speed + + + + Disable Min.SteerSpeed + + + + Disable DM + + + + Hotspot enabled on boot + + + + Enable Software Menu + + + + IsLdwsCar + + + + Hardware is C3x Lite + + + + Share Data + + + + 0:None, 1:TCP JSON Data(Reboot required) + + + + CURVE: Lower limit speed(30) + + + + When you approach a curve, reduce your speed. Minimum speed + + + + CURVE: Auto Control ratio(100%) + + + + RoadSpeedLimitOffset(-1) + + + + -1:NotUsed,RoadLimitSpeed+Offset + + + + Auto Roadlimit Speed adjust (50%) + + + + SpeedCamDecelEnd(6s) + + + + Sets the deceleration completion point. A larger value completes deceleration farther away from the camera. + + + + NaviSpeedControlMode(2) + + + + 0:No slowdown, 1: speed camera, 2: + accident prevention bump, 3: + mobile camera + + + + SpeedCamDecelRatex0.01m/s^2(80) + + + + Lower number, slows down from a greater distance + + + + SpeedCamSafetyFactor(105%) + + + + SpeedBumpTimeDistance(1s) + + + + SpeedBumpSpeed(35Km/h) + + + + NaviCountDown mode(2) + + + + 0: off, 1:tbt+camera, 2:tbt+camera+bump + + + + Turn Speed control mode(1) + + + + 0: off, 1:vision, 2:vision+route, 3: route + + + + Smart Speed Control(0) + + + + 0: off, 1:accel, 2:decel, 3: all + + + + Map TurnSpeed Factor(100) + + + + Model TurnSpeed Factor(0) + + + + ATC: Auto turn control(0) + + + + 0:None, 1: lane change, 2: lane change + speed, 3: speed + + + + ATC: Turn Speed (20) + + + + 0:None, turn speed + + + + ATC: Turn CtrlDistTime (6) + + + + dist=speed*time + + + + ATC Auto Map Change(0) + + + ConfirmationDialog @@ -113,6 +983,33 @@ 拒絕並解除安裝 %1 + + DestinationWidget + + Home + + + + Work + + + + No destination set + + + + home + + + + work + + + + No %1 location set + + + DeveloperPanel @@ -172,11 +1069,11 @@ Reset Calibration - 重設校準 + 重設校準 RESET - 重設 + 重設 Are you sure you want to reset calibration? @@ -268,7 +1165,7 @@ Reset - 重設 + 重設 Review @@ -286,6 +1183,81 @@ PAIR 配對 + + ReCalibration + + + + Git Pull & Reboot + + + + Git pull & Reboot? + + + + Yes + + + + Failed to start update process. + + + + Update process started. Device will reboot if updates are applied. + + + + Set default + + + + Set to default? + + + + Remove MapboxKey + + + + Remove Mapbox key? + + + + Calibration Status + + + + SHOW + + + + Reboot & Disengage to Calibration + + + + + DrawCarrot + + ECO + + + + SAFE + + + + NORM + + + + FAST + + + + ERRM + + DriverViewWindow @@ -338,6 +1310,13 @@ Firehose Mode allows you to maximize your training data uploads to improve openp + + HomeWindow + + Auto-Tuner: Driving pattern learned! + + + HudRenderer @@ -370,7 +1349,52 @@ Firehose Mode allows you to maximize your training data uploads to improve openp Installer Installing... - 安裝中… + 安裝中… + + + + MapETA + + eta + + + + min + + + + hr + + + + + MapSettings + + NAVIGATION + + + + Manage at %1 + + + + Manage at connect.comma.ai + + + + + MapWindow + + Map Loading + + + + Waiting for GPS(APN) + + + + Waiting for route + @@ -451,6 +1475,14 @@ Firehose Mode allows you to maximize your training data uploads to improve openp Device temperature too high. System cooling down before starting. Current internal component temperature: %1 裝置溫度過高。系統正在冷卻中,等冷卻完畢後才會啟動。目前內部組件溫度:%1 + + Poor visibility detected for driver monitoring. Ensure the device has a clear view of the driver. This can be checked in the device settings. Extreme lighting conditions and/or unconventional mounting positions may also trigger this alert. + + + + Excessive %1 actuation detected on your last drive. Please contact support at https://comma.ai/support and share your device's Dongle ID for troubleshooting. + + OffroadHome @@ -524,6 +1556,21 @@ Firehose Mode allows you to maximize your training data uploads to improve openp 啟用 + + PathEndDrawer + + Signal Error + + + + Signal Ready + + + + Signal slowing + + + PrimeAdWidget @@ -578,7 +1625,7 @@ Firehose Mode allows you to maximize your training data uploads to improve openp openpilot - openpilot + openpilot %n minute(s) ago @@ -602,6 +1649,26 @@ Firehose Mode allows you to maximize your training data uploads to improve openp now 現在 + + km + + + + m + + + + mi + + + + ft + + + + carrotpilot + + Reset @@ -674,6 +1741,10 @@ This may take up to a minute. Firehose 訓練上傳 + + CarrotPilot + + Setup @@ -1110,6 +2181,18 @@ This may take up to a minute. Enable driver monitoring even when openpilot is not engaged. 即使在openpilot未激活時也啟用駕駛監控。 + + Record and Upload Microphone Audio + + + + Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect. + + + + MoreRelaxed + + Updater diff --git a/system/timed.py b/system/timed.py index 873fbc4023..60cb787d38 100755 --- a/system/timed.py +++ b/system/timed.py @@ -9,19 +9,25 @@ from openpilot.common.swaglog import cloudlog from openpilot.common.params import Params from openpilot.common.gps import get_gps_location_service +from openpilot.system.timezone_helper import ( + SOURCE_PRIORITY, current_source, apply_timezone, + timezone_from_internet, timezone_from_gps, +) -def set_time(new_time): - diff = datetime.datetime.now() - new_time - if abs(diff) < datetime.timedelta(seconds=10): - cloudlog.debug(f"Time diff too small: {diff}") +def set_time(new_epoch): + # new_epoch: GPS에서 받은 UNIX epoch(UTC 초). 화면 시계(localtime)는 시스템시계 + 타임존을 + # 읽으므로, 시스템시계를 GPS UTC로만 정확히 맞추면 로컬 시각으로 표시된다. + diff = abs(time.time() - new_epoch) + if diff < 10: + cloudlog.debug(f"Time diff too small: {diff:.1f}s") return - cloudlog.debug(f"Setting time to {new_time}") - print(f"GPS Setting time to {new_time} => ignored") - return + cloudlog.info(f"Setting system time from GPS (diff {diff:.1f}s)") try: - subprocess.run(f"TZ=UTC date -s '{new_time}'", shell=True, check=True) + # '@'는 절대 UTC 순간을 지정 -> 타임존 이중변환 버그 없음. + # sudo 필수: 일반 권한의 `date -s`는 조용히 실패한다. + subprocess.run(["sudo", "date", "-s", f"@{int(new_epoch)}"], check=True) except subprocess.CalledProcessError: cloudlog.exception("timed.failed_setting_time") @@ -40,6 +46,8 @@ def main() -> NoReturn: pm = messaging.PubMaster(['clocks']) sm = messaging.SubMaster([gps_location_service]) + + last_tz_attempt = 0.0 while True: sm.update(1000) @@ -49,15 +57,32 @@ def main() -> NoReturn: pm.send('clocks', msg) gps = sm[gps_location_service] - gps_time = datetime.datetime.fromtimestamp(gps.unixTimestampMillis / 1000.) - if not sm.updated[gps_location_service] or (time.monotonic() - sm.logMonoTime[gps_location_service] / 1e9) > 2.0: - continue - if not gps.hasFix: + gps_ok = (sm.updated[gps_location_service] + and (time.monotonic() - sm.logMonoTime[gps_location_service] / 1e9) <= 2.0 + and gps.hasFix) + + # 로컬 타임존 해석(앱 미사용자용). 아직 신뢰 출처(app/wifi)로 설정되지 않았을 때만 시도. + # 타임존이 전혀 없으면 빠르게(30s) 재시도, GPS 폴백이 이미 있으면 느리게(5min) - + # 오프라인에서 인터넷 호출(최대 timeout)이 반복적으로 루프를 막지 않게 한다. + cur_prio = SOURCE_PRIORITY.get(current_source(params), 0) + if cur_prio < SOURCE_PRIORITY["wifi"]: + retry_interval = 30.0 if cur_prio == 0 else 300.0 + if time.monotonic() - last_tz_attempt > retry_interval: + last_tz_attempt = time.monotonic() + tz = timezone_from_internet() # [2순위] WiFi/인터넷 + if tz is not None: + apply_timezone(tz, "wifi", params) + elif gps_ok: # [3순위] GPS 경도 근사 (최후수단) + apply_timezone(timezone_from_gps(gps.longitude), "gps", params) + + # 시스템 시계(절대 UTC)는 GPS fix가 있어야 설정 가능 + if not gps_ok: continue - if gps_time < min_date(): + gps_epoch = gps.unixTimestampMillis / 1000. + if datetime.datetime.fromtimestamp(gps_epoch) < min_date(): continue - set_time(gps_time) + set_time(gps_epoch) time.sleep(10) if __name__ == "__main__": diff --git a/system/timezone_helper.py b/system/timezone_helper.py new file mode 100644 index 0000000000..71051ffb11 --- /dev/null +++ b/system/timezone_helper.py @@ -0,0 +1,93 @@ +""" +로컬 타임존(화면 표시용) 해석 및 영구 저장 헬퍼. + +GPS는 UTC(절대시각)만 주므로, 화면에 "그 나라의 로컬 시각"으로 표시하려면 +타임존(IANA 이름)이 필요하다. 타임존은 아래 우선순위로 해석한다: + + 1) app - 캐롯 앱이 보낸 타임존 (carrot_serv) -- 가장 신뢰 + 2) wifi - 인터넷(IP 기반 지오로케이션)으로 받은 타임존 + 3) gps - GPS 경도 기반 근사 (오프라인 최후 수단, DST 미반영) + +해석된 타임존은 /data/etc/localtime(시스템이 읽는 경로) 심볼릭링크로 적용하고, +이름/출처를 Params에 기록한다. 한 번 기록되면 오프라인 재부팅에도 유지된다. +낮은 우선순위 출처(gps)는 높은 출처(app/wifi)가 설정한 값을 덮어쓰지 않는다. +""" +import json +import os +import subprocess +import urllib.request + +from openpilot.common.params import Params +from openpilot.common.swaglog import cloudlog + +LOCALTIME_PATH = "/data/etc/localtime" +ZONEINFO_DIR = "/usr/share/zoneinfo" + +# 숫자가 클수록 더 신뢰. ""(미설정)은 0. +SOURCE_PRIORITY = {"": 0, "gps": 1, "wifi": 2, "app": 3} + + +def _valid_zone(tz: str) -> bool: + return bool(tz) and os.path.isfile(os.path.join(ZONEINFO_DIR, tz)) + + +def current_source(params: Params | None = None) -> str: + params = params or Params() + return params.get("TimezoneSource") or "" + + +def apply_timezone(tz: str, source: str, params: Params | None = None) -> bool: + """tz(IANA 이름)를 적용. source 우선순위가 기존보다 낮으면 무시(다운그레이드 방지).""" + params = params or Params() + if not _valid_zone(tz): + cloudlog.error(f"timezone: invalid zone '{tz}' (source={source})") + return False + + cur = current_source(params) + if SOURCE_PRIORITY.get(source, 0) < SOURCE_PRIORITY.get(cur, 0): + return False # 더 신뢰도 높은 출처가 이미 설정함 -> 유지 + + # 이미 같은 타임존이면 심링크 재작성 생략 (출처만 갱신) + target = os.path.join(ZONEINFO_DIR, tz) + already = os.path.islink(LOCALTIME_PATH) and os.path.realpath(LOCALTIME_PATH) == os.path.realpath(target) + if not already: + try: + os.makedirs(os.path.dirname(LOCALTIME_PATH), exist_ok=True) + subprocess.run(["sudo", "rm", "-f", LOCALTIME_PATH], check=True) + subprocess.run(["sudo", "ln", "-s", target, LOCALTIME_PATH], check=True) + except subprocess.CalledProcessError: + cloudlog.exception("timezone: failed to set /data/etc/localtime") + return False + + params.put("TimezoneName", tz) + params.put("TimezoneSource", source) + cloudlog.info(f"timezone: set to {tz} (source={source})") + return True + + +def timezone_from_internet(timeout: float = 5.0) -> str | None: + """[2순위] WiFi/인터넷 연결 시 IP 기반 지오로케이션으로 IANA 타임존을 받아온다. + 키 불필요한 무료 엔드포인트(ip-api.com). 실패 시 None.""" + try: + req = urllib.request.Request( + "http://ip-api.com/json/?fields=status,timezone", + headers={"User-Agent": "openpilot-timed"}, + ) + with urllib.request.urlopen(req, timeout=timeout) as resp: + data = json.loads(resp.read().decode()) + if data.get("status") == "success": + tz = data.get("timezone") + return tz if _valid_zone(tz) else None + except Exception: + return None + return None + + +def timezone_from_gps(longitude: float) -> str: + """[3순위/최후수단] GPS 경도 기반 근사 타임존(Etc/GMT 고정 오프셋, DST 미반영). + POSIX Etc/GMT 부호는 반대: Etc/GMT-9 == UTC+9.""" + offset = int(round(longitude / 15.0)) + offset = max(-12, min(14, offset)) # Etc/GMT+12 .. Etc/GMT-14 범위 + if offset == 0: + return "Etc/GMT" + return f"Etc/GMT{'-' if offset > 0 else '+'}{abs(offset)}"