diff --git a/.github/workflows/compile_frogpilot.yaml b/.github/workflows/compile_frogpilot.yaml index d24df5dda..c5b747eb6 100644 --- a/.github/workflows/compile_frogpilot.yaml +++ b/.github/workflows/compile_frogpilot.yaml @@ -3,6 +3,14 @@ name: Compile FrogPilot on: workflow_dispatch: inputs: + runner: + description: "Select the runner" + required: true + default: "c3" + type: choice + options: + - c3 + - c3x not_vetted: description: "This branch is not vetted" required: false @@ -46,7 +54,9 @@ env: jobs: get_branch: - runs-on: [self-hosted, c3x] + runs-on: + - self-hosted + - ${{ github.event.inputs.runner }} outputs: branch: ${{ steps.get_branch.outputs.branch }} python_version: ${{ steps.get_python_version.outputs.python_version }} @@ -108,7 +118,9 @@ jobs: - name: Vet Existing Translations if: github.event.inputs.vet_existing_translations == 'true' + continue-on-error: true run: python selfdrive/ui/translations/auto_translate.py --all-files --vet-translations + timeout-minutes: 300 - name: Commit and Push Translation Updates run: | @@ -128,7 +140,9 @@ jobs: - get_branch - translate if: always() - runs-on: [self-hosted, c3x] + runs-on: + - self-hosted + - ${{ github.event.inputs.runner }} permissions: contents: write defaults: @@ -185,19 +199,12 @@ jobs: rm -f panda/board/obj/version find . -name '*.a' -delete - find . -name '*.cc' -delete find . -name '*.o' -delete find . -name '*.onnx' -delete find . -name '*.os' -delete find . -name '*.pyc' -delete find . -name 'moc_*' -delete - find . -name '*.h' | while read -r header; do - if [[ "$header" != *"common/version.h" && "$header" != *"system/camerad/sensors/"* ]]; then - rm -f "$header" - fi - done - rm -rf .devcontainer/ rm -rf .vscode/ rm -rf body/ @@ -215,7 +222,7 @@ jobs: -name 'review_pull_request.yaml' -o \ -name 'schedule_update.yaml' -o \ -name 'update_pr_branch.yaml' -o \ - -name 'update_release_branch.yaml' \ + -name 'update_release_branch.yaml' -o \ -name 'update_tinygrad.yaml' \ \) \ \) -exec rm -rf {} + diff --git a/.github/workflows/update_release_branch.yaml b/.github/workflows/update_release_branch.yaml index 8732a4fc6..cb08c6995 100644 --- a/.github/workflows/update_release_branch.yaml +++ b/.github/workflows/update_release_branch.yaml @@ -69,21 +69,25 @@ jobs: - name: Update README Date and Remove update file run: | DAY=$(TZ="${{ env.TZ }}" date +'%d' | sed 's/^0//') + case "$DAY" in 1|21|31) SUFFIX="st" ;; - 2|22) SUFFIX="nd" ;; - 3|23) SUFFIX="rd" ;; - *) SUFFIX="th" ;; + 2|22) SUFFIX="nd" ;; + 3|23) SUFFIX="rd" ;; + *) SUFFIX="th" ;; esac + MONTH=$(TZ="${{ env.TZ }}" date +'%B') YEAR=$(TZ="${{ env.TZ }}" date +'%Y') - NEW_DATE="**${MONTH} ${DAY}${SUFFIX}, ${YEAR}**" - sed -i "/FrogPilot was last updated on:/ { N; N; s/\(\n\)\n.*$/\1\n${NEW_DATE}/; }" README.md + DATE="${MONTH} ${DAY}${SUFFIX}, ${YEAR}" + DATE_ESCAPED=$(printf '%s' "$DATE" | sed -E 's/ /%20/g; s/,/%2C/g') + + sed -i -E "s|(Last%20Updated-)[^-)]*|\1${DATE_ESCAPED}|g" README.md git add README.md git rm -f "${{ env.UPDATE_FILE }}" - git commit -m "Updated README date to ${NEW_DATE}" + git commit -m "Updated README date to ${DATE}" - name: Squash Commits run: | diff --git a/.github/workflows/update_tinygrad.yaml b/.github/workflows/update_tinygrad.yaml index 5035da092..dddca0105 100644 --- a/.github/workflows/update_tinygrad.yaml +++ b/.github/workflows/update_tinygrad.yaml @@ -2,11 +2,22 @@ name: Update Tinygrad on: workflow_dispatch: + inputs: + runner: + description: "Select the runner" + required: true + default: "c3" + type: choice + options: + - c3 + - c3x jobs: update_tinygrad: name: Update Tinygrad - runs-on: [self-hosted, c3x] + runs-on: + - self-hosted + - ${{ github.event.inputs.runner }} steps: - name: Get version @@ -14,32 +25,43 @@ jobs: run: | VERSION=$(grep -oP '^VERSION\s*=\s*"\K[^"]+' /data/openpilot/frogpilot/assets/model_manager.py) echo "VERSION=$VERSION" - echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "version=$VERSION" >> "$GITHUB_OUTPUT" - name: Clone GitLab repo env: GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }} run: | + rm -rf /data/tmp/frogpilot_tinygrad mkdir -p /data/tmp/frogpilot_tinygrad cd /data/tmp/frogpilot_tinygrad - git clone --depth 1 --branch Tinygrad https://oauth2:${GITLAB_TOKEN}@gitlab.com/FrogAi/FrogPilot-Resources.git + + git clone --depth 1 --branch Tinygrad "https://oauth2:${GITLAB_TOKEN}@gitlab.com/FrogAi/FrogPilot-Resources.git" - name: Create Tinygrad Archive run: | + set -euo pipefail cd /data/openpilot + ARCHIVE_DIR="/data/tmp/frogpilot_tinygrad/FrogPilot-Resources" ARCHIVE_NAME="Tinygrad_${{ steps.get_version.outputs.version }}.tar.gz" - tar -czf "/data/tmp/frogpilot_tinygrad/FrogPilot-Resources/$ARCHIVE_NAME" \ + TMPDIR="$(mktemp -d)" + touch "$TMPDIR/SConscript" + + tar -czf "$ARCHIVE_NAME" \ --exclude="*.a" \ - --exclude="*.cc" \ - --exclude="*.h" \ --exclude="*.o" \ --exclude="*.onnx" \ - --exclude="*.pkl" \ --exclude="*__pycache__*" \ --exclude="*tests*" \ - frogpilot/tinygrad_modeld tinygrad tinygrad_repo + --exclude="frogpilot/tinygrad_modeld/SConscript" \ + frogpilot/tinygrad_modeld tinygrad_repo \ + -C "$TMPDIR" \ + --transform 's|^SConscript$|frogpilot/tinygrad_modeld/SConscript|' \ + SConscript + + mv "$ARCHIVE_NAME" "$ARCHIVE_DIR/" + rm -rf "$TMPDIR" - name: Push updated Tinygrad run: | @@ -49,7 +71,7 @@ jobs: git config user.email "91348155+FrogAi@users.noreply.github.com" git add Tinygrad_*.tar.gz - git commit -m "Updated Tinygrad: ${{ steps.get_version.outputs.version }}" + git commit -m "Updated Tinygrad: ${{ steps.get_version.outputs.version }}" || echo "No changes to commit" git push origin Tinygrad - name: Cleanup temporary files diff --git a/README.md b/README.md index 0677c6751..5670b5f8b 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,33 @@ -[](https://comma.ai/shop/comma-3x) +
+ openpilot is an operating system for robotics.
+
+ Currently, it upgrades the driver assistance system in 300+ supported cars.
+
-
-[](https://deepwiki.com/FrogAi/FrogPilot)
-
-[](https://codecov.io/gh/commaai/openpilot)
+[](https://www.star-history.com/#FrogAi/FrogPilot&Date)
diff --git a/cereal/car.capnp b/cereal/car.capnp
index 9f3505338..f0522253e 100644
--- a/cereal/car.capnp
+++ b/cereal/car.capnp
@@ -118,37 +118,6 @@ struct CarEvent @0x9b1657f34caf3ad3 {
paramsdPermanentError @119;
actuatorsApiUnavailable @120;
- # FrogPilot Events
- accel30 @122;
- accel35 @123;
- accel40 @124;
- blockUser @125;
- customStartupAlert @126;
- dejaVuCurve @127;
- firefoxSteerSaturated @128;
- forcingStop @129;
- goatSteerSaturated @130;
- greenLight @131;
- hal9000 @132;
- holidayActive @133;
- laneChangeBlockedLoud @134;
- leadDeparting @135;
- noLaneAvailable @136;
- openpilotCrashed @137;
- openpilotCrashedRandomEvent @138;
- pedalInterceptorNoBrake @139;
- speedLimitChanged @140;
- thisIsFineSteerSaturated @141;
- toBeContinued @142;
- torqueNNLoad @143;
- trafficModeActive @144;
- trafficModeInactive @145;
- turningLeft @146;
- turningRight @147;
- vCruise69 @148;
- yourFrogTriedToKillMe @149;
- youveGotMail @150;
-
radarCanErrorDEPRECATED @15;
communityFeatureDisallowedDEPRECATED @62;
radarCommIssueDEPRECATED @67;
@@ -444,22 +413,6 @@ struct CarControl {
prompt @6;
promptRepeat @7;
promptDistracted @8;
-
- # FrogPilot sounds
- angry @9;
- continued @10;
- dejaVu @11;
- doc @12;
- fart @13;
- firefox @14;
- goat @15;
- hal9000 @16;
- mail @17;
- nessie @18;
- noice @19;
- startup @20;
- thisIsFine @21;
- uwu @22;
}
}
diff --git a/cereal/custom.capnp b/cereal/custom.capnp
index a21897277..44409b9c5 100644
--- a/cereal/custom.capnp
+++ b/cereal/custom.capnp
@@ -10,19 +10,113 @@ using Car = import "car.capnp";
# cereal, so use these if you want custom events in your fork.
# you can rename the struct, but don't change the identifier
-struct FrogPilotCarParams @0x81c2f05a394cf4af {
- fpFlags @0 :UInt32;
- isHDA2 @1 :Bool;
- openpilotLongitudinalControlDisabled @2 :Bool;
-}
+struct FrogPilotCarControl {
+ hudControl @0 :HUDControl;
-struct FrogPilotCarState @0xaedffd8f31e7b55d {
- struct ButtonEvent {
- enum Type {
- lkas @0;
+ struct HUDControl {
+ audibleAlert @0 :AudibleAlert;
+
+ enum AudibleAlert {
+ none @0;
+
+ engage @1;
+ disengage @2;
+ refuse @3;
+
+ warningSoft @4;
+ warningImmediate @5;
+
+ prompt @6;
+ promptRepeat @7;
+ promptDistracted @8;
+
+ # Random Events
+ angry @9;
+ continued @10;
+ dejaVu @11;
+ doc @12;
+ fart @13;
+ firefox @14;
+ goat @15;
+ hal9000 @16;
+ mail @17;
+ nessie @18;
+ noice @19;
+ startup @20;
+ thisIsFine @21;
+ uwu @22;
}
}
+}
+struct FrogPilotCarEvent @0x81c2f05a394cf4af {
+ name @0 :EventName;
+
+ enable @1 :Bool;
+ noEntry @2 :Bool;
+ warning @3 :Bool;
+ userDisable @4 :Bool;
+ softDisable @5 :Bool;
+ immediateDisable @6 :Bool;
+ preEnable @7 :Bool;
+ permanent @8 :Bool;
+ overrideLateral @10 :Bool;
+ overrideLongitudinal @9 :Bool;
+
+ enum EventName @0xaedffd8f31e7b55d {
+ blockUser @0;
+ customStartupAlert @1;
+ forcingStop @2;
+ goatSteerSaturated @3;
+ greenLight @4;
+ holidayActive @5;
+ laneChangeBlockedLoud @6;
+ leadDeparting @7;
+ noLaneAvailable @8;
+ openpilotCrashed @9;
+ pedalInterceptorNoBrake @10;
+ speedLimitChanged @11;
+ torqueNNLoad @12;
+ trafficModeActive @13;
+ trafficModeInactive @14;
+ turningLeft @15;
+ turningRight @16;
+
+ # Random Events
+ accel30 @17;
+ accel35 @18;
+ accel40 @19;
+ dejaVuCurve @20;
+ firefoxSteerSaturated @21;
+ hal9000 @22;
+ openpilotCrashedRandomEvent @23;
+ thisIsFineSteerSaturated @24;
+ toBeContinued @25;
+ vCruise69 @26;
+ yourFrogTriedToKillMe @27;
+ youveGotMail @28;
+ }
+}
+
+struct FrogPilotCarParams @0xf35cc4560bbf6ec2 {
+ canUsePedal @0 :Bool;
+ canUseSDSU @1 :Bool;
+ fpFlags @2 :UInt32;
+ isHDA2 @3 :Bool;
+ openpilotLongitudinalControlDisabled @4 :Bool;
+ safetyConfigs @5 :List(SafetyConfig);
+
+ lateralTuning :union {
+ pid @6 :Car.CarParams.LateralPIDTuning;
+ torque @7 :Car.CarParams.LateralTorqueTuning;
+ }
+
+ struct SafetyConfig {
+ safetyParam @0 :UInt16;
+ }
+}
+
+struct FrogPilotCarState @0xda96579883444c35 {
accelPressed @0 :Bool;
alwaysOnLateralAllowed @1 :Bool;
alwaysOnLateralEnabled @2 :Bool;
@@ -38,67 +132,118 @@ struct FrogPilotCarState @0xaedffd8f31e7b55d {
pauseLongitudinal @12 :Bool;
sportGear @13 :Bool;
trafficModeEnabled @14 :Bool;
+
+ struct ButtonEvent {
+ enum Type {
+ lkas @0;
+ }
+ }
}
-struct FrogPilotDeviceState @0xf35cc4560bbf6ec2 {
+struct FrogPilotControlsState @0x80ae746ee2596b11 {
+ alertStatus @0 :AlertStatus;
+ alertText1 @1 :Text;
+ alertText2 @2 :Text;
+ alertSize @3 :AlertSize;
+ alertBlinkingRate @4 :Float32;
+ alertType @5 :Text;
+ alertSound @6 :Car.CarControl.HUDControl.AudibleAlert;
+
+ enum AlertSize {
+ none @0; # don't display the alert
+ small @1; # small box
+ mid @2; # mid screen
+ full @3; # full screen
+ }
+
+ enum AlertStatus {
+ normal @0; # low priority alert for user's convenience
+ userPrompt @1; # mid priority alert that might require user intervention
+ critical @2; # high priority alert that needs immediate user intervention
+ frogpilot @3; # FrogPilot startup alert
+ }
+}
+
+struct FrogPilotDeviceState @0xa5cd762cd951a455 {
freeSpace @0 :Int16;
usedSpace @1 :Int16;
}
-struct FrogPilotNavigation @0xda96579883444c35 {
+struct FrogPilotModelDataV2 @0xf98d843bfd7004a3 {
+ turnDirection @0 :TurnDirection;
+
+ enum TurnDirection {
+ none @0;
+ turnLeft @1;
+ turnRight @2;
+ }
+}
+
+struct FrogPilotNavigation @0xf416ec09499d9d19 {
approachingIntersection @0 :Bool;
approachingTurn @1 :Bool;
navigationSpeedLimit @2 :Float32;
}
-struct FrogPilotPlan @0x80ae746ee2596b11 {
+struct FrogPilotPlan @0xa1680744031fdb2d {
accelerationJerk @0 :Float32;
accelerationJerkStock @1 :Float32;
- dangerJerk @2 :Float32;
- desiredFollowDistance @3 :Int64;
- experimentalMode @4 :Bool;
- forcingStop @5 :Bool;
- forcingStopLength @6 :Float32;
- frogpilotEvents @7 :List(Car.CarEvent);
- lateralCheck @8 :Bool;
- laneWidthLeft @9 :Float32;
- laneWidthRight @10 :Float32;
- maxAcceleration @11 :Float32;
- minAcceleration @12 :Float32;
- mtscSpeed @13 :Float32;
- redLight @14 :Bool;
- roadCurvature @15 :Float32;
- slcMapSpeedLimit @16 :Float32;
- slcMapboxSpeedLimit @17 :Float32;
- slcNextSpeedLimit @18 :Float32;
- slcOverriddenSpeed @19 :Float32;
- slcSpeedLimit @20 :Float32;
- slcSpeedLimitOffset @21 :Float32;
- slcSpeedLimitSource @22 :Text;
- speedJerk @23 :Float32;
- speedJerkStock @24 :Float32;
- speedLimitChanged @25 :Bool;
- tFollow @26 :Float32;
- themeUpdated @27 :Bool;
- togglesUpdated @28 :Bool;
- trackingLead @29 :Bool;
- unconfirmedSlcSpeedLimit @30 :Float32;
- vCruise @31 :Float32;
- vtscControllingCurve @32 :Bool;
- vtscSpeed @33 :Float32;
+ cscControllingSpeed @2 :Bool;
+ cscSpeed @3 :Float32;
+ cscTraining @4 :Bool;
+ dangerJerk @5 :Float32;
+ desiredFollowDistance @6 :Int64;
+ experimentalMode @7 :Bool;
+ forcingStop @8 :Bool;
+ forcingStopLength @9 :Float32;
+ frogpilotEvents @10 :List(FrogPilotCarEvent);
+ increasedStoppedDistance @11 :Float32;
+ lateralCheck @12 :Bool;
+ laneWidthLeft @13 :Float32;
+ laneWidthRight @14 :Float32;
+ maxAcceleration @15 :Float32;
+ minAcceleration @16 :Float32;
+ redLight @17 :Bool;
+ roadCurvature @18 :Float32;
+ slcMapSpeedLimit @19 :Float32;
+ slcMapboxSpeedLimit @20 :Float32;
+ slcNextSpeedLimit @21 :Float32;
+ slcOverriddenSpeed @22 :Float32;
+ slcSpeedLimit @23 :Float32;
+ slcSpeedLimitOffset @24 :Float32;
+ slcSpeedLimitSource @25 :Text;
+ speedJerk @26 :Float32;
+ speedJerkStock @27 :Float32;
+ speedLimitChanged @28 :Bool;
+ tFollow @29 :Float32;
+ themeUpdated @30 :Bool;
+ togglesUpdated @31 :Bool;
+ trackingLead @32 :Bool;
+ unconfirmedSlcSpeedLimit @33 :Float32;
+ vCruise @34 :Float32;
}
-struct CustomReserved5 @0xa5cd762cd951a455 {
-}
+struct FrogPilotRadarState @0xcb9fd56c7057593a {
+ leadLeft @0 :LeadData;
+ leadRight @1 :LeadData;
-struct CustomReserved6 @0xf98d843bfd7004a3 {
-}
+ struct LeadData {
+ dRel @0 :Float32;
+ yRel @1 :Float32;
+ vRel @2 :Float32;
+ aRel @3 :Float32;
+ vLead @4 :Float32;
+ dPath @6 :Float32;
+ vLat @7 :Float32;
+ vLeadK @8 :Float32;
+ aLeadK @9 :Float32;
+ fcw @10 :Bool;
+ status @11 :Bool;
+ aLeadTau @12 :Float32;
+ modelProb @13 :Float32;
+ radar @14 :Bool;
+ radarTrackId @15 :Int32 = -1;
-struct CustomReserved7 @0xb86e6369214c01c8 {
-}
-
-struct CustomReserved8 @0xf416ec09499d9d19 {
-}
-
-struct CustomReserved9 @0xa1680744031fdb2d {
+ aLeadDEPRECATED @5 :Float32;
+ }
}
diff --git a/cereal/log.capnp b/cereal/log.capnp
index c06556e94..54e877f2a 100644
--- a/cereal/log.capnp
+++ b/cereal/log.capnp
@@ -335,12 +335,6 @@ enum LaneChangeDirection {
right @2;
}
-enum TurnDirection {
- none @0;
- turnLeft @1;
- turnRight @2;
-}
-
struct CanData {
address @0 :UInt32;
busTime @1 :UInt16;
@@ -618,8 +612,6 @@ struct RadarState @0x9a185389d6fdd05f {
leadOne @3 :LeadData;
leadTwo @4 :LeadData;
- leadLeft @13 :LeadData;
- leadRight @14 :LeadData;
cumLagMs @5 :Float32;
struct LeadData {
@@ -638,7 +630,6 @@ struct RadarState @0x9a185389d6fdd05f {
modelProb @13 :Float32;
radar @14 :Bool;
radarTrackId @15 :Int32 = -1;
- farLead @16 :Bool;
aLeadDEPRECATED @5 :Float32;
}
@@ -754,7 +745,6 @@ struct ControlsState @0x97ff69c53601abf1 {
normal @0; # low priority alert for user's convenience
userPrompt @1; # mid priority alert that might require user intervention
critical @2; # high priority alert that needs immediate user intervention
- frogpilot @3; # FrogPilot startup alert
}
enum AlertSize {
@@ -805,7 +795,6 @@ struct ControlsState @0x97ff69c53601abf1 {
saturated @7 :Bool;
actualLateralAccel @9 :Float32;
desiredLateralAccel @10 :Float32;
- nnLog @11 :List(Float32);
}
struct LateralLQRState {
@@ -908,7 +897,6 @@ struct DrivingModelData {
struct MetaData {
laneChangeState @0 :LaneChangeState;
laneChangeDirection @1 :LaneChangeDirection;
- turnDirection @2 :TurnDirection;
}
}
@@ -1002,7 +990,6 @@ struct ModelDataV2 {
hardBrakePredicted @7 :Bool;
laneChangeState @8 :LaneChangeState;
laneChangeDirection @9 :LaneChangeDirection;
- turnDirection @10 :TurnDirection;
# deprecated
@@ -2418,16 +2405,16 @@ struct Event {
customReservedRawData2 @126 :Data;
# *********** Custom: reserved for forks ***********
- frogpilotCarParams @107 :Custom.FrogPilotCarParams;
- frogpilotCarState @108 :Custom.FrogPilotCarState;
- frogpilotDeviceState @109 :Custom.FrogPilotDeviceState;
- frogpilotNavigation @110 :Custom.FrogPilotNavigation;
- frogpilotPlan @111 :Custom.FrogPilotPlan;
- customReserved5 @112 :Custom.CustomReserved5;
- customReserved6 @113 :Custom.CustomReserved6;
- customReserved7 @114 :Custom.CustomReserved7;
- customReserved8 @115 :Custom.CustomReserved8;
- customReserved9 @116 :Custom.CustomReserved9;
+ frogpilotCarControl @107 :Custom.FrogPilotCarControl;
+ frogpilotCarParams @108 :Custom.FrogPilotCarParams;
+ frogpilotCarState @109 :Custom.FrogPilotCarState;
+ frogpilotControlsState @110 :Custom.FrogPilotControlsState;
+ frogpilotDeviceState @111 :Custom.FrogPilotDeviceState;
+ frogpilotModelV2 @112 :Custom.FrogPilotModelDataV2;
+ frogpilotNavigation @113 :Custom.FrogPilotNavigation;
+ frogpilotOnroadEvents @114: List(Custom.FrogPilotCarEvent);
+ frogpilotPlan @115 :Custom.FrogPilotPlan;
+ frogpilotRadarState @116 :Custom.FrogPilotRadarState;
# *********** legacy + deprecated ***********
model @9 :Legacy.ModelData; # TODO: rename modelV2 and mark this as deprecated
diff --git a/cereal/services.py b/cereal/services.py
index 76dff67f6..5fab38afd 100755
--- a/cereal/services.py
+++ b/cereal/services.py
@@ -94,11 +94,16 @@ _services: dict[str, tuple] = {
"customReservedRawData2": (True, 0.),
# FrogPilot
+ "frogpilotCarControl": (True, 100., 10),
"frogpilotCarParams": (True, 0.02, 1),
"frogpilotCarState": (True, 100., 10),
+ "frogpilotControlsState": (True, 100., 10),
"frogpilotDeviceState": (True, 2., 1),
+ "frogpilotModelV2": (True, 20., 40),
"frogpilotNavigation": (True, 1., 10),
+ "frogpilotOnroadEvents": (True, 1., 1),
"frogpilotPlan": (True, 20., 5),
+ "frogpilotRadarState": (True, 20., 5),
}
SERVICE_LIST = {name: Service(*vals) for
idx, (name, vals) in enumerate(_services.items())}
diff --git a/common/conversions.py b/common/conversions.py
index 62c8debdb..3cdbd94da 100644
--- a/common/conversions.py
+++ b/common/conversions.py
@@ -14,6 +14,8 @@ class Conversions:
# Distance
METER_TO_FOOT = 3.28084
FOOT_TO_METER = 1. / METER_TO_FOOT
+ METER_TO_MILE = METER_TO_FOOT / 5280
+ MILE_TO_METER = 1. / METER_TO_MILE
CM_TO_INCH = 1. / 2.54
INCH_TO_CM = 1. / CM_TO_INCH
diff --git a/common/params.cc b/common/params.cc
index 73942a86c..a65d522ae 100644
--- a/common/params.cc
+++ b/common/params.cc
@@ -246,6 +246,8 @@ std::unordered_mapSelect components to apply:
+ + ${() => hasDistanceIcons() && html``} + ${() => hasIcons() && html``} + ${() => hasSounds() && html``} + ${() => hasSteeringWheel() && html``} + ${() => hasTurnSignals() && html``} +Select components to save:
+ + ${() => hasDistanceIcons() && html``} + ${() => hasIcons() && html``} + ${() => hasSounds() && html``} + ${() => hasSteeringWheel() && html``} + ${() => hasTurnSignals() && html``} +Submit your theme for everyone to use!
+Please enter your Discord username below so we can contact you if needed.
+ state.discordUsername = e.target.value}" /> +Select components to submit:
+ + ${() => hasDistanceIcons() && html``} + ${() => hasSounds() && html``} + ${() => hasSteeringWheel() && html``} + ${() => hasTurnSignals() && html``} +