From 639426658ba06452a92ddbf4a75f84e91d7848f4 Mon Sep 17 00:00:00 2001 From: ajouatom Date: Sat, 14 Feb 2026 16:21:59 +0900 Subject: [PATCH 1/4] fix.. branch checkout --- selfdrive/carrot/carrot_server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/carrot/carrot_server.py b/selfdrive/carrot/carrot_server.py index d7fe1658f4..5645983b78 100644 --- a/selfdrive/carrot/carrot_server.py +++ b/selfdrive/carrot/carrot_server.py @@ -500,7 +500,7 @@ def run(cmd: List[str], cwd: Optional[str] = None) -> Tuple[int, str]: branch = (body.get("branch") or "").strip() if not branch: return web.json_response({"ok": False, "error": "missing branch"}, status=400) - rc, out = run(["git", "checkout", branch], cwd=REPO_DIR) + rc, out = run(["git", "checkout", "-f", branch], cwd=REPO_DIR) return web.json_response({"ok": rc == 0, "rc": rc, "out": out}) if action == "git_branch_list": From dc97498e4f1a19d3905e49f7a7fd54ed0ec24437 Mon Sep 17 00:00:00 2001 From: ajouatom Date: Mon, 16 Feb 2026 09:59:09 +0900 Subject: [PATCH 2/4] fix.. dynamicTFollow LC --- selfdrive/carrot/carrot_functions.py | 11 ++++++++--- selfdrive/carrot_settings.json | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/selfdrive/carrot/carrot_functions.py b/selfdrive/carrot/carrot_functions.py index aac3bcb50a..43d73b1545 100644 --- a/selfdrive/carrot/carrot_functions.py +++ b/selfdrive/carrot/carrot_functions.py @@ -73,7 +73,7 @@ def __init__(self): self.traffic_starting_count = 0 self.user_stop_distance = -1 - #self.t_follow = 0 + self.t_follow_last = 1.5 self.startSignCount = 0 self.stopSignCount = 0 @@ -281,8 +281,9 @@ def dynamic_t_follow(self, t_follow, lead, desired_follow_distance, prev_a): self.jerk_factor_apply = self.jerk_factor if self.desireState > 0.9 and self.desireStateCount < int(1.5 / DT_MDL): # lane change state, 1.5초동안만. - t_follow *= self.dynamicTFollowLC # 차선변경시 t_follow를 줄임. - self.jerk_factor_apply = self.jerk_factor * self.dynamicTFollowLC # 차선변경시 jerk factor를 줄여 aggresive하게 + dynamicTFollowLC = max(0.2, self.dynamicTFollowLC) + t_follow *= dynamicTFollowLC # 차선변경시 t_follow를 줄임. + self.jerk_factor_apply = self.jerk_factor * dynamicTFollowLC # 차선변경시 jerk factor를 줄여 aggresive하게 elif lead.status: t_follow += np.interp(prev_a[0], [-2.0, -0.5], [0.1, 0.0]) if self.dynamicTFollow > 0.0: @@ -292,6 +293,10 @@ def dynamic_t_follow(self, t_follow, lead, desired_follow_distance, prev_a): self.jerk_factor_apply = self.jerk_factor * 0.5 # 전방차량을 따라갈때는 aggressive하게. #self.jerk_factor_apply = np.interp(abs(lead.jLead), [0, 2], [self.jerk_factor, self.jerk_factor * self.j_lead_factor]) + if t_follow > self.t_follow_last: + t_follow = min(t_follow, self.t_follow_last + 0.1 * DT_MDL) + pass + self.t_follow_last = t_follow return t_follow def update_stop_dist(self, stop_x): diff --git a/selfdrive/carrot_settings.json b/selfdrive/carrot_settings.json index 6a88143327..a811207aee 100644 --- a/selfdrive/carrot_settings.json +++ b/selfdrive/carrot_settings.json @@ -1583,7 +1583,7 @@ "egroup": "FDIST", "etitle": "Dynamic TFollow LaneChange(100)%", "edescr": "Temporarily reduces the gap between vehicles when starting a lane change", - "min": 0, + "min": 20, "max": 100, "default": 0, "unit": 5 From 61fd8d6123bc6af2a91f32d23a2ee50e2922566b Mon Sep 17 00:00:00 2001 From: ajouatom Date: Mon, 16 Feb 2026 10:42:23 +0900 Subject: [PATCH 3/4] lat suspend --- common/params_keys.h | 1 + selfdrive/carrot_settings.json | 15 ++++++++++++++- selfdrive/controls/controlsd.py | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/common/params_keys.h b/common/params_keys.h index 2a274bc3d5..ac362eb116 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -222,6 +222,7 @@ inline static std::unordered_map keys = { {"SteerActuatorDelay", {PERSISTENT, INT, "0"}}, {"LatSmoothSec", {PERSISTENT, INT, "13"}}, + {"LatSuspendAngleDeg", {PERSISTENT, INT, "300"}}, {"CruiseOnDist", {PERSISTENT, INT, "400"}}, {"CruiseMaxVals0", {PERSISTENT, INT, "160"}}, diff --git a/selfdrive/carrot_settings.json b/selfdrive/carrot_settings.json index a811207aee..66c5389b7d 100644 --- a/selfdrive/carrot_settings.json +++ b/selfdrive/carrot_settings.json @@ -267,13 +267,26 @@ "title": "LatSmoothSec(13)x0.01", "descr": "조향필터링값\n 높으면 부드러운조향", "egroup": "LAT", - "etitle": "LatSmoothSec(30)", + "etitle": "LatSmoothSec(13)", "edescr": "Lat smoothing values — Higher values smoother", "min": 1, "max": 30, "default": 13, "unit": 1 }, + { + "group": "조향튜닝", + "name": "LatSuspendAngleDeg", + "title": "자동조향일시중지각도(300)", + "descr": "조향각이 커지면 자동조향을 일시중지. 15도 이내도 조향각이 돌아오면 재개함.", + "egroup": "LAT", + "etitle": "AutoSteering Suspend Angle(300)", + "edescr": "Auto steering pauses at large steering angles and resumes when it returns within 15.", + "min": 45, + "max": 300, + "default": 300, + "unit": 10 + }, { "group": "크루즈", "name": "CruiseOnDist", diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 5093c3246b..80bf11980c 100644 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -56,6 +56,10 @@ def __init__(self) -> None: self.curvature = 0.0 self.desired_curvature = 0.0 + self.lat_suspend_active = False + self.lat_suspend_enter_t = 0.0 + self.lat_suspend_hold_t = 0.0 + self.pose_calibrator = PoseCalibrator() self.calibrated_pose: Pose | None = None @@ -119,6 +123,35 @@ def state_control(self): standstill = abs(CS.vEgo) <= max(self.CP.minSteerSpeed, MIN_LATERAL_CONTROL_SPEED) or CS.standstill CC.latActive = ((self.sm['selfdriveState'].active or lateral_enabled) and CS.latEnabled and not CS.steerFaultTemporary and not CS.steerFaultPermanent and not standstill) + + suspend_angle = float(self.params.get_int("LatSuspendAngleDeg")) + resume_angle = 15 + delay_sec = 1.0 + hold_sec = 0.5 + + # 1) enter condition timer + enter_cond = CS.steeringPressed and abs(CS.steeringAngleDeg) > suspend_angle + if not self.lat_suspend_active: + if enter_cond: + self.lat_suspend_enter_t += DT_CTRL + if self.lat_suspend_enter_t >= delay_sec: + self.lat_suspend_active = True + self.lat_suspend_hold_t = 0.0 + else: + self.lat_suspend_enter_t = 0.0 + + # 2) while suspended: enforce minimum hold time + hysteresis exit + if self.lat_suspend_active: + self.lat_suspend_hold_t += DT_CTRL + + exit_cond = (abs(CS.steeringAngleDeg) < resume_angle) and (not CS.steeringPressed) + if (self.lat_suspend_hold_t >= hold_sec) and exit_cond: + self.lat_suspend_active = False + self.lat_suspend_enter_t = 0.0 + + if self.lat_suspend_active: + CC.latActive = False + CC.longActive = CC.enabled and not any(e.overrideLongitudinal for e in self.sm['onroadEvents']) and self.CP.openpilotLongitudinalControl actuators = CC.actuators From 6f2d04600a506065ca1029d3aae55632a0d83d5f Mon Sep 17 00:00:00 2001 From: ajouatom Date: Thu, 19 Feb 2026 11:48:45 +0900 Subject: [PATCH 4/4] canfd.. counter. Add Chinese to Carrotweb tool (#249), hyundai can parser update.. --- cereal/log.capnp | 3 +- common/params_keys.h | 2 +- opendbc_repo/opendbc/can/packer.py | 10 +- .../opendbc/car/hyundai/carcontroller.py | 23 +- opendbc_repo/opendbc/car/hyundai/carstate.py | 237 +++--- .../opendbc/car/hyundai/hyundaicanfd.py | 439 +++-------- selfdrive/carrot/carrot_controls.py | 40 + selfdrive/carrot/carrot_man.py | 53 +- selfdrive/carrot/carrot_server.py | 175 ++++- selfdrive/carrot/web/app.js | 319 ++++++-- selfdrive/carrot/web/hud_card.js | 10 +- selfdrive/carrot/web/index.html | 32 +- selfdrive/carrot_settings.json | 698 ++++++++++++++---- selfdrive/controls/controlsd.py | 37 +- selfdrive/controls/lib/desire_helper.py | 645 +++++----------- selfdrive/controls/lib/desire_lib/__init__.py | 0 .../lib/desire_lib/blinker_manager.py | 106 +++ .../controls/lib/desire_lib/constants.py | 41 + .../controls/lib/desire_lib/hysteresis.py | 21 + .../controls/lib/desire_lib/lane_math.py | 15 + .../lib/desire_lib/maneuver_classifier.py | 45 ++ .../controls/lib/desire_lib/side_state.py | 156 ++++ selfdrive/modeld/modeld.py | 10 +- 23 files changed, 1871 insertions(+), 1246 deletions(-) create mode 100644 selfdrive/carrot/carrot_controls.py create mode 100644 selfdrive/controls/lib/desire_lib/__init__.py create mode 100644 selfdrive/controls/lib/desire_lib/blinker_manager.py create mode 100644 selfdrive/controls/lib/desire_lib/constants.py create mode 100644 selfdrive/controls/lib/desire_lib/hysteresis.py create mode 100644 selfdrive/controls/lib/desire_lib/lane_math.py create mode 100644 selfdrive/controls/lib/desire_lib/maneuver_classifier.py create mode 100644 selfdrive/controls/lib/desire_lib/side_state.py diff --git a/cereal/log.capnp b/cereal/log.capnp index 931b4b2f1b..c33fc763de 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -1180,7 +1180,8 @@ struct ModelDataV2 { laneChangeProb @15 :Float32; desireLog @16 : Text; modelTurnSpeed @17 :Float32; - + laneChangeAvailableLeft @18 :Bool; + laneChangeAvailableRight @19 :Bool; # deprecated brakeDisengageProbDEPRECATED @2 :Float32; diff --git a/common/params_keys.h b/common/params_keys.h index ac362eb116..d3ba8e4640 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -141,7 +141,7 @@ inline static std::unordered_map keys = { {"GMapKey", {PERSISTENT, STRING}}, {"SearchInput", {PERSISTENT, INT}}, - {"CarSelected3", {PERSISTENT, STRING}}, + {"CarSelected3", {PERSISTENT, STRING, "MOCK"}}, {"SupportedCars", {PERSISTENT, STRING}}, {"SupportedCars_gm", {PERSISTENT, STRING}}, {"ShowDebugUI", {PERSISTENT, INT, "0"}}, diff --git a/opendbc_repo/opendbc/can/packer.py b/opendbc_repo/opendbc/can/packer.py index 882dc0b669..0736129384 100644 --- a/opendbc_repo/opendbc/can/packer.py +++ b/opendbc_repo/opendbc/can/packer.py @@ -8,7 +8,7 @@ def __init__(self, dbc_name: str): self.dbc = DBC(dbc_name) self.counters: dict[int, int] = {} - def pack(self, address: int, values: dict[str, float]) -> bytearray: + def pack(self, address: int, values: dict[str, float], rx_counter: int | None = None) -> bytearray: msg = self.dbc.addr_to_msg.get(address) if msg is None: return bytearray() @@ -27,8 +27,8 @@ def pack(self, address: int, values: dict[str, float]) -> bytearray: counter_set = True sig_counter = next((s for s in msg.sigs.values() if s.type == SignalType.COUNTER or s.name == "COUNTER"), None) if sig_counter and not counter_set: - if address not in self.counters: - self.counters[address] = 0 + if address not in self.counters: + self.counters[address] = 0 if rx_counter is None else (int(rx_counter) + 1) % (1 << sig_counter.size) set_value(dat, sig_counter, self.counters[address]) self.counters[address] = (self.counters[address] + 1) % (1 << sig_counter.size) sig_checksum = next((s for s in msg.sigs.values() if s.type > SignalType.COUNTER), None) @@ -37,7 +37,7 @@ def pack(self, address: int, values: dict[str, float]) -> bytearray: set_value(dat, sig_checksum, checksum) return dat - def make_can_msg(self, name_or_addr, bus: int, values: dict[str, float]): + def make_can_msg(self, name_or_addr, bus: int, values: dict[str, float], rx_counter: int | None = None): if isinstance(name_or_addr, int): addr = name_or_addr else: @@ -45,7 +45,7 @@ def make_can_msg(self, name_or_addr, bus: int, values: dict[str, float]): if msg is None: return 0, b'', bus addr = msg.address - dat = self.pack(addr, values) + dat = self.pack(addr, values, rx_counter = rx_counter) if len(dat) == 0: return 0, b'', bus return addr, bytes(dat), bus diff --git a/opendbc_repo/opendbc/car/hyundai/carcontroller.py b/opendbc_repo/opendbc/car/hyundai/carcontroller.py index 777feb65fa..02b0a59215 100644 --- a/opendbc_repo/opendbc/car/hyundai/carcontroller.py +++ b/opendbc_repo/opendbc/car/hyundai/carcontroller.py @@ -319,13 +319,12 @@ def update(self, CC, CS, now_nanos): if self.CP.flags & HyundaiFlags.CANFD: hda2 = self.CP.flags & HyundaiFlags.CANFD_HDA2 hda2_long = hda2 and self.CP.openpilotLongitudinalControl - # steering control if camera_scc: can_sends.extend(hyundaicanfd.create_steering_messages_camera_scc(self.frame, self.packer, self.CP, self.CAN, CC, apply_steer_req, apply_torque, CS, apply_angle, self.lkas_max_torque, angle_control)) else: can_sends.extend(hyundaicanfd.create_steering_messages(self.packer, self.CP, self.CAN, CC.enabled, apply_steer_req, apply_torque, apply_angle, self.lkas_max_torque, angle_control)) - + # prevent LFA from activating on HDA2 by sending "no lane lines detected" to ADAS ECU if self.frame % 5 == 0 and hda2 and not camera_scc: can_sends.extend(hyundaicanfd.create_suppress_lfa(self.packer, self.CAN, CS)) @@ -352,8 +351,10 @@ def update(self, CC, CS, now_nanos): can_sends.extend(hyundaicanfd.create_fca_warning_light(self.CP, self.packer, self.CAN, self.frame)) if self.frame % 2 == 0: if self.CP.flags & HyundaiFlags.CAMERA_SCC.value: - can_sends.append(hyundaicanfd.create_acc_control_scc2(self.packer, self.CAN, CC.enabled, self.accel_last, accel, stopping, CC.cruiseControl.override, - set_speed_in_units, hud_control, self.hyundai_jerk, CS)) + msg = hyundaicanfd.create_acc_control_scc2(self.packer, self.CAN, CC.enabled, self.accel_last, accel, stopping, CC.cruiseControl.override, + set_speed_in_units, hud_control, self.hyundai_jerk, CS) + if msg is not None: + can_sends.append(msg) can_sends.extend(hyundaicanfd.create_tcs_messages(self.packer, self.CAN, CS)) # for sorento SCC radar... else: can_sends.append(hyundaicanfd.create_acc_control(self.packer, self.CAN, CC.enabled, self.accel_last, accel, stopping, CC.cruiseControl.override, @@ -366,16 +367,16 @@ def update(self, CC, CS, now_nanos): can_sends.extend(hyundaicanfd.forward_button_message(self.packer, self.CAN, self.frame, CS, send_button, self.MainMode_ACC_trigger, self.LFA_trigger)) else: can_sends.extend(self.create_button_messages(CC, CS, use_clu11=False)) - else: - can_sends.append(hyundaican.create_lkas11(self.packer, self.frame, self.CP, apply_torque, apply_steer_req, - torque_fault, CS.lkas11, sys_warning, sys_state, CC.enabled, - hud_control.leftLaneVisible, hud_control.rightLaneVisible, - left_lane_warning, right_lane_warning, self.is_ldws_car)) + if CS.lkas11 is not None: + can_sends.append(hyundaican.create_lkas11(self.packer, self.frame, self.CP, apply_torque, apply_steer_req, + torque_fault, CS.lkas11, sys_warning, sys_state, CC.enabled, + hud_control.leftLaneVisible, hud_control.rightLaneVisible, + left_lane_warning, right_lane_warning, self.is_ldws_car)) if not self.CP.openpilotLongitudinalControl: can_sends.extend(self.create_button_messages(CC, CS, use_clu11=True)) - if self.CP.carFingerprint in CAN_GEARS["send_mdps12"]: # send mdps12 to LKAS to prevent LKAS error + if self.CP.carFingerprint in CAN_GEARS["send_mdps12"] and CS.mdps12 is not None: # send mdps12 to LKAS to prevent LKAS error can_sends.append(hyundaican.create_mdps12(self.packer, self.frame, CS.mdps12)) casper_opt = self.CP.carFingerprint in (CAR.HYUNDAI_CASPER_EV) @@ -464,7 +465,7 @@ def create_button_messages(self, CC: structs.CarControl, CS: CarState, use_clu11 if (self.frame - self.last_button_frame) * DT_CTRL > 0.1: print("cruiseControl.cancel222222") if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS: - #can_sends.append(hyundaicanfd.create_acc_cancel(self.packer, self.CP, self.CAN, CS.cruise_info)) + #can_sends.append(hyundaicanfd.create_acc_cancel(self.packer, self.CP, self.CAN, CS.scc_control)) if self.cruise_buttons_msg_values is not None: can_sends.append(hyundaicanfd.alt_cruise_buttons(self.packer, self.CP, self.CAN, Buttons.CANCEL, self.cruise_buttons_msg_values, self.cruise_buttons_msg_cnt)) diff --git a/opendbc_repo/opendbc/car/hyundai/carstate.py b/opendbc_repo/opendbc/car/hyundai/carstate.py index 7785201e53..fb121f2c89 100644 --- a/opendbc_repo/opendbc/car/hyundai/carstate.py +++ b/opendbc_repo/opendbc/car/hyundai/carstate.py @@ -76,24 +76,32 @@ def __init__(self, CP): self.is_metric = False self.buttons_counter = 0 - self.cruise_info = {} - self.lfa_info = {} - self.lfa_alt_info = {} - self.lfahda_cluster_info = None - self.adrv_info_161 = None - self.adrv_info_200 = None - self.adrv_info_1ea = None - self.adrv_info_160 = None - self.adrv_info_162 = None - self.hda_info_4a3 = None - self.new_msg_4b4 = None - self.tcs_info_373 = None - self.mdps_info = {} - self.steer_touch_info = {} - + # for generic CAN parsing + self.fca11 = None + self.scc11 = None + self.scc12 = None + self.scc13 = None + self.scc14 = None + self.lkas11 = None + self.clu11 = None + + # for CANFD parsing + self.scc_control = None + self.lfa = None + self.lfa_alt = None + self.lfahda_cluster = None + self.adrv_0x161 = None + self.adrv_0x200 = None + self.adrv_0x1ea = None + self.adrv_0x160 = None + self.ccnc_0x162 = None + self.hda_info_4a3 = None + self.tcs = None + self.mdps = None + self.steer_touch_2af = None self.cruise_buttons_msg = None - self.msg_0x362 = None - self.msg_0x2a4 = None + self.cam_0x362 = None + self.cam_0x2a4 = None # On some cars, CLU15->CF_Clu_VehicleSpeed can oscillate faster than the dash updates. Sample at 5 Hz self.cluster_speed = 0 @@ -128,67 +136,115 @@ def __init__(self, CP): if self.CP.openpilotLongitudinalControl and not (self.CP.flags & HyundaiFlags.CANFD_CAMERA_SCC): ecu_disabled = True - if ecu_disabled: - self.SCC11 = self.SCC12 = self.SCC13 = self.SCC14 = self.FCA11 = False - else: - bus_cruise = 2 if self.CP.flags & HyundaiFlags.CAMERA_SCC else 0 - self.SCC11 = True if 1056 in fingerprints[bus_cruise] else False - self.SCC12 = True if 1057 in fingerprints[bus_cruise] else False - self.SCC13 = True if 1290 in fingerprints[bus_cruise] else False - self.SCC14 = True if 905 in fingerprints[bus_cruise] else False - self.FCA11 = False - self.FCA11_bus = Bus.cam - + self.HAS_LFA_BUTTON = True if 913 in fingerprints[0] else False self.CRUISE_BUTTON_ALT = True if 1007 in fingerprints[0] else False cam_bus = CanBus(CP).CAM pt_bus = CanBus(CP).ECAN alt_bus = CanBus(CP).ACAN - self.CCNC_0x161 = True if 0x161 in fingerprints[cam_bus] else False - self.CCNC_0x162 = True if 0x162 in fingerprints[cam_bus] else False - self.ADRV_0x200 = True if 0x200 in fingerprints[cam_bus] else False - self.ADRV_0x1ea = True if 0x1ea in fingerprints[cam_bus] else False - self.ADRV_0x160 = True if 0x160 in fingerprints[cam_bus] else False - self.LFAHDA_CLUSTER = True if 480 in fingerprints[cam_bus] else False - self.HDA_INFO_4A3 = True if 0x4a3 in fingerprints[pt_bus] else False - self.NEW_MSG_4B4 = True if 0x4b4 in fingerprints[pt_bus] else False self.GEAR = True if 69 in fingerprints[pt_bus] else False self.GEAR_ALT = True if 64 in fingerprints[pt_bus] else False - self.CAM_0x362 = True if 0x362 in fingerprints[alt_bus] else False - self.CAM_0x2a4 = True if 0x2a4 in fingerprints[alt_bus] else False - self.STEER_TOUCH_2AF = True if 0x2af in fingerprints[pt_bus] else False self.TPMS = True if 0x3a0 in fingerprints[pt_bus] else False self.LOCAL_TIME = True if 1264 in fingerprints[pt_bus] else False self.cp_bsm = None self.time_zone = "UTC" + self.cp = None + self.cp_cam = None + self.cp_alt = None self.controls_ready_count = 0 - def update(self, can_parsers) -> structs.CarState: - + def monitor_fingerprint(self, can_parsers, canfd): if self.controls_ready_count <= 200: if Params().get_bool("ControlsReady"): self.controls_ready_count += 1 + self.cp = can_parsers[Bus.pt] + self.cp_cam = can_parsers[Bus.cam] + self.cp_alt = can_parsers[Bus.alt] if Bus.alt in can_parsers else None + + def add_if_seen(parser, name): + msg = parser.dbc.name_to_msg.get(name) + if not msg: + print(f"{name} not in DBC") + return + if msg.address not in parser.seen_addresses: + return + if msg.address in parser.addresses: + return + parser._add_message(name) # ← 이름으로 등록 + + def add_and_cache(parser, name: str, attr: str): + add_if_seen(parser, name) + if name in parser.vl: # 등록 성공했을 때만 + setattr(self, attr, parser.vl[name]) + return True + return False + + if self.controls_ready_count == 50: + self.cp.controls_ready = self.cp_cam.controls_ready = True + if self.cp_alt is not None: + self.cp_alt.controls_ready = True + elif self.controls_ready_count == 100: + self.cp.enable_capture = self.cp_cam.enable_capture = False + if self.cp_alt is not None: + self.cp_alt.enable_capture = False + elif self.controls_ready_count == 101: + print("cp_cam.seen_addresses =", self.cp_cam.seen_addresses) + elif self.controls_ready_count == 102: + print("cp.seen_addresses =", self.cp.seen_addresses) + elif self.controls_ready_count == 103: + if self.cp_alt is not None: + print("cp_alt.seen_addresses =", self.cp_alt.seen_addresses) + else: + print("cp_alt.seen_addresses = None") + if not canfd: + if self.controls_ready_count == 104: + if not add_and_cache(self.cp_cam, "FCA11", "fca11"): + add_and_cache(self.cp, "FCA11", "fca11") + add_and_cache(self.cp_cam, "LKAS11", "lkas11") + add_and_cache(self.cp, "CLU11", "clu11") + elif self.controls_ready_count == 105: + cp_cruise = self.cp_cam if self.CP.flags & HyundaiFlags.CAMERA_SCC else self.cp + add_and_cache(cp_cruise, "SCC11", "scc11") + add_and_cache(cp_cruise, "SCC12", "scc12") + add_and_cache(cp_cruise, "SCC13", "scc13") + add_and_cache(cp_cruise, "SCC14", "scc14") + else: # canfd + if self.controls_ready_count == 120: + cp_cruise = self.cp_cam if self.CP.flags & HyundaiFlags.CANFD_CAMERA_SCC else self.cp + add_and_cache(cp_cruise, "SCC_CONTROL", "scc_control") + elif self.controls_ready_count == 121: + add_and_cache(self.cp_cam, "LFA", "lfa") + add_and_cache(self.cp_cam, "LFA_ALT", "lfa_alt") + add_and_cache(self.cp_cam, "LFAHDA_CLUSTER", "lfahda_cluster") + elif self.controls_ready_count == 122: + add_and_cache(self.cp_cam, "ADRV_0x161", "adrv_0x161") + add_and_cache(self.cp_cam, "ADRV_0x200", "adrv_0x200") + add_and_cache(self.cp_cam, "ADRV_0x1ea", "adrv_0x1ea") + add_and_cache(self.cp_cam, "ADRV_0x160", "adrv_0x160") + add_and_cache(self.cp_cam, "CCNC_0x162", "ccnc_0x162") + elif self.controls_ready_count == 123: + add_and_cache(self.cp, "HDA_INFO_4A3", "hda_info_4a3") + add_and_cache(self.cp, "TCS", "tcs") + add_and_cache(self.cp, "MDPS", "mdps") + add_and_cache(self.cp, "STEER_TOUCH_2AF", "steer_touch_2af") + elif self.controls_ready_count == 124: + add_and_cache(self.cp, self.cruise_btns_msg_canfd, "cruise_buttons_msg") + if not add_and_cache(self.cp_cam, "CAM_0x362", "cam_0x362") and self.cp_alt is not None: + add_and_cache(self.cp_alt, "CAM_0x362", "cam_0x362") + if not add_and_cache(self.cp_alt, "CAM_0x2a4", "cam_0x2a4") and self.cp_cam is not None: + add_and_cache(self.cp_cam, "CAM_0x2a4", "cam_0x2a4") + + + + + def update(self, can_parsers) -> structs.CarState: + self.monitor_fingerprint(can_parsers, self.CP.flags & HyundaiFlags.CANFD) cp = can_parsers[Bus.pt] cp_cam = can_parsers[Bus.cam] cp_alt = can_parsers[Bus.alt] if Bus.alt in can_parsers else None - if self.controls_ready_count == 50: - cp.controls_ready = cp_cam.controls_ready = True - if cp_alt is not None: - cp_alt.controls_ready = True - elif self.controls_ready_count == 100: - print("cp_cam.seen_addresses =", cp_cam.seen_addresses) - print("cp.seen_addresses =", cp.seen_addresses) - if 909 in cp_cam.seen_addresses: - self.FCA11 = True - self.FCA11_bus = Bus.cam - elif 909 in cp.seen_addresses: - self.FCA11 = True - self.FCA11_bus = Bus.pt - if cp_alt is not None: - print("cp_alt.seen_addresses =", cp_alt.seen_addresses) if self.CP.flags & HyundaiFlags.CANFD: return self.update_canfd(can_parsers) @@ -325,9 +381,6 @@ def update(self, can_parsers) -> structs.CarState: ret.leftBlindspot = cp.vl["LCA11"]["CF_Lca_IndLeft"] != 0 ret.rightBlindspot = cp.vl["LCA11"]["CF_Lca_IndRight"] != 0 - # save the entire LKAS11 and CLU11 - self.lkas11 = copy.copy(cp_cam.vl["LKAS11"]) - self.clu11 = copy.copy(cp.vl["CLU11"]) self.steer_state = cp.vl["MDPS12"]["CF_Mdps_ToiActive"] # 0 NOT ACTIVE, 1 ACTIVE prev_cruise_buttons = self.cruise_buttons[-1] #self.cruise_buttons.extend(cp.vl_all["CLU11"]["CF_Clu_CruiseSwState"]) @@ -364,11 +417,6 @@ def update(self, can_parsers) -> structs.CarState: ret.tpms.rl = tpms_unit * cp.vl["TPMS11"]["PRESSURE_RL"] ret.tpms.rr = tpms_unit * cp.vl["TPMS11"]["PRESSURE_RR"] - self.scc11 = cp_cruise.vl["SCC11"] if self.SCC11 else None - self.scc12 = cp_cruise.vl["SCC12"] if self.SCC12 else None - self.scc13 = cp_cruise.vl["SCC13"] if self.SCC13 else None - self.scc14 = cp_cruise.vl["SCC14"] if self.SCC14 else None - self.fca11 = can_parsers[self.FCA11_bus].vl["FCA11"] if self.FCA11 else None cluSpeed = cp.vl["CLU11"]["CF_Clu_Vanz"] decimal = cp.vl["CLU11"]["CF_Clu_VanzDecimal"] if 0. < decimal < 0.5: @@ -466,10 +514,6 @@ def update_canfd(self, can_parsers) -> structs.CarState: ret.steerFaultTemporary = cp.vl["MDPS"]["LKA_FAULT"] != 0 or cp.vl["MDPS"]["LFA2_FAULT"] != 0 #ret.steerFaultTemporary = False - self.mdps_info = copy.copy(cp.vl["MDPS"]) - if self.STEER_TOUCH_2AF: - self.steer_touch_info = cp.vl["STEER_TOUCH_2AF"] - blinkers_info = cp.vl["BLINKERS"] left_blinker_lamp = blinkers_info["LEFT_LAMP"] or blinkers_info["LEFT_LAMP_ALT"] right_blinker_lamp = blinkers_info["RIGHT_LAMP"] or blinkers_info["RIGHT_LAMP_ALT"] @@ -512,40 +556,27 @@ def update_canfd(self, can_parsers) -> structs.CarState: ret.pcmCruiseGap = int(np.clip(cp_cruise_info.vl["SCC_CONTROL"]["DISTANCE_SETTING"], 1, 4)) ret.cruiseState.standstill = cp_cruise_info.vl["SCC_CONTROL"]["InfoDisplay"] >= 4 ret.cruiseState.speed = cp_cruise_info.vl["SCC_CONTROL"]["VSetDis"] * speed_factor - self.cruise_info = copy.copy(cp_cruise_info.vl["SCC_CONTROL"]) ret.brakeHoldActive = cp.vl["ESP_STATUS"]["AUTO_HOLD"] == 1 and cp_cruise_info.vl["SCC_CONTROL"]["ACCMode"] not in (1, 2) speed_limit_cam = False if self.CP.flags & HyundaiFlags.CAMERA_SCC.value: - self.cruise_info = copy.copy(cp_cam.vl["SCC_CONTROL"]) - self.lfa_info = copy.copy(cp_cam.vl["LFA"]) - if self.CP.flags & HyundaiFlags.ANGLE_CONTROL.value: - self.lfa_alt_info = copy.copy(cp_cam.vl["LFA_ALT"]) - - if self.LFAHDA_CLUSTER: - self.lfahda_cluster_info = cp_cam.vl["LFAHDA_CLUSTER"] - corner = False - self.adrv_info_161 = cp_cam.vl["ADRV_0x161"] if self.CCNC_0x161 else None - self.adrv_info_162 = cp_cam.vl["CCNC_0x162"] if self.CCNC_0x162 else None - if self.adrv_info_161 is not None: - ret.leftLongDist = self.lf_distance = self.adrv_info_162["LF_DETECT_DISTANCE"] - ret.rightLongDist = self.rf_distance = self.adrv_info_162["RF_DETECT_DISTANCE"] - self.lr_distance = self.adrv_info_162["LR_DETECT_DISTANCE"] - self.rr_distance = self.adrv_info_162["RR_DETECT_DISTANCE"] - ret.leftLatDist = self.adrv_info_162["LF_DETECT_LATERAL"] - ret.rightLatDist = self.adrv_info_162["RF_DETECT_LATERAL"] + if self.ccnc_0x162 is not None: + ret.leftLongDist = self.lf_distance = self.ccnc_0x162["LF_DETECT_DISTANCE"] + ret.rightLongDist = self.rf_distance = self.ccnc_0x162["RF_DETECT_DISTANCE"] + self.lr_distance = self.ccnc_0x162["LR_DETECT_DISTANCE"] + self.rr_distance = self.ccnc_0x162["RR_DETECT_DISTANCE"] + ret.leftLatDist = self.ccnc_0x162["LF_DETECT_LATERAL"] + ret.rightLatDist = self.ccnc_0x162["RF_DETECT_LATERAL"] corner = True - self.adrv_info_200 = cp_cam.vl["ADRV_0x200"] if self.ADRV_0x200 else None - self.adrv_info_1ea = cp_cam.vl["ADRV_0x1ea"] if self.ADRV_0x1ea else None - if self.adrv_info_1ea is not None: + if self.adrv_0x1ea is not None: if not corner: - ret.leftLongDist = self.adrv_info_1ea["LF_DETECT_DISTANCE"] - ret.rightLongDist = self.adrv_info_1ea["RF_DETECT_DISTANCE"] - self.lr_distance = self.adrv_info_1ea["LR_DETECT_DISTANCE"] - self.rr_distance = self.adrv_info_1ea["RR_DETECT_DISTANCE"] - ret.leftLatDist = self.adrv_info_1ea["LF_DETECT_LATERAL"] - ret.rightLatDist = self.adrv_info_1ea["RF_DETECT_LATERAL"] + ret.leftLongDist = self.adrv_0x1ea["LF_DETECT_DISTANCE"] + ret.rightLongDist = self.adrv_0x1ea["RF_DETECT_DISTANCE"] + self.lr_distance = self.adrv_0x1ea["LR_DETECT_DISTANCE"] + self.rr_distance = self.adrv_0x1ea["RR_DETECT_DISTANCE"] + ret.leftLatDist = self.adrv_0x1ea["LF_DETECT_LATERAL"] + ret.rightLatDist = self.adrv_0x1ea["RF_DETECT_LATERAL"] corner = True if corner: left_block = True if 0 < ret.leftLongDist < 7.0 or 0 < self.lr_distance < 7.0 else False @@ -555,9 +586,6 @@ def update_canfd(self, can_parsers) -> structs.CarState: if right_block: ret.rightBlindspot = True - self.adrv_info_160 = cp_cam.vl["ADRV_0x160"] if self.ADRV_0x160 else None - - self.hda_info_4a3 = cp.vl["HDA_INFO_4A3"] if self.HDA_INFO_4A3 else None if self.hda_info_4a3 is not None: speedLimit = self.hda_info_4a3["SPEED_LIMIT"] if not self.is_metric: @@ -570,18 +598,13 @@ def update_canfd(self, can_parsers) -> structs.CarState: country_code = int(self.hda_info_4a3["CountryCode"]) self.time_zone = ZoneInfo(NUMERIC_TO_TZ.get(country_code, "UTC")) - self.new_msg_4b4 = cp.vl["NEW_MSG_4B4"] if self.NEW_MSG_4B4 else None - self.tcs_info_373 = cp.vl["TCS"] - ret.gearStep = cp.vl["GEAR"]["GEAR_STEP"] if self.GEAR else 0 if 1 <= ret.gearStep <= 8 and ret.gearShifter == GearShifter.unknown: ret.gearShifter = GearShifter.drive ret.gearStep = cp.vl["GEAR_ALT"]["GEAR_STEP"] if self.GEAR_ALT else ret.gearStep if cp_alt and self.CP.flags & HyundaiFlags.CAMERA_SCC: - lane_info = None - lane_info = cp_alt.vl["CAM_0x362"] if self.CAM_0x362 else None - lane_info = cp_alt.vl["CAM_0x2a4"] if self.CAM_0x2a4 else lane_info + lane_info = self.cam_0x2a4 if self.cam_0x2a4 is not None else self.cam_0x362 if lane_info is not None: left_lane_prob = lane_info["LEFT_LANE_PROB"] @@ -644,12 +667,6 @@ def update_canfd(self, can_parsers) -> structs.CarState: self.buttons_counter = cp.vl[self.cruise_btns_msg_canfd]["COUNTER"] ret.accFaulted = cp.vl["TCS"]["ACCEnable"] != 0 # 0 ACC CONTROL ENABLED, 1-3 ACC CONTROL DISABLED - if not (self.CP.flags & HyundaiFlags.CAMERA_SCC): - if self.msg_0x362 is not None or 0x362 in cp_cam.seen_addresses: - self.msg_0x362 = cp_cam.vl["CAM_0x362"] - elif self.msg_0x2a4 is not None or 0x2a4 in cp_cam.seen_addresses: - self.msg_0x2a4 = cp_cam.vl["CAM_0x2a4"] - speed_conv = CV.KPH_TO_MS # if self.is_metric else CV.MPH_TO_MS cluSpeed = cp.vl["CRUISE_BUTTONS_ALT"]["CLU_SPEED"] ret.vEgoCluster = cluSpeed * speed_conv # MPH단위에서도 KPH로 나오는듯.. diff --git a/opendbc_repo/opendbc/car/hyundai/hyundaicanfd.py b/opendbc_repo/opendbc/car/hyundai/hyundaicanfd.py index bb18cc0e33..30f7116f1a 100644 --- a/opendbc_repo/opendbc/car/hyundai/hyundaicanfd.py +++ b/opendbc_repo/opendbc/car/hyundai/hyundaicanfd.py @@ -89,27 +89,29 @@ def CAM(self): def create_steering_messages_camera_scc(frame, packer, CP, CAN, CC, lat_active, apply_steer, CS, apply_angle, max_torque, angle_control): emergency_steering = False - if CS.adrv_info_161 is not None: - values = CS.adrv_info_161 + if CS.adrv_0x161 is not None: + values = CS.adrv_0x161 emergency_steering = values["ALERTS_1"] in [11, 12, 13, 14, 15, 21, 22, 23, 24, 25, 26] ret = [] - values = copy.copy(CS.mdps_info) - if angle_control: - if CS.lfa_alt_info is not None: - values["LFA2_ACTIVE"] = CS.lfa_alt_info["LKAS_ANGLE_ACTIVE"] - else: - if CS.lfa_info is not None: - values["LKA_ACTIVE"] = 1 if CS.lfa_info["STEER_REQ"] == 1 else 0 + if CS.mdps is not None: + values = copy.copy(CS.mdps) + rx_counter = values.pop("COUNTER", None) + if angle_control: + if CS.lfa_alt is not None: + values["LFA2_ACTIVE"] = CS.lfa_alt["LKAS_ANGLE_ACTIVE"] + else: + if CS.lfa is not None: + values["LKA_ACTIVE"] = 1 if CS.lfa["STEER_REQ"] == 1 else 0 - if frame % 1000 < 40: - values["STEERING_COL_TORQUE"] += 220 - ret.append(packer.make_can_msg("MDPS", CAN.CAM, values)) + if frame % 1000 < 40: + values["STEERING_COL_TORQUE"] += 220 + ret.append(packer.make_can_msg("MDPS", CAN.CAM, values, rx_counter = rx_counter)) if frame % 10 == 0: - if CS.steer_touch_info is not None: - values = copy.copy(CS.steer_touch_info) + if CS.steer_touch_2af is not None: + values = copy.copy(CS.steer_touch_2af) if frame % 1000 < 40: values["TOUCH_DETECT"] = 3 values["TOUCH1"] = 50 @@ -121,29 +123,35 @@ def create_steering_messages_camera_scc(frame, packer, CP, CAN, CC, lat_active, ret.append(packer.make_can_msg("STEER_TOUCH_2AF", CAN.CAM, values)) if angle_control: - if emergency_steering: - values = copy.copy(CS.lfa_alt_info) - else: - values = {} #CS.lfa_alt_info - values["LKAS_ANGLE_ACTIVE"] = 2 if CC.latActive else 1 - values["LKAS_ANGLE_CMD"] = -apply_angle - values["LKAS_ANGLE_MAX_TORQUE"] = max_torque if CC.latActive else 0 - ret.append(packer.make_can_msg("LFA_ALT", CAN.ECAN, values)) - - values = copy.copy(CS.lfa_info) - if not emergency_steering: - values["LKA_MODE"] = 0 - values["LKA_ICON"] = 2 if CC.latActive else 1 - values["TORQUE_REQUEST"] = -1024 # apply_steer, - values["VALUE63"] = 0 # LKA_ASSIST - values["STEER_REQ"] = 0 # 1 if lat_active else 0, - values["HAS_LANE_SAFETY"] = 0 # hide LKAS settings - values["LKA_ACTIVE"] = 3 if CC.latActive else 0 # this changes sometimes, 3 seems to indicate engaged - values["VALUE64"] = 0 #STEER_MODE, NEW_SIGNAL_2 - values["LKAS_ANGLE_CMD"] = -25.6 #-apply_angle, - values["LKAS_ANGLE_ACTIVE"] = 0 #2 if lat_active else 1, - values["LKAS_ANGLE_MAX_TORQUE"] = 0 #max_torque if lat_active else 0, - values["NEW_SIGNAL_1"] = 10 + if CS.lfa_alt is not None: + values = copy.copy(CS.lfa_alt) + rx_counter = values.pop("COUNTER", None) + if emergency_steering: + pass + else: + #values = {} #CS.lfa_alt + values["LKAS_ANGLE_ACTIVE"] = 2 if CC.latActive else 1 + values["LKAS_ANGLE_CMD"] = -apply_angle + values["LKAS_ANGLE_MAX_TORQUE"] = max_torque if CC.latActive else 0 + ret.append(packer.make_can_msg("LFA_ALT", CAN.ECAN, values, rx_counter = rx_counter)) + + if CS.lfa is not None: + values = copy.copy(CS.lfa) + rx_counter = values.pop("COUNTER", None) + if not emergency_steering: + values["LKA_MODE"] = 0 + values["LKA_ICON"] = 2 if CC.latActive else 1 + values["TORQUE_REQUEST"] = -1024 # apply_steer, + values["VALUE63"] = 0 # LKA_ASSIST + values["STEER_REQ"] = 0 # 1 if lat_active else 0, + values["HAS_LANE_SAFETY"] = 0 # hide LKAS settings + values["LKA_ACTIVE"] = 3 if CC.latActive else 0 # this changes sometimes, 3 seems to indicate engaged + values["VALUE64"] = 0 #STEER_MODE, NEW_SIGNAL_2 + values["LKAS_ANGLE_CMD"] = -25.6 #-apply_angle, + values["LKAS_ANGLE_ACTIVE"] = 0 #2 if lat_active else 1, + values["LKAS_ANGLE_MAX_TORQUE"] = 0 #max_torque if lat_active else 0, + values["NEW_SIGNAL_1"] = 10 + ret.append(packer.make_can_msg("LFA", CAN.ECAN, values, rx_counter = rx_counter)) else: values = {} @@ -160,7 +168,7 @@ def create_steering_messages_camera_scc(frame, packer, CP, CAN, CC, lat_active, #values["VALUE82_SET256"] = 0 - ret.append(packer.make_can_msg("LFA", CAN.ECAN, values)) + ret.append(packer.make_can_msg("LFA", CAN.ECAN, values)) return ret @@ -214,12 +222,12 @@ def create_steering_messages(packer, CP, CAN, enabled, lat_active, apply_steer, return ret def create_suppress_lfa(packer, CAN, CS): - if CS.msg_0x362 is not None: + if CS.cam_0x362 is not None: suppress_msg = "CAM_0x362" - lfa_block_msg = CS.msg_0x362 - elif CS.msg_0x2a4 is not None: + lfa_block_msg = CS.cam_0x362 + elif CS.cam_0x2a4 is not None: suppress_msg = "CAM_0x2a4" - lfa_block_msg = CS.msg_0x2a4 + lfa_block_msg = CS.cam_0x2a4 else: return [] @@ -275,7 +283,7 @@ def create_acc_cancel(packer, CP, CAN, cruise_info_copy): def create_lfahda_cluster(packer, CS, CAN, long_active, lat_active): - if CS.lfahda_cluster_info is not None: + if CS.lfahda_cluster is not None: values = {} # values["HDA_CntrlModSta"] = 2 if long_active else 0 values["HDA_LFA_SymSta"] = 2 if lat_active else 0 @@ -287,6 +295,9 @@ def create_lfahda_cluster(packer, CS, CAN, long_active, lat_active): def create_acc_control_scc2(packer, CAN, enabled, accel_last, accel, stopping, gas_override, set_speed, hud_control, hyundai_jerk, CS): + + if CS.scc_control is None: + return None enabled = (enabled or CS.softHoldActive > 0) and CS.paddle_button_prev == 0 acc_mode = 0 if not enabled else (2 if gas_override else 1) @@ -309,8 +320,8 @@ def create_acc_control_scc2(packer, CAN, enabled, accel_last, accel, stopping, g a_raw = accel a_val = accel #np.clip(accel, accel_last - jn, accel_last + jn) - values = copy.copy(CS.cruise_info) - values.pop("COUNTER", None) + values = copy.copy(CS.scc_control) + rx_counter = values.pop("COUNTER", None) values["ACCMode"] = acc_mode values["MainMode_ACC"] = 1 values["StopReq"] = 1 if stopping or CS.softHoldActive > 0 else 0 # 1: Stop control is required, 2: Not used, 3: Error Indicator @@ -358,7 +369,7 @@ def create_acc_control_scc2(packer, CAN, enabled, accel_last, accel, stopping, g values["ZEROS_7"] = 1 - return packer.make_can_msg("SCC_CONTROL", CAN.ECAN, values) + return packer.make_can_msg("SCC_CONTROL", CAN.ECAN, values, rx_counter = rx_counter) def create_acc_control(packer, CAN, enabled, accel_last, accel, stopping, gas_override, set_speed, hud_control, jerk_u, jerk_l, CS): @@ -437,8 +448,9 @@ def create_fca_warning_light(CP, packer, CAN, frame): def create_tcs_messages(packer, CAN, CS): ret = [] - if CS.tcs_info_373 is not None: - values = copy.copy(CS.tcs_info_373) + if CS.tcs is not None: + values = copy.copy(CS.tcs) + rx_counter = values.pop("COUNTER", None) values["DriverBraking"] = 0 values["NEW_SIGNAL_20"] = 0 values["NEW_SIGNAL_11"] = 0 @@ -446,7 +458,7 @@ def create_tcs_messages(packer, CAN, CS): #values["NEW_SIGNAL_1"] = 0 # accel과 관련.. 옆두부 꺼지는것과 관련? 확인필요 #values["ACC_REQ"] = 1 # 옆두부 꺼지는것과 관련? 확인필요.. 항상 켜지게함.. values["NEW_SIGNAL_1"] = 0 if values["ACC_REQ"] == 1 else 1 # 옆두부.. - ret.append(packer.make_can_msg("TCS", CAN.CAM, values)) + ret.append(packer.make_can_msg("TCS", CAN.CAM, values, rx_counter = rx_counter)) return ret def forward_button_message(packer, CAN, frame, CS, cruise_button, MainMode_ACC_trigger, LFA_trigger): @@ -454,6 +466,7 @@ def forward_button_message(packer, CAN, frame, CS, cruise_button, MainMode_ACC_t if frame % 2 == 0: if CS.cruise_buttons_msg is not None: values = copy.copy(CS.cruise_buttons_msg) + rx_counter = values.pop("COUNTER", None) cruise_button_driver = values["CRUISE_BUTTONS"] if cruise_button_driver == 0: values["CRUISE_BUTTONS"] = cruise_button @@ -463,288 +476,9 @@ def forward_button_message(packer, CAN, frame, CS, cruise_button, MainMode_ACC_t elif LFA_trigger > 0: values["LFA_BTN"] = 1 - ret.append(packer.make_can_msg(CS.cruise_btns_msg_canfd, CAN.CAM, values)) + ret.append(packer.make_can_msg(CS.cruise_btns_msg_canfd, CAN.CAM, values, rx_counter = rx_counter)) return ret -""" -def _make_ccnc_values___(values, CS, lat_active, frame, hud_control, lane_line = True, corner_radar = True): - if lane_line: - curvature = round(CS.out.steeringAngleDeg / 3) - values["LANELINE_CURVATURE"] = (min(abs(curvature), 15) + (-1 if curvature < 0 else 0)) if lat_active else 0 - values["LANELINE_CURVATURE_DIRECTION"] = 1 if curvature < 0 and lat_active else 0 - - md = CS.MD - if md is not None: - desire = md.meta.desire.raw - if desire == 1: # # 좌회전 - values['LANE_CHANGING'] = 1 # 왼쪽 화살표 - values["LANELINE_CURVATURE"] = 15 # 커브 최대 - values["LANELINE_CURVATURE_DIRECTION"] = 0 # 왼쪽으로 - - elif desire == 2: # 우회전 - values['LANE_CHANGING'] = 2 # 오른쪽 화살표 - values["LANELINE_CURVATURE"] = 15 # 차선커브 최대로 - values["LANELINE_CURVATURE_DIRECTION"] = 1 # 오른쪽으로 - - elif desire == 3: # 좌차선변경 - values['LANE_CHANGING'] = 3 # 왼쪽 화살표 + 바닥 - - elif desire == 4: # 우차선변경 - values['LANE_CHANGING'] = 4 # 오른쪽 화살표 + 바닥 - - if corner_radar: - if values['LF_DETECT'] >= 4 and values['LF_DETECT_DISTANCE'] != 0: values['LF_DETECT'] = 1 - if values['RF_DETECT'] >= 4 and values['RF_DETECT_DISTANCE'] != 0: values['RF_DETECT'] = 1 - if values['LR_DETECT'] >= 4 and values['LR_DETECT_DISTANCE'] != 0: values['LR_DETECT'] = 1 - if values['RR_DETECT'] >= 4 and values['RR_DETECT_DISTANCE'] != 0: values['RR_DETECT'] = 1 - - disp_dist = 30.0 - min_dist = 14.0 - max_interval = 100 - t = 1.0 # 이 값만 바꾸면 전체 깜빡임 속도 조절됨 (0.6 빠름, 1.0 기본, 1.5 느림) - def apply_one(detect_key, dist_key): - dist = values.get(dist_key, 0.0) - if dist <= min_dist: - return - d = min(dist, disp_dist) - interval = int((1 + (max_interval - 1) * (d / disp_dist)) * t) - interval = max(1, min(interval, max_interval)) - blink = (frame // interval) & 1 - values[detect_key] = 2 - blink - values[dist_key] = min_dist - - apply_one('LR_DETECT', 'LR_DETECT_DISTANCE') - apply_one('RR_DETECT', 'RR_DETECT_DISTANCE') - -def create_ccnc_messages___(CP, packer, CAN, frame, CC, CS, hud_control, disp_angle, left_lane_warning, right_lane_warning, enable_corner_radar): - ret = [] - md = CS.MD - desire = 0 - lane_changing = 0 - if md is not None: - desire = md.meta.desire.raw - desire_state = md.meta.desireState - if len(desire_state) > 4: - if desire_state[1] > 0.3 : lane_changing = 1 - if desire_state[2] > 0.3 : lane_changing = 2 - if desire_state[3] > 0.3 : lane_changing = 3 - if desire_state[4] > 0.3 : lane_changing = 4 - - if CP.flags & HyundaiFlags.CAMERA_SCC.value: - HDA_CntrlModSta = 0 - if CS.lfahda_cluster_info is not None: - HDA_CntrlModSta = CS.lfahda_cluster_info["HDA_CntrlModSta"] - - if frame % 2 == 0: - if CS.adrv_info_160 is not None: - values = copy.copy(CS.adrv_info_160) - #values["NEW_SIGNAL_1"] = 0 # steer_temp관련없음, 계기판에러 - #values["SET_ME_9"] = 17 # steer_temp관련없음, 계기판에러 - #values["SET_ME_2"] = 0 #커멘트해도 steer_temp에러남, 2값은 콤마에서 찾은거니... - #values["DATA102"] = 0 # steer_temp관련없음 - ret.append(packer.make_can_msg("ADRV_0x160", CAN.ECAN, values)) - - if CS.cruise_buttons_msg is not None: - values = copy.copy(CS.cruise_buttons_msg) - if CS.lfahda_cluster_info["HDA_LFA_SymSta"] == 0 and 0 < frame % 200 < 12: - values["LFA_BTN"] = 1 - #else: - # values["LFA_BTN"] = 0 - - if CC.enabled and CS.MainMode_ACC: - if CS.ACCMode in [0, 4] and 10 < frame % 200 < 22: - values["CRUISE_BUTTONS"] = 2 - elif CC.enabled and not CS.MainMode_ACC and 10 < frame % 200 <= 16 and CS.out.vEgo > 3.: - values["ADAPTIVE_CRUISE_MAIN_BTN"] = 1 - else: - values["ADAPTIVE_CRUISE_MAIN_BTN"] = 0 - - ret.append(packer.make_can_msg(CS.cruise_btns_msg_canfd, CAN.CAM, values)) - - - if frame % 5 == 0: - lat_active = CC.latActive - if CS.adrv_info_161 is not None: - main_enabled = CS.out.cruiseState.available - cruise_enabled = CC.enabled - lat_enabled = CS.out.latEnabled - nav_active = hud_control.activeCarrot > 1 - - # hdpuse carrot - hdp_use = int(Params().get("HDPuse")) - hdp_active = False - if hdp_use == 1: - hdp_active = cruise_enabled and nav_active - elif hdp_use == 2: - hdp_active = cruise_enabled - # hdpuse carrot - - values = copy.copy(CS.adrv_info_161) - #print("adrv_info_161 = ", CS.adrv_info_161) - - values["SETSPEED"] = (6 if hdp_active else 3 if cruise_enabled else 1) if main_enabled else 0 - values["SETSPEED_HUD"] = (5 if hdp_active else 3 if cruise_enabled else 1) if main_enabled else 0 - set_speed_in_units = hud_control.setSpeed * (CV.MS_TO_KPH if CS.is_metric else CV.MS_TO_MPH) - values["vSetDis"] = int(set_speed_in_units + 0.5) - - values["DISTANCE"] = 4 if hdp_active else hud_control.leadDistanceBars - values["DISTANCE_LEAD"] = 2 if cruise_enabled and hud_control.leadVisible else 1 if main_enabled and hud_control.leadVisible else 0 - values["DISTANCE_CAR"] = 3 if hdp_active else 2 if cruise_enabled else 1 if main_enabled else 0 - values["DISTANCE_SPACING"] = 5 if hdp_active else 1 if cruise_enabled else 0 - - values["TARGET"] = 1 if main_enabled else 0 - values["TARGET_DISTANCE"] = int(hud_control.leadDistance) - - values["BACKGROUND"] = 6 if CS.paddle_button_prev > 0 else 1 if cruise_enabled else 3 if main_enabled else 7 - values["CENTERLINE"] = 1 if HDA_CntrlModSta > 0 else 0 #lat_enabled else 0 - values["CAR_CIRCLE"] = 2 if hdp_active else 1 if cruise_enabled else 0 - - values["NAV_ICON"] = 2 if nav_active else 0 - values["HDA_ICON"] = 5 if hdp_active else 2 if cruise_enabled else 1 if main_enabled else 0 - values["LFA_ICON"] = 5 if hdp_active else 2 if lat_active else 1 if lat_enabled else 0 - values["LKA_ICON"] = 4 if lat_active else 3 if lat_enabled else 0 - values["FCA_ALT_ICON"] = 0 - - if values["ALERTS_2"] in [1, 2, 5, 6, 10, 21, 22]: # 10,21,22: 운전자모니터 알람/경고, 6: enable lanechange alert - values["ALERTS_2"] = 0 - values["DAW_ICON"] = 0 - - values["SOUNDS_1"] = 0 # 운전자모니터경고음. - values["SOUNDS_2"] = 0 # 2: STEER중지 경고후에도 사운드가 나옴. - values["SOUNDS_4"] = 0 # 차선변경알림? 에이 그냥0으로.. - - if values["ALERTS_3"] in [3, 4, 13, 17, 19, 26, 7, 8, 9, 10]: - values["ALERTS_3"] = 0 - values["SOUNDS_3"] = 0 - - if values["ALERTS_5"] in [1, 2, 4, 5]: - values["ALERTS_5"] = 0 - - if values["ALERTS_5"] in [11] and CS.softHoldActive == 0: - values["ALERTS_5"] = 0 - - curvature = round(CS.out.steeringAngleDeg / 3) - - values["LANELINE_CURVATURE"] = (min(abs(curvature), 15) + (-1 if curvature < 0 else 0)) if lat_active else 0 - values["LANELINE_CURVATURE_DIRECTION"] = 1 if curvature < 0 and lat_active else 0 - - # lane_color = 6 if lat_active else 2 - #lane_color = 2 # 6: green, 2: white, 4: yellow - lane_color = 2 if CS.out.leftLaneLine < 20 else 4 - if hud_control.leftLaneDepart: - values["LANELINE_LEFT"] = 4 if (frame // 50) % 2 == 0 else 1 - else: - values["LANELINE_LEFT"] = lane_color if hud_control.leftLaneVisible else 0 - lane_color = 2 if CS.out.rightLaneLine < 20 else 4 - if hud_control.rightLaneDepart: - values["LANELINE_RIGHT"] = 4 if (frame // 50) % 2 == 0 else 1 - else: - values["LANELINE_RIGHT"] = lane_color if hud_control.rightLaneVisible else 0 - #values["LANELINE_LEFT_POSITION"] = 15 - #values["LANELINE_RIGHT_POSITION"] = 15 - - values["LCA_LEFT_ARROW"] = 2 if CS.out.leftBlinker else 0 - values["LCA_RIGHT_ARROW"] = 2 if CS.out.rightBlinker else 0 - - values["LCA_LEFT_ICON"] = 1 if CS.out.leftBlindspot else 2 - values["LCA_RIGHT_ICON"] = 1 if CS.out.rightBlindspot else 2 - - values["LANE_LEFT"] = 1 if desire in (1, 3) else 0 - values["LANE_RIGHT"] = 1 if desire in (2, 4) else 0 - - ret.append(packer.make_can_msg("ADRV_0x161", CAN.ECAN, values)) - - if CS.adrv_info_200 is not None: - values = copy.copy(CS.adrv_info_200) - values["TauGapSet"] = hud_control.leadDistanceBars - ret.append(packer.make_can_msg("ADRV_0x200", CAN.ECAN, values)) - - if CS.adrv_info_1ea is not None: - values = copy.copy(CS.adrv_info_1ea) - #values["HDA_MODE1"] = 8 - #values["HDA_MODE2"] = 1 - if lane_changing == 3: - values['LEFT_BLINK_HOLD'] = 1 - elif lane_changing == 4: - values['RIGHT_BLINK_HOLD'] = 1 - - _make_ccnc_values(values, CS, lat_active, frame, hud_control) - # values['AUTOLANECHANGE_MSG'] = 1 # 주변 상황을 확인하세요 - # values['AUTOLANECHANGE_MSG'] = 2 # 작동 조건이 아닙니다 - # values['AUTOLANECHANGE_MSG'] = 3 # 주행 차로를 분석중입니다 - # values['AUTOLANECHANGE_MSG'] = 4 # 급커브 구간입니다 - # values['AUTOLANECHANGE_MSG'] = 5 # 주행 중인 차로의 폭이 좁습니다 - # values['AUTOLANECHANGE_MSG'] = 6 # 작동 구간이 아닙니다. - # values['AUTOLANECHANGE_MSG'] = 7 # 비상등이 켜져있습니다 - # values['AUTOLANECHANGE_MSG'] = 8 # 주행속도가 낮습니다 - # values['AUTOLANECHANGE_MSG'] = 9 # 핸들을 잡으십시오 - # values['AUTOLANECHANGE_MSG'] = 10 # 작동 가능한 차로가 아닙니다 - # values['AUTOLANECHANGE_MSG'] = 11 # 핸들 조작이 감지되었습니다. - # 얘는 우측 RPM 게이지에 크게 나옴 - # values['AUTOLANECHANGE_MSG'] = 12 # ok 버튼을 누르면 차로변경 보조기능이 켜집니다 - # values['AUTOLANECHANGE_MSG'] = 13 # 없음. - # values['AUTOLANECHANGE_MSG'] = 14 # 없음. - # values['AUTOLANECHANGE_MSG'] = 15 # 없음. - ret.append(packer.make_can_msg("ADRV_0x1ea", CAN.ECAN, values)) - - if CS.adrv_info_162 is not None: - values = copy.copy(CS.adrv_info_162) - if hud_control.leadDistance > 0: - values["FF_DISTANCE"] = hud_control.leadDistance - #values["FF_DETECT"] = 11 if hud_control.leadRelSpeed > -0.1 else 12 # bicycle - #values["FF_DETECT"] = 5 if hud_control.leadRelSpeed > -0.1 else 6 # truck - ff_type = 3 if hud_control.leadRadar == 1 else 13 - values["FF_DETECT"] = ff_type if hud_control.leadRelSpeed > -0.1 else ff_type + 1 - #values["FF_DETECT_LAT"] = - hud_control.leadDPath - _make_ccnc_values(values, CS, lat_active, frame, hud_control, lane_line = False, corner_radar= True) - - #values["FAULT_FCA"] = 0 - #values["FAULT_LSS"] = 0 - #values["FAULT_LFA"] = 0 - #values["FAULT_LCA"] = 0 - #values["FAULT_DAS"] = 0 - #values["FAULT_HDA"] = 0 - - if (left_lane_warning and not CS.out.leftBlinker) or (right_lane_warning and not CS.out.rightBlinker): - values["VIBRATE"] = 1 - ret.append(packer.make_can_msg("CCNC_0x162", CAN.ECAN, values)) - - if enable_corner_radar > 0: - if HDA_CntrlModSta == 0: - if frame % 500 in [10,20,30]: - values = { - 'BYTE_1': 0, - 'BYTE_2': 0, - 'BYTE_3': 0x80, - 'BYTE_4': 0x8A, - 'BYTE_5': 0x32, - 'BYTE_6': 0x30, - 'BYTE_7': 0x01, - 'BYTE_8': 0x00, - } - ret.append(packer.make_can_msg("NEW_MSG_4B9", CAN.CAM, values)) - elif frame % 500 in [40,50,60]: - values = { - 'BYTE_1': 0xff, - 'BYTE_2': 0xff, - 'BYTE_3': 0xff, - 'BYTE_4': 0xff, - 'BYTE_5': 0xff, - 'BYTE_6': 0xff, - 'BYTE_7': 0xff, - 'BYTE_8': 0xff, - } - ret.append(packer.make_can_msg("NEW_MSG_4B9", CAN.CAM, values)) - if False: #canfd_debug > 1 and frame % 20 == 0: # 아직 시험중.. - if CS.hda_info_4a3 is not None: - values = copy.copy(CS.hda_info_4a3) - values["LinkClass"] = 1 - values["SPEED_LIMIT"] = 100 - ret.append(packer.make_can_msg("HDA_INFO_4A3", CAN.CAM, values)) - - return ret -""" - def create_adrv_messages(CP, packer, CAN, frame): # messages needed to car happy after disabling # the ADAS Driving ECU to do longitudinal control @@ -911,18 +645,20 @@ def create_ccnc_messages(CP, packer, CAN, frame, CC, CS, hud_control, if CP.flags & HyundaiFlags.CAMERA_SCC.value: HDA_CntrlModSta = 0 - if CS.lfahda_cluster_info is not None: - HDA_CntrlModSta = CS.lfahda_cluster_info["HDA_CntrlModSta"] + HDA_LFA_SymSta = 0 + if CS.lfahda_cluster is not None: + HDA_CntrlModSta = CS.lfahda_cluster["HDA_CntrlModSta"] + HDA_LFA_SymSta = CS.lfahda_cluster["HDA_LFA_SymSta"] if frame % 2 == 0: - #if CS.adrv_info_160 is not None: - # values = copy.copy(CS.adrv_info_160) + #if CS.adrv_0x160 is not None: + # values = copy.copy(CS.adrv_0x160) # ret.append(packer.make_can_msg("ADRV_0x160", CAN.ECAN, values)) if CS.cruise_buttons_msg is not None: values = copy.copy(CS.cruise_buttons_msg) - if CS.lfahda_cluster_info["HDA_LFA_SymSta"] == 0 and 0 < frame % 200 < 12: + if HDA_LFA_SymSta == 0 and 0 < frame % 200 < 12: values["LFA_BTN"] = 1 if CC.enabled and CS.MainMode_ACC: @@ -939,7 +675,7 @@ def create_ccnc_messages(CP, packer, CAN, frame, CC, CS, hud_control, if frame % 5 == 0: lat_active = CC.latActive - if CS.adrv_info_161 is not None: + if CS.adrv_0x161 is not None: main_enabled = CS.out.cruiseState.available cruise_enabled = CC.enabled lat_enabled = CS.out.latEnabled @@ -954,8 +690,8 @@ def create_ccnc_messages(CP, packer, CAN, frame, CC, CS, hud_control, hdp_active = cruise_enabled # hdpuse carrot - values = copy.copy(CS.adrv_info_161) - + values = copy.copy(CS.adrv_0x161) + rx_counter = values.pop("COUNTER", None) values["SETSPEED"] = (6 if hdp_active else 3 if cruise_enabled else 1) if main_enabled else 0 values["SETSPEED_HUD"] = (5 if hdp_active else 3 if cruise_enabled else 1) if main_enabled else 0 @@ -989,7 +725,7 @@ def create_ccnc_messages(CP, packer, CAN, frame, CC, CS, hud_control, values["SOUNDS_2"] = 0 values["SOUNDS_4"] = 0 - if values["ALERTS_3"] in [3, 4, 13, 17, 19, 26, 7, 8, 9, 10]: + if values["ALERTS_3"] in [3, 4, 11, 12, 13, 14, 17, 19, 26, 7, 8, 9, 10]: # hide gap distance msg.(11,12,13,14) values["ALERTS_3"] = 0 values["SOUNDS_3"] = 0 @@ -1004,13 +740,15 @@ def create_ccnc_messages(CP, packer, CAN, frame, CC, CS, hud_control, values["LANELINE_CURVATURE"] = (min(abs(curvature), 15) + (-1 if curvature < 0 else 0)) if lat_active else 0 values["LANELINE_CURVATURE_DIRECTION"] = 1 if curvature < 0 and lat_active else 0 - lane_color = 4 if CS.out.leftLaneLine >= 20 or CS.out.leftBlindspot else 2 + lane_color = 6 if md is not None and md.meta.laneChangeAvailableLeft else 2 + lane_color = 4 if CS.out.leftLaneLine >= 20 or CS.out.leftBlindspot else lane_color if hud_control.leftLaneDepart: values["LANELINE_LEFT"] = 4 if (frame // 50) % 2 == 0 else 1 else: values["LANELINE_LEFT"] = lane_color if hud_control.leftLaneVisible else 0 - lane_color = 4 if CS.out.rightLaneLine >= 20 or CS.out.rightBlindspot else 2 + lane_color = 6 if md is not None and md.meta.laneChangeAvailableRight else 2 + lane_color = 4 if CS.out.rightLaneLine >= 20 or CS.out.rightBlindspot else lane_color if hud_control.rightLaneDepart: values["LANELINE_RIGHT"] = 4 if (frame // 50) % 2 == 0 else 1 else: @@ -1025,16 +763,17 @@ def create_ccnc_messages(CP, packer, CAN, frame, CC, CS, hud_control, values["LANE_LEFT"] = 1 if desire in (1, 3) else 0 values["LANE_RIGHT"] = 1 if desire in (2, 4) else 0 - ret.append(packer.make_can_msg("ADRV_0x161", CAN.ECAN, values)) + ret.append(packer.make_can_msg("ADRV_0x161", CAN.ECAN, values, rx_counter = rx_counter)) - if CS.adrv_info_200 is not None: - values = copy.copy(CS.adrv_info_200) + if CS.adrv_0x200 is not None: + values = copy.copy(CS.adrv_0x200) + rx_counter = values.pop("COUNTER", None) values["TauGapSet"] = hud_control.leadDistanceBars - ret.append(packer.make_can_msg("ADRV_0x200", CAN.ECAN, values)) - - if CS.adrv_info_1ea is not None: - values = copy.copy(CS.adrv_info_1ea) + ret.append(packer.make_can_msg("ADRV_0x200", CAN.ECAN, values, rx_counter = rx_counter)) + if CS.adrv_0x1ea is not None: + values = copy.copy(CS.adrv_0x1ea) + rx_counter = values.pop("COUNTER", None) # blinker hold values['LEFT_BLINK_HOLD'] = 1 if lane_changing == 3 else 0 values['RIGHT_BLINK_HOLD'] = 1 if lane_changing == 4 else 0 @@ -1050,10 +789,10 @@ def create_ccnc_messages(CP, packer, CAN, frame, CC, CS, hud_control, blink_t=1.0 ) - ret.append(packer.make_can_msg("ADRV_0x1ea", CAN.ECAN, values)) + ret.append(packer.make_can_msg("ADRV_0x1ea", CAN.ECAN, values, rx_counter = rx_counter)) - if CS.adrv_info_162 is not None: - values = copy.copy(CS.adrv_info_162) + if CS.ccnc_0x162 is not None: + values = copy.copy(CS.ccnc_0x162) if hud_control.leadDistance > 0: values["FF_DISTANCE"] = hud_control.leadDistance diff --git a/selfdrive/carrot/carrot_controls.py b/selfdrive/carrot/carrot_controls.py new file mode 100644 index 0000000000..d709876e85 --- /dev/null +++ b/selfdrive/carrot/carrot_controls.py @@ -0,0 +1,40 @@ +from openpilot.common.realtime import DT_CTRL +from openpilot.common.params import Params + +class CarrotControls: + def __init__(self, CP): + self.CP = CP + self.params = Params() + self.lat_suspend_active = False + self.lat_suspend_enter_t = 0.0 + self.lat_suspend_hold_t = 0.0 + + def lat_suspend_control(self, CS, latActive): + suspend_angle = float(self.params.get_int("LatSuspendAngleDeg")) + resume_angle = 15 + delay_sec = 1.0 + hold_sec = 0.5 + + # 1) enter condition timer + enter_cond = CS.steeringPressed and abs(CS.steeringAngleDeg) > suspend_angle + if not self.lat_suspend_active: + if enter_cond: + self.lat_suspend_enter_t += DT_CTRL + if self.lat_suspend_enter_t >= delay_sec: + self.lat_suspend_active = True + self.lat_suspend_hold_t = 0.0 + else: + self.lat_suspend_enter_t = 0.0 + + # 2) while suspended: enforce minimum hold time + hysteresis exit + if self.lat_suspend_active: + self.lat_suspend_hold_t += DT_CTRL + + exit_cond = (abs(CS.steeringAngleDeg) < resume_angle) and (not CS.steeringPressed) + if (self.lat_suspend_hold_t >= hold_sec) and exit_cond: + self.lat_suspend_active = False + self.lat_suspend_enter_t = 0.0 + + if self.lat_suspend_active: + latActive = False + return latActive \ No newline at end of file diff --git a/selfdrive/carrot/carrot_man.py b/selfdrive/carrot/carrot_man.py index 87869dc030..286154c972 100644 --- a/selfdrive/carrot/carrot_man.py +++ b/selfdrive/carrot/carrot_man.py @@ -260,55 +260,7 @@ def get_local_ip(self): except Exception as e: return f"Error: {e}" - def register_my_ip(self): - try: - token = "12345678" - local_ip = self.get_local_ip() - version = self.params.get("Version") - github_id = self.params.get("GithubUsername") - port = 7000 - is_onroad = self.params.get_bool("IsOnroad") - ts = int(time.time()) - url = "https://shind0.synology.me/carrot/api_heartbeat.php" - timeout_s = 3.5 - payload = { - "github_id": github_id, - "token": token, - "local_ip": local_ip, - "port": int(port), - "version": version, - "is_onroad": bool(is_onroad), - "ts": int(time.time()), - } - #if extra: - # payload.update(extra) - - data = json.dumps(payload).encode("utf-8") - print(data) - req = urllib.request.Request( - url=url, - data=data, - headers={"Content-Type": "application/json"}, - method="POST", - ) - - try: - ctx = ssl._create_unverified_context() - with urllib.request.urlopen(req, timeout=timeout_s, context=ctx) as resp: - body = resp.read().decode("utf-8", errors="replace") - # 서버가 {"ok":true} 같은 JSON을 주는 경우가 많음 - return (200 <= resp.status < 300), body - except urllib.error.HTTPError as e: - try: - body = e.read().decode("utf-8", errors="replace") - except Exception: - body = "" - return False, f"HTTPError {e.code}: {body}" - except Exception as e: - return False, f"Exception: {e}" - except Exception as e: - print(f"register_my_ip error: {e}") - + # 브로드캐스트 메시지 전송 def broadcast_version_info(self): sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) @@ -355,9 +307,6 @@ def broadcast_version_info(self): if carrot_speed_active_count > 0: self.carrot_speed_serv(carrot_speed, frame) - if frame % (20 * 30) == 0: - ok, msg = self.register_my_ip() - print(f"[heartbeat] ok: {ok}, msg: {msg}") if frame % 20 == 0 or remote_addr is not None: try: self.broadcast_ip = self.get_broadcast_address() if remote_addr is None else remote_addr[0] diff --git a/selfdrive/carrot/carrot_server.py b/selfdrive/carrot/carrot_server.py index 5645983b78..e51844853a 100644 --- a/selfdrive/carrot/carrot_server.py +++ b/selfdrive/carrot/carrot_server.py @@ -30,6 +30,11 @@ from cereal import messaging from opendbc.car import structs import shlex +import shutil +import socket +import urllib.request +import urllib.error +import ssl BASE_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -82,6 +87,93 @@ async def log_mw(request, handler): WEBRTCD_URL = "http://127.0.0.1:5001/stream" +def _get_local_ip() -> str: + try: + with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: + s.connect(("8.8.8.8", 80)) + return s.getsockname()[0] + except Exception: + # fallback: hostname 방식(가끔 127.0.1.1 나올 수 있음) + try: + return socket.gethostbyname(socket.gethostname()) + except Exception: + return "0.0.0.0" + + +def _register_my_ip_sync(params: "Params") -> tuple[bool, str]: + """ + 기존 carrot_man.py의 register_my_ip()를 그대로 옮긴 버전 (동기) + """ + try: + token = "12345678" + local_ip = _get_local_ip() + version = params.get("Version") + github_id = params.get("GithubUsername") + port = 7000 + is_onroad = params.get_bool("IsOnroad") + url = "https://shind0.synology.me/carrot/api_heartbeat.php" + timeout_s = 3.5 + + payload = { + "github_id": github_id, + "token": token, + "local_ip": local_ip, + "port": int(port), + "version": version, + "is_onroad": bool(is_onroad), + "ts": int(time.time()), + } + + data = json.dumps(payload).encode("utf-8") + req = urllib.request.Request( + url=url, + data=data, + headers={"Content-Type": "application/json"}, + method="POST", + ) + + ctx = ssl._create_unverified_context() + with urllib.request.urlopen(req, timeout=timeout_s, context=ctx) as resp: + body = resp.read().decode("utf-8", errors="replace") + return (200 <= resp.status < 300), body + + except urllib.error.HTTPError as e: + try: + body = e.read().decode("utf-8", errors="replace") + except Exception: + body = "" + return False, f"HTTPError {e.code}: {body}" + except Exception as e: + return False, f"Exception: {e}" + + +async def heartbeat_loop(app: web.Application): + """ + aiohttp startup에서 create_task로 돌릴 백그라운드 루프 + - 이벤트 루프 블로킹 방지 위해 to_thread 사용 + """ + if not HAS_PARAMS: + app["hb_last"] = {"ok": False, "msg": "Params not available"} + return + + params = Params() + interval_s = 30.0 # 기존: frame%(20*30) = 30초 + while True: + try: + ok, msg = await asyncio.to_thread(_register_my_ip_sync, params) + app["hb_last"] = { + "ok": bool(ok), + "msg": str(msg)[:800], + "ts": time.time(), + "local_ip": _get_local_ip(), + } + # 원하면 로그 + # print(f"[heartbeat] ok:{ok}, msg:{msg}") + except asyncio.CancelledError: + break + except Exception as e: + app["hb_last"] = {"ok": False, "msg": f"Exception: {e}", "ts": time.time()} + await asyncio.sleep(interval_s) async def proxy_stream(request: web.Request) -> web.StreamResponse: @@ -102,14 +194,28 @@ async def proxy_stream(request: web.Request) -> web.StreamResponse: except Exception as e: return web.json_response({"ok": False, "error": str(e)}, status=502) +async def api_heartbeat_status(request: web.Request) -> web.Response: + return web.json_response({"ok": True, "hb": request.app.get("hb_last")}) + async def on_startup(app: web.Application): app["http"] = ClientSession() - + app["hb_last"] = {"ok": None, "msg": "not yet", "ts": 0} + if HAS_PARAMS: + app["hb_task"] = asyncio.create_task(heartbeat_loop(app)) + async def on_cleanup(app: web.Application): + t = app.get("hb_task") + if t: + t.cancel() + try: + await t + except Exception: + pass + sess = app.get("http") if sess: await sess.close() - + # ----------------------- # Settings cache (mtime based) # ----------------------- @@ -133,20 +239,28 @@ def _group_index(settings: Dict[str, Any]) -> Tuple[Dict[str, list], Dict[str, D params = settings.get("params", []) for p in params: - g = p.get("group", "UNGROUPED") + g = p.get("group", "기타") + if g == "기타": + if "egroup" not in p: p["egroup"] = "Other" + if "cgroup" not in p: p["cgroup"] = "其他" + groups.setdefault(g, []).append(p) n = p.get("name") if n: by_name[n] = p - # group list with egroup guess + # group list with egroup/cgroup guess for g, items in groups.items(): egroup = None + cgroup = None for it in items: - if it.get("egroup"): + if not egroup and it.get("egroup"): egroup = it.get("egroup") + if not cgroup and it.get("cgroup"): + cgroup = it.get("cgroup") + if egroup and cgroup: break - groups_list.append({"group": g, "egroup": egroup, "count": len(items)}) + groups_list.append({"group": g, "egroup": egroup, "cgroup": cgroup, "count": len(items)}) return groups, by_name, groups_list @@ -549,13 +663,21 @@ def run(cmd: List[str], cwd: Optional[str] = None) -> Tuple[int, str]: for pth in paths: if not os.path.isdir(pth): continue - for fn in glob.glob(os.path.join(pth, "*")): + + for name in os.listdir(pth): + full_path = os.path.join(pth, name) try: - os.remove(fn) - deleted += 1 - except Exception: - pass - return web.json_response({"ok": True, "out": f"deleted files: {deleted}"}) + if os.path.isfile(full_path) or os.path.islink(full_path): + os.remove(full_path) + deleted += 1 + elif os.path.isdir(full_path): + shutil.rmtree(full_path) + deleted += 1 + except Exception as e: + print("delete error:", e) + + return web.json_response({"ok": True, "out": f"deleted entries: {deleted}"}) + if action == "send_tmux_log": @@ -662,7 +784,7 @@ def run(cmd: List[str], cwd: Optional[str] = None) -> Tuple[int, str]: except Exception as e: return web.json_response({"ok": False, "error": str(e)}, status=500) - + async def ws_state(request: web.Request) -> web.WebSocketResponse: ws = web.WebSocketResponse(heartbeat=20) await ws.prepare(request) @@ -767,13 +889,13 @@ async def ws_carstate(request: web.Request) -> web.WebSocketResponse: temp_speed = { "speed": apply_speed, "source": apply_source if apply_speed >= v_cruise else "", "is_decel": True if apply_speed < v_cruise else False} drive_mode = lp.myDrivingMode if drive_mode == 1: - drive_mode_obj = {"name": "연비", "kind": "eco"} + drive_mode_obj = {"name": "Eco", "kind": "eco"} elif drive_mode == 2: - drive_mode_obj = {"name": "안전", "kind": "safe"} + drive_mode_obj = {"name": "Safe", "kind": "safe"} elif drive_mode == 4: - drive_mode_obj = {"name": "고속", "kind": "sport"} + drive_mode_obj = {"name": "Sport", "kind": "sport"} else: - drive_mode_obj = {"name": "일반", "kind": "normal"} + drive_mode_obj = {"name": "Normal", "kind": "normal"} gps_ok = True @@ -822,10 +944,23 @@ async def ws_carstate(request: web.Request) -> web.WebSocketResponse: "apm": " ", } - await ws.send_str(json.dumps(payload)) + try: + await ws.send_str(json.dumps(payload)) + except (asyncio.CancelledError, GeneratorExit): + raise + except (ConnectionResetError, BrokenPipeError, web.HTTPException): + break + except Exception as e: + # aiohttp에서 클라이언트가 끊길 때 나는 대표 예외 + if isinstance(e, (aiohttp.client_exceptions.ClientConnectionResetError,)): + break + if "Cannot write to closing transport" in str(e): + break + # traceback.print_exc() + break await asyncio.sleep(0.1) # 10Hz except Exception: - #traceback.print_exc() + traceback.print_exc() pass try: @@ -973,7 +1108,7 @@ def make_app() -> web.Application: app = web.Application(middlewares=[log_mw]) app.on_startup.append(on_startup) app.on_cleanup.append(on_cleanup) - + # static-like routes app.router.add_get("/", handle_index) app.router.add_get("/app.js", handle_appjs) diff --git a/selfdrive/carrot/web/app.js b/selfdrive/carrot/web/app.js index 3bf57dc0b5..44b8d304d2 100644 --- a/selfdrive/carrot/web/app.js +++ b/selfdrive/carrot/web/app.js @@ -2,7 +2,148 @@ const DEBUG_UI = false; let SETTINGS = null; let CURRENT_GROUP = null; -let LANG = "ko"; // "ko" | "en" +let LANG = "ko"; // "ko" | "en" | "zh" + +const UI_STRINGS = { + ko: { + home: "홈", + setting: "설정", + tools: "도구", + fleet: "플릿", + lang: "언어", + server_state: "서버 상태", + quick_link: "퀵 링크", + car_select: "차량 선택", + makers: "제조사", + models: "모델", + groups: "그룹", + items: "항목", + back: "뒤로", + change: "변경", + git_commands: "Git 명령", + user_system: "사용자 / 시스템", + reboot: "재부팅", + backup: "백업", + restore: "복구", + apply: "적용", + confirm_car: "이 차량을 선택하시겠습니까?", + confirm_reboot: "지금 재부팅하시겠습니까?", + reboot_later: "선택되었습니다. 적용하려면 나중에 재부팅하세요.", + rebooting: "재부팅 중...", + git_sync_confirm: "Git sync를 실행하시겠습니까?", + git_reset_confirm: "Git reset을 실행하시겠습니까? (위험)", + delete_videos_confirm: "모든 비디오를 삭제하시겠습니까? (위험)", + delete_logs_confirm: "모든 로그를 삭제하시겠습니까? (위험)", + select_backup_file: "먼저 백업 json 파일을 선택하세요.", + restore_confirm: "파일에서 설정을 복구하시겠습니까?\n\n많은 Params 값이 덮어씌워집니다.", + restore_done_reboot: "복구가 완료되었습니다.\n지금 재부팅하시겠습니까?", + checkout_confirm: "브랜치를 체크아웃하시겠습니까?", + branch_changed: "브랜치가 변경되었습니다.", + quick_link_hint: "* 길게 눌러 링크저장", + git_hint: "* reset/branch는 위험할 수 있으니 confirm 뜹니다.", + sys_hint: "* delete/reboot는 confirm 후 실행합니다.", + restore_hint: "* restore 후 reboot 권장", + failed_set_car: "차량 선택 저장 실패: ", + reboot_failed: "재부팅 실패: ", + set_failed: "설정 실패: ", + branch_dom_missing: "브랜치 DOM 요소를 찾을 수 없습니다.", + fullscreen_not_supported: "이 브라우저는 전체화면을 지원하지 않습니다.", + }, + en: { + home: "Home", + setting: "Setting", + tools: "Tools", + fleet: "Fleet", + lang: "Lang", + server_state: "Server State", + quick_link: "Quick Link", + car_select: "Car Select", + makers: "Makers", + models: "Models", + groups: "Groups", + items: "Items", + back: "Back", + change: "Change", + git_commands: "Git Commands", + user_system: "User / System", + reboot: "Reboot", + backup: "Backup", + restore: "Restore", + apply: "Apply", + confirm_car: "Select this car?", + confirm_reboot: "Reboot now?", + reboot_later: "Selected. Reboot later to apply.", + rebooting: "Rebooting...", + git_sync_confirm: "Run git sync?", + git_reset_confirm: "Run git reset? (DANGEROUS)", + delete_videos_confirm: "Delete ALL videos? (DANGEROUS)", + delete_logs_confirm: "Delete ALL logs? (DANGEROUS)", + select_backup_file: "Select a backup json file first.", + restore_confirm: "Restore settings from file?\n\nThis will overwrite many Params values.", + restore_done_reboot: "Restore done.\nReboot now?", + checkout_confirm: "Checkout branch?", + branch_changed: "Branch changed.", + quick_link_hint: "* Long press to save link", + git_hint: "* Reset/branch will prompt for confirmation.", + sys_hint: "* Delete/reboot will prompt for confirmation.", + restore_hint: "* Reboot recommended after restore.", + failed_set_car: "Failed to set car: ", + reboot_failed: "Reboot failed: ", + set_failed: "Set failed: ", + branch_dom_missing: "Branch DOM elements missing.", + fullscreen_not_supported: "Fullscreen not supported on this browser.", + }, + zh: { + home: "首页", + setting: "设置", + tools: "工具", + fleet: "车队", + lang: "语言", + server_state: "服务器状态", + quick_link: "快速链接", + car_select: "车辆选择", + makers: "制造商", + models: "车型", + groups: "分组", + items: "项", + back: "返回", + change: "修改", + git_commands: "Git 命令", + user_system: "用户 / 系统", + reboot: "重启", + backup: "备份", + restore: "还原", + apply: "应用", + confirm_car: "选择此车辆吗?", + confirm_reboot: "现在重启吗?", + reboot_later: "已选择。请稍后重启以应用更改。", + rebooting: "正在重启...", + git_sync_confirm: "执行 Git 同步吗?", + git_reset_confirm: "执行 Git 重置吗?(危险)", + delete_videos_confirm: "删除所有视频吗?(危险)", + delete_logs_confirm: "删除所有日志吗?(危险)", + select_backup_file: "请先选择一个备份 JSON 文件。", + restore_confirm: "从文件还原设置吗?\n\n这将覆盖许多参数值。", + restore_done_reboot: "还原完成。\n现在重启吗?", + checkout_confirm: "切换分支吗?", + branch_changed: "分支已切换。", + quick_link_hint: "* 长按保存链接", + git_hint: "* 重置/分支操作会弹出确认提示。", + sys_hint: "* 删除/重启操作会弹出确认提示。", + restore_hint: "* 还原后建议重启。", + failed_set_car: "保存车辆选择失败: ", + reboot_failed: "重启失败: ", + set_failed: "设置失败: ", + branch_dom_missing: "找不到分支 DOM 元素。", + fullscreen_not_supported: "此浏览器不支持全屏。", + } +}; + +const DRIVE_MODES = { + ko: { normal: "일반", eco: "연비", safe: "안전", sport: "고속" }, + en: { normal: "Normal", eco: "Eco", safe: "Safe", sport: "Sport" }, + zh: { normal: "标准", eco: "经济", safe: "安全", sport: "运动" } +}; let UNIT_CYCLE = [1, 2, 5, 10, 50, 100]; const UNIT_INDEX = {}; // per name @@ -58,7 +199,7 @@ btnLang.onclick = () => toggleLang(); btnChangeCar.onclick = () => showPage("car", true); btnBackCar.onclick = () => history.back(); carTitle.onclick = () => history.back(); -modelTitle.onclick = () => showCarScreen("makers"); // ��ȭ�鿡�� Ÿ��Ʋ ���� makers�� +modelTitle.onclick = () => showCarScreen("makers"); // ��ȭ�鿡�� Ÿ��Ʋ ���� makers�� // Branch select let BRANCHES = []; @@ -155,14 +296,69 @@ function showCarScreen(which, pushHistory = false) { } function toggleLang() { - LANG = (LANG === "ko") ? "en" : "ko"; - langLabel.textContent = (LANG === "ko") ? "KO" : "EN"; + if (LANG === "ko") LANG = "en"; + else if (LANG === "en") LANG = "zh"; + else LANG = "ko"; + + langLabel.textContent = LANG.toUpperCase(); + + // Update static UI text + renderUIText(); + if (SETTINGS) { renderGroups(); if (CURRENT_GROUP) renderItems(CURRENT_GROUP); } } +function renderUIText() { + const s = UI_STRINGS[LANG]; + if (!s) return; + + setText("btnHome", s.home); + setText("btnSetting", s.setting); + setText("btnTools", s.tools); + setText("btnFleet", s.fleet); + // langLabel is handled in toggleLang + + // Home + setText("homeTitle", s.home); + setText("serverStateTitle", s.server_state); + setText("quickLinkTitle", s.quick_link); + + // Car Select + setText("carTitle", s.car_select); + setText("btnBackCar", s.back); + setText("makersTitle", s.makers); + setText("modelTitle", s.models); + + // Setting + setText("settingTitleText", s.setting); // Use a specific ID if needed + setText("btnBackGroups", s.back); + setText("btnChangeCar", s.change); + setText("groupsTitle", s.groups); + setText("itemsTitle", s.items); + + // Tools + setText("toolsTitle", s.tools); + setText("btnToolsBack", s.back); + setText("gitCommandsTitle", s.git_commands); + setText("userSystemTitle", s.user_system); + setText("btnReboot", s.reboot); + setText("btnBackupSettings", s.backup); + setText("btnRestoreSettings", s.restore); + + setText("quickLinkHint", s.quick_link_hint); + setText("gitHint", s.git_hint); + setText("sysHint", s.sys_hint); + setText("restoreHint", s.restore_hint); +} + +function setText(id, txt) { + const el = document.getElementById(id); + if (el) el.textContent = txt; +} + function escapeHtml(s) { return String(s) .replaceAll("&", "&") @@ -173,6 +369,7 @@ function escapeHtml(s) { } function formatItemText(p, keyKo, keyEn, fallback = "") { + if (LANG === "zh") return (p["c" + keyEn.slice(1)] || p[keyEn] || p[keyKo] || fallback); if (LANG === "ko") return (p[keyKo] ?? fallback); return (p[keyEn] ?? p[keyKo] ?? fallback); } @@ -263,10 +460,10 @@ function renderModels(maker) { modelTitle.textContent = maker; modelMeta.textContent = `${arr.length} models`; - // �� ����̴ϱ� ��ư ��/�� ���ϰ�: groupBtn ���� + // �� ����̴ϱ� ��ư ��/�� ���ϰ�: groupBtn ���� for (const fullLine of arr) { - // fullLine ��: "Hyundai Grandeur 2018-19" - // CarSelected3���� maker�� ���� �־�� �� �� "Grandeur 2018-19" + // fullLine ��: "Hyundai Grandeur 2018-19" + // CarSelected3���� maker�� ���� �־�� �� �� "Grandeur 2018-19" const modelOnly = stripMaker(fullLine, maker); const b = document.createElement("button"); @@ -278,33 +475,33 @@ function renderModels(maker) { } function stripMaker(fullLine, maker) { - // maker + ������ 1���� ���� + // maker + ������ 1���� ���� const prefix = maker + " "; if (fullLine.startsWith(prefix)) return fullLine.slice(prefix.length).trim(); - // Ȥ�� "Hyundai"�� �ƴ� �ٸ� ǥ��� fallback: ù �ܾ� ���� + // Ȥ�� "Hyundai"�� �ƴ� �ٸ� ǥ��� fallback: ù �ܾ� ���� const sp = fullLine.split(" "); if (sp.length >= 2) return sp.slice(1).join(" ").trim(); return fullLine.trim(); } async function onSelectCar(maker, modelOnly, fullLine) { - const ok = confirm(`Select this car?\n\n${maker} ${modelOnly}\n\nThis will set CarSelected3 = "${modelOnly}".`); - if (!ok) return; + const msg = (UI_STRINGS[LANG].confirm_car || "Select this car?") + `\n\n${maker} ${modelOnly}\n\nThis will set CarSelected3 = "${modelOnly}".`; + if (!confirm(msg)) return; try { await setParam("CarSelected3", fullLine); } catch (e) { - alert("Failed to set CarSelected3: " + e.message); + alert((UI_STRINGS[LANG].failed_set_car || "Failed to set car: ") + e.message); return; } - // Home ǥ�� ������Ʈ + // Home ǥ�� ������Ʈ curCarLabelCar.textContent = modelOnly; curCarLabelSetting.textContent = modelOnly; - const rb = confirm("Reboot now?"); + const rb = confirm(UI_STRINGS[LANG].confirm_reboot || "Reboot now?"); if (!rb) { - alert("Selected. Reboot later to apply."); + alert(UI_STRINGS[LANG].reboot_later || "Selected. Reboot later to apply."); return; } @@ -312,9 +509,9 @@ async function onSelectCar(maker, modelOnly, fullLine) { const r = await fetch("/api/reboot", { method: "POST" }); const j = await r.json(); if (!j.ok) throw new Error(j.error || "reboot failed"); - alert("Rebooting..."); + alert(UI_STRINGS[LANG].rebooting || "Rebooting..."); } catch (e) { - alert("Reboot failed: " + e.message); + alert((UI_STRINGS[LANG].reboot_failed || "Reboot failed: ") + e.message); } } @@ -353,7 +550,10 @@ function renderGroups() { box.innerHTML = ""; (SETTINGS.groups || []).forEach(g => { - const label = (LANG === "ko") ? g.group : (g.egroup || g.group); + let label = g.group; + if (LANG === "zh") label = g.cgroup || g.egroup || g.group; + else if (LANG === "en") label = g.egroup || g.group; + const b = document.createElement("button"); b.className = "btn groupBtn"; b.textContent = `${label} (${g.count})`; @@ -466,7 +666,7 @@ async function renderItems(group) { await setParam(name, next); val.textContent = String(next); } catch (e) { - alert("set failed: " + e.message); + alert((UI_STRINGS[LANG].set_failed || "set failed: ") + e.message); } } @@ -546,7 +746,7 @@ window.addEventListener("popstate", async (ev) => { if (st.page === "branch") { showPage("branch", false); - // �귣ġ ����� ������ �ٽ� �ε� + // �귣ġ ����� ������ �ٽ� �ε� if (!BRANCHES || !BRANCHES.length) { loadBranchesAndShow().catch(() => {}); } @@ -580,7 +780,7 @@ async function runTool(action, payload) { toolsMetaSet("running: " + action); toolsOutSet("..."); - // �������� { ok:true, out:"...", rc:0 } �̷� ���·� �ָ� ���� ���� + // �������� { ok:true, out:"...", rc:0 } �̷� ���·� �ָ� ���� ���� const j = await postJson("/api/tools", { action, ...(payload || {}) }); toolsMetaSet("done: " + action); @@ -601,7 +801,7 @@ function confirmText(msg, placeholder = "") { function initToolsPage() { - // ��ư ���ε� (�� ����) + // ��ư ���ε� (�� ����) const bindOnce = (id, fn) => { const el = document.getElementById(id); if (!el || el.dataset.bound === "1") return; @@ -622,7 +822,7 @@ function initToolsPage() { }); bindOnce("btnGitSync", async () => { - if (!confirm("Run git sync?")) return; + if (!confirm(UI_STRINGS[LANG].git_sync_confirm || "Run git sync?")) return; try { await runTool("git_sync"); } catch (e) { @@ -633,10 +833,10 @@ function initToolsPage() { }); bindOnce("btnGitReset", async () => { - if (!confirm("Run git reset? (DANGEROUS)")) return; + if (!confirm(UI_STRINGS[LANG].git_reset_confirm || "Run git reset? (DANGEROUS)")) return; - // �ɼ� �ʿ��ϸ� prompt�� �ޱ� - // ��: hard / soft, target + // �ɼ� �ʿ��ϸ� prompt�� �ޱ� + // ��: hard / soft, target const mode = confirmText("reset mode? (hard/soft/mixed)", "hard"); if (!mode) return; @@ -671,7 +871,7 @@ function initToolsPage() { }); bindOnce("btnDeleteVideos", async () => { - if (!confirm("Delete ALL videos? (DANGEROUS)")) return; + if (!confirm(UI_STRINGS[LANG].delete_videos_confirm || "Delete ALL videos? (DANGEROUS)")) return; try { await runTool("delete_all_videos"); } catch (e) { @@ -682,7 +882,7 @@ function initToolsPage() { }); bindOnce("btnDeleteLogs", async () => { - if (!confirm("Delete ALL logs? (DANGEROUS)")) return; + if (!confirm(UI_STRINGS[LANG].delete_logs_confirm || "Delete ALL logs? (DANGEROUS)")) return; try { await runTool("delete_all_logs"); } catch (e) { @@ -695,7 +895,7 @@ function initToolsPage() { bindOnce("btnBackupSettings", async () => { try { const j = await runTool("backup_settings"); - if (j.file) window.location.href = j.file; // �ٿ�ε� + if (j.file) window.location.href = j.file; // �ٿ�ε� } catch (e) { toolsMetaSet("error"); toolsOutSet("backup failed: " + e.message); @@ -706,11 +906,11 @@ function initToolsPage() { bindOnce("btnRestoreSettings", async () => { const inp = document.getElementById("restoreFile"); if (!inp || !inp.files || !inp.files[0]) { - alert("Select a backup json file first."); + alert(UI_STRINGS[LANG].select_backup_file || "Select a backup json file first."); return; } - if (!confirm("Restore settings from file?\n\nThis will overwrite many Params values.")) return; + if (!confirm(UI_STRINGS[LANG].restore_confirm || "Restore settings from file?\n\nThis will overwrite many Params values.")) return; try { toolsMetaSet("uploading..."); @@ -726,7 +926,7 @@ function initToolsPage() { toolsMetaSet("restore done"); toolsOutSet(JSON.stringify(j.result, null, 2)); - if (confirm("Restore done.\nReboot now?")) { + if (confirm(UI_STRINGS[LANG].restore_done_reboot || "Restore done.\nReboot now?")) { await runTool("reboot"); toolsMetaSet("rebooting..."); toolsOutSet("reboot requested"); @@ -739,9 +939,9 @@ function initToolsPage() { }); bindOnce("btnReboot", async () => { - if (!confirm("Reboot now?")) return; + if (!confirm(UI_STRINGS[LANG].confirm_reboot || "Reboot now?")) return; try { - // �װ� �̹� ���� /api/reboot�� �� �Ÿ� �̰ɷ� �ٲ㵵 ��: + // �װ� �̹� ���� /api/reboot�� �� �Ÿ� �̰ɷ� �ٲ㵵 ��: // await postJson("/api/reboot", {}); await runTool("reboot"); toolsMetaSet("rebooting..."); @@ -762,7 +962,7 @@ function initToolsPage() { try { const j = await runTool("shell_cmd", { cmd }); - // j.out�� stdout/stderr ��ģ ��� + // j.out�� stdout/stderr ��ģ ��� toolsOutSet(j.out || "(no output)"); } catch (e) { toolsOutSet("error: " + e.message); @@ -774,7 +974,7 @@ function initToolsPage() { async function loadBranchesAndShow() { showPage("branch", true); if (!branchMeta || !branchList) { - alert("Branch DOM missing (branchMeta / branchList)"); + alert(UI_STRINGS[LANG].branch_dom_missing || "Branch DOM missing"); return; } branchMeta.textContent = "loading..."; @@ -805,22 +1005,22 @@ function renderBranchList() { } async function onSelectBranch(branch) { - if (!confirm(`Checkout branch?\n\n${branch}\n\nContinue?`)) return; + if (!confirm((UI_STRINGS[LANG].checkout_confirm || "Checkout branch?") + `\n\n${branch}\n\nContinue?`)) return; try { await runTool("git_checkout", { branch }); - alert("Branch changed."); + alert(UI_STRINGS[LANG].branch_changed || "Branch changed."); } catch (e) { - alert("Checkout failed: " + e.message); + alert((UI_STRINGS[LANG].set_failed || "Checkout failed: ") + e.message); return; } - const rb = confirm("Reboot now?"); + const rb = confirm(UI_STRINGS[LANG].confirm_reboot || "Reboot now?"); if (!rb) return; try { - await runTool("reboot"); // �Ǵ� /api/reboot - alert("Rebooting..."); + await runTool("reboot"); // 또는 /api/reboot + alert(UI_STRINGS[LANG].rebooting || "Rebooting..."); } catch (e) { alert("Reboot failed: " + e.message); } @@ -846,7 +1046,7 @@ function rtcCancelRetry() { } async function rtcDisconnect() { - rtcCancelRetry(); // �߰� + rtcCancelRetry(); // �߰� try { if (RTC_PC) RTC_PC.close(); } catch {} RTC_PC = null; const v = document.getElementById("rtcVideo"); @@ -859,7 +1059,7 @@ async function rtcDisconnect() { } function rtcScheduleRetry(ms = 2000) { - rtcCancelRetry(); // �׻� ���� ��´� + rtcCancelRetry(); // �׻� ���� ��´� RTC_RETRY_T = setTimeout(async () => { RTC_RETRY_T = null; await rtcConnectOnce().catch(() => {}); @@ -968,7 +1168,7 @@ async function rtcConnectOnce() { await waitIceComplete(pc, 8000); - const url = "/stream"; + const url = "/stream"; const body = { sdp: pc.localDescription.sdp, cameras: ["road"], @@ -997,8 +1197,8 @@ async function rtcConnectOnce() { } catch (e) { rtcStatusSet("error: " + e.message); - await rtcDisconnect(); // ���� �� ������ ���� - rtcScheduleRetry(2000); // ���⼭�� ������ ��õ� + await rtcDisconnect(); // ���� �� ������ ���� + rtcScheduleRetry(2000); // ���⼭�� ������ ��õ� throw e; } } @@ -1007,7 +1207,7 @@ async function waitServerReady(timeoutMs = 8000) { const t0 = Date.now(); while (Date.now() - t0 < timeoutMs) { try { - // ���� ����ִ����� Ȯ�� (������ API) + // ���� ����ִ����� Ȯ�� (������ API) const r = await fetch("/api/settings", { cache: "no-store" }); if (r.ok) return true; } catch {} @@ -1019,7 +1219,7 @@ async function waitServerReady(timeoutMs = 8000) { function rtcInitAuto() { (async () => { rtcStatusSet("waiting server..."); - await waitServerReady(8000); // �����ص� ��� ���� + await waitServerReady(8000); // �����ص� ��� ���� await rtcConnectOnce().catch(() => {}); })(); @@ -1031,11 +1231,11 @@ const btnRtcFs = document.getElementById("btnRtcFs"); const rtcVideoEl = document.getElementById("rtcVideo"); const rtcWrap = document.getElementById("rtcWrap"); -// ���� ����ó������ ȣ��ǵ���: ��ư Ŭ�� / ���� �� �̺�Ʈ������ ���� +// ���� ����ó������ ȣ��ǵ���: ��ư Ŭ�� / ���� �� �̺�Ʈ������ ���� async function rtcToggleFullscreen() { const target = rtcWrap || rtcVideoEl; - // �̹� Ǯ��ũ���̸� ���� + // �̹� Ǯ��ũ���̸� ���� const fsEl = document.fullscreenElement || document.webkitFullscreenElement; if (fsEl) { if (document.exitFullscreen) await document.exitFullscreen().catch(()=>{}); @@ -1043,32 +1243,32 @@ async function rtcToggleFullscreen() { return; } - // 1) ǥ�� Fullscreen API (��κ��� ũ��/�ȵ�/����ũž) + // 1) ǥ�� Fullscreen API (��κ��� ũ��/�ȵ�/����ũž) if (target.requestFullscreen) { await target.requestFullscreen().catch(()=>{}); return; } - // 2) Safari (�Ϻδ� webkitRequestFullscreen) + // 2) Safari (�Ϻδ� webkitRequestFullscreen) if (target.webkitRequestFullscreen) { target.webkitRequestFullscreen(); return; } - // 3) iOS Safari: video ���� ��üȭ�� (���� �� ����) - // (����: iOS�� inline ���/��å ������ �� ����� �� ������) + // 3) iOS Safari: video ���� ��üȭ�� (���� �� ����) + // (����: iOS�� inline ���/��å ������ �� ����� �� ������) if (target.webkitEnterFullscreen) { target.webkitEnterFullscreen(); return; } - alert("Fullscreen not supported on this browser."); + alert(UI_STRINGS[LANG].fullscreen_not_supported || "Fullscreen not supported on this browser."); } -// ��ư +// ��ư if (btnRtcFs) btnRtcFs.onclick = rtcToggleFullscreen; -// ���� ��(���ϸ�) +// ���� ��(���ϸ�) if (rtcVideoEl) { rtcVideoEl.style.cursor = "pointer"; rtcVideoEl.addEventListener("click", rtcToggleFullscreen); @@ -1153,7 +1353,7 @@ function drivingHudUpdateFromCarPayload(j) { window.DrivingHud.update(payload); } function carWsConnect() { - // �̹� ��������� �н� + // �̹� ��������� �н� if (CAR_WS && (CAR_WS.readyState === WebSocket.OPEN || CAR_WS.readyState === WebSocket.CONNECTING)) return; const wsProto = (location.protocol === "https:") ? "wss" : "ws"; @@ -1224,6 +1424,7 @@ async function updateQuickLink() { function startAll() { + renderUIText(); showPage("home", false); rtcInitAuto(); updateQuickLink().catch(() => {}); diff --git a/selfdrive/carrot/web/hud_card.js b/selfdrive/carrot/web/hud_card.js index 41249e5363..e77b392f4a 100644 --- a/selfdrive/carrot/web/hud_card.js +++ b/selfdrive/carrot/web/hud_card.js @@ -49,7 +49,13 @@ function setDriveMode(name, kind){ const el = $("hudDriveMode"); if (!el) return; - el.textContent = name || "일반"; + + let modeName = name; + if (window.DRIVE_MODES && window.LANG) { + modeName = window.DRIVE_MODES[window.LANG][kind] || name; + } + + el.textContent = modeName || (window.DRIVE_MODES && window.LANG ? window.DRIVE_MODES[window.LANG].normal : "Normal"); el.classList.remove("mode_normal","mode_eco","mode_safe","mode_sport"); if (kind === "eco") el.classList.add("mode_eco"); else if (kind === "safe") el.classList.add("mode_safe"); @@ -133,7 +139,7 @@ setBars(0); setSignalDot("off"); setGps(false); - setDriveMode("일반","normal"); + setDriveMode("","normal"); setRoadLimit(null, false); setGapNum(null); setGear("U"); diff --git a/selfdrive/carrot/web/index.html b/selfdrive/carrot/web/index.html index a651624d4c..08580d455a 100644 --- a/selfdrive/carrot/web/index.html +++ b/selfdrive/carrot/web/index.html @@ -261,7 +261,7 @@
-

Home

+

Home

@@ -301,8 +301,8 @@

Home

-
GPS
-
일반
+
GPS
+
Normal
@@ -324,7 +324,7 @@

Home