Compare commits

...

3 Commits

Author SHA1 Message Date
yair
969d716283 feat: add simplified syntax to camera control scripts
- 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
  * Remove argcomplete dependency to simplify requirements
  * 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.
2025-11-16 05:40:32 +02:00
yair
02dc12a5c4 feat: add simplified syntax to camera control scripts
- 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.
2025-11-16 05:39:44 +02:00
yair
c95178829a ommit tests 2025-11-16 05:32:17 +02:00
2 changed files with 217 additions and 244 deletions

View File

@@ -1,31 +1,30 @@
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.8"
# dependencies = ["argcomplete"]
# dependencies = []
# ///
"""
Test client for UDP exposure control
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 test # Run full test suite
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 both individual control commands and full test suite functionality
This script provides control commands
for the UDP control interface for the IDS uEye camera.
Make sure launch-ids.py is running before executing commands.
"""
@@ -35,10 +34,6 @@ import socket
import time
import sys
try:
import argcomplete
except ImportError:
argcomplete = None
def send_command(command, host="127.0.0.1", port=5001, timeout=1.0):
"""Send a command and return the response"""
@@ -60,27 +55,11 @@ def send_command(command, host="127.0.0.1", port=5001, timeout=1.0):
finally:
sock.close()
def print_test(test_num, description, command, response):
"""Print formatted test result"""
print(f"\nTest {test_num}: {description}")
print(f" Command: {command}")
print(f" Response: {response}")
# Check if response indicates success
if response.startswith("OK"):
print(" ✓ PASS")
elif response.startswith("ERROR"):
if "OUT_OF_RANGE" in response or "INVALID" in response:
print(" ✓ PASS (Expected error)")
else:
print(" ✗ FAIL (Unexpected error)")
else:
print(" ? UNKNOWN")
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
@@ -93,7 +72,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")
@@ -111,7 +90,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
@@ -119,246 +98,186 @@ def get_all_settings():
print("=" * 70)
return all_success
def run_full_tests():
"""Run the full test suite (original functionality)"""
print("=" * 70)
print("UDP Exposure Control Test Client")
print("=" * 70)
print("Testing UDP control interface on 127.0.0.1:5001")
print()
# Check if server is reachable
print("Checking if control server is reachable...")
response = send_command("STATUS", timeout=2.0)
if "Timeout" in response:
print("✗ FAILED: Control server not responding")
print(" Make sure launch-ids.py is running first!")
sys.exit(1)
print("✓ Control server is reachable\n")
time.sleep(0.2)
# Test 1: Get current exposure
response = send_command("GET_EXPOSURE")
print_test(1, "Get current exposure", "GET_EXPOSURE", response)
time.sleep(0.2)
# Test 2: Set exposure to 10ms
response = send_command("SET_EXPOSURE 10")
print_test(2, "Set exposure to 10ms", "SET_EXPOSURE 10", response)
time.sleep(5.2)
# Test 3: Verify exposure was set
response = send_command("GET_EXPOSURE")
print_test(3, "Verify exposure changed", "GET_EXPOSURE", response)
time.sleep(0.2)
# Test 4: Set exposure to 2ms
response = send_command("SET_EXPOSURE 2")
print_test(4, "Set exposure to 2ms", "SET_EXPOSURE 2", response)
time.sleep(0.2)
# Test 5: Get framerate
response = send_command("GET_FRAMERATE")
print_test(5, "Get current framerate", "GET_FRAMERATE", response)
time.sleep(0.2)
# Test 6: Set framerate
response = send_command("SET_FRAMERATE 44")
print_test(6, "Set framerate to 44 fps", "SET_FRAMERATE 44", response)
time.sleep(0.2)
# Test 7: Verify framerate
response = send_command("GET_FRAMERATE")
print_test(7, "Verify framerate changed", "GET_FRAMERATE", response)
time.sleep(0.2)
# Test 8: Get status
response = send_command("STATUS")
print_test(8, "Get pipeline status", "STATUS", response)
time.sleep(0.2)
# Test 9: Invalid command
response = send_command("INVALID_CMD")
print_test(9, "Send invalid command", "INVALID_CMD", response)
time.sleep(0.2)
# Test 14: Restore original exposure (2ms)
response = send_command("SET_EXPOSURE 2")
print_test(14, "Restore exposure to 2ms", "SET_EXPOSURE 2", response)
time.sleep(0.2)
# Test 15: Restore original framerate (22 fps)
response = send_command("SET_FRAMERATE 22")
print_test(15, "Restore framerate to 22 fps", "SET_FRAMERATE 22", response)
print()
print("=" * 70)
print("Test completed!")
print()
print("Quick reference:")
print(" echo 'SET_EXPOSURE 10' | nc -u 127.0.0.1 5001")
print(" echo 'GET_EXPOSURE' | nc -u 127.0.0.1 5001")
print(" echo 'STATUS' | nc -u 127.0.0.1 5001")
print("=" * 70)
def main():
"""Main function with argument parsing"""
parser = argparse.ArgumentParser(
description="UDP Exposure Control Test Client",
description="UDP Camera Control Client",
epilog="""
Examples:
%(prog)s # Get all camera settings (default)
%(prog)s test # Run full test suite
%(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=['test', '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)')
# Enable tab completion if argcomplete is available
if argcomplete:
argcomplete.autocomplete(parser)
parser.add_argument('--timeout',
type=float,
default=1.0,
metavar='SECONDS',
help='Timeout for UDP requests in seconds (default: 1.0)')
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 test command (full test suite)
if args.command == 'test':
run_full_tests()
return
# 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")
# 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.command == 'get-framerate':
success = simple_command("GET_FRAMERATE", "Getting current framerate")
elif args.command == 'set-framerate':
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")
# 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.command == 'get-gain':
success = simple_command("GET_GAIN", "Getting current gain")
elif args.command == 'set-gain':
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}")
# 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.command == 'get-auto-exposure':
success = simple_command("GET_AUTO_EXPOSURE", "Getting auto-exposure status")
elif args.command == 'set-auto-exposure':
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")
# 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.command == 'get-auto-gain':
success = simple_command("GET_AUTO_GAIN", "Getting auto-gain status")
elif args.command == 'set-auto-gain':
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")
# 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.command == 'get-gain-boost':
success = simple_command("GET_GAIN_BOOST", "Getting gain boost status")
elif args.command == 'set-gain-boost':
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")
# 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)
elif args.command == 'status':
success = simple_command("STATUS", "Getting pipeline status")
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)

View File

@@ -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}")