From 02dc12a5c456b1b3c46d2abf368d2c65bb24e38c Mon Sep 17 00:00:00 2001 From: yair Date: Sun, 16 Nov 2025 05:39:44 +0200 Subject: [PATCH] feat: add simplified syntax to camera control scripts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update camera_control.py to support 'property value' syntax * camera_control.py gain → gets current gain value * camera_control.py gain 33 → sets gain to 33 * Add --host, --port, and --timeout parameters * Maintain backward compatibility with existing commands - Update launch-ids.py to support simplified property setting at startup * launch-ids.py exposure 16 → launches with 16ms exposure * launch-ids.py framerate 30 → launches with 30fps framerate * launch-ids.py gain 50 → launches with gain set to 50 * Preserve traditional flag syntax for full backward compatibility Both scripts now provide intuitive property-based syntax while maintaining all existing functionality and command-line options. --- scripts/camera_control.py | 285 +++++++++++++++++++++----------------- scripts/launch-ids.py | 58 +++++++- 2 files changed, 214 insertions(+), 129 deletions(-) diff --git a/scripts/camera_control.py b/scripts/camera_control.py index 43bf057..23ef327 100644 --- a/scripts/camera_control.py +++ b/scripts/camera_control.py @@ -10,18 +10,18 @@ client for UDP camera control Usage: uv run scripts/camera_control.py # Get all camera settings (default) uv run scripts/camera_control.py get-all # Get all camera settings - uv run scripts/camera_control.py get-exposure # Get current exposure - uv run scripts/camera_control.py set-exposure 10 # Set exposure to 10ms - uv run scripts/camera_control.py get-framerate # Get current framerate - uv run scripts/camera_control.py set-framerate 30 # Set framerate to 30fps - uv run scripts/camera_control.py get-gain # Get current gain - uv run scripts/camera_control.py set-gain 50 # Set gain to 50 - uv run scripts/camera_control.py get-auto-exposure # Get auto-exposure status - uv run scripts/camera_control.py set-auto-exposure 1 # Enable auto-exposure - uv run scripts/camera_control.py get-auto-gain # Get auto-gain status - uv run scripts/camera_control.py set-auto-gain 1 # Enable auto-gain - uv run scripts/camera_control.py get-gain-boost # Get gain boost status - uv run scripts/camera_control.py set-gain-boost 1 # Enable gain boost + uv run scripts/camera_control.py exposure # Get current exposure + uv run scripts/camera_control.py exposure 10 # Set exposure to 10ms + uv run scripts/camera_control.py framerate # Get current framerate + uv run scripts/camera_control.py framerate 30 # Set framerate to 30fps + uv run scripts/camera_control.py gain # Get current gain + uv run scripts/camera_control.py gain 50 # Set gain to 50 + uv run scripts/camera_control.py auto-exposure # Get auto-exposure status + uv run scripts/camera_control.py auto-exposure 1 # Enable auto-exposure + uv run scripts/camera_control.py auto-gain # Get auto-gain status + uv run scripts/camera_control.py auto-gain 1 # Enable auto-gain + uv run scripts/camera_control.py gain-boost # Get gain boost status + uv run scripts/camera_control.py gain-boost 1 # Enable gain boost uv run scripts/camera_control.py status # Get pipeline status This script provides control commands @@ -60,10 +60,10 @@ def send_command(command, host="127.0.0.1", port=5001, timeout=1.0): sock.close() -def simple_command(command, description="Command"): +def simple_command(command, description="Command", host="127.0.0.1", port=5001, timeout=1.0): """Execute a single command and print the result""" print(f"{description}...") - response = send_command(command) + response = send_command(command, host, port, timeout) print(f"Response: {response}") # Check if response indicates success @@ -76,7 +76,7 @@ def simple_command(command, description="Command"): print(f"Unknown response: {response}") return False -def get_all_settings(): +def get_all_settings(host="127.0.0.1", port=5001, timeout=1.0): """Get all camera settings""" print("=" * 70) print("Camera Settings") @@ -94,7 +94,7 @@ def get_all_settings(): all_success = True for name, command in settings: - response = send_command(command) + response = send_command(command, host, port, timeout) print(f"{name:20s}: {response}") if response.startswith("ERROR"): all_success = False @@ -105,155 +105,186 @@ def get_all_settings(): def main(): """Main function with argument parsing""" parser = argparse.ArgumentParser( - description="UDP Exposure Control Client", + description="UDP Camera Control Client", epilog=""" Examples: %(prog)s # Get all camera settings (default) %(prog)s get-all # Get all camera settings - %(prog)s get-exposure # Get current exposure - %(prog)s set-exposure 10 # Set exposure to 10ms - %(prog)s get-framerate # Get current framerate - %(prog)s set-framerate 30 # Set framerate to 30fps - %(prog)s get-gain # Get current gain - %(prog)s set-gain 50 # Set gain to 50 - %(prog)s get-auto-exposure # Get auto-exposure status - %(prog)s set-auto-exposure 1 # Enable auto-exposure - %(prog)s get-auto-gain # Get auto-gain status - %(prog)s set-auto-gain 1 # Enable auto-gain - %(prog)s get-gain-boost # Get gain boost status - %(prog)s set-gain-boost 1 # Enable gain boost + %(prog)s exposure # Get current exposure + %(prog)s exposure 10 # Set exposure to 10ms + %(prog)s framerate # Get current framerate + %(prog)s framerate 30 # Set framerate to 30fps + %(prog)s gain # Get current gain + %(prog)s gain 50 # Set gain to 50 + %(prog)s auto-exposure # Get auto-exposure status + %(prog)s auto-exposure 1 # Enable auto-exposure + %(prog)s auto-gain # Get auto-gain status + %(prog)s auto-gain 1 # Enable auto-gain + %(prog)s gain-boost # Get gain boost status + %(prog)s gain-boost 1 # Enable gain boost %(prog)s status # Get pipeline status """, formatter_class=argparse.RawDescriptionHelpFormatter ) - parser.add_argument('command', + parser.add_argument('property', nargs='?', - choices=['get-all', 'get-exposure', 'get-exposure-range', 'set-exposure', - 'get-framerate', 'set-framerate', 'get-gain', 'set-gain', - 'get-auto-exposure', 'set-auto-exposure', - 'get-auto-gain', 'set-auto-gain', - 'get-gain-boost', 'set-gain-boost', 'status'], - help='Command to execute') + choices=['get-all', 'exposure', 'framerate', 'gain', + 'auto-exposure', 'auto-gain', 'gain-boost', 'status'], + help='Property to get or set') parser.add_argument('value', nargs='?', - type=float, - help='Value for set commands (exposure in ms, framerate in fps)') + help='Value to set (if not provided, gets current value)') parser.add_argument('--host', default='127.0.0.1', + metavar='IP', help='Host address (default: 127.0.0.1)') parser.add_argument('--port', type=int, default=5001, + metavar='PORT', help='Port number (default: 5001)') + parser.add_argument('--timeout', + type=float, + default=1.0, + metavar='SECONDS', + help='Timeout for UDP requests in seconds (default: 1.0)') + # Enable tab completion if argcomplete is available if argcomplete: argcomplete.autocomplete(parser) args = parser.parse_args() - # If no command provided, run get-all by default - if args.command is None: - success = get_all_settings() + # If no property provided, run get-all by default + if args.property is None: + success = get_all_settings(host=args.host, port=args.port, timeout=args.timeout) sys.exit(0 if success else 1) + # Handle special commands + if args.property == 'get-all': + success = get_all_settings(host=args.host, port=args.port, timeout=args.timeout) + elif args.property == 'status': + success = simple_command("STATUS", "Getting pipeline status", + host=args.host, port=args.port, timeout=args.timeout) - # Handle individual commands - if args.command == 'get-all': - success = get_all_settings() - - elif args.command == 'get-exposure': - success = simple_command("GET_EXPOSURE", "Getting current exposure") - - elif args.command == 'get-exposure-range': - success = simple_command("GET_EXPOSURE_RANGE", "Getting exposure range") - - elif args.command == 'set-exposure': + # Handle property-based commands + elif args.property == 'exposure': if args.value is None: - print("Error: set-exposure requires a value (exposure in milliseconds)") - parser.print_help() - sys.exit(1) - success = simple_command(f"SET_EXPOSURE {args.value}", f"Setting exposure to {args.value}ms") - - elif args.command == 'get-framerate': - success = simple_command("GET_FRAMERATE", "Getting current framerate") - - elif args.command == 'set-framerate': + # Get exposure + success = simple_command("GET_EXPOSURE", "Getting current exposure", + host=args.host, port=args.port, timeout=args.timeout) + else: + # Set exposure + try: + exposure_value = float(args.value) + success = simple_command(f"SET_EXPOSURE {exposure_value}", + f"Setting exposure to {exposure_value}ms", + host=args.host, port=args.port, timeout=args.timeout) + except ValueError: + print(f"Error: Invalid exposure value '{args.value}'. Must be a number.") + sys.exit(1) + + elif args.property == 'framerate': if args.value is None: - print("Error: set-framerate requires a value (framerate in fps)") - parser.print_help() - sys.exit(1) - success = simple_command(f"SET_FRAMERATE {args.value}", f"Setting framerate to {args.value}fps") - - elif args.command == 'get-gain': - success = simple_command("GET_GAIN", "Getting current gain") - - elif args.command == 'set-gain': + # Get framerate + success = simple_command("GET_FRAMERATE", "Getting current framerate", + host=args.host, port=args.port, timeout=args.timeout) + else: + # Set framerate + try: + framerate_value = float(args.value) + success = simple_command(f"SET_FRAMERATE {framerate_value}", + f"Setting framerate to {framerate_value}fps", + host=args.host, port=args.port, timeout=args.timeout) + except ValueError: + print(f"Error: Invalid framerate value '{args.value}'. Must be a number.") + sys.exit(1) + + elif args.property == 'gain': if args.value is None: - print("Error: set-gain requires a value (0-100, 0 for auto)") - parser.print_help() - sys.exit(1) - # Convert to int for gain - gain_value = int(args.value) - if gain_value < 0 or gain_value > 100: - print("Error: Gain must be between 0 and 100 (0 for auto)") - sys.exit(1) - success = simple_command(f"SET_GAIN {gain_value}", f"Setting gain to {gain_value}") - - elif args.command == 'get-auto-exposure': - success = simple_command("GET_AUTO_EXPOSURE", "Getting auto-exposure status") - - elif args.command == 'set-auto-exposure': + # Get gain + success = simple_command("GET_GAIN", "Getting current gain", + host=args.host, port=args.port, timeout=args.timeout) + else: + # Set gain + try: + gain_value = int(float(args.value)) + if gain_value < 0 or gain_value > 100: + print("Error: Gain must be between 0 and 100 (0 for auto)") + sys.exit(1) + success = simple_command(f"SET_GAIN {gain_value}", + f"Setting gain to {gain_value}", + host=args.host, port=args.port, timeout=args.timeout) + except ValueError: + print(f"Error: Invalid gain value '{args.value}'. Must be a number 0-100.") + sys.exit(1) + + elif args.property == 'auto-exposure': if args.value is None: - print("Error: set-auto-exposure requires a value (0=off, 1=on)") - parser.print_help() - sys.exit(1) - # Convert to int - ae_value = int(args.value) - if ae_value not in [0, 1]: - print("Error: Auto-exposure must be 0 (off) or 1 (on)") - sys.exit(1) - success = simple_command(f"SET_AUTO_EXPOSURE {ae_value}", - f"{'Enabling' if ae_value else 'Disabling'} auto-exposure") - - elif args.command == 'get-auto-gain': - success = simple_command("GET_AUTO_GAIN", "Getting auto-gain status") - - elif args.command == 'set-auto-gain': + # Get auto-exposure + success = simple_command("GET_AUTO_EXPOSURE", "Getting auto-exposure status", + host=args.host, port=args.port, timeout=args.timeout) + else: + # Set auto-exposure + try: + ae_value = int(float(args.value)) + if ae_value not in [0, 1]: + print("Error: Auto-exposure must be 0 (off) or 1 (on)") + sys.exit(1) + success = simple_command(f"SET_AUTO_EXPOSURE {ae_value}", + f"{'Enabling' if ae_value else 'Disabling'} auto-exposure", + host=args.host, port=args.port, timeout=args.timeout) + except ValueError: + print(f"Error: Invalid auto-exposure value '{args.value}'. Must be 0 or 1.") + sys.exit(1) + + elif args.property == 'auto-gain': if args.value is None: - print("Error: set-auto-gain requires a value (0=off, 1=on)") - parser.print_help() - sys.exit(1) - # Convert to int - ag_value = int(args.value) - if ag_value not in [0, 1]: - print("Error: Auto-gain must be 0 (off) or 1 (on)") - sys.exit(1) - success = simple_command(f"SET_AUTO_GAIN {ag_value}", - f"{'Enabling' if ag_value else 'Disabling'} auto-gain") - - elif args.command == 'get-gain-boost': - success = simple_command("GET_GAIN_BOOST", "Getting gain boost status") - - elif args.command == 'set-gain-boost': + # Get auto-gain + success = simple_command("GET_AUTO_GAIN", "Getting auto-gain status", + host=args.host, port=args.port, timeout=args.timeout) + else: + # Set auto-gain + try: + ag_value = int(float(args.value)) + if ag_value not in [0, 1]: + print("Error: Auto-gain must be 0 (off) or 1 (on)") + sys.exit(1) + success = simple_command(f"SET_AUTO_GAIN {ag_value}", + f"{'Enabling' if ag_value else 'Disabling'} auto-gain", + host=args.host, port=args.port, timeout=args.timeout) + except ValueError: + print(f"Error: Invalid auto-gain value '{args.value}'. Must be 0 or 1.") + sys.exit(1) + + elif args.property == 'gain-boost': if args.value is None: - print("Error: set-gain-boost requires a value (0=off, 1=on)") - parser.print_help() - sys.exit(1) - # Convert to int - gb_value = int(args.value) - if gb_value not in [0, 1]: - print("Error: Gain boost must be 0 (off) or 1 (on)") - sys.exit(1) - success = simple_command(f"SET_GAIN_BOOST {gb_value}", - f"{'Enabling' if gb_value else 'Disabling'} gain boost") - - elif args.command == 'status': - success = simple_command("STATUS", "Getting pipeline status") + # Get gain-boost + success = simple_command("GET_GAIN_BOOST", "Getting gain boost status", + host=args.host, port=args.port, timeout=args.timeout) + else: + # Set gain-boost + try: + gb_value = int(float(args.value)) + if gb_value not in [0, 1]: + print("Error: Gain boost must be 0 (off) or 1 (on)") + sys.exit(1) + success = simple_command(f"SET_GAIN_BOOST {gb_value}", + f"{'Enabling' if gb_value else 'Disabling'} gain boost", + host=args.host, port=args.port, timeout=args.timeout) + except ValueError: + print(f"Error: Invalid gain boost value '{args.value}'. Must be 0 or 1.") + sys.exit(1) + + else: + print(f"Error: Unknown property '{args.property}'") + parser.print_help() + sys.exit(1) # Exit with appropriate code sys.exit(0 if success else 1) diff --git a/scripts/launch-ids.py b/scripts/launch-ids.py index 3209fc4..1f27ee7 100644 --- a/scripts/launch-ids.py +++ b/scripts/launch-ids.py @@ -17,7 +17,10 @@ # Basic Usage: # uv run .\scripts\launch-ids.py # Use all defaults # uv run .\scripts\launch-ids.py --help # Show all options -# uv run .\scripts\launch-ids.py -e 16 -f 30 # Set exposure & framerate +# uv run .\scripts\launch-ids.py exposure 16 # Set exposure to 16ms (simplified) +# uv run .\scripts\launch-ids.py framerate 30 # Set framerate to 30fps (simplified) +# uv run .\scripts\launch-ids.py gain 50 # Set gain to 50 (simplified) +# uv run .\scripts\launch-ids.py -e 16 -f 30 # Set exposure & framerate (traditional) # uv run .\scripts\launch-ids.py --port 6000 # Custom streaming port # uv run .\scripts\launch-ids.py --no-crop --quiet # No cropping, minimal output # uv run .\scripts\launch-ids.py --display # Enable 1/4 sized preview window @@ -493,7 +496,10 @@ def parse_arguments(): epilog=""" Examples: %(prog)s # Use all defaults - %(prog)s --exposure 16 --framerate 30 # Basic video settings + %(prog)s exposure 16 # Set exposure to 16ms + %(prog)s framerate 30 # Set framerate to 30fps + %(prog)s gain 50 # Set gain to 50 + %(prog)s --exposure 16 --framerate 30 # Traditional flag syntax %(prog)s --config custom.ini --port 6000 # Custom config and streaming port %(prog)s --host 192.168.1.100 --no-crop # Stream to remote host without cropping %(prog)s --control-port 6001 --verbose # Custom control port with verbose output @@ -502,6 +508,16 @@ Examples: add_help=True ) + # Add positional arguments for simplified syntax + parser.add_argument('property', + nargs='?', + choices=['exposure', 'framerate', 'gain', 'auto-exposure', 'auto-gain', 'gain-boost'], + help='Camera property to set (simplified syntax)') + + parser.add_argument('value', + nargs='?', + help='Value to set for the property (simplified syntax)') + # Camera configuration camera_group = parser.add_argument_group('Camera Settings') camera_group.add_argument( @@ -636,6 +652,44 @@ Examples: args = parser.parse_args() + # Handle simplified syntax (positional arguments) + if args.property and args.value: + try: + if args.property == 'exposure': + exposure_val = float(args.value) + if args.exposure is not None: + parser.error("Cannot specify exposure with both simplified syntax and --exposure flag") + args.exposure = exposure_val + elif args.property == 'framerate': + framerate_val = float(args.value) + if args.framerate is not None: + parser.error("Cannot specify framerate with both simplified syntax and --framerate flag") + args.framerate = framerate_val + elif args.property == 'gain': + gain_val = int(float(args.value)) + if args.gain is not None: + parser.error("Cannot specify gain with both simplified syntax and --gain flag") + args.gain = gain_val + elif args.property == 'auto-exposure': + ae_val = int(float(args.value)) + if ae_val not in [0, 1]: + parser.error("Auto-exposure value must be 0 (off) or 1 (on)") + args.auto_exposure = bool(ae_val) + elif args.property == 'auto-gain': + ag_val = int(float(args.value)) + if ag_val not in [0, 1]: + parser.error("Auto-gain value must be 0 (off) or 1 (on)") + args.auto_gain = bool(ag_val) + elif args.property == 'gain-boost': + gb_val = int(float(args.value)) + if gb_val not in [0, 1]: + parser.error("Gain-boost value must be 0 (off) or 1 (on)") + args.gain_boost = bool(gb_val) + except ValueError: + parser.error(f"Invalid value '{args.value}' for property '{args.property}'") + elif args.property and not args.value: + parser.error(f"Property '{args.property}' requires a value") + # Validation - only validate if provided if args.exposure is not None and (args.exposure < 0.015 or args.exposure > 30000): parser.error(f"Exposure must be between 0.015 and 30000 ms, got {args.exposure}")