diff --git a/auto_tuner_guide.md b/auto_tuner_guide.md new file mode 100644 index 0000000000..7e474dd5ef --- /dev/null +++ b/auto_tuner_guide.md @@ -0,0 +1,63 @@ +# ๐Ÿฅ• CarrotPilot Auto-Tuner ์‚ฌ์šฉ ์•ˆ๋‚ด + +Auto-Tuner๋Š” ์šด์ „์ž์˜ ์‹ค์ œ ์ฃผํ–‰ ํŒจํ„ด์„ ํ•™์Šตํ•˜์—ฌ ์˜คํ”ˆํŒŒ์ผ๋Ÿฟ์˜ ์„ค์ •๊ฐ’์„ **๋‚ด ์ฐจ์™€ ๋‚ด ์šด์ „ ์Šต๊ด€์— ๋งž๊ฒŒ ์ž๋™์œผ๋กœ ์ตœ์ ํ™”(ํŠœ๋‹)**ํ•ด์ฃผ๋Š” ์Šค๋งˆํŠธํ•œ ์ฃผํ–‰ ๋ณด์กฐ ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. + +--- + +## ๐Ÿ“Š ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ ๋ฐ ์ ์šฉ ๋ฐฉ์‹ + +1. **์ฃผํ–‰ ์ค‘ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ (Learning)** + ์šด์ „์ž๊ฐ€ ์˜คํ”ˆํŒŒ์ผ๋Ÿฟ์„ ์ผ  ์ƒํƒœ๋กœ ์ฃผํ–‰ํ•  ๋•Œ, ์‹œ์Šคํ…œ์€ ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ฐจ๋Ÿ‰์˜ ์†๋„, ๊ฐ€์†๋„, ๋ธŒ๋ ˆ์ดํฌ ํƒ€์ด๋ฐ, ์กฐํ–ฅ ๊ฐ๋„ ๋“ฑ์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค. ํŠนํžˆ ์‚ฌ์šฉ์ž๊ฐ€ ๋‹ต๋‹ตํ•จ์„ ๋А๊ปด **๊ฐ€์† ํŽ˜๋‹ฌ์„ ์ง์ ‘ ๋ฐŸ๊ฑฐ๋‚˜(์˜ค๋ฒ„๋ผ์ด๋“œ)**, ๋ถˆ์•ˆํ•ด์„œ **์ง์ ‘ ๋ธŒ๋ ˆ์ดํฌ๋ฅผ ๋ฐŸ๋Š” ๊ฐœ์ž…(Intervention)** ์ˆœ๊ฐ„์„ ์ค‘์ ์ ์œผ๋กœ ์ˆ˜์ง‘ํ•ฉ๋‹ˆ๋‹ค. + +2. **ํŒจํ„ด ๋ถ„์„ (Analyzing)** + ์ˆ˜์ง‘๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ํ˜„์žฌ ์˜คํ”ˆํŒŒ์ผ๋Ÿฟ ์„ค์ •๊ฐ’์ด ์šด์ „์ž์˜ ์‹ค์ œ ์ฃผํ–‰ ์„ฑํ–ฅ๊ณผ ์–ผ๋งˆ๋‚˜ ์ฐจ์ด๋‚˜๋Š”์ง€ ๋ถ„์„ํ•˜์—ฌ ์ด์ƒ์ ์ธ ํŒŒ๋ผ๋ฏธํ„ฐ ์ˆ˜์น˜๋ฅผ ๊ณ„์‚ฐํ•ฉ๋‹ˆ๋‹ค. + +3. **์ถ”์ฒœ ๋ฐ ์ ์šฉ (Applying)** + ์ฃผํ–‰์„ ๋งˆ์น˜๊ณ  **๊ธฐ์–ด๋ฅผ ์ฃผ์ฐจ(P)๋กœ ๋ณ€๊ฒฝ**ํ•˜๋ฉด, ๋ถ„์„๋œ ์ถ”์ฒœ ์„ค์ •๊ฐ’์ด ํŒ์—…์œผ๋กœ ์•ˆ๋‚ด๋ฉ๋‹ˆ๋‹ค. ์›ํ•˜๋Š” ํ•ญ๋ชฉ๋งŒ ์ฒดํฌํ•˜์—ฌ **[์„ ํƒ ์ ์šฉ]**์„ ๋ˆ„๋ฅด๋ฉด ์ฆ‰์‹œ ์‹œ์Šคํ…œ์— ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค. ์ ์šฉ๋œ ๋‚ด์—ญ์€ ์„ค์ • ๋ฉ”๋‰ด์˜ **[ํŠœ๋‹ ์ด๋ ฅ]** ํƒญ์—์„œ ์–ธ์ œ๋“ ์ง€ ํ™•์ธํ•˜๊ณ  ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +--- + +## โš™๏ธ ๊ทธ๋ฃน๋ณ„ ํŠœ๋‹ ์„ค์ • ์•ˆ๋‚ด + +Auto-Tuner๋Š” ํฌ๊ฒŒ ๋‹ค์„ฏ ๊ฐ€์ง€ ์˜์—ญ์˜ ์ฃผํ–‰๊ฐ์„ ํ•™์Šตํ•˜๊ณ  ๊ฐœ์„ ํ•ฉ๋‹ˆ๋‹ค. + +### ๐Ÿš€ [๊ฐ€์†] (Acceleration) +์˜คํ”ˆํŒŒ์ผ๋Ÿฟ์˜ ํฌ๋ฃจ์ฆˆ ๊ฐ€์† ๋Šฅ๋ ฅ(์†๋„ ๋Œ€์—ญ๋ณ„ ์—‘์…€ ๋ฐŸ๋Š” ์–‘)์„ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค. +์šด์ „์ž๊ฐ€ ํŠน์ • ์†๋„ ๊ตฌ๊ฐ„์—์„œ ์ง์ ‘ ์—‘์…€์„ ๋ฐŸ๋Š” ์‹œ๊ฐ„์ด ์ผ์ • ๊ธฐ์ค€(์ดˆ)์„ ์ดˆ๊ณผํ•˜๋ฉด, ๊ทธ ๊ตฌ๊ฐ„์˜ ๊ฐ€์† ํ•œ๊ณ„์น˜๋ฅผ ์ž๋™์œผ๋กœ ์˜ฌ๋ ค์ค๋‹ˆ๋‹ค. +- **CruiseMaxVals0 [0~10km/h]**: ์ถœ๋ฐœ ๋ฐ ๊ทน์ €์† ๊ตฌ๊ฐ„์˜ ๊ฐ€์†๋ ฅ +- **CruiseMaxVals1 [10~40km/h]**: ์ €์† ์‹œ๋‚ด ์ฃผํ–‰ ๊ตฌ๊ฐ„์˜ ๊ฐ€์†๋ ฅ +- **CruiseMaxVals2 [40~60km/h]**: ์ค‘์† ๊ตฌ๊ฐ„์˜ ๊ฐ€์†๋ ฅ +- **CruiseMaxVals3 [60~80km/h]**: ์ค‘๊ณ ์† ๊ตฌ๊ฐ„์˜ ๊ฐ€์†๋ ฅ +- **CruiseMaxVals4 [80~100km/h]**: ๊ณ ์† ๊ตฌ๊ฐ„์˜ ๊ฐ€์†๋ ฅ +- **CruiseMaxVals5 [100~130km/h]**: ์ดˆ๊ณ ์† ๊ตฌ๊ฐ„์˜ ๊ฐ€์†๋ ฅ +- **CruiseMaxVals6 [130km/h~]**: ์ตœ๊ณ ์† ๊ตฌ๊ฐ„์˜ ๊ฐ€์†๋ ฅ + +### ๐Ÿš™ [์ฃผํ–‰] (Driving) +์„ ํ–‰ ์ฐจ๋Ÿ‰๊ณผ์˜ ๊ฐ„๊ฒฉ ์œ ์ง€, ๊ฐ์† ํƒ€์ด๋ฐ ๋“ฑ ์ข…๋ฐฉํ–ฅ(๋ธŒ๋ ˆ์ดํฌ) ์ œ์–ด ๋Šฅ๋ ฅ์„ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค. +์šด์ „์ž๊ฐ€ ๋ธŒ๋ ˆ์ดํฌ๋ฅผ ์ง์ ‘ ๋ฐŸ์•„ ๊ฐœ์ž…(Intervention)ํ•˜๋Š” ํšŸ์ˆ˜๊ฐ€ ๋ˆ„์ ๋˜๋ฉด, ์‹œ์Šคํ…œ์ด ์•ž์ฐจ์™€์˜ ๊ฐ„๊ฒฉ์„ ๋” ์ผ์ฐ ๋ฒŒ๋ฆฌ๊ฑฐ๋‚˜ ๋ธŒ๋ ˆ์ดํฌ๋ฅผ ๋” ๊ฐ•ํ•˜๊ฒŒ ๋ฐŸ๋„๋ก ์œ ๋„ํ•ฉ๋‹ˆ๋‹ค. +- **JLeadFactor3 (๊ฐ์† ๋ฐ ์ •์ง€ ์ œ์–ด)**: ์•ž์ฐจ๊ฐ€ ๋ฉˆ์ถ”๊ฑฐ๋‚˜ ๊ฐ€๊นŒ์›Œ์งˆ ๋•Œ ๋ธŒ๋ ˆ์ดํฌ๋ฅผ ๋ฐŸ๋Š” ํƒ€์ด๋ฐ๊ณผ ๊ฐ•๋„๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค. ์ถ”์ฒœ๊ฐ’์„ ์ ์šฉํ•˜๋ฉด ์˜คํ”ˆํŒŒ์ผ๋Ÿฟ์ด ์กฐ๊ธˆ ๋” ์ผ์ฐ, ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ๊ฐ์†์„ ์‹œ์ž‘ํ•˜๋„๋ก ํŠœ๋‹๋˜์–ด ๋Šฆ์€ ์ œ๋™์œผ๋กœ ์ธํ•œ ๋ถˆ์•ˆ๊ฐ์„ ํ•ด์†Œํ•ฉ๋‹ˆ๋‹ค. + +### ๐Ÿ›ฃ๏ธ [๊ฑฐ๋ฆฌ] (Following Distance) +๊ณ ์†๋„๋กœ ์žฅ๊ฑฐ๋ฆฌ ์ฃผํ–‰ ์‹œ ์„ ํ–‰ ์ฐจ๋Ÿ‰๊ณผ์˜ ์ถ”์ข… ๊ฑฐ๋ฆฌ(TFollow)๋ฅผ ์ตœ์ ํ™”ํ•ฉ๋‹ˆ๋‹ค. +์šด์ „์ž๊ฐ€ ํฌ๋ฃจ์ฆˆ ์ธ๊ฒŒ์ด์ง€ ์ƒํƒœ์—์„œ ์„ ํ–‰์ฐจ๊ฐ€ ์žˆ์Œ์—๋„ ๊ฐ€์† ํŽ˜๋‹ฌ์„ ์ž์ฃผ ๋ฐŸ๋Š” ํŒจํ„ด์„ ํ•™์Šตํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” "์‹œ์Šคํ…œ์ด ์„ค์ •๋œ ๊ฒƒ๋ณด๋‹ค ์ง€๋‚˜์น˜๊ฒŒ ๊ฑฐ๋ฆฌ๋ฅผ ๋„“๊ฒŒ ๋ฒŒ๋ ค ์ฃผํ–‰ํ•˜๊ณ  ์žˆ์–ด, ์šด์ „์ž๊ฐ€ ๋Šฅ๋™์ ์œผ๋กœ ๊ฐ„๊ฒฉ์„ ์ขํžˆ๋ ค ํ•œ๋‹ค"๋Š” ์‹ ํ˜ธ๋กœ ํ•ด์„ํ•ฉ๋‹ˆ๋‹ค. ํ•ด๋‹น GAP ๋‹จ๊ณ„์˜ TFollowGap ๊ฐ’์„ ์ž๋™์œผ๋กœ ๋‚ฎ์ถฐ์ฃผ์–ด, ์šด์ „์ž๊ฐ€ ์›ํ•˜๋Š” ๋” ์ด˜์ด˜ํ•œ ๊ฐ„๊ฒฉ์œผ๋กœ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ถ”์ข…ํ•˜๋„๋ก ๊ฐœ์„ ํ•ฉ๋‹ˆ๋‹ค. +- **TFollowGap1 (GAP1, ๊ฐ€์žฅ ์ข์Œ)**: ๊ณต๊ฒฉ์  ์ฃผํ–‰ ์Šคํƒ€์ผ์— ์ ํ•ฉํ•œ ์ตœ์†Œ ์ถ”์ข… ๊ฑฐ๋ฆฌ (0.7s ์ด์ƒ) +- **TFollowGap2 (GAP2)**: ์ผ๋ฐ˜ ์ฃผํ–‰์šฉ ์ถ”์ข… ๊ฑฐ๋ฆฌ +- **TFollowGap3 (GAP3)**: ์—ฌ์œ ๋กœ์šด ์ถ”์ข… ๊ฑฐ๋ฆฌ +- **TFollowGap4 (GAP4, ๊ฐ€์žฅ ๋„“์Œ)**: ์žฅ๊ฑฐ๋ฆฌ ๊ณ ์† ์ฃผํ–‰์šฉ ์ตœ๋Œ€ ์ถ”์ข… ๊ฑฐ๋ฆฌ + +### ๐ŸŽ›๏ธ [๋™์ ์ œ์–ด] (Dynamic Control) +์„ ํ–‰์ฐจ ๋˜๋Š” ๋‚ด ์ฐจ์˜ ์†๋„ ๋ณ€ํ™”์— ๋”ฐ๋ผ ์ฐจ๊ฐ„๊ฑฐ๋ฆฌ๋ฅผ **๋™์ ์œผ๋กœ** ์กฐ์ ˆํ•˜๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ํ•™์Šตํ•ฉ๋‹ˆ๋‹ค. +๋ธŒ๋ ˆ์ดํฌ ๊ด€๋ จ ํŒŒ๋ผ๋ฏธํ„ฐ(JLeadFactor3 / DynamicTFollow / TFollowDecelBoost)๊ฐ€ ๋™์‹œ์— ์—ฌ๋Ÿฌ ๊ฐœ ๋ฐœ๋™๋˜๋ฉด, ๋ชจ๋‘ "๊ฑฐ๋ฆฌ ํ™•๋Œ€" ๋ฐฉํ–ฅ์ด๋ฏ€๋กœ ํ•ฉ์‚ฐ ์‹œ ๊ณผ๋ณด์ • ์œ„ํ—˜์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ Auto-Tuner๋Š” **์ด๋ฒคํŠธ๊ฐ€ ๊ฐ€์žฅ ๋งŽ์€ 1๊ฐœ๋งŒ ๋จผ์ € ์ถ”์ฒœ**ํ•˜๊ณ , ๋‚˜๋จธ์ง€๋Š” ๋‹ค์Œ ์„ธ์…˜์—์„œ ์žฌํ‰๊ฐ€ํ•˜๋„๋ก ์•ˆ๋‚ดํ•ฉ๋‹ˆ๋‹ค. +- **DynamicTFollow (์•ž์ฐจ ๊ธ‰๊ฐ์† ๋ฐ˜์‘ ๋ฏผ๊ฐ๋„)**: ์•ž์ฐจ๊ฐ€ ๊ฐ‘์ž๊ธฐ ๊ฐ์†ํ•  ๋•Œ ์ฐจ๊ฐ„๊ฑฐ๋ฆฌ๋ฅผ ์–ผ๋งˆ๋‚˜ ๋น ๋ฅด๊ฒŒ ๋„“ํž์ง€ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค. ์•ž์ฐจ์˜ ๊ธ‰๊ฒฉํ•œ ์†๋„ ๊ฐ์†Œ(jLead < -1.0) ์ค‘์— ์šด์ „์ž๊ฐ€ ์ง์ ‘ ๋ธŒ๋ ˆ์ดํฌ๋ฅผ ๋ฐŸ์€ ํšŸ์ˆ˜๊ฐ€ ์Œ“์ด๋ฉด, ์ด ๊ฐ’์„ ๋†’์—ฌ ์‹œ์Šคํ…œ์ด ์•ž์ฐจ์˜ ๊ฐ์†์— ๋” ๋ฏผ๊ฐํ•˜๊ฒŒ ๋ฐ˜์‘ํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. (0์ด๋ฉด ๋™์  ์กฐ์ ˆ ์—†์Œ) +- **TFollowDecelBoost (๊ฐ์† ์ค‘ ์ถ”๊ฐ€ ๊ฐ„๊ฒฉ ํ™•๋ณด)**: ๋‚ด ์ฐจ๊ฐ€ ๊ฐ•ํ•˜๊ฒŒ ๊ฐ์†ํ•˜๋Š” ์ƒํ™ฉ์—์„œ ์†๋„๊ฐ€ ์ค„์–ด๋“ค๋ฉด ์‹ค์ œ ๊ฑฐ๋ฆฌ ์—ฌ์œ ๋„ ํ•จ๊ป˜ ์ค„์–ด๋“ค๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๊ฐ•๊ฐ์†(a_ego < -0.8 m/sยฒ) ์ค‘์— ๋ธŒ๋ ˆ์ดํฌ ๊ฐœ์ž…์ด ๋ฐ˜๋ณต๋˜๋ฉด ์ด ๊ฐ’์„ ๋†’์—ฌ, ๊ฐ์†์ด ๊ฐ•ํ• ์ˆ˜๋ก ์ž๋™์œผ๋กœ ๋” ๋„‰๋„‰ํ•œ ๊ฐ„๊ฒฉ์„ ์œ ์ง€ํ•˜๋„๋ก ๋ณด์™„ํ•ฉ๋‹ˆ๋‹ค. (๊ธฐ๋ณธ๊ฐ’ 10, ๋ฒ”์œ„ 0~100) + +### ๐Ÿ”„ [์กฐํ–ฅ] (Steering) +์ง์ง„ ์ฃผํ–‰ ์‹œ์˜ ์ ๋ฆผ ํ˜„์ƒ๊ณผ ๊ณก์„ (์ปค๋ธŒ) ๋„๋กœ์—์„œ์˜ ํ•ธ๋“ค๋ง ๋ฐ˜์‘์„ฑ์„ ์กฐ์ •ํ•ฉ๋‹ˆ๋‹ค. +- **PathOffset (์ง์ง„ ํŽธ์ฐจ)**: ์ง์„  ๋„๋กœ๋ฅผ ๋‹ฌ๋ฆด ๋•Œ ์šด์ „์ž๊ฐ€ ์ง€์†์ ์œผ๋กœ ํ•ธ๋“ค์„ ๋ฏธ์„ธํ•˜๊ฒŒ ํ•œ์ชฝ์œผ๋กœ ๋‹น๊ธฐ๊ณ  ์žˆ๋Š” ํŒจํ„ด์„ ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค. ์ฐจ๊ฐ€ ๋ฏธ์„ธํ•˜๊ฒŒ ์ขŒ์ธก์ด๋‚˜ ์šฐ์ธก์œผ๋กœ ์ ๋ ค์„œ ์ฃผํ–‰ํ•œ๋‹ค๊ณ  ํŒ๋‹จ๋˜๋ฉด ์ฐจ์„  ๋‚ด ์ค‘์•™ ์œ„์น˜(Offset)๋ฅผ ๋ณด์ •ํ•ฉ๋‹ˆ๋‹ค. +- **SteerActuatorDelay (์กฐํ–ฅ ๋ฐ˜์‘ ์ง€์—ฐ)**: ์ปค๋ธŒ ์ง„์ž… ์‹œ ์˜คํ”ˆํŒŒ์ผ๋Ÿฟ์˜ ์กฐํ–ฅ์ด ๋Šฆ์–ด ์šด์ „์ž๊ฐ€ ํ•ธ๋“ค์„ ์ง์ ‘ ๊บพ์–ด(์˜ค๋ฒ„๋ผ์ด๋“œ) ๊ถค์ ์„ ์ˆ˜์ •ํ•˜๋Š” ํŒจํ„ด์„ ํ•™์Šตํ•ฉ๋‹ˆ๋‹ค. ์žฆ์€ ์˜ค๋ฒ„๋ผ์ด๋“œ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์•ก์ถ”์—์ดํ„ฐ์˜ ์ง€์—ฐ ์‹œ๊ฐ„(Delay) ๋ณ€์ˆ˜๋ฅผ ๋Š˜๋ ค, ์„ ํ–‰ ์กฐํ–ฅ๊ฐ์„ ๋” ๊ณต๊ฒฉ์ ์œผ๋กœ ๊บพ์–ด์ฃผ์–ด ๋น ๋ฅด๊ณ  ๋‚ ์นด๋กœ์šด ์ฝ”๋„ˆ๋ง์„ ์ˆ˜ํ–‰ํ•˜๋„๋ก ์ตœ์ ํ™”ํ•ฉ๋‹ˆ๋‹ค. +- **SteerRatioRate (์กฐํ–ฅ๋น„์œจ ๋ฐฐ์œจ)**: SteerActuatorDelay๋ฅผ ์˜ฌ๋ ค๋„ ์ปค๋ธŒ ์ถ”์ข…์ด ๊ฐœ์„ ๋˜์ง€ ์•Š๊ณ  ์˜ค๋ฒ„๋ผ์ด๋“œ ๋นˆ๋„๊ฐ€ ์—ฌ์ „ํžˆ ๋งค์šฐ ๋†’์€ ๊ฒฝ์šฐ, ์กฐํ–ฅ๋ ฅ ์ž์ฒด๊ฐ€ ๋ถ€์กฑํ•œ ๊ฒƒ์œผ๋กœ ํŒ๋‹จํ•ฉ๋‹ˆ๋‹ค. LiveParameters์—์„œ ์ธก์ •๋œ ์‹ค์ œ ์ฐจ๋Ÿ‰์˜ ์กฐํ–ฅ๋น„(SR)์— ์ถ”๊ฐ€๋กœ ๋ฐฐ์œจ์„ ๊ณฑํ•ด์ฃผ์–ด, ๊ฐ™์€ ํ•ธ๋“ค ๊ฐ๋„์—์„œ ๋” ๊ฐ•ํ•˜๊ฒŒ ์กฐํ–ฅํ•˜๋„๋ก ๋งŒ๋“ค์–ด ์ฝ”๋„ˆ์—์„œ ์ฐจ์„  ์ค‘์•™ ์œ ์ง€ ๋Šฅ๋ ฅ์„ ๊ฐœ์„ ํ•ฉ๋‹ˆ๋‹ค. + +--- + +## โ„น๏ธ ์ฐธ๊ณ : ์ปค๋ธŒ ๊ตฌ๊ฐ„ ์†๋„ ์ œ์–ด +์ปค๋ธŒ ๊ตฌ๊ฐ„์—์„œ ๊ณ ์ •๋œ ์†๋„ ์ œํ•œ์€ ์ง์„  ์ฃผํ–‰์—์„œ๋Š” ๋‹ต๋‹ตํ•˜๊ณ , ๊ธ‰์ปค๋ธŒ์—์„œ๋Š” ๋ถˆ์•ˆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. CarrotPilot์€ **์˜คํ”ˆํŒŒ์ผ๋Ÿฟ ๋ชจ๋ธ์ด ์˜ˆ์ธกํ•˜๋Š” ๋„๋กœ ๊ณก๋ฅ (Curvature)**์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ฝ์–ด ์ปค๋ธŒ์˜ ๊ฐ•๋„์— ๋”ฐ๋ผ ๊ฐ€์†์„ ์ž๋™์œผ๋กœ ์ œํ•œํ•˜๋Š” **๋™์  ์ปค๋ธŒ ์†๋„ ์ œ์–ด(Adaptive Curve Speed)** ๊ธฐ๋Šฅ์„ ๋ณ„๋„ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ง€์›ํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค. ์ด ๊ธฐ๋Šฅ์€ Auto-Tuner์™€๋Š” ๋ณ„๊ฐœ๋กœ ์„ค์ • ๋ฉ”๋‰ด์˜ ํฌ๋ฃจ์ฆˆ/์†๋„ ํƒญ์—์„œ ์ง์ ‘ ์กฐ์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. diff --git a/common/params_keys.h b/common/params_keys.h index 5b5e4a76a4..055de571d2 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -161,6 +161,12 @@ inline static std::unordered_map keys = { {"ShowPathColorLane", {PERSISTENT, INT, "13"}}, {"ShowPlotMode", {PERSISTENT, INT, "0"}}, {"CarrotTireTrajectory", {PERSISTENT, INT, "0"}}, + {"CarrotLearningActive", {PERSISTENT, INT, "0"}}, // Auto-Tuner: ํ•™์Šต ํ™œ์„ฑํ™” (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) {"RecordRoadCam", {PERSISTENT, INT, "0"}}, {"HDPuse", {PERSISTENT, INT, "0"}}, diff --git a/karpathy.md b/karpathy.md new file mode 100644 index 0000000000..c5fec7f502 --- /dev/null +++ b/karpathy.md @@ -0,0 +1,73 @@ + +Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed. + +**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment. + +## 1. Think Before Coding + +**Don't assume. Don't hide confusion. Surface tradeoffs.** + +Before implementing: +- State your assumptions explicitly. If uncertain, ask. +- If multiple interpretations exist, present them - don't pick silently. +- If a simpler approach exists, say so. Push back when warranted. +- If something is unclear, stop. Name what's confusing. Ask. + +## 2. Simplicity First + +**Minimum code that solves the problem. Nothing speculative.** + +- No features beyond what was asked. +- No abstractions for single-use code. +- No "flexibility" or "configurability" that wasn't requested. +- No error handling for impossible scenarios. +- If you write 200 lines and it could be 50, rewrite it. + +Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify. + +## 3. Surgical Changes + +**Touch only what you must. Clean up only your own mess.** + +When editing existing code: +- Don't "improve" adjacent code, comments, or formatting. +- Don't refactor things that aren't broken. +- Match existing style, even if you'd do it differently. +- If you notice unrelated dead code, mention it - don't delete it. + +When your changes create orphans: +- Remove imports/variables/functions that YOUR changes made unused. +- Don't remove pre-existing dead code unless asked. + +The test: Every changed line should trace directly to the user's request. + +## 4. Goal-Driven Execution + +**Define success criteria. Loop until verified.** + +Transform tasks into verifiable goals: +- "Add validation" โ†’ "Write tests for invalid inputs, then make them pass" +- "Fix the bug" โ†’ "Write a test that reproduces it, then make it pass" +- "Refactor X" โ†’ "Ensure tests pass before and after" + +For multi-step tasks, state a brief plan: +``` +1. [Step] โ†’ verify: [check] +2. [Step] โ†’ verify: [check] +3. [Step] โ†’ verify: [check] +``` + +Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification. + +--- + +**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes. + +## 5. Domain-Specific Verification (Openpilot / Cereal) + +**Never guess dynamic schemas. Verify against existing patterns.** + +When working with Openpilot's `cereal` (Cap'n Proto) framework or similar dynamic data structures: +- **No Blind Imports:** Do not assume standard Python directory structures (e.g., `from cereal.car import CarState`). `cereal` generates objects dynamically from `.capnp` schemas. +- **Cross-Reference Usage:** Before using a new attribute or enum (e.g., `GearShifter`), always `grep` or `view_file` existing Openpilot codebase to see exactly how it is imported and referenced (e.g., `from cereal import car` -> `car.CarState.GearShifter.park`). +- **Assume Local Context Blindness:** Understand that local Mac environments often lack the `capnp` libraries or hardware dependencies required to test Openpilot code locally. Because local unit tests cannot catch `AttributeError` or `ModuleNotFoundError` for these hardware schemas, extreme caution and code-pattern matching must be used before committing. diff --git a/selfdrive/carrot/carrot_functions.py b/selfdrive/carrot/carrot_functions.py index 6de61ae8e3..8544efa20d 100644 --- a/selfdrive/carrot/carrot_functions.py +++ b/selfdrive/carrot/carrot_functions.py @@ -8,6 +8,7 @@ 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 EventName = log.OnroadEvent.EventName LaneChangeState = log.LaneChangeState @@ -139,6 +140,7 @@ def __init__(self): self._stop_x_rl = None self.last_event_time = 0.0 + self.learner = CarrotLearner() def _params_update(self): self.frame += 1 @@ -628,6 +630,28 @@ 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 + + # ํ˜„์žฌ 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) + + 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) + 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..f7a8b7b684 --- /dev/null +++ b/selfdrive/carrot/carrot_learning.py @@ -0,0 +1,593 @@ +""" +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 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 # ๊ฐ€์† ์ค‘ ๋ธŒ๋ ˆ์ดํฌ ๊ฐœ์ž… ๋ˆ„์  ๊ธฐ์ค€ (์ดˆ) + +# โ”€โ”€ Phase 2 ์ƒ์ˆ˜ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +_STRAIGHT_DEG = 5.0 # ์ง์ง„ ํŒ๋‹จ ์กฐํ–ฅ๊ฐ ์ž„๊ณ„๊ฐ’ (๋„) +_CURVE_RATE_DEG_S = 10.0 # ์ปค๋ธŒ ์ง„์ž… ํŒ๋‹จ: ์กฐํ–ฅ๊ฐ ๋ณ€ํ™”์œจ ์ž„๊ณ„๊ฐ’ (๋„/์ดˆ) +_LATERAL_MIN_SAMPLES = 200 # PathOffset ์ถ”์ฒœ์„ ์œ„ํ•œ ์ตœ์†Œ ์ง์ง„ ์ƒ˜ํ”Œ ์ˆ˜ +_LATERAL_MIN_CURVE = 20 # SteerActuatorDelay ์ถ”์ฒœ์„ ์œ„ํ•œ ์ตœ์†Œ ์ปค๋ธŒ ์ด๋ฒคํŠธ ์ˆ˜ +_PATH_OFFSET_DEG_THRESHOLD = 1.5 # ํ‰๊ท  ํŽธ์ฐจ๊ฐ€ ์ด ์ด์ƒ์ด๋ฉด PathOffset ์ถ”์ฒœ (๋„) +_PATH_OFFSET_DEG_PER_UNIT = 0.1 # 1 ๋„ ํŽธ์ฐจ โ‰ˆ 10 units PathOffset ๋ณ€ํ™” (์‹คํ—˜๊ฐ’) +_CURVE_OVERRIDE_RATIO = 0.5 # ์ปค๋ธŒ ์ง„์ž…์˜ 50% ์ด์ƒ์—์„œ override โ†’ SteerActuatorDelay ์ฆ๊ฐ€ +_DELAY_STEP_UNIT = 10 # SteerActuatorDelay ํ•œ ๋ฒˆ ์ถ”์ฒœ ์‹œ ๋ณ€ํ™”๋Ÿ‰ (UI ๋‹จ์œ„, +0.1s) +_SR_RATE_OVERRIDE_RATIO = 0.7 # ์ปค๋ธŒ ์ง„์ž…์˜ 70% ์ด์ƒ์—์„œ override โ†’ SteerRatioRate ์ถ”๊ฐ€ ์ถ”์ฒœ +_SR_RATE_STEP_UNIT = 3 # SteerRatioRate ํ•œ ๋ฒˆ ์ถ”์ฒœ ์‹œ ๋ณ€ํ™”๋Ÿ‰ (+3%) + +# โ”€โ”€ Phase 3 ์ƒ์ˆ˜ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +_BRAKE_MIN_COUNT = 5 # ์ถ”์ฒœ์„ ์œ„ํ•œ ์ตœ์†Œ ์ˆ˜๋™ ๋ธŒ๋ ˆ์ดํฌ ํšŸ์ˆ˜ +_JLEAD_STEP_UNIT = 10 # JLeadFactor3 ํ•œ ๋ฒˆ ์ถ”์ฒœ ์‹œ ๋ณ€ํ™”๋Ÿ‰ +_JLEAD_REDUCE_STEP = -7 # ์ œ๋™ ๊ณผ๋‹ค ์‹œ ๋ณ€ํ™”๋Ÿ‰ +_JLEAD_GAS_THRESHOLD_SEC = 5.0 # ์ œ๋™ ์ค‘ ๊ฐ€์† ๊ฐœ์ž… ๋ˆ„์  ๊ธฐ์ค€ (์ดˆ) + +# โ”€โ”€ Phase 5 ์ƒ์ˆ˜ (DynamicTFollow / TFollowDecelBoost) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +# DynamicTFollow: ์•ž์ฐจ ๊ธ‰๊ฐ์†(jLeadโ†“) ์ค‘ ๋ธŒ๋ ˆ์ดํฌ ๊ฐœ์ž… ํšŸ์ˆ˜ ๊ธฐ๋ฐ˜ +_DYN_TFOLLOW_BRAKE_MIN = 4 # ์ถ”์ฒœ ๋ฐœ๋™ ์ตœ์†Œ ์ด๋ฒคํŠธ ์ˆ˜ +_DYN_JLEAD_THRESHOLD = -1.0 # ์•ž์ฐจ ๊ฐ€๊ฐ์†๋„ (m/s^3) ์ดํ•˜์ด๋ฉด '๊ธ‰๊ฐ์†' ํŒ์ • +_DYN_TFOLLOW_STEP = 5 # DynamicTFollow ํ•œ ๋ฒˆ ์ถ”์ฒœ ์‹œ ๋ณ€ํ™”๋Ÿ‰ (+0.05) +_DYN_TFOLLOW_MAX = 100 # ์ตœ๋Œ€๊ฐ’ (=1.0) +# TFollowDecelBoost: ๋‚ด ์ฐจ ๊ฐ์† ์ค‘ ๋ธŒ๋ ˆ์ดํฌ ๊ฐœ์ž… ํšŸ์ˆ˜ ๊ธฐ๋ฐ˜ +_DECEL_BOOST_BRAKE_MIN = 4 # ์ถ”์ฒœ ๋ฐœ๋™ ์ตœ์†Œ ์ด๋ฒคํŠธ ์ˆ˜ +_DECEL_A_THRESHOLD = -0.8 # ๋‚ด ์ฐจ ๊ฐ์†๋„ (m/s^2) ์ดํ•˜์ด๋ฉด '๊ฐ•ํ•œ ๊ฐ์†' ํŒ์ • +_DECEL_BOOST_STEP = 5 # TFollowDecelBoost ํ•œ ๋ฒˆ ์ถ”์ฒœ ์‹œ ๋ณ€ํ™”๋Ÿ‰ (+0.05) +_DECEL_BOOST_MAX = 60 # ์ตœ๋Œ€๊ฐ’ + +# โ”€โ”€ 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_STEP_UNIT = -5 # ๊ฐ์†Œ ์ถ”์ฒœ (-5 units = -0.05s) +_TFOLLOW_MIN_LEAD_DREL = 120.0 # ์„ ํ–‰์ฐจ ๊ฑฐ๋ฆฌ๊ฐ€ ์ด๋ณด๋‹ค ๊ฐ€๊นŒ์šฐ๋ฉด '๊ฐ„๊ฒฉ ์ขํžˆ๊ธฐ' ํŒจํ„ด ์•„๋‹˜ +_TFOLLOW_MIN_V_KPH = 60.0 # ๊ณ ์†๋„๋กœ ์ฃผํ–‰ ๊ตฌ๊ฐ„์—์„œ๋งŒ ํ•™์Šต +_TFOLLOW_WIDEN_STEP = 5 # ์ฆ๊ฐ€ ์ถ”์ฒœ (+0.05s) +_TFOLLOW_BRAKE_THRESHOLD_SEC = 10.0 # ๊ฑฐ๋ฆฌ ๋ถ€์กฑ์œผ๋กœ ์ธํ•œ ๋ธŒ๋ ˆ์ดํฌ ๋ˆ„์  ๊ธฐ์ค€ (์ดˆ) +_AUTO_HUNTING_THRESHOLD = 0.8 # ์ž์œจ ์ฃผํ–‰ ์ค‘ ๊ฐ€๊ฐ์† ๋ณ€๋™(Hunting) ๊ฐ์ง€ ์ž„๊ณ„์น˜ (m/s^2) + +# โ”€โ”€ ๊ณตํ†ต โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +_DT = 0.1 # update() ํ˜ธ์ถœ ์ฃผ๊ธฐ (์ดˆ) + + +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 # ์ž์œจ ์ฃผํ–‰ ์ค‘ ๊ธ‰๊ฐ€์† ๊ฐ์ง€ + # Phase 2 + self._steer_acc = 0.0 # ์ง์ง„ ๊ตฌ๊ฐ„ ์กฐํ–ฅ๊ฐ ๋ˆ„์ ํ•ฉ (๋„) + self._steer_count = 0 # ์ง์ง„ ์ƒ˜ํ”Œ ์ˆ˜ + self._prev_steer_deg = 0.0 # ์ด์ „ ํ”„๋ ˆ์ž„ ์กฐํ–ฅ๊ฐ + self._curve_entries = 0 # ์ปค๋ธŒ ์ง„์ž… ์ด๋ฒคํŠธ ์ˆ˜ + self._curve_overrides = 0 # ์ปค๋ธŒ ์ง„์ž… ์ค‘ steeringPressed ์ด๋ฒคํŠธ ์ˆ˜ + 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 + # 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._current_gap = 1 # ํ˜„์žฌ ํ™œ์„ฑํ™”๋œ GAP ๋‹จ๊ณ„ (1~4) + # Phase 5 (DynamicTFollow / TFollowDecelBoost) + self._dyn_brake_count = 0 # ์•ž์ฐจ ๊ธ‰๊ฐ์† ์ค‘ ๋ธŒ๋ ˆ์ดํฌ ๊ฐœ์ž… ํšŸ์ˆ˜ + self._decel_brake_count = 0 # ๋‚ด ์ฐจ ๊ฐ•ํ•œ ๊ฐ์† ์ค‘ ๋ธŒ๋ ˆ์ดํฌ ๊ฐœ์ž… ํšŸ์ˆ˜ + + self._prev_gear_park = True # ์ดˆ๊ธฐ๊ฐ’(์‹œ๋™ ์‹œ P๋‹จ ๊ฐ„์ฃผ) + self._has_driven = False # ์ฃผํ–‰(D๋‹จ/์ด๋™) ์—ฌ๋ถ€ ํ”Œ๋ž˜๊ทธ + self._prev_a_ego = 0.0 # ์ด์ „ ํ”„๋ ˆ์ž„ ๊ฐ€์†๋„ + self._accel_swing_count = 0 # ๊ฐ€๊ฐ์† ๋ฐ˜์ „(Hunting) ์นด์šดํŠธ + + self._load() + + # ------------------------------------------------------------------ + # ๊ณต๊ฐœ API + # ------------------------------------------------------------------ + + def set_current_gap(self, gap: int): + """ํ˜„์žฌ GAP ๋‹จ๊ณ„ ์„ค์ • (1~4). CarrotPlanner์—์„œ ๋งค ํ”„๋ ˆ์ž„ ์ „๋‹ฌ.""" + self._current_gap = max(1, min(4, gap)) + + 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): + """ + ๋งค ํ”„๋ ˆ์ž„ ํ˜ธ์ถœ. + - 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 ์ €์žฅ + """ + if not self._is_active(): + return + + # UI๋กœ๋ถ€ํ„ฐ ์ดˆ๊ธฐํ™”(Clear) ์‹ ํ˜ธ๊ฐ€ ์˜ค๋ฉด ๋‚ด๋ถ€ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๋น„์›€ + if self._params.get_bool("CarrotLearningClear"): + self._clear_all_data() + self._params.put_bool("CarrotLearningClear", False) + + # ์ฃผํ–‰ ์—ฌ๋ถ€ ํŒ๋‹จ (์ธ๊ฒŒ์ด์ง€ ๋˜๊ฑฐ๋‚˜ ์†๋„๊ฐ€ 5km/h ์ด์ƒ์ด๋ฉด ์ฃผํ–‰ํ•œ ๊ฒƒ์œผ๋กœ ๊ฐ„์ฃผ) + if engaged or v_ego_kph >= 5.0: + self._has_driven = True + + # โ”€โ”€ Phase 1: ๊ฐ€์† ๊ฐœ์ž… โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + # ๋‹จ์ˆœํžˆ ์„ค์ •์†๋„์— ๋„๋‹ฌํ–ˆ๋Š”๋ฐ ๋” ๋นจ๋ฆฌ ๊ฐ€๊ณ  ์‹ถ์–ด ๋ฐŸ๋Š” ๊ฒฝ์šฐ๋Š” ์ œ์™ธ (์„ค์ •์†๋„ ์˜ค๋ฒ„๋ผ์ด๋“œ) + # ์ฆ‰, ์„ค์ •์†๋„๋ณด๋‹ค ์ถฉ๋ถ„ํžˆ ๋‚ฎ์€๋ฐ๋„ ๊ฐ€์†์ด ๋‹ต๋‹ตํ•  ๋•Œ๋งŒ ํ•™์Šต์— ํฌํ•จ + if engaged and gas_pressed and v_ego_kph >= 1.0: + if v_ego_kph < (v_cruise_kph - 3.0): + self._gas_acc[_speed_band(v_ego_kph)] += _DT + + # ๊ฐ€์† ๊ณผ๋‹ค ํ•™์Šต ๋ฐฉ์ง€: ๊ฐ€์† ์ค‘์ธ๋ฐ ๋ธŒ๋ ˆ์ดํฌ๋ฅผ ๋ฐŸ๋Š” ๊ฒฝ์šฐ OR ์ž์œจ ์ฃผํ–‰ ์ค‘ ๊ณผ๋„ํ•œ ๊ฐ€์† + if engaged and v_ego_kph < (v_cruise_kph - 3.0): + idx = _speed_band(v_ego_kph) + if brake_pressed: + if lead_drel == 0 or lead_drel > 120.0: + self._gas_dec_acc[idx] += _DT + elif not gas_pressed and a_ego > 1.5: # ์ž์œจ ์ฃผํ–‰ ์ค‘ ๊ธ‰๊ฐ€์† ๊ฐ์ง€ + self._gas_dec_acc[idx] += _DT * 0.5 + + # โ”€โ”€ Phase 2: ์กฐํ–ฅ ํŒจํ„ด (์†๋„ 20km/h ์ด์ƒ, ์ธ๊ฒŒ์ด์ง€ ์ƒํƒœ) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + if engaged and v_ego_kph >= 20.0: + steer_rate = abs(steer_deg - self._prev_steer_deg) / _DT + + # ์ง์ง„ ๊ตฌ๊ฐ„ ํŽธ์ฐจ ์ˆ˜์ง‘ (override ์—†์„ ๋•Œ๋งŒ ์ˆœ์ˆ˜ ์ž๋™์กฐํ–ฅ ํŽธ์ฐจ) + if abs(steer_deg) < _STRAIGHT_DEG and not steer_pressed: + self._steer_acc += steer_deg + self._steer_count += 1 + + # ์ปค๋ธŒ ์ง„์ž… ๊ฐ์ง€ (์กฐํ–ฅ๊ฐ ๋ณ€ํ™”์œจ์ด ์ž„๊ณ„๊ฐ’ ์ดˆ๊ณผ) + if steer_rate > _CURVE_RATE_DEG_S: + if not self._in_curve_entry: + self._curve_entries += 1 + if steer_pressed: + self._curve_overrides += 1 + self._in_curve_entry = True + else: + self._in_curve_entry = False + + self._prev_steer_deg = steer_deg + + # Phase 3: ์ˆ˜๋™ ์ œ๋™ (์„ ํ–‰์ฐจ ๊ทผ์ ‘ ์‹œ) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + if engaged and not gear_park and 0 < lead_drel < 100.0: + # (1) ์ˆ˜๋™ ์ œ๋™ ํŠธ๋ฆฌ๊ฑฐ + if brake_pressed and not self._prev_brake: + self._brake_count += 1 + # (2) ์ž์œจ ์ฃผํ–‰ ์ค‘ ๋„ˆ๋ฌด ๋Šฆ๊ฒŒ ๊ธ‰์ œ๋™ ๋ฐœ์ƒ -> ์ œ๋™ ์‹œ์  ์•ž๋‹น๊ธฐ๊ธฐ ํ•„์š” + elif not brake_pressed and a_ego < -1.7: + self._brake_count += 0.3 + + # ์ œ๋™ ๊ณผ๋‹ค ํ•™์Šต ๋ฐฉ์ง€: ๊ฐ•ํ•œ ์ œ๋™ ์ค‘ ๊ฐ€์† ํŽ˜๋‹ฌ์„ ๋ฐŸ๋Š” ๊ฒฝ์šฐ (๋ถˆํ•„์š”ํ•œ ์ œ๋™ ์–ต์ œ) + if engaged and gas_pressed and a_ego < -0.8: + if 0 < lead_drel < 150.0: + self._jlead_gas_acc += _DT + + # โ”€โ”€ Phase 5: DynamicTFollow / TFollowDecelBoost โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + if brake_pressed and not self._prev_brake: + # 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 + + self._prev_brake = brake_pressed + + # โ”€โ”€ Phase 4: TFollowGap (์„ ํ–‰์ฐจ ์ถ”์ข… ์ค‘ ๊ฐ€์† ๊ฐœ์ž…) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + # ๊ณ ์† ํฌ๋ฃจ์ฆˆ ์ค‘ ์„ ํ–‰์ฐจ๊ฐ€ ์ถฉ๋ถ„ํžˆ ๋ฉ€๋ฆฌ ์žˆ๋Š”๋ฐ๋„ gas๋ฅผ ๋ฐŸ๋Š”๋‹ค๋ฉด + # โ†’ ์‹œ์Šคํ…œ์ด ์„ค์ •๋œ ๊ฑฐ๋ฆฌ๋ณด๋‹ค ๋„“๊ฒŒ ๋ฒŒ์–ด์ ธ ์žˆ์–ด ์šด์ „์ž๊ฐ€ ์ขํžˆ๋ ค๋Š” ๊ฒƒ + if (engaged and gas_pressed and v_ego_kph >= _TFOLLOW_MIN_V_KPH + and lead_drel > _TFOLLOW_MIN_LEAD_DREL): + gap_idx = self._current_gap - 1 # 0-indexed + self._tfollow_gas_acc[gap_idx] += _DT + + # ๊ฑฐ๋ฆฌ ๋ถ€์กฑ ํ•™์Šต ๋ฐฉ์ง€: ์ •์† ์ถ”์ข… ์ค‘ ๋ถˆ์•ˆํ•ด์„œ ๋ธŒ๋ ˆ์ดํฌ๋ฅผ ๋ฐŸ๋Š” ๊ฒฝ์šฐ OR Hunting ๊ฐ์ง€ + if engaged and v_ego_kph >= 40.0 and 0 < lead_drel < 80.0: + gap_idx = self._current_gap - 1 + if brake_pressed: + if abs(v_ego_kph - lead_v_kph) < 5.0: + self._tfollow_brake_acc[gap_idx] += _DT + else: + # ์ž์œจ ์ฃผํ–‰ ์ค‘ ๊ฐ€๊ฐ์† ํ—ŒํŒ…(Swing) ๊ฐ์ง€ + 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_acc[gap_idx] += _DT * 0.2 + self._accel_swing_count = 0 + + # ์ฃผ์ฐจ ๊ฐ์ง€ (์ด์ „์— ์ฃผ์ฐจ๊ฐ€ ์•„๋‹ˆ์—ˆ๊ณ , ์ฃผํ–‰์„ ํ•œ ๋ฒˆ์ด๋ผ๋„ ํ•œ ๊ฒฝ์šฐ์—๋งŒ ๋ฐœ๋™) + 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 + + def _clear_all_data(self): + """๋ชจ๋“  ๋ˆ„์  ๋ฐ์ดํ„ฐ๋ฅผ 0์œผ๋กœ ์ดˆ๊ธฐํ™”ํ•˜๊ณ  DB์—์„œ๋„ ์‚ญ์ œ""" + self._gas_acc = [0.0] * _NUM_BANDS + self._steer_acc = 0.0 + self._steer_count = 0 + self._curve_entries = 0 + self._curve_overrides = 0 + self._brake_count = 0 + self._tfollow_gas_acc = [0.0] * 4 + self._dyn_brake_count = 0 + self._decel_brake_count = 0 + self._params.remove("CarrotLearningData") + self._params.remove("CarrotLearningRecommend") + + 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] + # 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)) + # 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)) + # 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] + # 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)) + except Exception: + pass # ๋ฐ์ดํ„ฐ ์†์ƒ ์‹œ ๊ธฐ๋ณธ๊ฐ’ ์œ ์ง€ + + def _save(self): + """ํ˜„์žฌ ๋ˆ„์  ๋ฐ์ดํ„ฐ๋ฅผ Params์— ์ €์žฅ""" + data = { + "gas_acc": self._gas_acc, + "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, + }, + "lon": { + "brake_count": self._brake_count, + "jlead_gas_acc": self._jlead_gas_acc, + }, + "tfollow_gas_acc": self._tfollow_gas_acc, + "tfollow_brake_acc": self._tfollow_brake_acc, + "tfollow_brake_auto_acc": self._tfollow_brake_auto_acc, + "gas_dec_auto_acc": self._gas_dec_auto_acc, + "phase5": { + "dyn_brake_count": self._dyn_brake_count, + "decel_brake_count": self._decel_brake_count, + }, + } + self._params.put("CarrotLearningData", json.dumps(data).encode('utf8')) + + def _on_parking(self): + """์ฃผ์ฐจ ์ „ํ™˜ ์‹œ: ์ €์žฅ โ†’ ์ถ”์ฒœ ๊ณ„์‚ฐ โ†’ ํŒ์—… ์‹ ํ˜ธ""" + self._save() + recommendations = self._calc_recommendations() + if not recommendations: + return + self._params.put("CarrotLearningRecommend", json.dumps(recommendations).encode('utf8')) + self._params.put_bool("CarrotLearningPopupReady", True) + + def _calc_recommendations(self) -> dict: + """Phase 1~4 ์ถ”์ฒœ๊ฐ’ ๊ณ„์‚ฐ. ์ถ”์ฒœ ์—†์œผ๋ฉด ๋นˆ dict ๋ฐ˜ํ™˜.""" + result = { + "๊ฐ€์† (Acceleration)": {}, + "์กฐํ–ฅ (Steering)": {}, + "์ฃผํ–‰ (Driving)": {}, + "๊ฑฐ๋ฆฌ (Following Distance)": {}, + "๋™์ ์ œ์–ด (Dynamic Control)": {}, + } + + # โ”€โ”€ Phase 1: CruiseMaxVals โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + 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 + + total_dec = self._gas_dec_acc[i] + self._gas_dec_auto_acc[i] + if acc_sec >= _GAS_THRESHOLD_SEC: + recommended_raw = min(250, int(current_raw * (1.0 + _GAS_RECOMMEND_RATIO))) + reason = "gas help (manual)" + sec = acc_sec + elif total_dec >= _GAS_REDUCE_THRESHOLD_SEC: + recommended_raw = max(50, int(current_raw * (1.0 + _GAS_REDUCE_RATIO))) + reason = "aggressive accel (auto)" if self._gas_dec_auto_acc[i] > self._gas_dec_acc[i] else "too aggressive (manual)" + sec = total_dec + else: + continue + + if recommended_raw != current_raw: + 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 2a: PathOffset (์ง์ง„ ํŽธ์ฐจ) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + if self._steer_count >= _LATERAL_MIN_SAMPLES: + avg_deg = self._steer_acc / self._steer_count + if abs(avg_deg) >= _PATH_OFFSET_DEG_THRESHOLD: + current_offset = self._params.get_int("PathOffset") + # ์–‘์ˆ˜ avg_deg: ์ฐจ๊ฐ€ ์šฐ์ธก์œผ๋กœ ์ ๋ฆผ โ†’ PathOffset ์ฆ๊ฐ€ (๊ฒฝ๋กœ๋ฅผ ์šฐ์ธก์œผ๋กœ) + delta = int(avg_deg / _PATH_OFFSET_DEG_PER_UNIT) + recommended = int(np.clip(current_offset + delta, -200, 200)) + if recommended != current_offset: + result["์กฐํ–ฅ (Steering)"]["PathOffset"] = { + "current": current_offset, + "recommended": recommended, + "band_kph": "straight driving", + "avg_deg": round(avg_deg, 2), + } + + # โ”€โ”€ Phase 2b: SteerActuatorDelay (์ปค๋ธŒ ์ง„์ž… override ๋น„์œจ) โ”€โ”€โ”€โ”€โ”€โ”€ + if self._curve_entries >= _LATERAL_MIN_CURVE: + override_ratio = self._curve_overrides / self._curve_entries + if override_ratio >= _CURVE_OVERRIDE_RATIO: + current_delay = self._params.get_int("SteerActuatorDelay") + recommended = min(300, current_delay + _DELAY_STEP_UNIT) + if recommended != current_delay: + result["์กฐํ–ฅ (Steering)"]["SteerActuatorDelay"] = { + "current": current_delay, + "recommended": recommended, + "band_kph": "curve entry", + "override_ratio": round(override_ratio * 100, 1), + } + + # โ”€โ”€ Phase 2c: SteerRatioRate (์ปค๋ธŒ override ๋น„์œจ์ด ๋งค์šฐ ๋†’์„ ๋•Œ) โ”€ + # SteerActuatorDelay ์ฆ๊ฐ€๋กœ๋„ ๋ถ€์กฑํ•œ ๊ฒฝ์šฐ, SteerRatioRate(SR ๋ฐฐ์œจ)๋ฅผ ์ถ”๊ฐ€๋กœ ๋†’์ž„ + if override_ratio >= _SR_RATE_OVERRIDE_RATIO: + current_sr_rate = self._params.get_int("SteerRatioRate") + if current_sr_rate <= 0: + current_sr_rate = 100 # ๊ธฐ๋ณธ๊ฐ’ 100% + recommended_sr = min(150, current_sr_rate + _SR_RATE_STEP_UNIT) + if recommended_sr != current_sr_rate: + result["์กฐํ–ฅ (Steering)"]["SteerRatioRate"] = { + "current": current_sr_rate, + "recommended": recommended_sr, + "band_kph": "curve entry (high override)", + "override_ratio": round(override_ratio * 100, 1), + } + + # โ”€โ”€ Phase 3: JLeadFactor3 (์ˆ˜๋™ ์ œ๋™) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + jlead_candidate = None + total_brake = self._brake_count + self._brake_auto_count + if total_brake >= _BRAKE_MIN_COUNT: + current_jlead = self._params.get_int("JLeadFactor3") + recommended = min(200, current_jlead + _JLEAD_STEP_UNIT) + if recommended != current_jlead: + reason = "late braking (auto)" if self._brake_auto_count > self._brake_count else "approaching lead (manual)" + jlead_candidate = { + "current": current_jlead, + "recommended": recommended, + "band_kph": f"{reason}", + "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(50, current_jlead + _JLEAD_REDUCE_STEP) + 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, + } + + # โ”€โ”€ Phase 4: TFollowGap (์„ ํ–‰์ฐจ ์ถ”์ข… ์ค‘ ๊ฑฐ๋ฆฌ ์ขํžˆ๊ธฐ ๊ฐ€์† ๊ฐœ์ž…) โ”€โ”€ + 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] + if gas_sec >= _TFOLLOW_GAS_THRESHOLD_SEC: + recommended_val = max(70, current_val + _TFOLLOW_STEP_UNIT) + reason = "too wide (manual gas)" + sec = gas_sec + elif total_dec >= _TFOLLOW_BRAKE_THRESHOLD_SEC: + recommended_val = min(200, current_val + _TFOLLOW_WIDEN_STEP) + reason = "hunting detected (auto)" if self._tfollow_brake_auto_acc[i] > self._tfollow_brake_acc[i] else "too short (manual brake)" + 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 5a: DynamicTFollow (์•ž์ฐจ ๊ธ‰๊ฐ์† ๋ฐ˜์‘ ๋ฏผ๊ฐ๋„) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + dyn_candidate = None + if 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, + } + + # โ”€โ”€ Phase 5b: TFollowDecelBoost (๋‚ด ์ฐจ ๊ฐ์† ์ค‘ ๋ฒ„ํผ ํ™•๋ณด) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + boost_candidate = None + if 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, + } + + # โ”€โ”€ ๋ธŒ๋ ˆ์ดํฌ ํŒŒ๋ผ๋ฏธํ„ฐ ์ถฉ๋Œ ๋ฐฉ์ง€: ๋™์‹œ ๋‹ค์ค‘ ์ ์šฉ ์‹œ ๊ณผ๋ณด์ • ์–ต์ œ โ”€โ”€ + # 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 + + 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 + for group in recommendations: + for key in recommendations[group]: + info = recommendations[group][key] + self._params.put_int(key, info["recommended"]) + + # ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™” + 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._steer_acc = 0.0 + self._steer_count = 0 + self._curve_entries = 0 + self._curve_overrides = 0 + self._brake_count = 0 + self._brake_auto_count = 0 + self._jlead_gas_acc = 0.0 + self._tfollow_gas_acc = [0.0] * 4 + self._tfollow_brake_acc = [0.0] * 4 + self._tfollow_brake_auto_acc = [0.0] * 4 + self._dyn_brake_count = 0 + self._decel_brake_count = 0 + self._params.remove("CarrotLearningData") + self._params.remove("CarrotLearningRecommend") + self._params.put_bool("CarrotLearningPopupReady", False) diff --git a/selfdrive/carrot_settings.json b/selfdrive/carrot_settings.json index 5185d611e8..0def5d48d2 100644 --- a/selfdrive/carrot_settings.json +++ b/selfdrive/carrot_settings.json @@ -1473,6 +1473,22 @@ "ctitle": "ๆ˜พ็คบ่ทฏ็บฟไฟกๆฏ", "cdescr": "ไป…ๅœจ่ฟžๆŽฅ APN ๆ—ถ่ฟ่กŒใ€‚0: ๅ…ณ้—ญ, 1: ๅผ€ๅฏ" }, + { + "group": "ํ™”๋ฉดํŒจ์Šค", + "name": "CarrotLearningActive", + "title": "Auto-Tuner: ํ•™์Šต(0)", + "descr": "์ฃผํ–‰ ์ค‘ ์šด์ „์ž ๊ฐœ์ž…(๊ฐ€์†/์ œ๋™) ๋ฐ์ดํ„ฐ๋ฅผ ํ•™์Šตํ•˜์—ฌ ํŒŒ๋ผ๋ฏธํ„ฐ ์ตœ์ ํ™”๋ฅผ ์ถ”์ฒœํ•ฉ๋‹ˆ๋‹ค.\n 0: ๋„๊ธฐ\n 1: ์ผœ๊ธฐ", + "egroup": "DISP", + "etitle": "Auto-Tuner: Learning", + "edescr": "Learns from driver interventions (gas/brake) to recommend parameter adjustments.\n 0: Off, 1: On", + "min": 0, + "max": 1, + "default": 0, + "unit": 1, + "cgroup": "ๆ˜พ็คบ่ฎพ็ฝฎ", + "ctitle": "่‡ชๅŠจ่ฐƒๆ•ดๅ™จ: ๅญฆไน ", + "cdescr": "ไปŽ้ฉพ้ฉถๅ‘˜ๅนฒ้ข„ไธญๅญฆไน ไปฅๆŽจ่ๅ‚ๆ•ฐ่ฐƒๆ•ดใ€‚0: ๅ…ณ้—ญ, 1: ๅผ€ๅฏ" + }, { "group": "ํ™”๋ฉดํŒจ์Šค", "name": "CarrotTireTrajectory", diff --git a/selfdrive/ui/qt/home.cc b/selfdrive/ui/qt/home.cc index f027f477a6..c38e7d1ff5 100644 --- a/selfdrive/ui/qt/home.cc +++ b/selfdrive/ui/qt/home.cc @@ -4,10 +4,300 @@ #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 +#include +#include +#include +#include +#include +#include +#include +#include +#include "selfdrive/ui/qt/qt_window.h" +#include "selfdrive/ui/qt/widgets/input.h" + +class AutoTunerGuideDialog : public DialogBase { +public: + explicit AutoTunerGuideDialog(const QString &html_content, QWidget *parent = nullptr) : 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); // Maximize layout + + 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 showEvent(QShowEvent *event) override { + setMainWindow(this); + QDialog::showEvent(event); + } +}; + +class AutoTunerDialog : public DialogBase { +public: + QMap item_checkboxes; + QJsonObject recommendations; + + explicit AutoTunerDialog(const QString &title_text, const QJsonObject &recs, QWidget *parent = nullptr) : 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); // Maximize vertically, slightly wider horizontally + + 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()); + + QCheckBox *cb = new QCheckBox(); + cb->setText(item_text); + // Qt uses rich text in QCheckBox only if we set it this way or implicitly, but since Qt 5.11 text formats are auto-detected. + // If rich text doesn't render in QCheckBox text, we will use a QLabel alongside a checkbox. + // Wait, QCheckBox text does not support rich text by default natively in all styles. + // Let's create a horizontal layout for each item: Checkbox + QLabel + + 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 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; + } +}; #ifdef ENABLE_MAPS #include "selfdrive/ui/qt/maps/map_settings.h" @@ -80,6 +370,66 @@ 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(); + 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(); + int recommended = info["recommended"].toInt(0); + if (recommended > 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) { @@ -87,9 +437,10 @@ void HomeWindow::offroadTransition(bool offroad) { sidebar->setVisible(offroad); UIState* s = uiState(); if (offroad) { - + s->scene._current_carrot_display = 1; slayout->setCurrentWidget(home); + } else { slayout->setCurrentWidget(onroad); diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 3af6f16b59..ee6e0015c8 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -7,6 +7,11 @@ #include #include +#include +#include +#include +#include +#include #include "common/watchdog.h" #include "common/util.h" @@ -460,6 +465,180 @@ void SettingsWindow::setCurrentPanel(int index, const QString ¶m) { nav_btns->buttons()[index]->setChecked(true); } +AutoTunerHistoryPanel::AutoTunerHistoryPanel(QWidget* parent) : QFrame(parent) { + QVBoxLayout *main_layout = new QVBoxLayout(this); + main_layout->setContentsMargins(50, 50, 50, 50); + + QLabel *title = new QLabel(tr("Auto-Tuner History Log v2")); + title->setStyleSheet("font-size: 60px; font-weight: bold; margin-bottom: 20px; color: white;"); + main_layout->addWidget(title); + + QPushButton *btn_clear = new QPushButton(tr("์ „์ฒด ์ด๋ ฅ ์‚ญ์ œ (Clear All)")); + btn_clear->setStyleSheet("background-color: #bb3333; font-size: 45px; padding: 30px; border-radius: 10px; margin-bottom: 30px; color: white; font-weight: bold;"); + connect(btn_clear, &QPushButton::clicked, this, &AutoTunerHistoryPanel::clearAll); + main_layout->addWidget(btn_clear); + + 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(); + list_layout = new QVBoxLayout(scroll_widget); + list_layout->setSpacing(30); + + scroll->setWidget(scroll_widget); + main_layout->addWidget(scroll); + + refreshHistory(); +} + +void AutoTunerHistoryPanel::showEvent(QShowEvent *event) { + refreshHistory(); + QFrame::showEvent(event); +} + +void AutoTunerHistoryPanel::refreshHistory() { + 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 *empty = new QLabel(tr("์ด๋ ฅ์ด ์—†์Šต๋‹ˆ๋‹ค. (No tuning history available)")); + empty->setStyleSheet("font-size: 45px; color: #888888;"); + list_layout->addWidget(empty); + 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: #333333; border-radius: 15px; padding: 20px;"); + QHBoxLayout *row_layout = new QHBoxLayout(row); + + QString text = QString("[%1 ์ ์šฉ๋จ]
").arg(time_str); + for (const QString& group : changes.keys()) { + QJsonObject g_items = changes[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(); // e.g. "๊ฐ€์†" + } + 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("๋ณต๊ตฌ")); + if (is_latest) { + btn_restore->setStyleSheet("background-color: #178644; font-size: 40px; padding: 20px; border-radius: 10px; color: white;"); + } else { + btn_restore->setStyleSheet("background-color: #333333; font-size: 40px; padding: 20px; border-radius: 10px; color: #666666;"); + btn_restore->setEnabled(false); + } + btn_restore->setFixedSize(200, 120); + connect(btn_restore, &QPushButton::clicked, [this, id]() { restoreItem(id); }); + row_layout->addWidget(btn_restore); + + QPushButton *btn_del = new QPushButton(tr("์‚ญ์ œ")); + if (is_latest) { + btn_del->setStyleSheet("background-color: #555555; font-size: 40px; padding: 20px; border-radius: 10px; color: white;"); + } else { + btn_del->setStyleSheet("background-color: #333333; font-size: 40px; padding: 20px; border-radius: 10px; color: #666666;"); + btn_del->setEnabled(false); + } + btn_del->setFixedSize(200, 120); + connect(btn_del, &QPushButton::clicked, [this, id]() { deleteItem(id); }); + row_layout->addWidget(btn_del); + + list_layout->addWidget(row); + } + list_layout->addStretch(); +} + +void AutoTunerHistoryPanel::restoreItem(const QString& id) { + if (ConfirmationDialog::confirm(tr("์ด ์‹œ์ ์˜ ํŠœ๋‹์„ ์ทจ์†Œํ•˜๊ณ  ๋ชจ๋“  ๊ฐ’์„ ์ด์ „ ์ƒํƒœ๋กœ ๋˜๋Œ๋ฆฌ์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?"), tr("๋ณต๊ตฌ"), 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) { + // ๋ณต๊ตฌ ๋กœ์ง: ์ด๋ ฅ์— ์ €์žฅ๋œ 'current' ๊ฐ’์„ ๋‹ค์‹œ Params์— ๊ธฐ๋ก + 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("์ด์ „ ๊ฐ’์œผ๋กœ ๋ณต๊ตฌ๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."), this); + } +} + +void AutoTunerHistoryPanel::deleteItem(const QString& id) { + if (ConfirmationDialog::confirm(tr("์ •๋ง ์ด ํ•ญ๋ชฉ์„ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?"), tr("์‚ญ์ œ"), 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("๋ชจ๋“  ์ด๋ ฅ์„ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ? (์ด์ „ ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐ’์œผ๋กœ ๋˜๋Œ์•„๊ฐ€์ง€ ์•Š์Šต๋‹ˆ๋‹ค)"), tr("์ „์ฒด ์‚ญ์ œ"), this)) { + Params().remove("CarrotLearningHistory"); + refreshHistory(); + } +} + SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { // setup two main layouts @@ -509,6 +688,7 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { panels.append({tr("Firehose"), new FirehosePanel(this)}); } panels.append({ tr("CarrotPilot"), new CarrotPanel(this) }); + panels.append({ tr("ํŠœ๋‹ ์ด๋ ฅ"), new AutoTunerHistoryPanel(this) }); panels.append({ tr("Developer"), new DeveloperPanel(this) }); nav_btns = new QButtonGroup(this); @@ -692,6 +872,7 @@ 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); + latLongToggles->addItem(new CValueControl("CarrotLearningActive", tr("Auto-Tuner: Learning"), tr("Learn from driver interventions (gas/brake) and recommend parameter adjustments when parking. 0=Off, 1=On"), 0, 1, 1)); 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)); diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h index dd60ede091..cab002a001 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -153,3 +153,18 @@ private slots: int m_max; int m_unit; }; + +class AutoTunerHistoryPanel : public QFrame { + Q_OBJECT +public: + explicit AutoTunerHistoryPanel(QWidget* parent = nullptr); +private slots: + void refreshHistory(); + void deleteItem(const QString& id); + void restoreItem(const QString& id); + void clearAll(); +private: + QVBoxLayout *list_layout; +protected: + void showEvent(QShowEvent *event) override; +};