@@ -66,6 +66,14 @@ def main():
6666 sysid ARX system identification
6767 hardware Hardware configuration report
6868 magfit Magnetometer calibration analysis
69+ params Query/validate firmware parameter tables
70+
71+ \b
72+ Parameter query examples:
73+ stune params ap # List all ArduPilot parameters
74+ stune params ATC_RAT_RLL_P # Show param details
75+ stune params --search notch # Search across platforms
76+ stune params --validate ATC_RAT_RLL_P 0.15 -p ardupilot
6977
7078 \b
7179 The platform is auto-detected from the log file format.
@@ -921,5 +929,193 @@ def _run_single_analysis(capability: str, log_file: Path, platform_name: str,
921929# 入口
922930# ---------------------------------------------------------------------------
923931
932+ @main .command ()
933+ @click .argument ("query" , required = False )
934+ @click .option ("--platform" , "-p" , default = None , help = "Target platform: ardupilot, betaflight, px4" )
935+ @click .option ("--search" , "-s" , "search_term" , default = None , help = "Search keyword across all platforms" )
936+ @click .option ("--category" , "-c" , default = "all" , help = "Filter by category" )
937+ @click .option ("--validate" , "-v" , "validate_pair" , nargs = 2 , default = None , metavar = "NAME VALUE" ,
938+ help = "Validate a parameter name and value" )
939+ def params (query , platform , search_term , category , validate_pair ):
940+ """Query and validate flight controller parameters.
941+
942+ \b
943+ Query parameter tables loaded from knowledge base JSON files
944+ (smarttune/knowledge/params/). Data scraped from official firmware repos.
945+
946+ \b
947+ Examples:
948+ stune params ap # List all ArduPilot parameters
949+ stune params bf # List all Betaflight parameters
950+ stune params px4 # List all PX4 parameters
951+ stune params ATC_RAT_RLL_P # Show detail for a specific param
952+ stune params --search notch # Search for "notch" across all platforms
953+ stune params --validate ATC_RAT_RLL_P 0.15 --platform ardupilot
954+ stune params --category pid --platform betaflight
955+ """
956+ from smarttune .platform .params import ParamTable
957+ from rich .table import Table
958+ from rich .panel import Panel
959+ import json
960+
961+ _PLATFORM_ALIASES = {
962+ "ap" : "ardupilot" , "ardupilot" : "ardupilot" ,
963+ "bf" : "betaflight" , "betaflight" : "betaflight" ,
964+ "px4" : "px4" ,
965+ }
966+
967+ # ── validate mode ──────────────────────────────────────
968+ if validate_pair :
969+ name , val_str = validate_pair
970+ try :
971+ value = float (val_str )
972+ except ValueError :
973+ _console .print (f"[red]Invalid value: { val_str } [/red]" )
974+ sys .exit (1 )
975+
976+ if not platform :
977+ _console .print ("[red]--platform required for --validate[/red]" )
978+ sys .exit (1 )
979+
980+ platform = _PLATFORM_ALIASES .get (platform , platform )
981+ try :
982+ tbl = ParamTable .from_knowledge (platform )
983+ except FileNotFoundError as e :
984+ _console .print (f"[red]{ e } [/red]" )
985+ sys .exit (1 )
986+
987+ ok , msg = tbl .validate (name , value )
988+ if ok :
989+ _console .print (f"[green]✓ { msg } [/green]" )
990+ sys .exit (0 )
991+ else :
992+ _console .print (f"[red]✗ { msg } [/red]" )
993+ sys .exit (1 )
994+
995+ # ── search mode ────────────────────────────────────────
996+ if search_term :
997+ available = ParamTable .available_platforms ()
998+ if platform :
999+ platform = _PLATFORM_ALIASES .get (platform , platform )
1000+ available = [platform ] if platform in available else []
1001+
1002+ if not available :
1003+ _console .print ("[red]No platforms available[/red]" )
1004+ sys .exit (1 )
1005+
1006+ total = 0
1007+ for plat in available :
1008+ tbl = ParamTable .from_knowledge (plat )
1009+ results = tbl .search (search_term )
1010+ if not results :
1011+ continue
1012+ total += len (results )
1013+ _console .print (f"\n [bold cyan]{ tbl .platform } [/bold cyan] — { len (results )} match(es)" )
1014+ t = Table (show_header = True , box = None )
1015+ t .add_column ("Parameter" , style = "cyan" )
1016+ t .add_column ("Category" )
1017+ t .add_column ("Type" )
1018+ t .add_column ("Range" )
1019+ t .add_column ("Description" )
1020+ for r in results :
1021+ rng = ""
1022+ if r .min is not None or r .max is not None :
1023+ rng = f"[{ r .min } , { r .max } ]"
1024+ if r .unit :
1025+ rng += f" { r .unit } "
1026+ t .add_row (r .name , r .category , r .type , rng , r .description [:60 ])
1027+ _console .print (t )
1028+
1029+ if total == 0 :
1030+ _console .print (f"[yellow]No parameters matching '{ search_term } '[/yellow]" )
1031+ return
1032+
1033+ # ── query mode (specific param or platform list) ───────
1034+ if query :
1035+ # Try as platform alias first
1036+ plat_guess = _PLATFORM_ALIASES .get (query .lower ())
1037+ if plat_guess :
1038+ # List all for this platform
1039+ platform = plat_guess
1040+ else :
1041+ # Treat as parameter name — try all platforms
1042+ available = ParamTable .available_platforms ()
1043+ found = []
1044+ for plat in available :
1045+ tbl = ParamTable .from_knowledge (plat )
1046+ pd = tbl .query (query )
1047+ if pd :
1048+ found .append ((plat , pd ))
1049+
1050+ if found :
1051+ for plat , pd in found :
1052+ _console .print (Panel .fit (
1053+ f"[bold cyan]{ pd .name } [/bold cyan]\n "
1054+ f"[dim]Platform:[/dim] { plat } | "
1055+ f"[dim]Category:[/dim] { pd .category } | "
1056+ f"[dim]Type:[/dim] { pd .type } \n "
1057+ f"[dim]Default:[/dim] { pd .default } \n "
1058+ f"[dim]Range:[/dim] [{ pd .min } , { pd .max } ]"
1059+ + (f" { pd .unit } " if pd .unit else "" ) + "\n "
1060+ f"[dim]Description:[/dim] { pd .description or '—' } " ,
1061+ title = f"Parameter: { pd .name } "
1062+ ))
1063+ return
1064+ else :
1065+ _console .print (f"[yellow]Parameter '{ query } ' not found in any platform[/yellow]" )
1066+ _console .print (f"[dim]Try --search for partial matches[/dim]" )
1067+ sys .exit (1 )
1068+
1069+ # ── list mode (platform specified or no args) ──────────
1070+ if not platform :
1071+ # No args at all — show available platforms
1072+ available = ParamTable .available_platforms ()
1073+ if not available :
1074+ _console .print ("[yellow]No parameter tables found in knowledge base[/yellow]" )
1075+ return
1076+ _console .print ("[bold]Available parameter tables:[/bold]" )
1077+ for plat in available :
1078+ tbl = ParamTable .from_knowledge (plat )
1079+ cats = ", " .join (tbl .categories ())
1080+ _console .print (f" [cyan]{ tbl .platform } [/cyan] — { len (tbl )} params ({ cats } )" )
1081+ _console .print ("\n [dim]Usage: stune params ap | stune params bf | stune params px4[/dim]" )
1082+ return
1083+
1084+ platform = _PLATFORM_ALIASES .get (platform , platform )
1085+ try :
1086+ tbl = ParamTable .from_knowledge (platform )
1087+ except FileNotFoundError as e :
1088+ _console .print (f"[red]{ e } [/red]" )
1089+ sys .exit (1 )
1090+
1091+ params_list = tbl .list_all ()
1092+ if category != "all" :
1093+ params_list = tbl .list_by_category (category )
1094+
1095+ cat_counts = {}
1096+ for p in params_list :
1097+ cat_counts [p .category ] = cat_counts .get (p .category , 0 ) + 1
1098+
1099+ _console .print (f"\n [bold]{ tbl .platform } [/bold] — { len (params_list )} parameters"
1100+ + (f" (category: { category } )" if category != "all" else "" )
1101+ + (f" [dim]categories: { ', ' .join (f'{ k } ({ v } )' for k ,v in sorted (cat_counts .items ()))} [/dim]" if category == "all" else "" ))
1102+
1103+ t = Table (show_header = True , box = None )
1104+ t .add_column ("Parameter" , style = "cyan" )
1105+ t .add_column ("Category" )
1106+ t .add_column ("Type" )
1107+ t .add_column ("Default" )
1108+ t .add_column ("Range" )
1109+ t .add_column ("Description" )
1110+ for p in sorted (params_list , key = lambda x : (x .category , x .name )):
1111+ rng = ""
1112+ if p .min is not None or p .max is not None :
1113+ rng = f"[{ p .min } , { p .max } ]"
1114+ if p .unit :
1115+ rng += f" { p .unit } "
1116+ t .add_row (p .name , p .category , p .type , str (p .default ), rng , (p .description or "" )[:80 ])
1117+ _console .print (t )
1118+
1119+
9241120if __name__ == "__main__" :
9251121 main ()
0 commit comments