mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-08-22 00:33:44 +08:00
GLXY
This commit is contained in:
committed by
firestar5683
parent
73823319be
commit
bb972faba1
Executable
+121
@@ -0,0 +1,121 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
derive_feasible_params.py
|
||||
|
||||
Dynamically parses the OpenPilot/StarPilot codebase to cross-reference logically
|
||||
registered Param keys with UI string literals. This ensures that no hidden or
|
||||
dynamically-instantiated UI toggles are missed, outputting a highly accurate "Golden List"
|
||||
of parameters that can be safely modified by The Pond or other configuration interfaces.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
def get_repo_root() -> str:
|
||||
# Resolves to the root of the StarPilot repository based on this script's location
|
||||
return os.path.abspath(os.path.join(os.path.dirname(__file__), '../../'))
|
||||
|
||||
# Constants
|
||||
REPO_ROOT = get_repo_root()
|
||||
PARAMS_CC_PATH = os.path.join(REPO_ROOT, 'common/params.cc')
|
||||
UI_DIRECTORIES = [
|
||||
os.path.join(REPO_ROOT, 'selfdrive/ui'),
|
||||
os.path.join(REPO_ROOT, 'frogpilot/ui')
|
||||
]
|
||||
|
||||
# A curated list of parameters that are known to be strictly readable state metadata
|
||||
# rather than user-toggled configurations.
|
||||
KNOWN_READ_ONLY = {
|
||||
"ApiCache_Device", "ApiCache_DriveStats", "ApiCache_NavDestinations",
|
||||
"CarMake", "CarModel", "CarModelName", "CarParamsPersistent", "CarVin",
|
||||
"ClusterOffset", "Compass", "DeveloperSidebarMetric1", "DeveloperSidebarMetric2",
|
||||
"DeveloperSidebarMetric3", "DeveloperSidebarMetric4", "DeveloperSidebarMetric5",
|
||||
"DeveloperSidebarMetric6", "DeveloperSidebarMetric7", "DongleId",
|
||||
"FrogPilotCarParamsPersistent", "FrogPilotDrives", "FrogPilotKilometers",
|
||||
"FrogPilotMinutes", "GitBranch", "GitCommit", "GitCommitDate", "GitDiff",
|
||||
"GitRemote", "GithubSshKeys", "GithubUsername", "HardwareSerial", "IMEI",
|
||||
"InstallDate", "IsRhdDetected", "KonikMinutes", "LastGPSPosition",
|
||||
"LastMapsUpdate", "LastUpdateTime", "ModelDrivesAndScores", "ModelReleasedDates",
|
||||
"ModelVersions", "PrimeType", "TermsVersion", "TrainingVersion", "Version",
|
||||
"openpilotMinutes", "CompletedTrainingVersion"
|
||||
}
|
||||
|
||||
def extract_registered_keys(params_path: str) -> set:
|
||||
"""Extracts all legally registered parameter keys from common/params.cc"""
|
||||
registered_keys = set()
|
||||
try:
|
||||
with open(params_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Isolate the keys `unordered_map` block
|
||||
keys_block_match = re.search(r'unordered_map<std::string, uint32_t> keys = \{(.*?)\};', content, re.DOTALL)
|
||||
if not keys_block_match:
|
||||
print("Error: Could not locate 'keys' map in params.cc")
|
||||
return registered_keys
|
||||
|
||||
# Extract {"KeyName", FLAG} entries
|
||||
for match in re.finditer(r'\{"([A-Za-z0-9_]+)",\s*([^}]+)\}', keys_block_match.group(1)):
|
||||
key, flag = match.group(1), match.group(2)
|
||||
# Remove keys that are strictly internal ephemeral states
|
||||
if 'CLEAR_ON_MANAGER_START' not in flag:
|
||||
registered_keys.add(key)
|
||||
|
||||
except FileNotFoundError:
|
||||
print(f"Error: Could not find params source file at {params_path}")
|
||||
|
||||
return registered_keys
|
||||
|
||||
def extract_ui_string_literals(ui_dirs: list) -> set:
|
||||
"""Recursively walks UI directories to extract every string literal."""
|
||||
ui_strings = set()
|
||||
valid_extensions = ('.cc', '.h', '.cpp', '.hpp', '.qml')
|
||||
|
||||
for directory in ui_dirs:
|
||||
if not os.path.exists(directory):
|
||||
print(f"Warning: UI directory not found {directory}")
|
||||
continue
|
||||
|
||||
for root, _, files in os.walk(directory):
|
||||
for file in files:
|
||||
if file.endswith(valid_extensions):
|
||||
filepath = os.path.join(root, file)
|
||||
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
# Extract all "StringLiterals" block
|
||||
matches = re.findall(r'\"([A-Za-z0-9_]+)\"', f.read())
|
||||
ui_strings.update(matches)
|
||||
|
||||
return ui_strings
|
||||
|
||||
def main():
|
||||
print(f"Starting parameter derivation inside {REPO_ROOT}...")
|
||||
|
||||
# 1. Fetch
|
||||
registered_keys = extract_registered_keys(PARAMS_CC_PATH)
|
||||
ui_strings = extract_ui_string_literals(UI_DIRECTORIES)
|
||||
|
||||
# 2. Intersect
|
||||
feasible_keys = registered_keys.intersection(ui_strings)
|
||||
|
||||
# 3. Filter Read-Only
|
||||
editable_keys = feasible_keys - KNOWN_READ_ONLY
|
||||
|
||||
# 4. Export
|
||||
output_path = os.path.join(os.path.dirname(__file__), 'feasibleparams.txt')
|
||||
try:
|
||||
with open(output_path, 'w', encoding='utf-8') as f:
|
||||
f.write("Dynamically Derived Feasible Param Candidates (The Golden List)\n")
|
||||
f.write("===============================================================\n\n")
|
||||
f.write(f"Total globally registered C++ keys: {len(registered_keys)}\n")
|
||||
f.write(f"Total explicit UI string references: {len(feasible_keys)}\n")
|
||||
f.write(f"Total Editable/Toggleable targets: {len(editable_keys)}\n\n")
|
||||
|
||||
for key in sorted(list(editable_keys)):
|
||||
f.write(f"{key}\n")
|
||||
|
||||
print(f"Successfully derived {len(editable_keys)} highly feasible parameter targets.")
|
||||
print(f"Report exported to: {output_path}")
|
||||
except Exception as e:
|
||||
print(f"Error writing to output file: {e}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,338 @@
|
||||
Dynamically Derived Feasible Param Candidates (The Golden List)
|
||||
===============================================================
|
||||
|
||||
Total globally registered C++ keys: 417
|
||||
Total explicit UI string references: 369
|
||||
Total Editable/Toggleable targets: 331
|
||||
|
||||
AMapKey1
|
||||
AMapKey2
|
||||
AccelerationPath
|
||||
AccelerationProfile
|
||||
AdjacentLeadsUI
|
||||
AdjacentPath
|
||||
AdjacentPathMetrics
|
||||
AdvancedCustomUI
|
||||
AdvancedLateralTune
|
||||
AdvancedLongitudinalTune
|
||||
AggressiveFollow
|
||||
AggressiveFollowHigh
|
||||
AggressiveJerkAcceleration
|
||||
AggressiveJerkDanger
|
||||
AggressiveJerkDeceleration
|
||||
AggressiveJerkSpeed
|
||||
AggressiveJerkSpeedDecrease
|
||||
AggressivePersonalityProfile
|
||||
AlertVolumeControl
|
||||
AlwaysOnLateral
|
||||
AlwaysOnLateralLKAS
|
||||
AlwaysOnLateralMain
|
||||
AutomaticUpdates
|
||||
AutomaticallyDownloadModels
|
||||
AvailableModelNames
|
||||
AvailableModelSeries
|
||||
AvailableModels
|
||||
BigMap
|
||||
BlacklistedModels
|
||||
BlindSpotMetrics
|
||||
BlindSpotPath
|
||||
BootLogo
|
||||
BorderMetrics
|
||||
CECurves
|
||||
CECurvesLead
|
||||
CELead
|
||||
CEModelStopTime
|
||||
CENavigation
|
||||
CENavigationIntersections
|
||||
CENavigationLead
|
||||
CENavigationTurns
|
||||
CESignalLaneDetection
|
||||
CESignalSpeed
|
||||
CESlowerLead
|
||||
CESpeed
|
||||
CESpeedLead
|
||||
CEStatus
|
||||
CEStoppedLead
|
||||
CalibratedLateralAcceleration
|
||||
CalibrationParams
|
||||
CalibrationProgress
|
||||
CameraView
|
||||
CommunityFavorites
|
||||
ConditionalExperimental
|
||||
CurvatureData
|
||||
CurveSpeedController
|
||||
CustomAlerts
|
||||
CustomColors
|
||||
CustomCruise
|
||||
CustomCruiseLong
|
||||
CustomDistanceIcons
|
||||
CustomIcons
|
||||
CustomPersonalities
|
||||
CustomSignals
|
||||
CustomSounds
|
||||
CustomUI
|
||||
DebugMode
|
||||
DecelerationProfile
|
||||
DeveloperMetrics
|
||||
DeveloperSidebar
|
||||
DeveloperUI
|
||||
DeveloperWidgets
|
||||
DeviceManagement
|
||||
DeviceShutdown
|
||||
DisableOnroadUploads
|
||||
DisableOpenpilotLongitudinal
|
||||
DiscordUsername
|
||||
DisengageOnAccelerator
|
||||
DisengageVolume
|
||||
DistanceButtonControl
|
||||
DoToggleReset
|
||||
DoToggleResetStock
|
||||
DownloadableBootLogos
|
||||
DownloadableColors
|
||||
DownloadableDistanceIcons
|
||||
DownloadableIcons
|
||||
DownloadableSignals
|
||||
DownloadableSounds
|
||||
DownloadableWheels
|
||||
DriverCamera
|
||||
DynamicPathWidth
|
||||
DynamicPedalsOnUI
|
||||
EVTuning
|
||||
EngageVolume
|
||||
ExperimentalGMTune
|
||||
ExperimentalLongitudinalEnabled
|
||||
ExperimentalMode
|
||||
ExperimentalModeConfirmed
|
||||
FPSCounter
|
||||
Fahrenheit
|
||||
FavoriteDestinations
|
||||
ForceAutoTune
|
||||
ForceAutoTuneOff
|
||||
ForceFingerprint
|
||||
ForceMPHDashboard
|
||||
ForceStops
|
||||
ForceTorqueController
|
||||
FrogPilotStats
|
||||
FrogsGoMoosTweak
|
||||
FullMap
|
||||
GMPedalLongitudinal
|
||||
GoatScream
|
||||
GreenLightAlert
|
||||
GsmApn
|
||||
GsmMetered
|
||||
GsmRoaming
|
||||
HasAcceptedTerms
|
||||
HideAlerts
|
||||
HideLeadMarker
|
||||
HideMap
|
||||
HideMapIcon
|
||||
HideMaxSpeed
|
||||
HideSpeed
|
||||
HideSpeedLimit
|
||||
HigherBitrate
|
||||
HolidayThemes
|
||||
HumanAcceleration
|
||||
HumanFollowing
|
||||
IncreaseThermalLimits
|
||||
IncreasedStoppedDistance
|
||||
IsDriverViewEnabled
|
||||
IsLdwEnabled
|
||||
IsMetric
|
||||
LKASButtonControl
|
||||
LaneChangeTime
|
||||
LaneChanges
|
||||
LaneDetectionWidth
|
||||
LaneLinesWidth
|
||||
LanguageSetting
|
||||
LateralTune
|
||||
LeadDepartingAlert
|
||||
LeadDetectionThreshold
|
||||
LeadInfo
|
||||
LiveDelay
|
||||
LiveParameters
|
||||
LiveTorqueParameters
|
||||
LockDoors
|
||||
LockDoorsTimer
|
||||
LongDistanceButtonControl
|
||||
LongPitch
|
||||
LongitudinalActuatorDelay
|
||||
LongitudinalActuatorDelayStock
|
||||
LongitudinalPersonality
|
||||
LongitudinalTune
|
||||
LoudBlindspotAlert
|
||||
LowVoltageShutdown
|
||||
MapAcceleration
|
||||
MapDeceleration
|
||||
MapGears
|
||||
MapStyle
|
||||
MapboxPublicKey
|
||||
MapboxSecretKey
|
||||
MapsSelected
|
||||
MaxDesiredAcceleration
|
||||
MinimumLaneChangeSpeed
|
||||
Model
|
||||
ModelRandomizer
|
||||
ModelUI
|
||||
ModelVersion
|
||||
NNFF
|
||||
NNFFLite
|
||||
NavPastDestinations
|
||||
NavSettingLeftSide
|
||||
NavSettingTime24h
|
||||
NavigationUI
|
||||
NewLongAPI
|
||||
NoLogging
|
||||
NoUploads
|
||||
NudgelessLaneChange
|
||||
NumericalTemp
|
||||
OSMDownloadLocations
|
||||
Offset1
|
||||
Offset2
|
||||
Offset3
|
||||
Offset4
|
||||
Offset5
|
||||
Offset6
|
||||
Offset7
|
||||
OneLaneChange
|
||||
OnroadDistanceButton
|
||||
OpenpilotEnabledToggle
|
||||
OverpassRequests
|
||||
PathEdgeWidth
|
||||
PathWidth
|
||||
PauseAOLOnBrake
|
||||
PauseLateralOnSignal
|
||||
PauseLateralSpeed
|
||||
PedalsOnUI
|
||||
PersonalizeOpenpilot
|
||||
PreferredSchedule
|
||||
PromptDistractedVolume
|
||||
PromptVolume
|
||||
QOLLateral
|
||||
QOLLongitudinal
|
||||
QOLVisuals
|
||||
RadarTracksUI
|
||||
RainbowPath
|
||||
RandomEvents
|
||||
RandomThemes
|
||||
RecordFront
|
||||
RecordFrontLock
|
||||
RecoveryPower
|
||||
RedPanda
|
||||
RefuseVolume
|
||||
RelaxedFollow
|
||||
RelaxedFollowHigh
|
||||
RelaxedJerkAcceleration
|
||||
RelaxedJerkDanger
|
||||
RelaxedJerkDeceleration
|
||||
RelaxedJerkSpeed
|
||||
RelaxedJerkSpeedDecrease
|
||||
RelaxedPersonalityProfile
|
||||
RemoteStartBootsComma
|
||||
ReverseCruise
|
||||
RoadEdgesWidth
|
||||
RoadNameUI
|
||||
RotatingWheel
|
||||
SLCConfirmation
|
||||
SLCConfirmationHigher
|
||||
SLCConfirmationLower
|
||||
SLCFallback
|
||||
SLCLookaheadHigher
|
||||
SLCLookaheadLower
|
||||
SLCMapboxFiller
|
||||
SLCOverride
|
||||
SNGHack
|
||||
ScreenBrightness
|
||||
ScreenBrightnessOnroad
|
||||
ScreenManagement
|
||||
ScreenRecorder
|
||||
ScreenTimeout
|
||||
ScreenTimeoutOnroad
|
||||
SearchInput
|
||||
SetSpeedLimit
|
||||
SetSpeedOffset
|
||||
ShowCEMStatus
|
||||
ShowCPU
|
||||
ShowCSCStatus
|
||||
ShowGPU
|
||||
ShowIP
|
||||
ShowMemoryUsage
|
||||
ShowSLCOffset
|
||||
ShowSpeedLimits
|
||||
ShowSteering
|
||||
ShowStoppingPoint
|
||||
ShowStoppingPointMetrics
|
||||
ShowStorageLeft
|
||||
ShowStorageUsed
|
||||
ShownToggleDescriptions
|
||||
Sidebar
|
||||
SignalMetrics
|
||||
SpeedLimitChangedAlert
|
||||
SpeedLimitController
|
||||
SpeedLimitFiller
|
||||
SpeedLimitSources
|
||||
SshEnabled
|
||||
StandardFollow
|
||||
StandardFollowHigh
|
||||
StandardJerkAcceleration
|
||||
StandardJerkDanger
|
||||
StandardJerkDeceleration
|
||||
StandardJerkSpeed
|
||||
StandardJerkSpeedDecrease
|
||||
StandardPersonalityProfile
|
||||
StandbyMode
|
||||
StartAccel
|
||||
StartAccelStock
|
||||
StartupMessageBottom
|
||||
StartupMessageTop
|
||||
StaticPedalsOnUI
|
||||
SteerDelay
|
||||
SteerDelayStock
|
||||
SteerFriction
|
||||
SteerFrictionStock
|
||||
SteerKP
|
||||
SteerKPStock
|
||||
SteerLatAccel
|
||||
SteerLatAccelStock
|
||||
SteerOffset
|
||||
SteerOffsetStock
|
||||
SteerRatio
|
||||
SteerRatioStock
|
||||
StopAccel
|
||||
StopAccelStock
|
||||
StopDistance
|
||||
StoppedTimer
|
||||
StoppingDecelRate
|
||||
StoppingDecelRateStock
|
||||
TacoTune
|
||||
TacoTuneHacks
|
||||
TetheringEnabled
|
||||
ToyotaDoors
|
||||
TrafficFollow
|
||||
TrafficJerkAcceleration
|
||||
TrafficJerkDanger
|
||||
TrafficJerkDeceleration
|
||||
TrafficJerkSpeed
|
||||
TrafficJerkSpeedDecrease
|
||||
TrafficPersonalityProfile
|
||||
TrailerLoad
|
||||
TruckTuning
|
||||
TuningLevel
|
||||
TuningLevelConfirmed
|
||||
TurnDesires
|
||||
UnlimitedLength
|
||||
UnlockDoors
|
||||
UpdaterAvailableBranches
|
||||
UseKonikServer
|
||||
UseSI
|
||||
UseVienna
|
||||
UserFavorites
|
||||
VEgoStarting
|
||||
VEgoStartingStock
|
||||
VEgoStopping
|
||||
VEgoStoppingStock
|
||||
VeryLongDistanceButtonControl
|
||||
VoltSNG
|
||||
WarningImmediateVolume
|
||||
WarningSoftVolume
|
||||
WheelIcon
|
||||
WheelSpeed
|
||||
Executable
+342
@@ -0,0 +1,342 @@
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import json
|
||||
import ast
|
||||
|
||||
REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../'))
|
||||
|
||||
CATEGORIES = [
|
||||
{"file": "lateral_settings.cc", "name": "Lateral (Steering)", "icon": "bi-arrows-move"},
|
||||
{"file": "longitudinal_settings.cc", "name": "Longitudinal (Speed & Following)", "icon": "bi-speedometer2"},
|
||||
{"file": "visual_settings.cc", "name": "Visual (Display & UI)", "icon": "bi-eye"},
|
||||
{"file": "sounds_settings.cc", "name": "Sounds & Alerts", "icon": "bi-volume-up"},
|
||||
{"file": "vehicle_settings.cc", "name": "Vehicle", "icon": "bi-car-front"},
|
||||
{"file": "device_settings.cc", "name": "Device & Data", "icon": "bi-hdd"},
|
||||
{"file": "model_settings.cc", "name": "Model & Customization", "icon": "bi-cpu"},
|
||||
]
|
||||
|
||||
DROPDOWN_MAPPING = {
|
||||
"SelectModel": {
|
||||
"key": "Model",
|
||||
"options_endpoint": "/api/models/installed"
|
||||
}
|
||||
}
|
||||
|
||||
PARENT_KEYS_MAPPING = {
|
||||
"device_settings.cc": {
|
||||
"deviceManagementKeys": "DeviceManagement",
|
||||
"screenKeys": "ScreenManagement"
|
||||
},
|
||||
"lateral_settings.cc": {
|
||||
"advancedLateralTuneKeys": "AdvancedLateralTune",
|
||||
"aolKeys": "AlwaysOnLateral",
|
||||
"laneChangeKeys": "LaneChanges",
|
||||
"lateralTuneKeys": "LateralTune",
|
||||
"qolKeys": "QOLLateral"
|
||||
},
|
||||
"longitudinal_settings.cc": {
|
||||
"advancedLongitudinalTuneKeys": "AdvancedLongitudinalTune",
|
||||
"aggressivePersonalityKeys": "AggressivePersonalityProfile",
|
||||
"conditionalExperimentalKeys": "ConditionalExperimental",
|
||||
"curveSpeedKeys": "CurveSpeedControl",
|
||||
"customDrivingPersonalityKeys": "CustomDrivingPersonality",
|
||||
"longitudinalTuneKeys": "LongitudinalTune",
|
||||
"qolKeys": "QOLLongitudinal",
|
||||
"relaxedPersonalityKeys": "RelaxedPersonalityProfile",
|
||||
"speedLimitControllerKeys": "SpeedLimitController",
|
||||
"standardPersonalityKeys": "StandardPersonalityProfile",
|
||||
"trafficPersonalityKeys": "TrafficPersonalityProfile"
|
||||
},
|
||||
"sounds_settings.cc": {
|
||||
"alertVolumeControlKeys": "AlertVolumeControl",
|
||||
"customAlertsKeys": "CustomAlerts"
|
||||
},
|
||||
"theme_settings.cc": {
|
||||
"customThemeKeys": "CustomTheme"
|
||||
},
|
||||
"visual_settings.cc": {
|
||||
"advancedCustomOnroadUIKeys": "AdvancedCustomUI",
|
||||
"customOnroadUIKeys": "CustomUI",
|
||||
"developerMetricKeys": "DeveloperMetrics",
|
||||
"developerSidebarKeys": "DeveloperSidebar",
|
||||
"developerUIKeys": "DeveloperUI",
|
||||
"developerWidgetKeys": "DeveloperWidgets",
|
||||
"modelUIKeys": "ModelUI",
|
||||
"navigationUIKeys": "NavigationUI",
|
||||
"qualityOfLifeKeys": "QOLVisuals"
|
||||
},
|
||||
"vehicle_settings.cc": {}
|
||||
}
|
||||
|
||||
ALL_PARENT_KEYS = set()
|
||||
for cmap in PARENT_KEYS_MAPPING.values():
|
||||
for parent in cmap.values():
|
||||
ALL_PARENT_KEYS.add(parent)
|
||||
|
||||
def get_variables_data():
|
||||
filepath = os.path.join(REPO_ROOT, "frogpilot/common/frogpilot_variables.py")
|
||||
excluded = set()
|
||||
defaults = {}
|
||||
if not os.path.exists(filepath):
|
||||
return excluded, defaults
|
||||
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
tree = ast.parse(f.read())
|
||||
|
||||
def parse_params_list(value_node):
|
||||
try:
|
||||
if isinstance(value_node, ast.List):
|
||||
for elt in value_node.elts:
|
||||
if isinstance(elt, ast.Tuple) and len(elt.elts) >= 2:
|
||||
key_node = elt.elts[0]
|
||||
val_node = elt.elts[1]
|
||||
if isinstance(key_node, ast.Constant):
|
||||
key = key_node.value
|
||||
if isinstance(val_node, ast.Constant):
|
||||
val = val_node.value
|
||||
if isinstance(val, (str, bytes)):
|
||||
v = val.decode('utf-8') if isinstance(val, bytes) else str(val)
|
||||
if v in ("0", "1"):
|
||||
defaults[key] = "bool"
|
||||
elif "." in v and v.replace(".", "", 1).isdigit():
|
||||
defaults[key] = "float"
|
||||
elif v.isdigit():
|
||||
defaults[key] = "int"
|
||||
else:
|
||||
defaults[key] = "string"
|
||||
else:
|
||||
defaults[key] = "unknown"
|
||||
else:
|
||||
defaults[key] = "unknown"
|
||||
except:
|
||||
pass
|
||||
|
||||
for node in tree.body:
|
||||
if isinstance(node, ast.Assign):
|
||||
for target in node.targets:
|
||||
if getattr(target, 'id', '') == 'EXCLUDED_KEYS':
|
||||
try:
|
||||
excluded = ast.literal_eval(node.value)
|
||||
except:
|
||||
pass
|
||||
elif getattr(target, 'id', '') == 'frogpilot_default_params':
|
||||
parse_params_list(node.value)
|
||||
elif isinstance(node, ast.AnnAssign):
|
||||
if getattr(node.target, 'id', '') == 'frogpilot_default_params':
|
||||
parse_params_list(node.value)
|
||||
|
||||
return excluded, defaults
|
||||
|
||||
EXCLUDED_KEYS, DEFAULT_TYPES = get_variables_data()
|
||||
|
||||
def get_param_type(key):
|
||||
return DEFAULT_TYPES.get(key, "unknown")
|
||||
|
||||
def extract_bracket_block(text, start_idx):
|
||||
if text[start_idx] != '{': return ""
|
||||
depth = 0
|
||||
in_str = False
|
||||
escape = False
|
||||
for i in range(start_idx, len(text)):
|
||||
char = text[i]
|
||||
if escape:
|
||||
escape = False
|
||||
continue
|
||||
if char == '\\':
|
||||
escape = True
|
||||
continue
|
||||
if char == '"':
|
||||
in_str = not in_str
|
||||
continue
|
||||
if not in_str:
|
||||
if char == '{': depth += 1
|
||||
elif char == '}':
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
return text[start_idx:i+1]
|
||||
return ""
|
||||
|
||||
def parse_cpp_file(filename):
|
||||
filepath = os.path.join(REPO_ROOT, "frogpilot/ui/qt/offroad", filename)
|
||||
if not os.path.exists(filepath): return []
|
||||
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
vector_match = re.search(r'const std::vector<std::tuple<QString,\s*QString,\s*QString,\s*QString>> \w+\s*\{', content)
|
||||
if not vector_match: return []
|
||||
|
||||
start_idx = vector_match.end() - 1
|
||||
vector_content = extract_bracket_block(content, start_idx)
|
||||
|
||||
local_parent_map = PARENT_KEYS_MAPPING.get(filename, {})
|
||||
child_to_parent = {}
|
||||
|
||||
header_filename = filename.replace(".cc", ".h")
|
||||
header_filepath = os.path.join(REPO_ROOT, "frogpilot/ui/qt/offroad", header_filename)
|
||||
full_source = content
|
||||
if os.path.exists(header_filepath):
|
||||
with open(header_filepath, 'r', encoding='utf-8') as fh:
|
||||
full_source += "\n" + fh.read()
|
||||
|
||||
for qset_match in re.finditer(r'QSet<QString>\s+(\w+)\s*(?:=\s*)?\{([^}]+)\};', full_source):
|
||||
qset_name = qset_match.group(1)
|
||||
if qset_name in local_parent_map:
|
||||
parent_key = local_parent_map[qset_name]
|
||||
children_str = qset_match.group(2)
|
||||
children = [c.strip().strip('"') for c in children_str.split(',') if c.strip()]
|
||||
for child in children:
|
||||
child_to_parent[child] = parent_key
|
||||
|
||||
items = []
|
||||
|
||||
idx = 0
|
||||
while True:
|
||||
idx = vector_content.find('{"', idx)
|
||||
if idx == -1: break
|
||||
|
||||
block = extract_bracket_block(vector_content, idx)
|
||||
if not block:
|
||||
idx += 1
|
||||
continue
|
||||
|
||||
row_match = re.search(r'\{"([A-Za-z0-9_]+)"\s*,\s*(.*?)\s*\}$', block, re.DOTALL)
|
||||
if not row_match:
|
||||
idx += len(block)
|
||||
continue
|
||||
|
||||
key = row_match.group(1)
|
||||
rest = row_match.group(2)
|
||||
idx += len(block)
|
||||
|
||||
if key in EXCLUDED_KEYS or key.startswith("IgnoreMe"):
|
||||
continue
|
||||
|
||||
strings = re.findall(r'tr\("((?:[^"\\]|\\.)+)"\)|"((?:[^"\\]|\\.)+)"', rest)
|
||||
valid_strings = [s[0] or s[1] for s in strings if s[0] or s[1]]
|
||||
|
||||
if not valid_strings: continue
|
||||
|
||||
title = valid_strings[0]
|
||||
desc = valid_strings[1] if len(valid_strings) > 1 else ""
|
||||
options_endpoint = None
|
||||
|
||||
if key in DROPDOWN_MAPPING:
|
||||
m = DROPDOWN_MAPPING[key]
|
||||
key = m["key"]
|
||||
widget_type = "dropdown"
|
||||
options_endpoint = m["options_endpoint"]
|
||||
data_type = "string"
|
||||
else:
|
||||
data_type = get_param_type(key)
|
||||
if data_type == "unknown": continue
|
||||
widget_type = "toggle"
|
||||
min_val, max_val, step = None, None, None
|
||||
|
||||
for i in range(1, 10):
|
||||
placeholder = f"%{i}"
|
||||
if placeholder in desc and len(valid_strings) > i + 1:
|
||||
desc = desc.replace(placeholder, valid_strings[i + 1])
|
||||
|
||||
desc = re.sub(r'<br\s*/?>', '\n', desc, flags=re.IGNORECASE)
|
||||
desc = re.sub(r'<[^>]+>', '', desc)
|
||||
desc = desc.replace('\\"', '"').strip()
|
||||
title = re.sub(r'\s*\(\s*Default:\s*%\d\s*\)', '', title)
|
||||
title = re.sub(r'%\d', '', title).strip()
|
||||
desc = re.sub(r'\s*\(\s*Default:\s*%\d\s*\)', '', desc)
|
||||
desc = re.sub(r'%\d', '', desc).strip()
|
||||
|
||||
if widget_type == "toggle":
|
||||
snippet_match = None
|
||||
qset_name = ""
|
||||
if key in child_to_parent:
|
||||
parent_k = child_to_parent[key]
|
||||
for q, pk in local_parent_map.items():
|
||||
if pk == parent_k:
|
||||
qset_name = q
|
||||
break
|
||||
|
||||
# Let's match the original's regex for finding the Toggle = assignment line
|
||||
search_patterns = [r'param\s*==\s*"' + key + r'"']
|
||||
if qset_name:
|
||||
search_patterns.append(r'(?:' + qset_name + r'\.contains\(param\))')
|
||||
|
||||
for pattern in search_patterns:
|
||||
match = re.search(pattern + r'.*?[a-zA-Z]+Toggle\s*=\s*(.*?);', content, re.DOTALL)
|
||||
if match:
|
||||
snippet_match = match
|
||||
break
|
||||
|
||||
if snippet_match:
|
||||
assignment = snippet_match.group(1)
|
||||
if "FrogPilotParamValueControl" in assignment or "FrogPilotParamValueButtonControl" in assignment:
|
||||
widget_type = "numeric"
|
||||
if data_type in ("string", "bool", "unknown"):
|
||||
data_type = "float"
|
||||
|
||||
if qset_name == "alertVolumeControlKeys":
|
||||
if key in ["WarningImmediateVolume", "WarningSoftVolume"]:
|
||||
min_val, max_val, step = "25", "101", "1"
|
||||
else:
|
||||
min_val, max_val, step = "0", "101", "1"
|
||||
else:
|
||||
args_match = re.search(r'Control[^(]*\(([^;]+)\)', assignment)
|
||||
if args_match:
|
||||
args_str = args_match.group(1)
|
||||
num_match = re.search(r'icon\s*,\s*([-\d.]+)\s*,\s*([-\d.]+)\s*,(?:[^,]*,){2}\s*([-\d.]+)', args_str)
|
||||
if num_match:
|
||||
min_val, max_val, step = num_match.group(1), num_match.group(2), num_match.group(3)
|
||||
else:
|
||||
num_match = re.search(r'icon\s*,\s*([-\d.]+)\s*,\s*([-\d.]+)', args_str)
|
||||
if num_match:
|
||||
min_val, max_val = num_match.group(1), num_match.group(2)
|
||||
step_match = re.search(r'(?:std::map<float,\s*QString>\(\)|[a-zA-Z0-9_]+Labels)\s*,\s*([-\d.]+)', args_str)
|
||||
if step_match:
|
||||
step = step_match.group(1)
|
||||
|
||||
precision = None
|
||||
precision_match = re.search(r"QString::number\([^,]+,\s*'f'\s*,\s*(\d+)\)", rest)
|
||||
if precision_match:
|
||||
precision = int(precision_match.group(1))
|
||||
|
||||
if data_type == "float" and step and float(step).is_integer():
|
||||
data_type = "int"
|
||||
|
||||
s = {
|
||||
"key": key,
|
||||
"label": title,
|
||||
"description": desc,
|
||||
"data_type": data_type,
|
||||
"ui_type": widget_type
|
||||
}
|
||||
if widget_type == "numeric":
|
||||
if min_val is not None: s["min"] = float(min_val)
|
||||
if max_val is not None: s["max"] = float(max_val)
|
||||
if step is not None: s["step"] = float(step)
|
||||
if precision is not None: s["precision"] = precision
|
||||
elif widget_type == "dropdown":
|
||||
if options_endpoint: s["options_endpoint"] = options_endpoint
|
||||
if key in child_to_parent: s["parent_key"] = child_to_parent[key]
|
||||
if key in ALL_PARENT_KEYS: s["is_parent_toggle"] = True
|
||||
|
||||
items.append(s)
|
||||
|
||||
return items
|
||||
|
||||
def main():
|
||||
layout = []
|
||||
for cat in CATEGORIES:
|
||||
items = parse_cpp_file(cat["file"])
|
||||
if items:
|
||||
layout.append({
|
||||
"name": cat["name"],
|
||||
"icon": cat["icon"],
|
||||
"params": items
|
||||
})
|
||||
output_path = os.path.join(REPO_ROOT, "frogpilot/system/the_pond/assets/components/tools/device_settings_layout.json")
|
||||
with open(output_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(layout, f, indent=2)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user