Add shade profiles, 2D V×I sweep, meter format toggle, ON/OFF indicators, and GUI console

- Shade profile: CSV-driven irradiance/voltage sequences with load control
  (bench.run_shade_profile, CLI shade-profile command, GUI profile panel)
- 2D sweep: voltage × load current efficiency map with live graph updates
  (bench.sweep_vi, CLI sweep-vi command, GUI sweep panel with background thread)
- GUI: meter format selector (scientific/normal), supply/load ON/OFF indicators,
  console log with stdout redirect and color-coded messages
- Sample profiles: cloud_pass, partial_shade, intermittent_clouds

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 15:27:48 +07:00
parent 74917e05f2
commit 956be4b77a
8 changed files with 953 additions and 14 deletions

View File

@@ -438,6 +438,55 @@ def cmd_efficiency(bench: MPPTTestbench, args: argparse.Namespace) -> None:
print(f"Average efficiency: {result['avg_efficiency']:.2f} %")
def cmd_sweep_vi(bench: MPPTTestbench, args: argparse.Namespace) -> None:
"""Run a 2D voltage × load current sweep."""
print(
f"2D sweep: V={args.v_start:.1f}-{args.v_stop:.1f}V (step {args.v_step:.1f}), "
f"I_load={args.i_start:.2f}-{args.i_stop:.2f}A (step {args.i_step:.2f}), "
f"I_limit={args.current_limit:.1f}A, settle={args.settle:.1f}s"
)
print()
results = bench.sweep_vi(
v_start=args.v_start,
v_stop=args.v_stop,
v_step=args.v_step,
i_start=args.i_start,
i_stop=args.i_stop,
i_step=args.i_step,
current_limit=args.current_limit,
settle_time=args.settle,
)
_write_sweep_csv(results, args.output)
_print_sweep_summary(results)
def cmd_shade_profile(bench: MPPTTestbench, args: argparse.Namespace) -> None:
"""Run a shade / irradiance profile from a CSV file."""
steps = MPPTTestbench.load_shade_profile(args.profile)
duration = steps[-1]["time"] if steps else 0
print(f"Shade profile: {args.profile}")
print(f" {len(steps)} steps over {duration:.0f}s, settle={args.settle:.1f}s")
# Show what modes/values are used
modes = {s.get("load_mode", "?") for s in steps}
voltages = [s["voltage"] for s in steps]
currents = [s["current_limit"] for s in steps]
print(
f" Voltage: {min(voltages):.1f} - {max(voltages):.1f}V, "
f"I_limit: {min(currents):.1f} - {max(currents):.1f}A, "
f"Load modes: {', '.join(sorted(modes))}"
)
print()
results = bench.run_shade_profile(steps, settle_time=args.settle)
_write_sweep_csv(results, args.output)
_print_sweep_summary(results)
def cmd_supply(bench: MPPTTestbench, args: argparse.Namespace) -> None:
"""Control the DC supply directly."""
if args.action == "on":
@@ -515,6 +564,8 @@ examples:
%(prog)s sweep --v-start 10 --v-stop 50 --v-step 1 --current-limit 10 -o sweep.csv
%(prog)s sweep-load --voltage 75 --current-limit 10 --i-start 1 --i-stop 20 --i-step 1 -o load.csv
%(prog)s efficiency --voltage 36 --current-limit 10 --samples 10
%(prog)s sweep-vi --v-start 35 --v-stop 100 --v-step 5 --i-start 0.5 --i-stop 30 --i-step 1 --current-limit 35 -o map.csv
%(prog)s shade-profile --profile cloud_pass.csv --settle 2.0 -o shade_results.csv
%(prog)s supply set --voltage 24 --current 10
%(prog)s supply on
%(prog)s load set --mode CC --value 5.0
@@ -599,6 +650,24 @@ examples:
p_eff.add_argument("--load-mode", choices=["CC", "CR", "CV", "CP"])
p_eff.add_argument("--load-value", type=float)
# sweep-vi (2D)
p_svi = sub.add_parser("sweep-vi", help="2D voltage × load current sweep (efficiency map)")
p_svi.add_argument("--v-start", type=float, required=True, help="Start voltage (V)")
p_svi.add_argument("--v-stop", type=float, required=True, help="Stop voltage (V)")
p_svi.add_argument("--v-step", type=float, required=True, help="Voltage step (V)")
p_svi.add_argument("--i-start", type=float, required=True, help="Start load current (A)")
p_svi.add_argument("--i-stop", type=float, required=True, help="Stop load current (A)")
p_svi.add_argument("--i-step", type=float, required=True, help="Current step (A)")
p_svi.add_argument("--current-limit", type=float, required=True, help="Supply current limit (A)")
p_svi.add_argument("--settle", type=float, default=2.0, help="Settle time per step (s)")
p_svi.add_argument("-o", "--output", help="CSV output file")
# shade-profile
p_shade = sub.add_parser("shade-profile", help="Run a shade/irradiance profile from CSV")
p_shade.add_argument("--profile", required=True, help="Profile CSV file (time,voltage,current_limit,...)")
p_shade.add_argument("--settle", type=float, default=2.0, help="Settle time per step (s)")
p_shade.add_argument("-o", "--output", help="CSV output file for results")
# supply (direct control)
p_sup = sub.add_parser("supply", help="Direct supply control")
p_sup_sub = p_sup.add_subparsers(dest="action", required=True)
@@ -631,6 +700,8 @@ examples:
"sweep": cmd_sweep,
"sweep-load": cmd_sweep_load,
"efficiency": cmd_efficiency,
"sweep-vi": cmd_sweep_vi,
"shade-profile": cmd_shade_profile,
"supply": cmd_supply,
"load": cmd_load,
"safe-off": cmd_safe_off,