Add 'api/' from commit '14ac229098e65aa643f84e8e17e0c5f1aaf8d639'
git-subtree-dir: api git-subtree-mainline:4743f19aefgit-subtree-split:14ac229098
This commit is contained in:
173
api/tests/api/test_api_changes.py
Normal file
173
api/tests/api/test_api_changes.py
Normal file
@@ -0,0 +1,173 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script to verify the API changes for camera settings and filename handling.
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
# API base URL
|
||||
BASE_URL = "http://localhost:8000"
|
||||
|
||||
def test_api_endpoint(endpoint, method="GET", data=None):
|
||||
"""Test an API endpoint and return the response"""
|
||||
url = f"{BASE_URL}{endpoint}"
|
||||
|
||||
try:
|
||||
if method == "GET":
|
||||
response = requests.get(url)
|
||||
elif method == "POST":
|
||||
response = requests.post(url, json=data, headers={"Content-Type": "application/json"})
|
||||
|
||||
print(f"\n{method} {endpoint}")
|
||||
print(f"Status: {response.status_code}")
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
print(f"Response: {json.dumps(result, indent=2)}")
|
||||
return result
|
||||
else:
|
||||
print(f"Error: {response.text}")
|
||||
return None
|
||||
|
||||
except requests.exceptions.ConnectionError:
|
||||
print(f"Error: Could not connect to {url}")
|
||||
print("Make sure the API server is running with: python main.py")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
return None
|
||||
|
||||
def test_camera_recording_with_settings():
|
||||
"""Test camera recording with new settings parameters"""
|
||||
|
||||
print("=" * 60)
|
||||
print("Testing Camera Recording API with New Settings")
|
||||
print("=" * 60)
|
||||
|
||||
# Test 1: Basic recording without settings
|
||||
print("\n1. Testing basic recording (no settings)")
|
||||
basic_request = {
|
||||
"camera_name": "camera1",
|
||||
"filename": "test_basic.avi"
|
||||
}
|
||||
|
||||
result = test_api_endpoint("/cameras/camera1/start-recording", "POST", basic_request)
|
||||
if result and result.get("success"):
|
||||
print("✅ Basic recording started successfully")
|
||||
print(f" Filename: {result.get('filename')}")
|
||||
|
||||
# Stop recording
|
||||
time.sleep(2)
|
||||
test_api_endpoint("/cameras/camera1/stop-recording", "POST")
|
||||
else:
|
||||
print("❌ Basic recording failed")
|
||||
|
||||
# Test 2: Recording with camera settings
|
||||
print("\n2. Testing recording with camera settings")
|
||||
settings_request = {
|
||||
"camera_name": "camera1",
|
||||
"filename": "test_with_settings.avi",
|
||||
"exposure_ms": 2.0,
|
||||
"gain": 4.0,
|
||||
"fps": 5.0
|
||||
}
|
||||
|
||||
result = test_api_endpoint("/cameras/camera1/start-recording", "POST", settings_request)
|
||||
if result and result.get("success"):
|
||||
print("✅ Recording with settings started successfully")
|
||||
print(f" Filename: {result.get('filename')}")
|
||||
|
||||
# Stop recording
|
||||
time.sleep(2)
|
||||
test_api_endpoint("/cameras/camera1/stop-recording", "POST")
|
||||
else:
|
||||
print("❌ Recording with settings failed")
|
||||
|
||||
# Test 3: Recording with only settings (no filename)
|
||||
print("\n3. Testing recording with settings only (no filename)")
|
||||
settings_only_request = {
|
||||
"camera_name": "camera1",
|
||||
"exposure_ms": 1.5,
|
||||
"gain": 3.0,
|
||||
"fps": 7.0
|
||||
}
|
||||
|
||||
result = test_api_endpoint("/cameras/camera1/start-recording", "POST", settings_only_request)
|
||||
if result and result.get("success"):
|
||||
print("✅ Recording with settings only started successfully")
|
||||
print(f" Filename: {result.get('filename')}")
|
||||
|
||||
# Stop recording
|
||||
time.sleep(2)
|
||||
test_api_endpoint("/cameras/camera1/stop-recording", "POST")
|
||||
else:
|
||||
print("❌ Recording with settings only failed")
|
||||
|
||||
# Test 4: Test filename datetime prefix
|
||||
print("\n4. Testing filename datetime prefix")
|
||||
timestamp_before = datetime.now().strftime("%Y%m%d_%H%M")
|
||||
|
||||
filename_test_request = {
|
||||
"camera_name": "camera1",
|
||||
"filename": "my_custom_name.avi"
|
||||
}
|
||||
|
||||
result = test_api_endpoint("/cameras/camera1/start-recording", "POST", filename_test_request)
|
||||
if result and result.get("success"):
|
||||
returned_filename = result.get('filename', '')
|
||||
print(f" Original filename: my_custom_name.avi")
|
||||
print(f" Returned filename: {returned_filename}")
|
||||
|
||||
# Check if datetime prefix was added
|
||||
if timestamp_before in returned_filename and "my_custom_name.avi" in returned_filename:
|
||||
print("✅ Datetime prefix correctly added to filename")
|
||||
else:
|
||||
print("❌ Datetime prefix not properly added")
|
||||
|
||||
# Stop recording
|
||||
time.sleep(2)
|
||||
test_api_endpoint("/cameras/camera1/stop-recording", "POST")
|
||||
else:
|
||||
print("❌ Filename test failed")
|
||||
|
||||
def test_system_status():
|
||||
"""Test basic system status to ensure API is working"""
|
||||
print("\n" + "=" * 60)
|
||||
print("Testing System Status")
|
||||
print("=" * 60)
|
||||
|
||||
# Test system status
|
||||
result = test_api_endpoint("/system/status")
|
||||
if result:
|
||||
print("✅ System status API working")
|
||||
print(f" System started: {result.get('system_started')}")
|
||||
print(f" MQTT connected: {result.get('mqtt_connected')}")
|
||||
else:
|
||||
print("❌ System status API failed")
|
||||
|
||||
# Test camera status
|
||||
result = test_api_endpoint("/cameras")
|
||||
if result:
|
||||
print("✅ Camera status API working")
|
||||
for camera_name, camera_info in result.items():
|
||||
print(f" {camera_name}: {camera_info.get('status')}")
|
||||
else:
|
||||
print("❌ Camera status API failed")
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("USDA Vision Camera System - API Changes Test")
|
||||
print("This script tests the new camera settings parameters and filename handling")
|
||||
print("\nMake sure the system is running with: python main.py")
|
||||
|
||||
# Test system status first
|
||||
test_system_status()
|
||||
|
||||
# Test camera recording with new features
|
||||
test_camera_recording_with_settings()
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("Test completed!")
|
||||
print("=" * 60)
|
||||
92
api/tests/api/test_camera_recovery_api.py
Normal file
92
api/tests/api/test_camera_recovery_api.py
Normal file
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script for camera recovery API endpoints.
|
||||
|
||||
This script tests the new camera recovery functionality without requiring actual cameras.
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
import time
|
||||
from typing import Dict, Any
|
||||
|
||||
# API base URL
|
||||
BASE_URL = "http://localhost:8000"
|
||||
|
||||
def test_endpoint(method: str, endpoint: str, data: Dict[Any, Any] = None) -> Dict[Any, Any]:
|
||||
"""Test an API endpoint and return the response"""
|
||||
url = f"{BASE_URL}{endpoint}"
|
||||
|
||||
try:
|
||||
if method.upper() == "GET":
|
||||
response = requests.get(url, timeout=10)
|
||||
elif method.upper() == "POST":
|
||||
response = requests.post(url, json=data or {}, timeout=10)
|
||||
else:
|
||||
raise ValueError(f"Unsupported method: {method}")
|
||||
|
||||
print(f"\n{method} {endpoint}")
|
||||
print(f"Status: {response.status_code}")
|
||||
|
||||
if response.headers.get('content-type', '').startswith('application/json'):
|
||||
result = response.json()
|
||||
print(f"Response: {json.dumps(result, indent=2)}")
|
||||
return result
|
||||
else:
|
||||
print(f"Response: {response.text}")
|
||||
return {"text": response.text}
|
||||
|
||||
except requests.exceptions.ConnectionError:
|
||||
print(f"❌ Connection failed - API server not running at {BASE_URL}")
|
||||
return {"error": "connection_failed"}
|
||||
except requests.exceptions.Timeout:
|
||||
print(f"❌ Request timeout")
|
||||
return {"error": "timeout"}
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
return {"error": str(e)}
|
||||
|
||||
def main():
|
||||
"""Test camera recovery API endpoints"""
|
||||
print("🔧 Testing Camera Recovery API Endpoints")
|
||||
print("=" * 50)
|
||||
|
||||
# Test basic endpoints first
|
||||
print("\n📋 BASIC API TESTS")
|
||||
test_endpoint("GET", "/health")
|
||||
test_endpoint("GET", "/cameras")
|
||||
|
||||
# Test camera recovery endpoints
|
||||
print("\n🔧 CAMERA RECOVERY TESTS")
|
||||
|
||||
camera_names = ["camera1", "camera2"]
|
||||
|
||||
for camera_name in camera_names:
|
||||
print(f"\n--- Testing {camera_name} ---")
|
||||
|
||||
# Test connection
|
||||
test_endpoint("POST", f"/cameras/{camera_name}/test-connection")
|
||||
|
||||
# Test reconnect
|
||||
test_endpoint("POST", f"/cameras/{camera_name}/reconnect")
|
||||
|
||||
# Test restart grab
|
||||
test_endpoint("POST", f"/cameras/{camera_name}/restart-grab")
|
||||
|
||||
# Test reset timestamp
|
||||
test_endpoint("POST", f"/cameras/{camera_name}/reset-timestamp")
|
||||
|
||||
# Test full reset
|
||||
test_endpoint("POST", f"/cameras/{camera_name}/full-reset")
|
||||
|
||||
# Test reinitialize
|
||||
test_endpoint("POST", f"/cameras/{camera_name}/reinitialize")
|
||||
|
||||
time.sleep(0.5) # Small delay between tests
|
||||
|
||||
print("\n✅ Camera recovery API tests completed!")
|
||||
print("\nNote: Some operations may fail if cameras are not connected,")
|
||||
print("but the API endpoints should respond with proper error messages.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
168
api/tests/api/test_mqtt_events_api.py
Normal file
168
api/tests/api/test_mqtt_events_api.py
Normal file
@@ -0,0 +1,168 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script for MQTT events API endpoint
|
||||
|
||||
This script tests the new MQTT events history functionality by:
|
||||
1. Starting the system components
|
||||
2. Simulating MQTT messages
|
||||
3. Testing the API endpoint to retrieve events
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import time
|
||||
import requests
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
# Test configuration
|
||||
API_BASE_URL = "http://localhost:8000"
|
||||
MQTT_EVENTS_ENDPOINT = f"{API_BASE_URL}/mqtt/events"
|
||||
|
||||
def test_api_endpoint():
|
||||
"""Test the MQTT events API endpoint"""
|
||||
print("🧪 Testing MQTT Events API Endpoint")
|
||||
print("=" * 50)
|
||||
|
||||
try:
|
||||
# Test basic endpoint
|
||||
print("📡 Testing GET /mqtt/events (default limit=5)")
|
||||
response = requests.get(MQTT_EVENTS_ENDPOINT)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print(f"✅ API Response successful")
|
||||
print(f"📊 Total events: {data.get('total_events', 0)}")
|
||||
print(f"📋 Events returned: {len(data.get('events', []))}")
|
||||
|
||||
if data.get('events'):
|
||||
print(f"🕐 Last updated: {data.get('last_updated')}")
|
||||
print("\n📝 Recent events:")
|
||||
for i, event in enumerate(data['events'], 1):
|
||||
timestamp = datetime.fromisoformat(event['timestamp']).strftime('%H:%M:%S')
|
||||
print(f" {i}. [{timestamp}] {event['machine_name']}: {event['payload']} -> {event['normalized_state']}")
|
||||
else:
|
||||
print("📭 No events found")
|
||||
|
||||
else:
|
||||
print(f"❌ API Error: {response.status_code}")
|
||||
print(f" Response: {response.text}")
|
||||
|
||||
except requests.exceptions.ConnectionError:
|
||||
print("❌ Connection Error: API server not running")
|
||||
print(" Start the system first: python -m usda_vision_system.main")
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
|
||||
print()
|
||||
|
||||
# Test with custom limit
|
||||
try:
|
||||
print("📡 Testing GET /mqtt/events?limit=10")
|
||||
response = requests.get(f"{MQTT_EVENTS_ENDPOINT}?limit=10")
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print(f"✅ API Response successful")
|
||||
print(f"📋 Events returned: {len(data.get('events', []))}")
|
||||
else:
|
||||
print(f"❌ API Error: {response.status_code}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
|
||||
def test_system_status():
|
||||
"""Test system status to verify API is running"""
|
||||
print("🔍 Checking System Status")
|
||||
print("=" * 50)
|
||||
|
||||
try:
|
||||
response = requests.get(f"{API_BASE_URL}/system/status")
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print(f"✅ System Status: {'Running' if data.get('system_started') else 'Not Started'}")
|
||||
print(f"🔗 MQTT Connected: {'Yes' if data.get('mqtt_connected') else 'No'}")
|
||||
print(f"📡 Last MQTT Message: {data.get('last_mqtt_message', 'None')}")
|
||||
print(f"⏱️ Uptime: {data.get('uptime_seconds', 0):.1f} seconds")
|
||||
return True
|
||||
else:
|
||||
print(f"❌ System Status Error: {response.status_code}")
|
||||
return False
|
||||
|
||||
except requests.exceptions.ConnectionError:
|
||||
print("❌ Connection Error: API server not running")
|
||||
print(" Start the system first: python -m usda_vision_system.main")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
return False
|
||||
|
||||
def test_mqtt_status():
|
||||
"""Test MQTT status"""
|
||||
print("📡 Checking MQTT Status")
|
||||
print("=" * 50)
|
||||
|
||||
try:
|
||||
response = requests.get(f"{API_BASE_URL}/mqtt/status")
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print(f"🔗 MQTT Connected: {'Yes' if data.get('connected') else 'No'}")
|
||||
print(f"🏠 Broker: {data.get('broker_host')}:{data.get('broker_port')}")
|
||||
print(f"📋 Subscribed Topics: {len(data.get('subscribed_topics', []))}")
|
||||
print(f"📊 Message Count: {data.get('message_count', 0)}")
|
||||
print(f"❌ Error Count: {data.get('error_count', 0)}")
|
||||
|
||||
if data.get('subscribed_topics'):
|
||||
print("📍 Topics:")
|
||||
for topic in data['subscribed_topics']:
|
||||
print(f" - {topic}")
|
||||
|
||||
return True
|
||||
else:
|
||||
print(f"❌ MQTT Status Error: {response.status_code}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""Main test function"""
|
||||
print("🧪 MQTT Events API Test")
|
||||
print("=" * 60)
|
||||
print(f"🎯 API Base URL: {API_BASE_URL}")
|
||||
print(f"📡 Events Endpoint: {MQTT_EVENTS_ENDPOINT}")
|
||||
print()
|
||||
|
||||
# Test system status first
|
||||
if not test_system_status():
|
||||
print("\n❌ System not running. Please start the system first:")
|
||||
print(" python -m usda_vision_system.main")
|
||||
return
|
||||
|
||||
print()
|
||||
|
||||
# Test MQTT status
|
||||
if not test_mqtt_status():
|
||||
print("\n❌ MQTT not available")
|
||||
return
|
||||
|
||||
print()
|
||||
|
||||
# Test the events API
|
||||
test_api_endpoint()
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("🎯 Test Instructions:")
|
||||
print("1. Make sure the system is running")
|
||||
print("2. Turn machines on/off to generate MQTT events")
|
||||
print("3. Run this test again to see the events")
|
||||
print("4. Check the admin dashboard to see events displayed")
|
||||
print()
|
||||
print("📋 API Usage:")
|
||||
print(f" GET {MQTT_EVENTS_ENDPOINT}")
|
||||
print(f" GET {MQTT_EVENTS_ENDPOINT}?limit=10")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
80
api/tests/camera/test_frame_conversion.py
Normal file
80
api/tests/camera/test_frame_conversion.py
Normal file
@@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script to verify the frame conversion fix works correctly.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import numpy as np
|
||||
|
||||
# Add the current directory to Python path
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
# Add camera SDK to path
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), "camera_sdk"))
|
||||
|
||||
try:
|
||||
import mvsdk
|
||||
print("✅ mvsdk imported successfully")
|
||||
except ImportError as e:
|
||||
print(f"❌ Failed to import mvsdk: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
def test_frame_conversion():
|
||||
"""Test the frame conversion logic"""
|
||||
print("🧪 Testing frame conversion logic...")
|
||||
|
||||
# Simulate frame data
|
||||
width, height = 640, 480
|
||||
frame_size = width * height * 3 # RGB
|
||||
|
||||
# Create mock frame data
|
||||
mock_frame_data = np.random.randint(0, 255, frame_size, dtype=np.uint8)
|
||||
|
||||
# Create a mock frame buffer (simulate memory address)
|
||||
frame_buffer = mock_frame_data.ctypes.data
|
||||
|
||||
# Create mock FrameHead
|
||||
class MockFrameHead:
|
||||
def __init__(self):
|
||||
self.iWidth = width
|
||||
self.iHeight = height
|
||||
self.uBytes = frame_size
|
||||
|
||||
frame_head = MockFrameHead()
|
||||
|
||||
try:
|
||||
# Test the conversion logic (similar to what's in streamer.py)
|
||||
frame_data_buffer = (mvsdk.c_ubyte * frame_head.uBytes).from_address(frame_buffer)
|
||||
frame_data = np.frombuffer(frame_data_buffer, dtype=np.uint8)
|
||||
frame = frame_data.reshape((frame_head.iHeight, frame_head.iWidth, 3))
|
||||
|
||||
print(f"✅ Frame conversion successful!")
|
||||
print(f" Frame shape: {frame.shape}")
|
||||
print(f" Frame dtype: {frame.dtype}")
|
||||
print(f" Frame size: {frame.size} bytes")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Frame conversion failed: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
print("🔧 Frame Conversion Test")
|
||||
print("=" * 40)
|
||||
|
||||
success = test_frame_conversion()
|
||||
|
||||
if success:
|
||||
print("\n✅ Frame conversion fix is working correctly!")
|
||||
print("📋 The streaming issue should be resolved after system restart.")
|
||||
else:
|
||||
print("\n❌ Frame conversion fix needs more work.")
|
||||
|
||||
print("\n💡 To apply the fix:")
|
||||
print("1. Restart the USDA vision system")
|
||||
print("2. Test streaming again")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
131
api/tests/camera/test_max_fps.py
Normal file
131
api/tests/camera/test_max_fps.py
Normal file
@@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script to demonstrate maximum FPS capture functionality.
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
BASE_URL = "http://localhost:8000"
|
||||
|
||||
def test_fps_modes():
|
||||
"""Test different FPS modes to demonstrate the functionality"""
|
||||
|
||||
print("=" * 60)
|
||||
print("Testing Maximum FPS Capture Functionality")
|
||||
print("=" * 60)
|
||||
|
||||
# Test configurations
|
||||
test_configs = [
|
||||
{
|
||||
"name": "Normal FPS (3.0)",
|
||||
"data": {
|
||||
"filename": "normal_fps_test.avi",
|
||||
"exposure_ms": 1.0,
|
||||
"gain": 3.0,
|
||||
"fps": 3.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "High FPS (10.0)",
|
||||
"data": {
|
||||
"filename": "high_fps_test.avi",
|
||||
"exposure_ms": 0.5,
|
||||
"gain": 2.0,
|
||||
"fps": 10.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Maximum FPS (fps=0)",
|
||||
"data": {
|
||||
"filename": "max_fps_test.avi",
|
||||
"exposure_ms": 0.1, # Very short exposure for max speed
|
||||
"gain": 1.0, # Low gain to avoid overexposure
|
||||
"fps": 0 # Maximum speed - no delay
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Default FPS (omitted)",
|
||||
"data": {
|
||||
"filename": "default_fps_test.avi",
|
||||
"exposure_ms": 1.0,
|
||||
"gain": 3.0
|
||||
# fps omitted - uses camera config default
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
for i, config in enumerate(test_configs, 1):
|
||||
print(f"\n{i}. Testing {config['name']}")
|
||||
print("-" * 40)
|
||||
|
||||
# Start recording
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{BASE_URL}/cameras/camera1/start-recording",
|
||||
json=config['data'],
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
if result.get('success'):
|
||||
print(f"✅ Recording started successfully")
|
||||
print(f" Filename: {result.get('filename')}")
|
||||
print(f" Settings: {json.dumps(config['data'], indent=6)}")
|
||||
|
||||
# Record for a short time
|
||||
print(f" Recording for 3 seconds...")
|
||||
time.sleep(3)
|
||||
|
||||
# Stop recording
|
||||
stop_response = requests.post(f"{BASE_URL}/cameras/camera1/stop-recording")
|
||||
if stop_response.status_code == 200:
|
||||
stop_result = stop_response.json()
|
||||
if stop_result.get('success'):
|
||||
print(f"✅ Recording stopped successfully")
|
||||
if 'duration_seconds' in stop_result:
|
||||
print(f" Duration: {stop_result['duration_seconds']:.1f}s")
|
||||
else:
|
||||
print(f"❌ Failed to stop recording: {stop_result.get('message')}")
|
||||
else:
|
||||
print(f"❌ Stop request failed: {stop_response.status_code}")
|
||||
|
||||
else:
|
||||
print(f"❌ Recording failed: {result.get('message')}")
|
||||
else:
|
||||
print(f"❌ Request failed: {response.status_code} - {response.text}")
|
||||
|
||||
except requests.exceptions.ConnectionError:
|
||||
print(f"❌ Could not connect to {BASE_URL}")
|
||||
print("Make sure the API server is running with: python main.py")
|
||||
break
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
|
||||
# Wait between tests
|
||||
if i < len(test_configs):
|
||||
print(" Waiting 2 seconds before next test...")
|
||||
time.sleep(2)
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("FPS Test Summary:")
|
||||
print("=" * 60)
|
||||
print("• fps > 0: Controlled frame rate with sleep delay")
|
||||
print("• fps = 0: MAXIMUM speed capture (no delay between frames)")
|
||||
print("• fps omitted: Uses camera config default")
|
||||
print("• Video files with fps=0 are saved with 30 FPS metadata")
|
||||
print("• Actual capture rate with fps=0 depends on:")
|
||||
print(" - Camera hardware capabilities")
|
||||
print(" - Exposure time (shorter = faster)")
|
||||
print(" - Processing overhead")
|
||||
print("=" * 60)
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("USDA Vision Camera System - Maximum FPS Test")
|
||||
print("This script demonstrates fps=0 for maximum capture speed")
|
||||
print("\nMake sure the system is running with: python main.py")
|
||||
|
||||
test_fps_modes()
|
||||
199
api/tests/camera/test_streaming.py
Normal file
199
api/tests/camera/test_streaming.py
Normal file
@@ -0,0 +1,199 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script for camera streaming functionality.
|
||||
|
||||
This script tests the new streaming capabilities without interfering with recording.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import requests
|
||||
import threading
|
||||
from datetime import datetime
|
||||
|
||||
# Add the current directory to Python path
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
def test_api_endpoints():
|
||||
"""Test the streaming API endpoints"""
|
||||
base_url = "http://localhost:8000"
|
||||
|
||||
print("🧪 Testing Camera Streaming API Endpoints")
|
||||
print("=" * 50)
|
||||
|
||||
# Test system status
|
||||
try:
|
||||
response = requests.get(f"{base_url}/system/status", timeout=5)
|
||||
if response.status_code == 200:
|
||||
print("✅ System status endpoint working")
|
||||
data = response.json()
|
||||
print(f" System: {data.get('status', 'Unknown')}")
|
||||
print(f" Camera Manager: {'Running' if data.get('camera_manager_running') else 'Stopped'}")
|
||||
else:
|
||||
print(f"❌ System status endpoint failed: {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f"❌ System status endpoint error: {e}")
|
||||
|
||||
# Test camera list
|
||||
try:
|
||||
response = requests.get(f"{base_url}/cameras", timeout=5)
|
||||
if response.status_code == 200:
|
||||
print("✅ Camera list endpoint working")
|
||||
cameras = response.json()
|
||||
print(f" Found {len(cameras)} cameras: {list(cameras.keys())}")
|
||||
|
||||
# Test streaming for each camera
|
||||
for camera_name in cameras.keys():
|
||||
test_camera_streaming(base_url, camera_name)
|
||||
|
||||
else:
|
||||
print(f"❌ Camera list endpoint failed: {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f"❌ Camera list endpoint error: {e}")
|
||||
|
||||
def test_camera_streaming(base_url, camera_name):
|
||||
"""Test streaming for a specific camera"""
|
||||
print(f"\n🎥 Testing streaming for {camera_name}")
|
||||
print("-" * 30)
|
||||
|
||||
# Test start streaming
|
||||
try:
|
||||
response = requests.post(f"{base_url}/cameras/{camera_name}/start-stream", timeout=10)
|
||||
if response.status_code == 200:
|
||||
print(f"✅ Start stream endpoint working for {camera_name}")
|
||||
data = response.json()
|
||||
print(f" Response: {data.get('message', 'No message')}")
|
||||
else:
|
||||
print(f"❌ Start stream failed for {camera_name}: {response.status_code}")
|
||||
print(f" Error: {response.text}")
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"❌ Start stream error for {camera_name}: {e}")
|
||||
return
|
||||
|
||||
# Wait a moment for stream to initialize
|
||||
time.sleep(2)
|
||||
|
||||
# Test stream endpoint (just check if it responds)
|
||||
try:
|
||||
response = requests.get(f"{base_url}/cameras/{camera_name}/stream", timeout=5, stream=True)
|
||||
if response.status_code == 200:
|
||||
print(f"✅ Stream endpoint responding for {camera_name}")
|
||||
print(f" Content-Type: {response.headers.get('content-type', 'Unknown')}")
|
||||
|
||||
# Read a small amount of data to verify it's working
|
||||
chunk_count = 0
|
||||
for chunk in response.iter_content(chunk_size=1024):
|
||||
chunk_count += 1
|
||||
if chunk_count >= 3: # Read a few chunks then stop
|
||||
break
|
||||
|
||||
print(f" Received {chunk_count} data chunks")
|
||||
else:
|
||||
print(f"❌ Stream endpoint failed for {camera_name}: {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f"❌ Stream endpoint error for {camera_name}: {e}")
|
||||
|
||||
# Test stop streaming
|
||||
try:
|
||||
response = requests.post(f"{base_url}/cameras/{camera_name}/stop-stream", timeout=5)
|
||||
if response.status_code == 200:
|
||||
print(f"✅ Stop stream endpoint working for {camera_name}")
|
||||
data = response.json()
|
||||
print(f" Response: {data.get('message', 'No message')}")
|
||||
else:
|
||||
print(f"❌ Stop stream failed for {camera_name}: {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f"❌ Stop stream error for {camera_name}: {e}")
|
||||
|
||||
def test_concurrent_recording_and_streaming():
|
||||
"""Test that streaming doesn't interfere with recording"""
|
||||
base_url = "http://localhost:8000"
|
||||
|
||||
print("\n🔄 Testing Concurrent Recording and Streaming")
|
||||
print("=" * 50)
|
||||
|
||||
try:
|
||||
# Get available cameras
|
||||
response = requests.get(f"{base_url}/cameras", timeout=5)
|
||||
if response.status_code != 200:
|
||||
print("❌ Cannot get camera list for concurrent test")
|
||||
return
|
||||
|
||||
cameras = response.json()
|
||||
if not cameras:
|
||||
print("❌ No cameras available for concurrent test")
|
||||
return
|
||||
|
||||
camera_name = list(cameras.keys())[0] # Use first camera
|
||||
print(f"Using camera: {camera_name}")
|
||||
|
||||
# Start streaming
|
||||
print("1. Starting streaming...")
|
||||
response = requests.post(f"{base_url}/cameras/{camera_name}/start-stream", timeout=10)
|
||||
if response.status_code != 200:
|
||||
print(f"❌ Failed to start streaming: {response.text}")
|
||||
return
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
# Start recording
|
||||
print("2. Starting recording...")
|
||||
response = requests.post(f"{base_url}/cameras/{camera_name}/start-recording",
|
||||
json={"filename": "test_concurrent_recording.avi"}, timeout=10)
|
||||
if response.status_code == 200:
|
||||
print("✅ Recording started successfully while streaming")
|
||||
else:
|
||||
print(f"❌ Failed to start recording while streaming: {response.text}")
|
||||
|
||||
# Let both run for a few seconds
|
||||
print("3. Running both streaming and recording for 5 seconds...")
|
||||
time.sleep(5)
|
||||
|
||||
# Stop recording
|
||||
print("4. Stopping recording...")
|
||||
response = requests.post(f"{base_url}/cameras/{camera_name}/stop-recording", timeout=5)
|
||||
if response.status_code == 200:
|
||||
print("✅ Recording stopped successfully")
|
||||
else:
|
||||
print(f"❌ Failed to stop recording: {response.text}")
|
||||
|
||||
# Stop streaming
|
||||
print("5. Stopping streaming...")
|
||||
response = requests.post(f"{base_url}/cameras/{camera_name}/stop-stream", timeout=5)
|
||||
if response.status_code == 200:
|
||||
print("✅ Streaming stopped successfully")
|
||||
else:
|
||||
print(f"❌ Failed to stop streaming: {response.text}")
|
||||
|
||||
print("✅ Concurrent test completed successfully!")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Concurrent test error: {e}")
|
||||
|
||||
def main():
|
||||
"""Main test function"""
|
||||
print("🚀 USDA Vision Camera Streaming Test")
|
||||
print("=" * 50)
|
||||
print(f"Test started at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
print()
|
||||
|
||||
# Wait for system to be ready
|
||||
print("⏳ Waiting for system to be ready...")
|
||||
time.sleep(3)
|
||||
|
||||
# Run tests
|
||||
test_api_endpoints()
|
||||
test_concurrent_recording_and_streaming()
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("🏁 Test completed!")
|
||||
print("\n📋 Next Steps:")
|
||||
print("1. Open camera_preview.html in your browser")
|
||||
print("2. Click 'Start Stream' for any camera")
|
||||
print("3. Verify live preview works without blocking recording")
|
||||
print("4. Test concurrent recording and streaming")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
77
api/tests/core/check_time.py
Executable file
77
api/tests/core/check_time.py
Executable file
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Time verification script for USDA Vision Camera System
|
||||
Checks if system time is properly synchronized
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import pytz
|
||||
import requests
|
||||
import json
|
||||
|
||||
def check_system_time():
|
||||
"""Check system time against multiple sources"""
|
||||
print("🕐 USDA Vision Camera System - Time Verification")
|
||||
print("=" * 50)
|
||||
|
||||
# Get local time
|
||||
local_time = datetime.datetime.now()
|
||||
utc_time = datetime.datetime.utcnow()
|
||||
|
||||
# Get Atlanta timezone
|
||||
atlanta_tz = pytz.timezone('America/New_York')
|
||||
atlanta_time = datetime.datetime.now(atlanta_tz)
|
||||
|
||||
print(f"Local system time: {local_time}")
|
||||
print(f"UTC time: {utc_time}")
|
||||
print(f"Atlanta time: {atlanta_time}")
|
||||
print(f"Timezone: {atlanta_time.tzname()}")
|
||||
|
||||
# Check against multiple time APIs for reliability
|
||||
time_apis = [
|
||||
{
|
||||
"name": "WorldTimeAPI",
|
||||
"url": "http://worldtimeapi.org/api/timezone/America/New_York",
|
||||
"parser": lambda data: datetime.datetime.fromisoformat(data['datetime'].replace('Z', '+00:00'))
|
||||
},
|
||||
{
|
||||
"name": "WorldClockAPI",
|
||||
"url": "http://worldclockapi.com/api/json/est/now",
|
||||
"parser": lambda data: datetime.datetime.fromisoformat(data['currentDateTime'])
|
||||
}
|
||||
]
|
||||
|
||||
for api in time_apis:
|
||||
try:
|
||||
print(f"\n🌐 Checking against {api['name']}...")
|
||||
response = requests.get(api['url'], timeout=5)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
api_time = api['parser'](data)
|
||||
|
||||
# Compare times (allow 5 second difference)
|
||||
time_diff = abs((atlanta_time.replace(tzinfo=None) - api_time.replace(tzinfo=None)).total_seconds())
|
||||
|
||||
print(f"API time: {api_time}")
|
||||
print(f"Time difference: {time_diff:.2f} seconds")
|
||||
|
||||
if time_diff < 5:
|
||||
print("✅ Time is synchronized (within 5 seconds)")
|
||||
return True
|
||||
else:
|
||||
print("❌ Time is NOT synchronized (difference > 5 seconds)")
|
||||
return False
|
||||
else:
|
||||
print(f"⚠️ {api['name']} returned status {response.status_code}")
|
||||
continue
|
||||
except Exception as e:
|
||||
print(f"⚠️ Error checking {api['name']}: {e}")
|
||||
continue
|
||||
|
||||
print("⚠️ Could not reach any time API services")
|
||||
print("⚠️ This may be due to network connectivity issues")
|
||||
print("⚠️ System will continue but time synchronization cannot be verified")
|
||||
return None
|
||||
|
||||
if __name__ == "__main__":
|
||||
check_system_time()
|
||||
56
api/tests/core/test_timezone.py
Normal file
56
api/tests/core/test_timezone.py
Normal file
@@ -0,0 +1,56 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test timezone functionality for the USDA Vision Camera System.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Add the current directory to Python path
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from usda_vision_system.core.timezone_utils import (
|
||||
now_atlanta, format_atlanta_timestamp, format_filename_timestamp,
|
||||
check_time_sync, log_time_info
|
||||
)
|
||||
import logging
|
||||
|
||||
def test_timezone_functions():
|
||||
"""Test timezone utility functions"""
|
||||
print("🕐 Testing USDA Vision Camera System Timezone Functions")
|
||||
print("=" * 60)
|
||||
|
||||
# Test current time functions
|
||||
atlanta_time = now_atlanta()
|
||||
print(f"Current Atlanta time: {atlanta_time}")
|
||||
print(f"Timezone: {atlanta_time.tzname()}")
|
||||
print(f"UTC offset: {atlanta_time.strftime('%z')}")
|
||||
|
||||
# Test timestamp formatting
|
||||
timestamp_str = format_atlanta_timestamp()
|
||||
filename_str = format_filename_timestamp()
|
||||
|
||||
print(f"\nTimestamp formats:")
|
||||
print(f" Display format: {timestamp_str}")
|
||||
print(f" Filename format: {filename_str}")
|
||||
|
||||
# Test time sync
|
||||
print(f"\n🔄 Testing time synchronization...")
|
||||
sync_info = check_time_sync()
|
||||
print(f"Sync status: {sync_info['sync_status']}")
|
||||
if sync_info.get('time_diff_seconds') is not None:
|
||||
print(f"Time difference: {sync_info['time_diff_seconds']:.2f} seconds")
|
||||
|
||||
# Test logging
|
||||
print(f"\n📝 Testing time logging...")
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
log_time_info()
|
||||
|
||||
print(f"\n✅ All timezone tests completed successfully!")
|
||||
|
||||
# Show example filename that would be generated
|
||||
example_filename = f"camera1_recording_{filename_str}.avi"
|
||||
print(f"\nExample recording filename: {example_filename}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_timezone_functions()
|
||||
225
api/tests/integration/test_system.py
Normal file
225
api/tests/integration/test_system.py
Normal file
@@ -0,0 +1,225 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script for the USDA Vision Camera System.
|
||||
|
||||
This script performs basic tests to verify system components are working correctly.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
import requests
|
||||
from datetime import datetime
|
||||
|
||||
# Add the current directory to Python path
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
|
||||
def test_imports():
|
||||
"""Test that all modules can be imported"""
|
||||
print("Testing imports...")
|
||||
try:
|
||||
from usda_vision_system.core.config import Config
|
||||
from usda_vision_system.core.state_manager import StateManager
|
||||
from usda_vision_system.core.events import EventSystem
|
||||
from usda_vision_system.mqtt.client import MQTTClient
|
||||
from usda_vision_system.camera.manager import CameraManager
|
||||
from usda_vision_system.storage.manager import StorageManager
|
||||
from usda_vision_system.api.server import APIServer
|
||||
from usda_vision_system.main import USDAVisionSystem
|
||||
|
||||
print("✅ All imports successful")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ Import failed: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_configuration():
|
||||
"""Test configuration loading"""
|
||||
print("\nTesting configuration...")
|
||||
try:
|
||||
from usda_vision_system.core.config import Config
|
||||
|
||||
# Test default config
|
||||
config = Config()
|
||||
print(f"✅ Default config loaded")
|
||||
print(f" MQTT broker: {config.mqtt.broker_host}:{config.mqtt.broker_port}")
|
||||
print(f" Storage path: {config.storage.base_path}")
|
||||
print(f" Cameras configured: {len(config.cameras)}")
|
||||
|
||||
# Test config file if it exists
|
||||
if os.path.exists("config.json"):
|
||||
config_file = Config("config.json")
|
||||
print(f"✅ Config file loaded")
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ Configuration test failed: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_camera_discovery():
|
||||
"""Test camera discovery"""
|
||||
print("\nTesting camera discovery...")
|
||||
try:
|
||||
sys.path.append("./camera_sdk")
|
||||
import mvsdk
|
||||
|
||||
devices = mvsdk.CameraEnumerateDevice()
|
||||
print(f"✅ Camera discovery successful")
|
||||
print(f" Found {len(devices)} camera(s)")
|
||||
|
||||
for i, device in enumerate(devices):
|
||||
try:
|
||||
name = device.GetFriendlyName()
|
||||
port_type = device.GetPortType()
|
||||
print(f" Camera {i}: {name} ({port_type})")
|
||||
except Exception as e:
|
||||
print(f" Camera {i}: Error getting info - {e}")
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ Camera discovery failed: {e}")
|
||||
print(" Make sure GigE cameras are connected and camera SDK library is available")
|
||||
return False
|
||||
|
||||
|
||||
def test_storage_setup():
|
||||
"""Test storage directory setup"""
|
||||
print("\nTesting storage setup...")
|
||||
try:
|
||||
from usda_vision_system.core.config import Config
|
||||
from usda_vision_system.storage.manager import StorageManager
|
||||
from usda_vision_system.core.state_manager import StateManager
|
||||
|
||||
config = Config()
|
||||
state_manager = StateManager()
|
||||
storage_manager = StorageManager(config, state_manager)
|
||||
|
||||
# Test storage statistics
|
||||
stats = storage_manager.get_storage_statistics()
|
||||
print(f"✅ Storage manager initialized")
|
||||
print(f" Base path: {stats.get('base_path', 'Unknown')}")
|
||||
print(f" Total files: {stats.get('total_files', 0)}")
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ Storage setup failed: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_mqtt_config():
|
||||
"""Test MQTT configuration (without connecting)"""
|
||||
print("\nTesting MQTT configuration...")
|
||||
try:
|
||||
from usda_vision_system.core.config import Config
|
||||
from usda_vision_system.mqtt.client import MQTTClient
|
||||
from usda_vision_system.core.state_manager import StateManager
|
||||
from usda_vision_system.core.events import EventSystem
|
||||
|
||||
config = Config()
|
||||
state_manager = StateManager()
|
||||
event_system = EventSystem()
|
||||
|
||||
mqtt_client = MQTTClient(config, state_manager, event_system)
|
||||
status = mqtt_client.get_status()
|
||||
|
||||
print(f"✅ MQTT client initialized")
|
||||
print(f" Broker: {status['broker_host']}:{status['broker_port']}")
|
||||
print(f" Topics: {len(status['subscribed_topics'])}")
|
||||
for topic in status["subscribed_topics"]:
|
||||
print(f" - {topic}")
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ MQTT configuration test failed: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_system_initialization():
|
||||
"""Test full system initialization (without starting)"""
|
||||
print("\nTesting system initialization...")
|
||||
try:
|
||||
from usda_vision_system.main import USDAVisionSystem
|
||||
|
||||
# Create system instance
|
||||
system = USDAVisionSystem()
|
||||
|
||||
# Check system status
|
||||
status = system.get_system_status()
|
||||
print(f"✅ System initialized successfully")
|
||||
print(f" Running: {status['running']}")
|
||||
print(f" Components initialized: {len(status['components'])}")
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ System initialization failed: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_api_endpoints():
|
||||
"""Test API endpoints if server is running"""
|
||||
print("\nTesting API endpoints...")
|
||||
try:
|
||||
# Test health endpoint
|
||||
response = requests.get("http://localhost:8000/health", timeout=5)
|
||||
if response.status_code == 200:
|
||||
print("✅ API server is running")
|
||||
|
||||
# Test system status endpoint
|
||||
try:
|
||||
response = requests.get("http://localhost:8000/system/status", timeout=5)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print(f" System started: {data.get('system_started', False)}")
|
||||
print(f" MQTT connected: {data.get('mqtt_connected', False)}")
|
||||
print(f" Active recordings: {data.get('active_recordings', 0)}")
|
||||
else:
|
||||
print(f"⚠️ System status endpoint returned {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f"⚠️ System status test failed: {e}")
|
||||
|
||||
return True
|
||||
else:
|
||||
print(f"⚠️ API server returned status {response.status_code}")
|
||||
return False
|
||||
except requests.exceptions.ConnectionError:
|
||||
print("⚠️ API server not running (this is OK if system is not started)")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ API test failed: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""Run all tests"""
|
||||
print("USDA Vision Camera System - Test Suite")
|
||||
print("=" * 50)
|
||||
|
||||
tests = [test_imports, test_configuration, test_camera_discovery, test_storage_setup, test_mqtt_config, test_system_initialization, test_api_endpoints]
|
||||
|
||||
passed = 0
|
||||
total = len(tests)
|
||||
|
||||
for test in tests:
|
||||
try:
|
||||
if test():
|
||||
passed += 1
|
||||
except Exception as e:
|
||||
print(f"❌ Test {test.__name__} crashed: {e}")
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print(f"Test Results: {passed}/{total} tests passed")
|
||||
|
||||
if passed == total:
|
||||
print("🎉 All tests passed! System appears to be working correctly.")
|
||||
return 0
|
||||
else:
|
||||
print("⚠️ Some tests failed. Check the output above for details.")
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
BIN
api/tests/legacy_tests/Camera/Data/054012620023.mvdat
Normal file
BIN
api/tests/legacy_tests/Camera/Data/054012620023.mvdat
Normal file
Binary file not shown.
BIN
api/tests/legacy_tests/Camera/Data/054052320151.mvdat
Normal file
BIN
api/tests/legacy_tests/Camera/Data/054052320151.mvdat
Normal file
Binary file not shown.
@@ -0,0 +1,4 @@
|
||||
Log file created at: 2025/07/28 15:32:15
|
||||
Running on machine: vision
|
||||
Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg
|
||||
E0728 15:32:15.057651 191852 MVCAMAPI.cpp:369] CameraInit Failed, err:32774,Version:2.1.0.49,FriendlyName:Blower-Yield-Cam,SN:054012620023
|
||||
@@ -0,0 +1,4 @@
|
||||
Log file created at: 2025/07/28 15:32:33
|
||||
Running on machine: vision
|
||||
Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg
|
||||
E0728 15:32:33.490923 191852 MVCAMAPI.cpp:369] CameraInit Failed, err:32774,Version:2.1.0.49,FriendlyName:Blower-Yield-Cam,SN:054012620023
|
||||
@@ -0,0 +1,4 @@
|
||||
Log file created at: 2025/07/28 15:32:34
|
||||
Running on machine: vision
|
||||
Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg
|
||||
E0728 15:32:34.649940 191852 MVCAMAPI.cpp:369] CameraInit Failed, err:32774,Version:2.1.0.49,FriendlyName:Blower-Yield-Cam,SN:054012620023
|
||||
@@ -0,0 +1,4 @@
|
||||
Log file created at: 2025/07/28 15:32:39
|
||||
Running on machine: vision
|
||||
Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg
|
||||
E0728 15:32:39.753448 191852 MVCAMAPI.cpp:369] CameraInit Failed, err:32774,Version:2.1.0.49,FriendlyName:Blower-Yield-Cam,SN:054012620023
|
||||
@@ -0,0 +1,4 @@
|
||||
Log file created at: 2025/07/28 15:32:45
|
||||
Running on machine: vision
|
||||
Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg
|
||||
E0728 15:32:45.492905 191852 MVCAMAPI.cpp:369] CameraInit Failed, err:32774,Version:2.1.0.49,FriendlyName:Blower-Yield-Cam,SN:054012620023
|
||||
@@ -0,0 +1,4 @@
|
||||
Log file created at: 2025/07/28 15:33:40
|
||||
Running on machine: vision
|
||||
Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg
|
||||
E0728 15:33:40.702630 191852 MVCAMAPI.cpp:369] CameraInit Failed, err:32774,Version:2.1.0.49,FriendlyName:Blower-Yield-Cam,SN:054012620023
|
||||
@@ -0,0 +1,4 @@
|
||||
Log file created at: 2025/07/28 15:34:18
|
||||
Running on machine: vision
|
||||
Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg
|
||||
E0728 15:34:18.442386 191852 MVCAMAPI.cpp:369] CameraInit Failed, err:32774,Version:2.1.0.49,FriendlyName:Blower-Yield-Cam,SN:054012620023
|
||||
@@ -0,0 +1,4 @@
|
||||
Log file created at: 2025/07/28 15:34:28
|
||||
Running on machine: vision
|
||||
Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg
|
||||
E0728 15:34:28.207051 191852 MVCAMAPI.cpp:369] CameraInit Failed, err:32774,Version:2.1.0.49,FriendlyName:Blower-Yield-Cam,SN:054012620023
|
||||
@@ -0,0 +1,4 @@
|
||||
Log file created at: 2025/07/28 15:34:53
|
||||
Running on machine: vision
|
||||
Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg
|
||||
E0728 15:34:53.315912 191852 MVCAMAPI.cpp:369] CameraInit Failed, err:32774,Version:2.1.0.49,FriendlyName:Blower-Yield-Cam,SN:054012620023
|
||||
@@ -0,0 +1,4 @@
|
||||
Log file created at: 2025/07/28 15:35:00
|
||||
Running on machine: vision
|
||||
Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg
|
||||
E0728 15:35:00.929268 191852 MVCAMAPI.cpp:369] CameraInit Failed, err:32774,Version:2.1.0.49,FriendlyName:Blower-Yield-Cam,SN:054012620023
|
||||
@@ -0,0 +1,4 @@
|
||||
Log file created at: 2025/07/28 15:35:32
|
||||
Running on machine: vision
|
||||
Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg
|
||||
E0728 15:35:32.169682 191852 MVCAMAPI.cpp:369] CameraInit Failed, err:32774,Version:2.1.0.49,FriendlyName:Blower-Yield-Cam,SN:054012620023
|
||||
@@ -0,0 +1,4 @@
|
||||
Log file created at: 2025/07/28 15:35:34
|
||||
Running on machine: vision
|
||||
Log line format: [IWEF]mmdd hh:mm:ss.uuuuuu threadid file:line] msg
|
||||
E0728 15:35:34.519351 191852 MVCAMAPI.cpp:369] CameraInit Failed, err:32774,Version:2.1.0.49,FriendlyName:Blower-Yield-Cam,SN:054012620023
|
||||
291
api/tests/legacy_tests/camera_capture.py
Normal file
291
api/tests/legacy_tests/camera_capture.py
Normal file
@@ -0,0 +1,291 @@
|
||||
# coding=utf-8
|
||||
"""
|
||||
Simple GigE Camera Capture Script
|
||||
Captures 10 images every 200 milliseconds and saves them to the images directory.
|
||||
"""
|
||||
|
||||
import os
|
||||
import time
|
||||
import numpy as np
|
||||
import cv2
|
||||
import platform
|
||||
from datetime import datetime
|
||||
import sys
|
||||
|
||||
sys.path.append("./python demo")
|
||||
import mvsdk
|
||||
|
||||
|
||||
def is_camera_ready_for_capture():
|
||||
"""
|
||||
Check if camera is ready for capture.
|
||||
Returns: (ready: bool, message: str, camera_info: object or None)
|
||||
"""
|
||||
try:
|
||||
# Initialize SDK
|
||||
mvsdk.CameraSdkInit(1)
|
||||
|
||||
# Enumerate cameras
|
||||
DevList = mvsdk.CameraEnumerateDevice()
|
||||
if len(DevList) < 1:
|
||||
return False, "No cameras found", None
|
||||
|
||||
DevInfo = DevList[0]
|
||||
|
||||
# Check if already opened
|
||||
try:
|
||||
if mvsdk.CameraIsOpened(DevInfo):
|
||||
return False, f"Camera '{DevInfo.GetFriendlyName()}' is already opened by another process", DevInfo
|
||||
except:
|
||||
pass # Some cameras might not support this check
|
||||
|
||||
# Try to initialize
|
||||
try:
|
||||
hCamera = mvsdk.CameraInit(DevInfo, -1, -1)
|
||||
|
||||
# Quick capture test
|
||||
try:
|
||||
# Basic setup
|
||||
mvsdk.CameraSetTriggerMode(hCamera, 0)
|
||||
mvsdk.CameraPlay(hCamera)
|
||||
|
||||
# Try to get one frame with short timeout
|
||||
pRawData, FrameHead = mvsdk.CameraGetImageBuffer(hCamera, 500) # 0.5 second timeout
|
||||
mvsdk.CameraReleaseImageBuffer(hCamera, pRawData)
|
||||
|
||||
# Success - close and return
|
||||
mvsdk.CameraUnInit(hCamera)
|
||||
return True, f"Camera '{DevInfo.GetFriendlyName()}' is ready for capture", DevInfo
|
||||
|
||||
except mvsdk.CameraException as e:
|
||||
mvsdk.CameraUnInit(hCamera)
|
||||
if e.error_code == mvsdk.CAMERA_STATUS_TIME_OUT:
|
||||
return False, "Camera timeout - may be busy or not streaming properly", DevInfo
|
||||
else:
|
||||
return False, f"Camera capture test failed: {e.message}", DevInfo
|
||||
|
||||
except mvsdk.CameraException as e:
|
||||
if e.error_code == mvsdk.CAMERA_STATUS_DEVICE_IS_OPENED:
|
||||
return False, f"Camera '{DevInfo.GetFriendlyName()}' is already in use", DevInfo
|
||||
elif e.error_code == mvsdk.CAMERA_STATUS_ACCESS_DENY:
|
||||
return False, f"Access denied to camera '{DevInfo.GetFriendlyName()}'", DevInfo
|
||||
else:
|
||||
return False, f"Camera initialization failed: {e.message}", DevInfo
|
||||
|
||||
except Exception as e:
|
||||
return False, f"Camera check failed: {str(e)}", None
|
||||
|
||||
|
||||
def get_camera_ranges(hCamera):
|
||||
"""
|
||||
Get the available ranges for camera settings
|
||||
"""
|
||||
try:
|
||||
# Get exposure time range
|
||||
exp_min, exp_max, exp_step = mvsdk.CameraGetExposureTimeRange(hCamera)
|
||||
print(f"Exposure time range: {exp_min:.1f} - {exp_max:.1f} μs (step: {exp_step:.1f})")
|
||||
|
||||
# Get analog gain range
|
||||
gain_min, gain_max, gain_step = mvsdk.CameraGetAnalogGainXRange(hCamera)
|
||||
print(f"Analog gain range: {gain_min:.2f} - {gain_max:.2f}x (step: {gain_step:.3f})")
|
||||
|
||||
return (exp_min, exp_max, exp_step), (gain_min, gain_max, gain_step)
|
||||
except Exception as e:
|
||||
print(f"Could not get camera ranges: {e}")
|
||||
return None, None
|
||||
|
||||
|
||||
def capture_images(exposure_time_us=2000, analog_gain=1.0):
|
||||
"""
|
||||
Main function to capture images from GigE camera
|
||||
|
||||
Parameters:
|
||||
- exposure_time_us: Exposure time in microseconds (default: 2000 = 2ms)
|
||||
- analog_gain: Analog gain multiplier (default: 1.0)
|
||||
"""
|
||||
# Check if camera is ready for capture
|
||||
print("Checking camera availability...")
|
||||
ready, message, camera_info = is_camera_ready_for_capture()
|
||||
|
||||
if not ready:
|
||||
print(f"❌ Camera not ready: {message}")
|
||||
print("\nPossible solutions:")
|
||||
print("- Close any other camera applications (preview software, etc.)")
|
||||
print("- Check camera connection and power")
|
||||
print("- Wait a moment and try again")
|
||||
return False
|
||||
|
||||
print(f"✅ {message}")
|
||||
|
||||
# Initialize SDK (already done in status check, but ensure it's ready)
|
||||
try:
|
||||
mvsdk.CameraSdkInit(1) # Initialize SDK with English language
|
||||
except Exception as e:
|
||||
print(f"SDK initialization failed: {e}")
|
||||
return False
|
||||
|
||||
# Enumerate cameras
|
||||
DevList = mvsdk.CameraEnumerateDevice()
|
||||
nDev = len(DevList)
|
||||
|
||||
if nDev < 1:
|
||||
print("No camera was found!")
|
||||
return False
|
||||
|
||||
print(f"Found {nDev} camera(s):")
|
||||
for i, DevInfo in enumerate(DevList):
|
||||
print(f"{i}: {DevInfo.GetFriendlyName()} {DevInfo.GetPortType()}")
|
||||
|
||||
# Select camera (use first one if only one available)
|
||||
camera_index = 0 if nDev == 1 else int(input("Select camera index: "))
|
||||
DevInfo = DevList[camera_index]
|
||||
print(f"Selected camera: {DevInfo.GetFriendlyName()}")
|
||||
|
||||
# Initialize camera
|
||||
hCamera = 0
|
||||
try:
|
||||
hCamera = mvsdk.CameraInit(DevInfo, -1, -1)
|
||||
print("Camera initialized successfully")
|
||||
except mvsdk.CameraException as e:
|
||||
print(f"CameraInit Failed({e.error_code}): {e.message}")
|
||||
return False
|
||||
|
||||
try:
|
||||
# Get camera capabilities
|
||||
cap = mvsdk.CameraGetCapability(hCamera)
|
||||
|
||||
# Check if it's a mono or color camera
|
||||
monoCamera = cap.sIspCapacity.bMonoSensor != 0
|
||||
print(f"Camera type: {'Monochrome' if monoCamera else 'Color'}")
|
||||
|
||||
# Get camera ranges
|
||||
exp_range, gain_range = get_camera_ranges(hCamera)
|
||||
|
||||
# Set output format
|
||||
if monoCamera:
|
||||
mvsdk.CameraSetIspOutFormat(hCamera, mvsdk.CAMERA_MEDIA_TYPE_MONO8)
|
||||
else:
|
||||
mvsdk.CameraSetIspOutFormat(hCamera, mvsdk.CAMERA_MEDIA_TYPE_BGR8)
|
||||
|
||||
# Set camera to continuous capture mode
|
||||
mvsdk.CameraSetTriggerMode(hCamera, 0)
|
||||
|
||||
# Set manual exposure with improved control
|
||||
mvsdk.CameraSetAeState(hCamera, 0) # Disable auto exposure
|
||||
|
||||
# Clamp exposure time to valid range
|
||||
if exp_range:
|
||||
exp_min, exp_max, exp_step = exp_range
|
||||
exposure_time_us = max(exp_min, min(exp_max, exposure_time_us))
|
||||
|
||||
mvsdk.CameraSetExposureTime(hCamera, exposure_time_us)
|
||||
print(f"Set exposure time: {exposure_time_us/1000:.1f}ms")
|
||||
|
||||
# Set analog gain
|
||||
if gain_range:
|
||||
gain_min, gain_max, gain_step = gain_range
|
||||
analog_gain = max(gain_min, min(gain_max, analog_gain))
|
||||
|
||||
try:
|
||||
mvsdk.CameraSetAnalogGainX(hCamera, analog_gain)
|
||||
print(f"Set analog gain: {analog_gain:.2f}x")
|
||||
except Exception as e:
|
||||
print(f"Could not set analog gain: {e}")
|
||||
|
||||
# Start camera
|
||||
mvsdk.CameraPlay(hCamera)
|
||||
print("Camera started")
|
||||
|
||||
# Calculate frame buffer size
|
||||
FrameBufferSize = cap.sResolutionRange.iWidthMax * cap.sResolutionRange.iHeightMax * (1 if monoCamera else 3)
|
||||
|
||||
# Allocate frame buffer
|
||||
pFrameBuffer = mvsdk.CameraAlignMalloc(FrameBufferSize, 16)
|
||||
|
||||
# Create images directory if it doesn't exist
|
||||
if not os.path.exists("images"):
|
||||
os.makedirs("images")
|
||||
|
||||
print("Starting image capture...")
|
||||
print("Capturing 10 images with 200ms intervals...")
|
||||
|
||||
# Capture 10 images
|
||||
for i in range(10):
|
||||
try:
|
||||
# Get image from camera (timeout: 2000ms)
|
||||
pRawData, FrameHead = mvsdk.CameraGetImageBuffer(hCamera, 2000)
|
||||
|
||||
# Process the raw image data
|
||||
mvsdk.CameraImageProcess(hCamera, pRawData, pFrameBuffer, FrameHead)
|
||||
|
||||
# Release the raw data buffer
|
||||
mvsdk.CameraReleaseImageBuffer(hCamera, pRawData)
|
||||
|
||||
# Handle Windows image flip (images are upside down on Windows)
|
||||
if platform.system() == "Windows":
|
||||
mvsdk.CameraFlipFrameBuffer(pFrameBuffer, FrameHead, 1)
|
||||
|
||||
# Convert to numpy array for OpenCV
|
||||
frame_data = (mvsdk.c_ubyte * FrameHead.uBytes).from_address(pFrameBuffer)
|
||||
frame = np.frombuffer(frame_data, dtype=np.uint8)
|
||||
|
||||
# Reshape based on camera type
|
||||
if FrameHead.uiMediaType == mvsdk.CAMERA_MEDIA_TYPE_MONO8:
|
||||
frame = frame.reshape((FrameHead.iHeight, FrameHead.iWidth))
|
||||
else:
|
||||
frame = frame.reshape((FrameHead.iHeight, FrameHead.iWidth, 3))
|
||||
|
||||
# Generate filename with timestamp
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")[:-3] # milliseconds
|
||||
filename = f"images/image_{i+1:02d}_{timestamp}.jpg"
|
||||
|
||||
# Save image using OpenCV
|
||||
success = cv2.imwrite(filename, frame)
|
||||
|
||||
if success:
|
||||
print(f"Image {i+1}/10 saved: {filename} ({FrameHead.iWidth}x{FrameHead.iHeight})")
|
||||
else:
|
||||
print(f"Failed to save image {i+1}/10")
|
||||
|
||||
# Wait 200ms before next capture (except for the last image)
|
||||
if i < 9:
|
||||
time.sleep(0.2)
|
||||
|
||||
except mvsdk.CameraException as e:
|
||||
print(f"Failed to capture image {i+1}/10 ({e.error_code}): {e.message}")
|
||||
continue
|
||||
|
||||
print("Image capture completed!")
|
||||
|
||||
# Cleanup
|
||||
mvsdk.CameraAlignFree(pFrameBuffer)
|
||||
|
||||
finally:
|
||||
# Close camera
|
||||
mvsdk.CameraUnInit(hCamera)
|
||||
print("Camera closed")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("GigE Camera Image Capture Script")
|
||||
print("=" * 40)
|
||||
print("Note: If images are overexposed, you can adjust the exposure settings:")
|
||||
print("- Lower exposure_time_us for darker images (e.g., 1000-5000)")
|
||||
print("- Lower analog_gain for less amplification (e.g., 0.5-2.0)")
|
||||
print()
|
||||
|
||||
# for cracker
|
||||
# You can adjust these values to fix overexposure:
|
||||
success = capture_images(exposure_time_us=6000, analog_gain=16.0) # 2ms exposure (much lower than default 30ms) # 1x gain (no amplification)
|
||||
# for blower
|
||||
success = capture_images(exposure_time_us=1000, analog_gain=3.5) # 2ms exposure (much lower than default 30ms) # 1x gain (no amplification)
|
||||
|
||||
if success:
|
||||
print("\nCapture completed successfully!")
|
||||
print("Images saved in the 'images' directory")
|
||||
else:
|
||||
print("\nCapture failed!")
|
||||
|
||||
input("Press Enter to exit...")
|
||||
439
api/tests/legacy_tests/camera_video_recorder.py
Normal file
439
api/tests/legacy_tests/camera_video_recorder.py
Normal file
@@ -0,0 +1,439 @@
|
||||
# coding=utf-8
|
||||
import cv2
|
||||
import numpy as np
|
||||
import platform
|
||||
import time
|
||||
import threading
|
||||
from datetime import datetime
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Add the python demo directory to path to import mvsdk
|
||||
sys.path.append("python demo")
|
||||
|
||||
import mvsdk
|
||||
|
||||
|
||||
class CameraVideoRecorder:
|
||||
def __init__(self):
|
||||
self.hCamera = 0
|
||||
self.pFrameBuffer = 0
|
||||
self.cap = None
|
||||
self.monoCamera = False
|
||||
self.recording = False
|
||||
self.video_writer = None
|
||||
self.frame_count = 0
|
||||
self.start_time = None
|
||||
|
||||
def list_cameras(self):
|
||||
"""List all available cameras"""
|
||||
try:
|
||||
# Initialize SDK
|
||||
mvsdk.CameraSdkInit(1)
|
||||
except Exception as e:
|
||||
print(f"SDK initialization failed: {e}")
|
||||
return []
|
||||
|
||||
# Enumerate cameras
|
||||
DevList = mvsdk.CameraEnumerateDevice()
|
||||
nDev = len(DevList)
|
||||
|
||||
if nDev < 1:
|
||||
print("No cameras found!")
|
||||
return []
|
||||
|
||||
print(f"\nFound {nDev} camera(s):")
|
||||
cameras = []
|
||||
for i, DevInfo in enumerate(DevList):
|
||||
camera_info = {"index": i, "name": DevInfo.GetFriendlyName(), "port_type": DevInfo.GetPortType(), "serial": DevInfo.GetSn(), "dev_info": DevInfo}
|
||||
cameras.append(camera_info)
|
||||
print(f"{i}: {camera_info['name']} ({camera_info['port_type']}) - SN: {camera_info['serial']}")
|
||||
|
||||
return cameras
|
||||
|
||||
def initialize_camera(self, dev_info, exposure_ms=1.0, gain=3.5, target_fps=3.0):
|
||||
"""Initialize camera with specified settings"""
|
||||
self.target_fps = target_fps
|
||||
try:
|
||||
# Initialize camera
|
||||
self.hCamera = mvsdk.CameraInit(dev_info, -1, -1)
|
||||
print(f"Camera initialized successfully")
|
||||
|
||||
# Get camera capabilities
|
||||
self.cap = mvsdk.CameraGetCapability(self.hCamera)
|
||||
self.monoCamera = self.cap.sIspCapacity.bMonoSensor != 0
|
||||
print(f"Camera type: {'Monochrome' if self.monoCamera else 'Color'}")
|
||||
|
||||
# Set output format
|
||||
if self.monoCamera:
|
||||
mvsdk.CameraSetIspOutFormat(self.hCamera, mvsdk.CAMERA_MEDIA_TYPE_MONO8)
|
||||
else:
|
||||
mvsdk.CameraSetIspOutFormat(self.hCamera, mvsdk.CAMERA_MEDIA_TYPE_BGR8)
|
||||
|
||||
# Calculate RGB buffer size
|
||||
FrameBufferSize = self.cap.sResolutionRange.iWidthMax * self.cap.sResolutionRange.iHeightMax * (1 if self.monoCamera else 3)
|
||||
|
||||
# Allocate RGB buffer
|
||||
self.pFrameBuffer = mvsdk.CameraAlignMalloc(FrameBufferSize, 16)
|
||||
|
||||
# Set camera to continuous capture mode
|
||||
mvsdk.CameraSetTriggerMode(self.hCamera, 0)
|
||||
|
||||
# Set manual exposure
|
||||
mvsdk.CameraSetAeState(self.hCamera, 0) # Disable auto exposure
|
||||
exposure_time_us = exposure_ms * 1000 # Convert ms to microseconds
|
||||
|
||||
# Get exposure range and clamp value
|
||||
try:
|
||||
exp_min, exp_max, exp_step = mvsdk.CameraGetExposureTimeRange(self.hCamera)
|
||||
exposure_time_us = max(exp_min, min(exp_max, exposure_time_us))
|
||||
print(f"Exposure range: {exp_min:.1f} - {exp_max:.1f} μs")
|
||||
except Exception as e:
|
||||
print(f"Could not get exposure range: {e}")
|
||||
|
||||
mvsdk.CameraSetExposureTime(self.hCamera, exposure_time_us)
|
||||
print(f"Set exposure time: {exposure_time_us/1000:.1f}ms")
|
||||
|
||||
# Set analog gain
|
||||
try:
|
||||
gain_min, gain_max, gain_step = mvsdk.CameraGetAnalogGainXRange(self.hCamera)
|
||||
gain = max(gain_min, min(gain_max, gain))
|
||||
mvsdk.CameraSetAnalogGainX(self.hCamera, gain)
|
||||
print(f"Set analog gain: {gain:.2f}x (range: {gain_min:.2f} - {gain_max:.2f})")
|
||||
except Exception as e:
|
||||
print(f"Could not set analog gain: {e}")
|
||||
|
||||
# Start camera
|
||||
mvsdk.CameraPlay(self.hCamera)
|
||||
print("Camera started successfully")
|
||||
|
||||
return True
|
||||
|
||||
except mvsdk.CameraException as e:
|
||||
print(f"Camera initialization failed({e.error_code}): {e.message}")
|
||||
return False
|
||||
|
||||
def start_recording(self, output_filename=None):
|
||||
"""Start video recording"""
|
||||
if self.recording:
|
||||
print("Already recording!")
|
||||
return False
|
||||
|
||||
if not output_filename:
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
output_filename = f"video_{timestamp}.avi"
|
||||
|
||||
# Create output directory if it doesn't exist
|
||||
os.makedirs(os.path.dirname(output_filename) if os.path.dirname(output_filename) else ".", exist_ok=True)
|
||||
|
||||
# Get first frame to determine video properties
|
||||
try:
|
||||
pRawData, FrameHead = mvsdk.CameraGetImageBuffer(self.hCamera, 2000)
|
||||
mvsdk.CameraImageProcess(self.hCamera, pRawData, self.pFrameBuffer, FrameHead)
|
||||
mvsdk.CameraReleaseImageBuffer(self.hCamera, pRawData)
|
||||
|
||||
# Handle Windows frame flipping
|
||||
if platform.system() == "Windows":
|
||||
mvsdk.CameraFlipFrameBuffer(self.pFrameBuffer, FrameHead, 1)
|
||||
|
||||
# Convert to numpy array
|
||||
frame_data = (mvsdk.c_ubyte * FrameHead.uBytes).from_address(self.pFrameBuffer)
|
||||
frame = np.frombuffer(frame_data, dtype=np.uint8)
|
||||
|
||||
if self.monoCamera:
|
||||
frame = frame.reshape((FrameHead.iHeight, FrameHead.iWidth))
|
||||
# Convert mono to BGR for video writer
|
||||
frame = cv2.cvtColor(frame, cv2.COLOR_GRAY2BGR)
|
||||
else:
|
||||
frame = frame.reshape((FrameHead.iHeight, FrameHead.iWidth, 3))
|
||||
|
||||
except mvsdk.CameraException as e:
|
||||
print(f"Failed to get initial frame: {e.message}")
|
||||
return False
|
||||
|
||||
# Initialize video writer
|
||||
fourcc = cv2.VideoWriter_fourcc(*"XVID")
|
||||
fps = getattr(self, "target_fps", 3.0) # Use configured FPS or default to 3.0
|
||||
frame_size = (FrameHead.iWidth, FrameHead.iHeight)
|
||||
|
||||
self.video_writer = cv2.VideoWriter(output_filename, fourcc, fps, frame_size)
|
||||
|
||||
if not self.video_writer.isOpened():
|
||||
print(f"Failed to open video writer for {output_filename}")
|
||||
return False
|
||||
|
||||
self.recording = True
|
||||
self.frame_count = 0
|
||||
self.start_time = time.time()
|
||||
self.output_filename = output_filename
|
||||
|
||||
print(f"Started recording to: {output_filename}")
|
||||
print(f"Frame size: {frame_size}, FPS: {fps}")
|
||||
print("Press 'q' to stop recording...")
|
||||
|
||||
return True
|
||||
|
||||
def stop_recording(self):
|
||||
"""Stop video recording"""
|
||||
if not self.recording:
|
||||
print("Not currently recording!")
|
||||
return False
|
||||
|
||||
self.recording = False
|
||||
|
||||
if self.video_writer:
|
||||
self.video_writer.release()
|
||||
self.video_writer = None
|
||||
|
||||
duration = time.time() - self.start_time if self.start_time else 0
|
||||
avg_fps = self.frame_count / duration if duration > 0 else 0
|
||||
|
||||
print(f"\nRecording stopped!")
|
||||
print(f"Saved: {self.output_filename}")
|
||||
print(f"Frames recorded: {self.frame_count}")
|
||||
print(f"Duration: {duration:.1f} seconds")
|
||||
print(f"Average FPS: {avg_fps:.1f}")
|
||||
|
||||
return True
|
||||
|
||||
def record_loop(self):
|
||||
"""Main recording loop"""
|
||||
if not self.recording:
|
||||
return
|
||||
|
||||
print("Recording... Press 'q' in the preview window to stop")
|
||||
|
||||
while self.recording:
|
||||
try:
|
||||
# Get frame from camera
|
||||
pRawData, FrameHead = mvsdk.CameraGetImageBuffer(self.hCamera, 200)
|
||||
mvsdk.CameraImageProcess(self.hCamera, pRawData, self.pFrameBuffer, FrameHead)
|
||||
mvsdk.CameraReleaseImageBuffer(self.hCamera, pRawData)
|
||||
|
||||
# Handle Windows frame flipping
|
||||
if platform.system() == "Windows":
|
||||
mvsdk.CameraFlipFrameBuffer(self.pFrameBuffer, FrameHead, 1)
|
||||
|
||||
# Convert to numpy array
|
||||
frame_data = (mvsdk.c_ubyte * FrameHead.uBytes).from_address(self.pFrameBuffer)
|
||||
frame = np.frombuffer(frame_data, dtype=np.uint8)
|
||||
|
||||
if self.monoCamera:
|
||||
frame = frame.reshape((FrameHead.iHeight, FrameHead.iWidth))
|
||||
frame_bgr = cv2.cvtColor(frame, cv2.COLOR_GRAY2BGR)
|
||||
else:
|
||||
frame = frame.reshape((FrameHead.iHeight, FrameHead.iWidth, 3))
|
||||
frame_bgr = frame
|
||||
|
||||
# Write every frame to video (FPS is controlled by video file playback rate)
|
||||
if self.video_writer and self.recording:
|
||||
self.video_writer.write(frame_bgr)
|
||||
self.frame_count += 1
|
||||
|
||||
# Show preview (resized for display)
|
||||
display_frame = cv2.resize(frame_bgr, (640, 480), interpolation=cv2.INTER_LINEAR)
|
||||
|
||||
# Add small delay to control capture rate based on target FPS
|
||||
target_fps = getattr(self, "target_fps", 3.0)
|
||||
time.sleep(1.0 / target_fps)
|
||||
|
||||
# Add recording indicator
|
||||
cv2.putText(display_frame, f"REC - Frame: {self.frame_count}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
|
||||
|
||||
cv2.imshow("Camera Recording - Press 'q' to stop", display_frame)
|
||||
|
||||
# Check for quit key
|
||||
if cv2.waitKey(1) & 0xFF == ord("q"):
|
||||
self.stop_recording()
|
||||
break
|
||||
|
||||
except mvsdk.CameraException as e:
|
||||
if e.error_code != mvsdk.CAMERA_STATUS_TIME_OUT:
|
||||
print(f"Camera error: {e.message}")
|
||||
break
|
||||
|
||||
def cleanup(self):
|
||||
"""Clean up resources"""
|
||||
if self.recording:
|
||||
self.stop_recording()
|
||||
|
||||
if self.video_writer:
|
||||
self.video_writer.release()
|
||||
|
||||
if self.hCamera > 0:
|
||||
mvsdk.CameraUnInit(self.hCamera)
|
||||
self.hCamera = 0
|
||||
|
||||
if self.pFrameBuffer:
|
||||
mvsdk.CameraAlignFree(self.pFrameBuffer)
|
||||
self.pFrameBuffer = 0
|
||||
|
||||
cv2.destroyAllWindows()
|
||||
|
||||
|
||||
def interactive_menu():
|
||||
"""Interactive menu for camera operations"""
|
||||
recorder = CameraVideoRecorder()
|
||||
|
||||
try:
|
||||
# List available cameras
|
||||
cameras = recorder.list_cameras()
|
||||
if not cameras:
|
||||
return
|
||||
|
||||
# Select camera
|
||||
if len(cameras) == 1:
|
||||
selected_camera = cameras[0]
|
||||
print(f"\nUsing camera: {selected_camera['name']}")
|
||||
else:
|
||||
while True:
|
||||
try:
|
||||
choice = int(input(f"\nSelect camera (0-{len(cameras)-1}): "))
|
||||
if 0 <= choice < len(cameras):
|
||||
selected_camera = cameras[choice]
|
||||
break
|
||||
else:
|
||||
print("Invalid selection!")
|
||||
except ValueError:
|
||||
print("Please enter a valid number!")
|
||||
|
||||
# Get camera settings from user
|
||||
print(f"\nCamera Settings:")
|
||||
try:
|
||||
exposure = float(input("Enter exposure time in ms (default 1.0): ") or "1.0")
|
||||
gain = float(input("Enter gain value (default 3.5): ") or "3.5")
|
||||
fps = float(input("Enter target FPS (default 3.0): ") or "3.0")
|
||||
except ValueError:
|
||||
print("Using default values: exposure=1.0ms, gain=3.5x, fps=3.0")
|
||||
exposure, gain, fps = 1.0, 3.5, 3.0
|
||||
|
||||
# Initialize camera with specified settings
|
||||
print(f"\nInitializing camera with:")
|
||||
print(f"- Exposure: {exposure}ms")
|
||||
print(f"- Gain: {gain}x")
|
||||
print(f"- Target FPS: {fps}")
|
||||
|
||||
if not recorder.initialize_camera(selected_camera["dev_info"], exposure_ms=exposure, gain=gain, target_fps=fps):
|
||||
return
|
||||
|
||||
# Menu loop
|
||||
while True:
|
||||
print(f"\n{'='*50}")
|
||||
print("Camera Video Recorder Menu")
|
||||
print(f"{'='*50}")
|
||||
print("1. Start Recording")
|
||||
print("2. List Camera Info")
|
||||
print("3. Test Camera (Live Preview)")
|
||||
print("4. Exit")
|
||||
|
||||
try:
|
||||
choice = input("\nSelect option (1-4): ").strip()
|
||||
|
||||
if choice == "1":
|
||||
# Start recording
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
output_file = f"videos/camera_recording_{timestamp}.avi"
|
||||
|
||||
# Create videos directory
|
||||
os.makedirs("videos", exist_ok=True)
|
||||
|
||||
if recorder.start_recording(output_file):
|
||||
recorder.record_loop()
|
||||
|
||||
elif choice == "2":
|
||||
# Show camera info
|
||||
print(f"\nCamera Information:")
|
||||
print(f"Name: {selected_camera['name']}")
|
||||
print(f"Port Type: {selected_camera['port_type']}")
|
||||
print(f"Serial Number: {selected_camera['serial']}")
|
||||
print(f"Type: {'Monochrome' if recorder.monoCamera else 'Color'}")
|
||||
|
||||
elif choice == "3":
|
||||
# Live preview
|
||||
print("\nLive Preview - Press 'q' to stop")
|
||||
preview_loop(recorder)
|
||||
|
||||
elif choice == "4":
|
||||
print("Exiting...")
|
||||
break
|
||||
|
||||
else:
|
||||
print("Invalid option! Please select 1-4.")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\nReturning to menu...")
|
||||
continue
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\nInterrupted by user")
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
recorder.cleanup()
|
||||
print("Cleanup completed")
|
||||
|
||||
|
||||
def preview_loop(recorder):
|
||||
"""Live preview without recording"""
|
||||
print("Live preview mode - Press 'q' to return to menu")
|
||||
|
||||
while True:
|
||||
try:
|
||||
# Get frame from camera
|
||||
pRawData, FrameHead = mvsdk.CameraGetImageBuffer(recorder.hCamera, 200)
|
||||
mvsdk.CameraImageProcess(recorder.hCamera, pRawData, recorder.pFrameBuffer, FrameHead)
|
||||
mvsdk.CameraReleaseImageBuffer(recorder.hCamera, pRawData)
|
||||
|
||||
# Handle Windows frame flipping
|
||||
if platform.system() == "Windows":
|
||||
mvsdk.CameraFlipFrameBuffer(recorder.pFrameBuffer, FrameHead, 1)
|
||||
|
||||
# Convert to numpy array
|
||||
frame_data = (mvsdk.c_ubyte * FrameHead.uBytes).from_address(recorder.pFrameBuffer)
|
||||
frame = np.frombuffer(frame_data, dtype=np.uint8)
|
||||
|
||||
if recorder.monoCamera:
|
||||
frame = frame.reshape((FrameHead.iHeight, FrameHead.iWidth))
|
||||
frame_bgr = cv2.cvtColor(frame, cv2.COLOR_GRAY2BGR)
|
||||
else:
|
||||
frame = frame.reshape((FrameHead.iHeight, FrameHead.iWidth, 3))
|
||||
frame_bgr = frame
|
||||
|
||||
# Show preview (resized for display)
|
||||
display_frame = cv2.resize(frame_bgr, (640, 480), interpolation=cv2.INTER_LINEAR)
|
||||
|
||||
# Add info overlay
|
||||
cv2.putText(display_frame, f"PREVIEW - {FrameHead.iWidth}x{FrameHead.iHeight}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
|
||||
cv2.putText(display_frame, "Press 'q' to return to menu", (10, display_frame.shape[0] - 20), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
|
||||
|
||||
cv2.imshow("Camera Preview", display_frame)
|
||||
|
||||
# Check for quit key
|
||||
if cv2.waitKey(1) & 0xFF == ord("q"):
|
||||
cv2.destroyWindow("Camera Preview")
|
||||
break
|
||||
|
||||
except mvsdk.CameraException as e:
|
||||
if e.error_code != mvsdk.CAMERA_STATUS_TIME_OUT:
|
||||
print(f"Camera error: {e.message}")
|
||||
break
|
||||
|
||||
|
||||
def main():
|
||||
print("Camera Video Recorder")
|
||||
print("====================")
|
||||
print("This script allows you to:")
|
||||
print("- List all available cameras")
|
||||
print("- Record videos with custom exposure (1ms), gain (3.5x), and FPS (3.0) settings")
|
||||
print("- Save videos with timestamps")
|
||||
print("- Stop recording anytime with 'q' key")
|
||||
print()
|
||||
|
||||
interactive_menu()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
6
api/tests/legacy_tests/main.py
Normal file
6
api/tests/legacy_tests/main.py
Normal file
@@ -0,0 +1,6 @@
|
||||
def main():
|
||||
print("Hello from usda-vision-cameras!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
197
api/tests/legacy_tests/test_exposure.py
Normal file
197
api/tests/legacy_tests/test_exposure.py
Normal file
@@ -0,0 +1,197 @@
|
||||
#coding=utf-8
|
||||
"""
|
||||
Test script to help find optimal exposure settings for your GigE camera.
|
||||
This script captures a single test image with different exposure settings.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import mvsdk
|
||||
import numpy as np
|
||||
import cv2
|
||||
import platform
|
||||
from datetime import datetime
|
||||
|
||||
# Add the python demo directory to path
|
||||
sys.path.append('./python demo')
|
||||
|
||||
def test_exposure_settings():
|
||||
"""
|
||||
Test different exposure settings to find optimal values
|
||||
"""
|
||||
# Initialize SDK
|
||||
try:
|
||||
mvsdk.CameraSdkInit(1)
|
||||
print("SDK initialized successfully")
|
||||
except Exception as e:
|
||||
print(f"SDK initialization failed: {e}")
|
||||
return False
|
||||
|
||||
# Enumerate cameras
|
||||
DevList = mvsdk.CameraEnumerateDevice()
|
||||
nDev = len(DevList)
|
||||
|
||||
if nDev < 1:
|
||||
print("No camera was found!")
|
||||
return False
|
||||
|
||||
print(f"Found {nDev} camera(s):")
|
||||
for i, DevInfo in enumerate(DevList):
|
||||
print(f" {i}: {DevInfo.GetFriendlyName()} ({DevInfo.GetPortType()})")
|
||||
|
||||
# Use first camera
|
||||
DevInfo = DevList[0]
|
||||
print(f"\nSelected camera: {DevInfo.GetFriendlyName()}")
|
||||
|
||||
# Initialize camera
|
||||
try:
|
||||
hCamera = mvsdk.CameraInit(DevInfo, -1, -1)
|
||||
print("Camera initialized successfully")
|
||||
except mvsdk.CameraException as e:
|
||||
print(f"CameraInit Failed({e.error_code}): {e.message}")
|
||||
return False
|
||||
|
||||
try:
|
||||
# Get camera capabilities
|
||||
cap = mvsdk.CameraGetCapability(hCamera)
|
||||
monoCamera = (cap.sIspCapacity.bMonoSensor != 0)
|
||||
print(f"Camera type: {'Monochrome' if monoCamera else 'Color'}")
|
||||
|
||||
# Get camera ranges
|
||||
try:
|
||||
exp_min, exp_max, exp_step = mvsdk.CameraGetExposureTimeRange(hCamera)
|
||||
print(f"Exposure time range: {exp_min:.1f} - {exp_max:.1f} μs")
|
||||
|
||||
gain_min, gain_max, gain_step = mvsdk.CameraGetAnalogGainXRange(hCamera)
|
||||
print(f"Analog gain range: {gain_min:.2f} - {gain_max:.2f}x")
|
||||
except Exception as e:
|
||||
print(f"Could not get camera ranges: {e}")
|
||||
exp_min, exp_max = 100, 100000
|
||||
gain_min, gain_max = 1.0, 4.0
|
||||
|
||||
# Set output format
|
||||
if monoCamera:
|
||||
mvsdk.CameraSetIspOutFormat(hCamera, mvsdk.CAMERA_MEDIA_TYPE_MONO8)
|
||||
else:
|
||||
mvsdk.CameraSetIspOutFormat(hCamera, mvsdk.CAMERA_MEDIA_TYPE_BGR8)
|
||||
|
||||
# Set camera to continuous capture mode
|
||||
mvsdk.CameraSetTriggerMode(hCamera, 0)
|
||||
mvsdk.CameraSetAeState(hCamera, 0) # Disable auto exposure
|
||||
|
||||
# Start camera
|
||||
mvsdk.CameraPlay(hCamera)
|
||||
|
||||
# Allocate frame buffer
|
||||
FrameBufferSize = cap.sResolutionRange.iWidthMax * cap.sResolutionRange.iHeightMax * (1 if monoCamera else 3)
|
||||
pFrameBuffer = mvsdk.CameraAlignMalloc(FrameBufferSize, 16)
|
||||
|
||||
# Create test directory
|
||||
if not os.path.exists("exposure_tests"):
|
||||
os.makedirs("exposure_tests")
|
||||
|
||||
print("\nTesting different exposure settings...")
|
||||
print("=" * 50)
|
||||
|
||||
# Test different exposure times (in microseconds)
|
||||
exposure_times = [500, 1000, 2000, 5000, 10000, 20000] # 0.5ms to 20ms
|
||||
analog_gains = [1.0] # Start with 1x gain
|
||||
|
||||
test_count = 0
|
||||
for exp_time in exposure_times:
|
||||
for gain in analog_gains:
|
||||
# Clamp values to valid ranges
|
||||
exp_time = max(exp_min, min(exp_max, exp_time))
|
||||
gain = max(gain_min, min(gain_max, gain))
|
||||
|
||||
print(f"\nTest {test_count + 1}: Exposure={exp_time/1000:.1f}ms, Gain={gain:.1f}x")
|
||||
|
||||
# Set camera parameters
|
||||
mvsdk.CameraSetExposureTime(hCamera, exp_time)
|
||||
try:
|
||||
mvsdk.CameraSetAnalogGainX(hCamera, gain)
|
||||
except:
|
||||
pass # Some cameras might not support this
|
||||
|
||||
# Wait a moment for settings to take effect
|
||||
import time
|
||||
time.sleep(0.1)
|
||||
|
||||
# Capture image
|
||||
try:
|
||||
pRawData, FrameHead = mvsdk.CameraGetImageBuffer(hCamera, 2000)
|
||||
mvsdk.CameraImageProcess(hCamera, pRawData, pFrameBuffer, FrameHead)
|
||||
mvsdk.CameraReleaseImageBuffer(hCamera, pRawData)
|
||||
|
||||
# Handle Windows image flip
|
||||
if platform.system() == "Windows":
|
||||
mvsdk.CameraFlipFrameBuffer(pFrameBuffer, FrameHead, 1)
|
||||
|
||||
# Convert to numpy array
|
||||
frame_data = (mvsdk.c_ubyte * FrameHead.uBytes).from_address(pFrameBuffer)
|
||||
frame = np.frombuffer(frame_data, dtype=np.uint8)
|
||||
|
||||
if FrameHead.uiMediaType == mvsdk.CAMERA_MEDIA_TYPE_MONO8:
|
||||
frame = frame.reshape((FrameHead.iHeight, FrameHead.iWidth))
|
||||
else:
|
||||
frame = frame.reshape((FrameHead.iHeight, FrameHead.iWidth, 3))
|
||||
|
||||
# Calculate image statistics
|
||||
mean_brightness = np.mean(frame)
|
||||
max_brightness = np.max(frame)
|
||||
|
||||
# Save image
|
||||
filename = f"exposure_tests/test_{test_count+1:02d}_exp{exp_time/1000:.1f}ms_gain{gain:.1f}x.jpg"
|
||||
cv2.imwrite(filename, frame)
|
||||
|
||||
# Provide feedback
|
||||
status = ""
|
||||
if mean_brightness < 50:
|
||||
status = "TOO DARK"
|
||||
elif mean_brightness > 200:
|
||||
status = "TOO BRIGHT"
|
||||
elif max_brightness >= 255:
|
||||
status = "OVEREXPOSED"
|
||||
else:
|
||||
status = "GOOD"
|
||||
|
||||
print(f" → Saved: {filename}")
|
||||
print(f" → Brightness: mean={mean_brightness:.1f}, max={max_brightness:.1f} [{status}]")
|
||||
|
||||
test_count += 1
|
||||
|
||||
except mvsdk.CameraException as e:
|
||||
print(f" → Failed to capture: {e.message}")
|
||||
|
||||
print(f"\nCompleted {test_count} test captures!")
|
||||
print("Check the 'exposure_tests' directory to see the results.")
|
||||
print("\nRecommendations:")
|
||||
print("- Look for images marked as 'GOOD' - these have optimal exposure")
|
||||
print("- If all images are 'TOO BRIGHT', try lower exposure times or gains")
|
||||
print("- If all images are 'TOO DARK', try higher exposure times or gains")
|
||||
print("- Avoid 'OVEREXPOSED' images as they have clipped highlights")
|
||||
|
||||
# Cleanup
|
||||
mvsdk.CameraAlignFree(pFrameBuffer)
|
||||
|
||||
finally:
|
||||
# Close camera
|
||||
mvsdk.CameraUnInit(hCamera)
|
||||
print("\nCamera closed")
|
||||
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("GigE Camera Exposure Test Script")
|
||||
print("=" * 40)
|
||||
print("This script will test different exposure settings and save sample images.")
|
||||
print("Use this to find the optimal settings for your lighting conditions.")
|
||||
print()
|
||||
|
||||
success = test_exposure_settings()
|
||||
|
||||
if success:
|
||||
print("\nTesting completed successfully!")
|
||||
else:
|
||||
print("\nTesting failed!")
|
||||
|
||||
input("Press Enter to exit...")
|
||||
117
api/tests/mqtt/test_mqtt_logging.py
Normal file
117
api/tests/mqtt/test_mqtt_logging.py
Normal file
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script to demonstrate enhanced MQTT logging and API endpoints.
|
||||
|
||||
This script shows:
|
||||
1. Enhanced console logging for MQTT events
|
||||
2. New MQTT status API endpoint
|
||||
3. Machine status API endpoint
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import requests
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
# Add the current directory to Python path
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
def test_api_endpoints():
|
||||
"""Test the API endpoints for MQTT and machine status"""
|
||||
base_url = "http://localhost:8000"
|
||||
|
||||
print("🧪 Testing API Endpoints...")
|
||||
print("=" * 50)
|
||||
|
||||
# Test system status
|
||||
try:
|
||||
print("\n📊 System Status:")
|
||||
response = requests.get(f"{base_url}/system/status", timeout=5)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print(f" System Started: {data.get('system_started')}")
|
||||
print(f" MQTT Connected: {data.get('mqtt_connected')}")
|
||||
print(f" Last MQTT Message: {data.get('last_mqtt_message')}")
|
||||
print(f" Active Recordings: {data.get('active_recordings')}")
|
||||
print(f" Total Recordings: {data.get('total_recordings')}")
|
||||
else:
|
||||
print(f" ❌ Error: {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f" ❌ Connection Error: {e}")
|
||||
|
||||
# Test MQTT status
|
||||
try:
|
||||
print("\n📡 MQTT Status:")
|
||||
response = requests.get(f"{base_url}/mqtt/status", timeout=5)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print(f" Connected: {data.get('connected')}")
|
||||
print(f" Broker: {data.get('broker_host')}:{data.get('broker_port')}")
|
||||
print(f" Message Count: {data.get('message_count')}")
|
||||
print(f" Error Count: {data.get('error_count')}")
|
||||
print(f" Last Message: {data.get('last_message_time')}")
|
||||
print(f" Uptime: {data.get('uptime_seconds'):.1f}s" if data.get('uptime_seconds') else " Uptime: N/A")
|
||||
print(f" Subscribed Topics:")
|
||||
for topic in data.get('subscribed_topics', []):
|
||||
print(f" - {topic}")
|
||||
else:
|
||||
print(f" ❌ Error: {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f" ❌ Connection Error: {e}")
|
||||
|
||||
# Test machine status
|
||||
try:
|
||||
print("\n🏭 Machine Status:")
|
||||
response = requests.get(f"{base_url}/machines", timeout=5)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
if data:
|
||||
for machine_name, machine_info in data.items():
|
||||
print(f" {machine_name}:")
|
||||
print(f" State: {machine_info.get('state')}")
|
||||
print(f" Last Updated: {machine_info.get('last_updated')}")
|
||||
print(f" Last Message: {machine_info.get('last_message')}")
|
||||
print(f" MQTT Topic: {machine_info.get('mqtt_topic')}")
|
||||
else:
|
||||
print(" No machines found")
|
||||
else:
|
||||
print(f" ❌ Error: {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f" ❌ Connection Error: {e}")
|
||||
|
||||
def main():
|
||||
"""Main test function"""
|
||||
print("🔍 MQTT Logging and API Test")
|
||||
print("=" * 50)
|
||||
print()
|
||||
print("This script tests the enhanced MQTT logging and new API endpoints.")
|
||||
print("Make sure the USDA Vision System is running before testing.")
|
||||
print()
|
||||
|
||||
# Wait a moment
|
||||
time.sleep(1)
|
||||
|
||||
# Test API endpoints
|
||||
test_api_endpoints()
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("✅ Test completed!")
|
||||
print()
|
||||
print("📝 What to expect when running the system:")
|
||||
print(" 🔗 MQTT CONNECTED: [broker_host:port]")
|
||||
print(" 📋 MQTT SUBSCRIBED: [machine] → [topic]")
|
||||
print(" 📡 MQTT MESSAGE: [machine] → [payload]")
|
||||
print(" ⚠️ MQTT DISCONNECTED: [reason]")
|
||||
print()
|
||||
print("🌐 API Endpoints available:")
|
||||
print(" GET /system/status - Overall system status")
|
||||
print(" GET /mqtt/status - MQTT client status and statistics")
|
||||
print(" GET /machines - All machine states from MQTT")
|
||||
print(" GET /cameras - Camera statuses")
|
||||
print()
|
||||
print("💡 To see live MQTT logs, run: python main.py")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
267
api/tests/recording/test_auto_recording.py
Normal file
267
api/tests/recording/test_auto_recording.py
Normal file
@@ -0,0 +1,267 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script for auto-recording functionality.
|
||||
|
||||
This script tests the auto-recording feature by simulating MQTT state changes
|
||||
and verifying that cameras start and stop recording automatically.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
import requests
|
||||
from datetime import datetime
|
||||
|
||||
# Add the parent directory to Python path
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from usda_vision_system.core.config import Config
|
||||
from usda_vision_system.core.state_manager import StateManager
|
||||
from usda_vision_system.core.events import EventSystem, publish_machine_state_changed
|
||||
|
||||
|
||||
class AutoRecordingTester:
|
||||
"""Test class for auto-recording functionality"""
|
||||
|
||||
def __init__(self):
|
||||
self.api_base_url = "http://localhost:8000"
|
||||
self.config = Config("config.json")
|
||||
self.state_manager = StateManager()
|
||||
self.event_system = EventSystem()
|
||||
|
||||
# Test results
|
||||
self.test_results = []
|
||||
|
||||
def log_test(self, test_name: str, success: bool, message: str = ""):
|
||||
"""Log a test result"""
|
||||
status = "✅ PASS" if success else "❌ FAIL"
|
||||
timestamp = datetime.now().strftime("%H:%M:%S")
|
||||
result = f"[{timestamp}] {status} {test_name}"
|
||||
if message:
|
||||
result += f" - {message}"
|
||||
print(result)
|
||||
|
||||
self.test_results.append({
|
||||
"test_name": test_name,
|
||||
"success": success,
|
||||
"message": message,
|
||||
"timestamp": timestamp
|
||||
})
|
||||
|
||||
def check_api_available(self) -> bool:
|
||||
"""Check if the API server is available"""
|
||||
try:
|
||||
response = requests.get(f"{self.api_base_url}/cameras", timeout=5)
|
||||
return response.status_code == 200
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def get_camera_status(self, camera_name: str) -> dict:
|
||||
"""Get camera status from API"""
|
||||
try:
|
||||
response = requests.get(f"{self.api_base_url}/cameras", timeout=5)
|
||||
if response.status_code == 200:
|
||||
cameras = response.json()
|
||||
return cameras.get(camera_name, {})
|
||||
except Exception as e:
|
||||
print(f"Error getting camera status: {e}")
|
||||
return {}
|
||||
|
||||
def get_auto_recording_status(self) -> dict:
|
||||
"""Get auto-recording manager status"""
|
||||
try:
|
||||
response = requests.get(f"{self.api_base_url}/auto-recording/status", timeout=5)
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
print(f"Error getting auto-recording status: {e}")
|
||||
return {}
|
||||
|
||||
def enable_auto_recording(self, camera_name: str) -> bool:
|
||||
"""Enable auto-recording for a camera"""
|
||||
try:
|
||||
response = requests.post(f"{self.api_base_url}/cameras/{camera_name}/auto-recording/enable", timeout=5)
|
||||
return response.status_code == 200
|
||||
except Exception as e:
|
||||
print(f"Error enabling auto-recording: {e}")
|
||||
return False
|
||||
|
||||
def disable_auto_recording(self, camera_name: str) -> bool:
|
||||
"""Disable auto-recording for a camera"""
|
||||
try:
|
||||
response = requests.post(f"{self.api_base_url}/cameras/{camera_name}/auto-recording/disable", timeout=5)
|
||||
return response.status_code == 200
|
||||
except Exception as e:
|
||||
print(f"Error disabling auto-recording: {e}")
|
||||
return False
|
||||
|
||||
def simulate_machine_state_change(self, machine_name: str, state: str):
|
||||
"""Simulate a machine state change via event system"""
|
||||
print(f"🔄 Simulating machine state change: {machine_name} -> {state}")
|
||||
publish_machine_state_changed(machine_name, state, "test_script")
|
||||
|
||||
def test_api_connectivity(self) -> bool:
|
||||
"""Test API connectivity"""
|
||||
available = self.check_api_available()
|
||||
self.log_test("API Connectivity", available,
|
||||
"API server is reachable" if available else "API server is not reachable")
|
||||
return available
|
||||
|
||||
def test_auto_recording_status(self) -> bool:
|
||||
"""Test auto-recording status endpoint"""
|
||||
status = self.get_auto_recording_status()
|
||||
success = bool(status and "running" in status)
|
||||
self.log_test("Auto-Recording Status API", success,
|
||||
f"Status: {status}" if success else "Failed to get status")
|
||||
return success
|
||||
|
||||
def test_camera_auto_recording_config(self) -> bool:
|
||||
"""Test camera auto-recording configuration"""
|
||||
success = True
|
||||
|
||||
# Test enabling auto-recording for camera1
|
||||
enabled = self.enable_auto_recording("camera1")
|
||||
if enabled:
|
||||
self.log_test("Enable Auto-Recording (camera1)", True, "Successfully enabled")
|
||||
else:
|
||||
self.log_test("Enable Auto-Recording (camera1)", False, "Failed to enable")
|
||||
success = False
|
||||
|
||||
# Check camera status
|
||||
time.sleep(1)
|
||||
camera_status = self.get_camera_status("camera1")
|
||||
auto_enabled = camera_status.get("auto_recording_enabled", False)
|
||||
self.log_test("Auto-Recording Status Check", auto_enabled,
|
||||
f"Camera1 auto-recording enabled: {auto_enabled}")
|
||||
|
||||
if not auto_enabled:
|
||||
success = False
|
||||
|
||||
return success
|
||||
|
||||
def test_machine_state_simulation(self) -> bool:
|
||||
"""Test machine state change simulation"""
|
||||
try:
|
||||
# Test vibratory conveyor (camera1)
|
||||
self.simulate_machine_state_change("vibratory_conveyor", "on")
|
||||
time.sleep(2)
|
||||
|
||||
camera_status = self.get_camera_status("camera1")
|
||||
is_recording = camera_status.get("is_recording", False)
|
||||
auto_active = camera_status.get("auto_recording_active", False)
|
||||
|
||||
self.log_test("Machine ON -> Recording Start", is_recording,
|
||||
f"Camera1 recording: {is_recording}, auto-active: {auto_active}")
|
||||
|
||||
# Test turning machine off
|
||||
time.sleep(3)
|
||||
self.simulate_machine_state_change("vibratory_conveyor", "off")
|
||||
time.sleep(2)
|
||||
|
||||
camera_status = self.get_camera_status("camera1")
|
||||
is_recording_after = camera_status.get("is_recording", False)
|
||||
auto_active_after = camera_status.get("auto_recording_active", False)
|
||||
|
||||
self.log_test("Machine OFF -> Recording Stop", not is_recording_after,
|
||||
f"Camera1 recording: {is_recording_after}, auto-active: {auto_active_after}")
|
||||
|
||||
return is_recording and not is_recording_after
|
||||
|
||||
except Exception as e:
|
||||
self.log_test("Machine State Simulation", False, f"Error: {e}")
|
||||
return False
|
||||
|
||||
def test_retry_mechanism(self) -> bool:
|
||||
"""Test retry mechanism for failed recording attempts"""
|
||||
# This test would require simulating camera failures
|
||||
# For now, we'll just check if the retry queue is accessible
|
||||
try:
|
||||
status = self.get_auto_recording_status()
|
||||
retry_queue = status.get("retry_queue", {})
|
||||
|
||||
self.log_test("Retry Queue Access", True,
|
||||
f"Retry queue accessible, current items: {len(retry_queue)}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
self.log_test("Retry Queue Access", False, f"Error: {e}")
|
||||
return False
|
||||
|
||||
def run_all_tests(self):
|
||||
"""Run all auto-recording tests"""
|
||||
print("🧪 Starting Auto-Recording Tests")
|
||||
print("=" * 50)
|
||||
|
||||
# Check if system is running
|
||||
if not self.test_api_connectivity():
|
||||
print("\n❌ Cannot run tests - API server is not available")
|
||||
print("Please start the USDA Vision System first:")
|
||||
print(" python main.py")
|
||||
return False
|
||||
|
||||
# Run tests
|
||||
tests = [
|
||||
self.test_auto_recording_status,
|
||||
self.test_camera_auto_recording_config,
|
||||
self.test_machine_state_simulation,
|
||||
self.test_retry_mechanism,
|
||||
]
|
||||
|
||||
passed = 0
|
||||
total = len(tests)
|
||||
|
||||
for test in tests:
|
||||
try:
|
||||
if test():
|
||||
passed += 1
|
||||
time.sleep(1) # Brief pause between tests
|
||||
except Exception as e:
|
||||
self.log_test(test.__name__, False, f"Exception: {e}")
|
||||
|
||||
# Print summary
|
||||
print("\n" + "=" * 50)
|
||||
print(f"📊 Test Summary: {passed}/{total} tests passed")
|
||||
|
||||
if passed == total:
|
||||
print("🎉 All auto-recording tests passed!")
|
||||
return True
|
||||
else:
|
||||
print(f"⚠️ {total - passed} test(s) failed")
|
||||
return False
|
||||
|
||||
def cleanup(self):
|
||||
"""Cleanup after tests"""
|
||||
print("\n🧹 Cleaning up...")
|
||||
|
||||
# Disable auto-recording for test cameras
|
||||
self.disable_auto_recording("camera1")
|
||||
self.disable_auto_recording("camera2")
|
||||
|
||||
# Turn off machines
|
||||
self.simulate_machine_state_change("vibratory_conveyor", "off")
|
||||
self.simulate_machine_state_change("blower_separator", "off")
|
||||
|
||||
print("✅ Cleanup completed")
|
||||
|
||||
|
||||
def main():
|
||||
"""Main test function"""
|
||||
tester = AutoRecordingTester()
|
||||
|
||||
try:
|
||||
success = tester.run_all_tests()
|
||||
return 0 if success else 1
|
||||
except KeyboardInterrupt:
|
||||
print("\n⚠️ Tests interrupted by user")
|
||||
return 1
|
||||
except Exception as e:
|
||||
print(f"\n❌ Test execution failed: {e}")
|
||||
return 1
|
||||
finally:
|
||||
tester.cleanup()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
exit_code = main()
|
||||
sys.exit(exit_code)
|
||||
193
api/tests/recording/test_auto_recording_mqtt.py
Normal file
193
api/tests/recording/test_auto_recording_mqtt.py
Normal file
@@ -0,0 +1,193 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script to verify auto-recording functionality with simulated MQTT messages.
|
||||
|
||||
This script tests that:
|
||||
1. Auto recording manager properly handles machine state changes
|
||||
2. Recording starts when machine turns "on"
|
||||
3. Recording stops when machine turns "off"
|
||||
4. Camera configuration from config.json is used
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
# Add the current directory to Python path
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
|
||||
|
||||
def setup_logging():
|
||||
"""Setup logging for the test"""
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
||||
|
||||
|
||||
def test_auto_recording_with_mqtt():
|
||||
"""Test auto recording functionality with simulated MQTT messages"""
|
||||
print("🧪 Testing Auto Recording with MQTT Messages")
|
||||
print("=" * 50)
|
||||
|
||||
try:
|
||||
# Import required modules
|
||||
from usda_vision_system.core.config import Config
|
||||
from usda_vision_system.core.state_manager import StateManager
|
||||
from usda_vision_system.core.events import EventSystem, EventType
|
||||
from usda_vision_system.recording.auto_manager import AutoRecordingManager
|
||||
|
||||
print("✅ Modules imported successfully")
|
||||
|
||||
# Create system components
|
||||
config = Config("config.json")
|
||||
state_manager = StateManager()
|
||||
event_system = EventSystem()
|
||||
|
||||
# Create a mock camera manager for testing
|
||||
class MockCameraManager:
|
||||
def __init__(self):
|
||||
self.recording_calls = []
|
||||
self.stop_calls = []
|
||||
|
||||
def manual_start_recording(self, camera_name, filename, exposure_ms=None, gain=None, fps=None):
|
||||
call_info = {"camera_name": camera_name, "filename": filename, "exposure_ms": exposure_ms, "gain": gain, "fps": fps, "timestamp": datetime.now()}
|
||||
self.recording_calls.append(call_info)
|
||||
print(f"📹 MOCK: Starting recording for {camera_name}")
|
||||
print(f" - Filename: {filename}")
|
||||
print(f" - Settings: exposure={exposure_ms}ms, gain={gain}, fps={fps}")
|
||||
return True
|
||||
|
||||
def manual_stop_recording(self, camera_name):
|
||||
call_info = {"camera_name": camera_name, "timestamp": datetime.now()}
|
||||
self.stop_calls.append(call_info)
|
||||
print(f"⏹️ MOCK: Stopping recording for {camera_name}")
|
||||
return True
|
||||
|
||||
mock_camera_manager = MockCameraManager()
|
||||
|
||||
# Create auto recording manager
|
||||
auto_manager = AutoRecordingManager(config, state_manager, event_system, mock_camera_manager)
|
||||
|
||||
print("✅ Auto recording manager created")
|
||||
|
||||
# Start the auto recording manager
|
||||
if not auto_manager.start():
|
||||
print("❌ Failed to start auto recording manager")
|
||||
return False
|
||||
|
||||
print("✅ Auto recording manager started")
|
||||
|
||||
# Test 1: Simulate blower_separator turning ON (should trigger camera1)
|
||||
print("\n🔄 Test 1: Blower separator turns ON")
|
||||
print("📡 Publishing machine state change event...")
|
||||
# Use the same event system instance that the auto manager is subscribed to
|
||||
event_system.publish(EventType.MACHINE_STATE_CHANGED, "test_script", {"machine_name": "blower_separator", "state": "on", "previous_state": None})
|
||||
time.sleep(1.0) # Give more time for event processing
|
||||
|
||||
print(f"📊 Total recording calls so far: {len(mock_camera_manager.recording_calls)}")
|
||||
for call in mock_camera_manager.recording_calls:
|
||||
print(f" - {call['camera_name']}: {call['filename']}")
|
||||
|
||||
# Check if recording was started for camera1
|
||||
camera1_calls = [call for call in mock_camera_manager.recording_calls if call["camera_name"] == "camera1"]
|
||||
if camera1_calls:
|
||||
call = camera1_calls[-1]
|
||||
print(f"✅ Camera1 recording started with config:")
|
||||
print(f" - Exposure: {call['exposure_ms']}ms (expected: 0.3ms)")
|
||||
print(f" - Gain: {call['gain']} (expected: 4.0)")
|
||||
print(f" - FPS: {call['fps']} (expected: 0)")
|
||||
|
||||
# Verify settings match config.json
|
||||
if call["exposure_ms"] == 0.3 and call["gain"] == 4.0 and call["fps"] == 0:
|
||||
print("✅ Camera settings match config.json")
|
||||
else:
|
||||
print("❌ Camera settings don't match config.json")
|
||||
return False
|
||||
else:
|
||||
print("❌ Camera1 recording was not started")
|
||||
return False
|
||||
|
||||
# Test 2: Simulate vibratory_conveyor turning ON (should trigger camera2)
|
||||
print("\n🔄 Test 2: Vibratory conveyor turns ON")
|
||||
event_system.publish(EventType.MACHINE_STATE_CHANGED, "test_script", {"machine_name": "vibratory_conveyor", "state": "on", "previous_state": None})
|
||||
time.sleep(0.5)
|
||||
|
||||
# Check if recording was started for camera2
|
||||
camera2_calls = [call for call in mock_camera_manager.recording_calls if call["camera_name"] == "camera2"]
|
||||
if camera2_calls:
|
||||
call = camera2_calls[-1]
|
||||
print(f"✅ Camera2 recording started with config:")
|
||||
print(f" - Exposure: {call['exposure_ms']}ms (expected: 0.2ms)")
|
||||
print(f" - Gain: {call['gain']} (expected: 2.0)")
|
||||
print(f" - FPS: {call['fps']} (expected: 0)")
|
||||
|
||||
# Verify settings match config.json
|
||||
if call["exposure_ms"] == 0.2 and call["gain"] == 2.0 and call["fps"] == 0:
|
||||
print("✅ Camera settings match config.json")
|
||||
else:
|
||||
print("❌ Camera settings don't match config.json")
|
||||
return False
|
||||
else:
|
||||
print("❌ Camera2 recording was not started")
|
||||
return False
|
||||
|
||||
# Test 3: Simulate machines turning OFF
|
||||
print("\n🔄 Test 3: Machines turn OFF")
|
||||
event_system.publish(EventType.MACHINE_STATE_CHANGED, "test_script", {"machine_name": "blower_separator", "state": "off", "previous_state": None})
|
||||
event_system.publish(EventType.MACHINE_STATE_CHANGED, "test_script", {"machine_name": "vibratory_conveyor", "state": "off", "previous_state": None})
|
||||
time.sleep(0.5)
|
||||
|
||||
# Check if recordings were stopped
|
||||
camera1_stops = [call for call in mock_camera_manager.stop_calls if call["camera_name"] == "camera1"]
|
||||
camera2_stops = [call for call in mock_camera_manager.stop_calls if call["camera_name"] == "camera2"]
|
||||
|
||||
if camera1_stops and camera2_stops:
|
||||
print("✅ Both cameras stopped recording when machines turned OFF")
|
||||
else:
|
||||
print(f"❌ Recording stop failed - Camera1 stops: {len(camera1_stops)}, Camera2 stops: {len(camera2_stops)}")
|
||||
return False
|
||||
|
||||
# Stop the auto recording manager
|
||||
auto_manager.stop()
|
||||
print("✅ Auto recording manager stopped")
|
||||
|
||||
print("\n🎉 All auto recording tests passed!")
|
||||
print("\n📊 Summary:")
|
||||
print(f" - Total recording starts: {len(mock_camera_manager.recording_calls)}")
|
||||
print(f" - Total recording stops: {len(mock_camera_manager.stop_calls)}")
|
||||
print(f" - Camera1 starts: {len([c for c in mock_camera_manager.recording_calls if c['camera_name'] == 'camera1'])}")
|
||||
print(f" - Camera2 starts: {len([c for c in mock_camera_manager.recording_calls if c['camera_name'] == 'camera2'])}")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Test failed with error: {e}")
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""Run the auto recording test"""
|
||||
setup_logging()
|
||||
|
||||
success = test_auto_recording_with_mqtt()
|
||||
|
||||
if success:
|
||||
print("\n✅ Auto recording functionality is working correctly!")
|
||||
print("\n📝 The system should now properly:")
|
||||
print(" 1. Start recording when machines turn ON")
|
||||
print(" 2. Stop recording when machines turn OFF")
|
||||
print(" 3. Use camera settings from config.json")
|
||||
print(" 4. Generate appropriate filenames with timestamps")
|
||||
else:
|
||||
print("\n❌ Auto recording test failed!")
|
||||
print("Please check the implementation and try again.")
|
||||
|
||||
return success
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = main()
|
||||
sys.exit(0 if success else 1)
|
||||
214
api/tests/recording/test_auto_recording_simple.py
Normal file
214
api/tests/recording/test_auto_recording_simple.py
Normal file
@@ -0,0 +1,214 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Simple test script for auto-recording functionality.
|
||||
|
||||
This script performs basic checks to verify that the auto-recording feature
|
||||
is properly integrated and configured.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
|
||||
# Add the current directory to Python path
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
|
||||
def test_config_structure():
|
||||
"""Test that config.json has the required auto-recording fields"""
|
||||
print("🔍 Testing configuration structure...")
|
||||
|
||||
try:
|
||||
with open("config.json", "r") as f:
|
||||
config = json.load(f)
|
||||
|
||||
# Check system-level auto-recording setting
|
||||
system_config = config.get("system", {})
|
||||
if "auto_recording_enabled" not in system_config:
|
||||
print("❌ Missing 'auto_recording_enabled' in system config")
|
||||
return False
|
||||
|
||||
print(f"✅ System auto-recording enabled: {system_config['auto_recording_enabled']}")
|
||||
|
||||
# Check camera-level auto-recording settings
|
||||
cameras = config.get("cameras", [])
|
||||
if not cameras:
|
||||
print("❌ No cameras found in config")
|
||||
return False
|
||||
|
||||
for camera in cameras:
|
||||
camera_name = camera.get("name", "unknown")
|
||||
required_fields = ["auto_start_recording_enabled", "auto_recording_max_retries", "auto_recording_retry_delay_seconds"]
|
||||
|
||||
missing_fields = [field for field in required_fields if field not in camera]
|
||||
if missing_fields:
|
||||
print(f"❌ Camera {camera_name} missing fields: {missing_fields}")
|
||||
return False
|
||||
|
||||
print(f"✅ Camera {camera_name} auto-recording config:")
|
||||
print(f" - Enabled: {camera['auto_start_recording_enabled']}")
|
||||
print(f" - Max retries: {camera['auto_recording_max_retries']}")
|
||||
print(f" - Retry delay: {camera['auto_recording_retry_delay_seconds']}s")
|
||||
print(f" - Machine topic: {camera.get('machine_topic', 'unknown')}")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error reading config: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_module_imports():
|
||||
"""Test that all required modules can be imported"""
|
||||
print("\n🔍 Testing module imports...")
|
||||
|
||||
try:
|
||||
from usda_vision_system.recording.auto_manager import AutoRecordingManager
|
||||
|
||||
print("✅ AutoRecordingManager imported successfully")
|
||||
|
||||
from usda_vision_system.core.config import Config
|
||||
|
||||
config = Config("config.json")
|
||||
print("✅ Config loaded successfully")
|
||||
|
||||
from usda_vision_system.core.state_manager import StateManager
|
||||
|
||||
state_manager = StateManager()
|
||||
print("✅ StateManager created successfully")
|
||||
|
||||
from usda_vision_system.core.events import EventSystem
|
||||
|
||||
event_system = EventSystem()
|
||||
print("✅ EventSystem created successfully")
|
||||
|
||||
# Test creating AutoRecordingManager (without camera_manager for now)
|
||||
auto_manager = AutoRecordingManager(config, state_manager, event_system, None)
|
||||
print("✅ AutoRecordingManager created successfully")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Import error: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_camera_mapping():
|
||||
"""Test camera to machine topic mapping"""
|
||||
print("\n🔍 Testing camera to machine mapping...")
|
||||
|
||||
try:
|
||||
with open("config.json", "r") as f:
|
||||
config = json.load(f)
|
||||
|
||||
cameras = config.get("cameras", [])
|
||||
expected_mappings = {"camera1": "blower_separator", "camera2": "vibratory_conveyor"} # Blower separator # Conveyor/cracker cam
|
||||
|
||||
for camera in cameras:
|
||||
camera_name = camera.get("name")
|
||||
machine_topic = camera.get("machine_topic")
|
||||
|
||||
if camera_name in expected_mappings:
|
||||
expected_topic = expected_mappings[camera_name]
|
||||
if machine_topic == expected_topic:
|
||||
print(f"✅ {camera_name} correctly mapped to {machine_topic}")
|
||||
else:
|
||||
print(f"❌ {camera_name} mapped to {machine_topic}, expected {expected_topic}")
|
||||
return False
|
||||
else:
|
||||
print(f"⚠️ Unknown camera: {camera_name}")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error checking mappings: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_api_models():
|
||||
"""Test that API models include auto-recording fields"""
|
||||
print("\n🔍 Testing API models...")
|
||||
|
||||
try:
|
||||
from usda_vision_system.api.models import CameraStatusResponse, CameraConfigResponse, AutoRecordingConfigRequest, AutoRecordingConfigResponse, AutoRecordingStatusResponse
|
||||
|
||||
# Check CameraStatusResponse has auto-recording fields
|
||||
camera_response = CameraStatusResponse(name="test", status="available", is_recording=False, last_checked="2024-01-01T00:00:00", auto_recording_enabled=True, auto_recording_active=False, auto_recording_failure_count=0)
|
||||
print("✅ CameraStatusResponse includes auto-recording fields")
|
||||
|
||||
# Check CameraConfigResponse has auto-recording fields
|
||||
config_response = CameraConfigResponse(
|
||||
name="test",
|
||||
machine_topic="test_topic",
|
||||
storage_path="/test",
|
||||
enabled=True,
|
||||
auto_start_recording_enabled=True,
|
||||
auto_recording_max_retries=3,
|
||||
auto_recording_retry_delay_seconds=5,
|
||||
exposure_ms=1.0,
|
||||
gain=1.0,
|
||||
target_fps=30.0,
|
||||
sharpness=100,
|
||||
contrast=100,
|
||||
saturation=100,
|
||||
gamma=100,
|
||||
noise_filter_enabled=False,
|
||||
denoise_3d_enabled=False,
|
||||
auto_white_balance=True,
|
||||
color_temperature_preset=0,
|
||||
wb_red_gain=1.0,
|
||||
wb_green_gain=1.0,
|
||||
wb_blue_gain=1.0,
|
||||
anti_flicker_enabled=False,
|
||||
light_frequency=1,
|
||||
bit_depth=8,
|
||||
hdr_enabled=False,
|
||||
hdr_gain_mode=0,
|
||||
)
|
||||
print("✅ CameraConfigResponse includes auto-recording fields")
|
||||
|
||||
print("✅ All auto-recording API models available")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ API model error: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""Run all basic tests"""
|
||||
print("🧪 Auto-Recording Integration Test")
|
||||
print("=" * 40)
|
||||
|
||||
tests = [test_config_structure, test_module_imports, test_camera_mapping, test_api_models]
|
||||
|
||||
passed = 0
|
||||
total = len(tests)
|
||||
|
||||
for test in tests:
|
||||
try:
|
||||
if test():
|
||||
passed += 1
|
||||
except Exception as e:
|
||||
print(f"❌ Test {test.__name__} failed with exception: {e}")
|
||||
|
||||
print("\n" + "=" * 40)
|
||||
print(f"📊 Results: {passed}/{total} tests passed")
|
||||
|
||||
if passed == total:
|
||||
print("🎉 All integration tests passed!")
|
||||
print("\n📝 Next steps:")
|
||||
print("1. Start the system: python main.py")
|
||||
print("2. Run full tests: python tests/test_auto_recording.py")
|
||||
print("3. Test with MQTT messages to trigger auto-recording")
|
||||
return True
|
||||
else:
|
||||
print(f"⚠️ {total - passed} test(s) failed")
|
||||
print("Please fix the issues before running the full system")
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = main()
|
||||
sys.exit(0 if success else 1)
|
||||
185
api/tests/test_video_module.py
Normal file
185
api/tests/test_video_module.py
Normal file
@@ -0,0 +1,185 @@
|
||||
"""
|
||||
Test the modular video streaming functionality.
|
||||
|
||||
This test verifies that the video module integrates correctly with the existing system
|
||||
and provides the expected streaming capabilities.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
# Configure logging for tests
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
|
||||
async def test_video_module_integration():
|
||||
"""Test video module integration with the existing system"""
|
||||
print("\n🎬 Testing Video Module Integration...")
|
||||
|
||||
try:
|
||||
# Import the necessary components
|
||||
from usda_vision_system.core.config import Config
|
||||
from usda_vision_system.storage.manager import StorageManager
|
||||
from usda_vision_system.core.state_manager import StateManager
|
||||
from usda_vision_system.video.integration import create_video_module
|
||||
|
||||
print("✅ Successfully imported video module components")
|
||||
|
||||
# Initialize core components
|
||||
config = Config()
|
||||
state_manager = StateManager()
|
||||
storage_manager = StorageManager(config, state_manager)
|
||||
|
||||
print("✅ Core components initialized")
|
||||
|
||||
# Create video module
|
||||
video_module = create_video_module(
|
||||
config=config,
|
||||
storage_manager=storage_manager,
|
||||
enable_caching=True,
|
||||
enable_conversion=False # Disable conversion for testing
|
||||
)
|
||||
|
||||
print("✅ Video module created successfully")
|
||||
|
||||
# Test module status
|
||||
status = video_module.get_module_status()
|
||||
print(f"📊 Video module status: {status}")
|
||||
|
||||
# Test video service
|
||||
videos = await video_module.video_service.get_all_videos(limit=5)
|
||||
print(f"📹 Found {len(videos)} video files")
|
||||
|
||||
for video in videos[:3]: # Show first 3 videos
|
||||
print(f" - {video.file_id} ({video.camera_name}) - {video.file_size_bytes} bytes")
|
||||
|
||||
# Test streaming service
|
||||
if videos:
|
||||
video_file = videos[0]
|
||||
streaming_info = await video_module.streaming_service.get_video_info(video_file.file_id)
|
||||
if streaming_info:
|
||||
print(f"🎯 Streaming test: {streaming_info.file_id} is streamable: {streaming_info.is_streamable}")
|
||||
|
||||
# Test API routes creation
|
||||
api_routes = video_module.get_api_routes()
|
||||
admin_routes = video_module.get_admin_routes()
|
||||
|
||||
print(f"🛣️ API routes created: {len(api_routes.routes)} routes")
|
||||
print(f"🔧 Admin routes created: {len(admin_routes.routes)} routes")
|
||||
|
||||
# List some of the available routes
|
||||
print("📋 Available video endpoints:")
|
||||
for route in api_routes.routes:
|
||||
if hasattr(route, 'path') and hasattr(route, 'methods'):
|
||||
methods = ', '.join(route.methods) if route.methods else 'N/A'
|
||||
print(f" {methods} {route.path}")
|
||||
|
||||
# Cleanup
|
||||
await video_module.cleanup()
|
||||
print("✅ Video module cleanup completed")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Video module test failed: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
|
||||
async def test_video_streaming_endpoints():
|
||||
"""Test video streaming endpoints with a mock FastAPI app"""
|
||||
print("\n🌐 Testing Video Streaming Endpoints...")
|
||||
|
||||
try:
|
||||
from fastapi import FastAPI
|
||||
from fastapi.testclient import TestClient
|
||||
from usda_vision_system.core.config import Config
|
||||
from usda_vision_system.storage.manager import StorageManager
|
||||
from usda_vision_system.core.state_manager import StateManager
|
||||
from usda_vision_system.video.integration import create_video_module
|
||||
|
||||
# Create test app
|
||||
app = FastAPI()
|
||||
|
||||
# Initialize components
|
||||
config = Config()
|
||||
state_manager = StateManager()
|
||||
storage_manager = StorageManager(config, state_manager)
|
||||
|
||||
# Create video module
|
||||
video_module = create_video_module(
|
||||
config=config,
|
||||
storage_manager=storage_manager,
|
||||
enable_caching=True,
|
||||
enable_conversion=False
|
||||
)
|
||||
|
||||
# Add video routes to test app
|
||||
video_routes = video_module.get_api_routes()
|
||||
admin_routes = video_module.get_admin_routes()
|
||||
|
||||
app.include_router(video_routes)
|
||||
app.include_router(admin_routes)
|
||||
|
||||
print("✅ Test FastAPI app created with video routes")
|
||||
|
||||
# Create test client
|
||||
client = TestClient(app)
|
||||
|
||||
# Test video list endpoint
|
||||
response = client.get("/videos/")
|
||||
print(f"📋 GET /videos/ - Status: {response.status_code}")
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print(f" Found {data.get('total_count', 0)} videos")
|
||||
|
||||
# Test video module status (if we had added it to the routes)
|
||||
# This would be available in the main API server
|
||||
|
||||
print("✅ Video streaming endpoints test completed")
|
||||
|
||||
# Cleanup
|
||||
await video_module.cleanup()
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Video streaming endpoints test failed: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
|
||||
async def main():
|
||||
"""Run all video module tests"""
|
||||
print("🚀 Starting Video Module Tests")
|
||||
print("=" * 50)
|
||||
|
||||
# Test 1: Module Integration
|
||||
test1_success = await test_video_module_integration()
|
||||
|
||||
# Test 2: Streaming Endpoints
|
||||
test2_success = await test_video_streaming_endpoints()
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("📊 Test Results:")
|
||||
print(f" Module Integration: {'✅ PASS' if test1_success else '❌ FAIL'}")
|
||||
print(f" Streaming Endpoints: {'✅ PASS' if test2_success else '❌ FAIL'}")
|
||||
|
||||
if test1_success and test2_success:
|
||||
print("\n🎉 All video module tests passed!")
|
||||
print("\n📖 Next Steps:")
|
||||
print(" 1. Restart the usda-vision-camera service")
|
||||
print(" 2. Test video streaming in your React app")
|
||||
print(" 3. Use endpoints like: GET /videos/ and GET /videos/{file_id}/stream")
|
||||
else:
|
||||
print("\n⚠️ Some tests failed. Check the error messages above.")
|
||||
|
||||
return test1_success and test2_success
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user