Add comprehensive test suite for USDA Vision Camera System
- Implemented main test script to verify system components and functionality. - Added individual test scripts for camera exposure settings, API changes, camera recovery, maximum FPS, MQTT events, logging, and timezone functionality. - Created service file for system management and automatic startup. - Included detailed logging and error handling in test scripts for better diagnostics. - Ensured compatibility with existing camera SDK and API endpoints.
This commit is contained in:
77
tests/check_time.py
Executable file
77
tests/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()
|
||||
146
tests/legacy_tests/01README.md
Normal file
146
tests/legacy_tests/01README.md
Normal file
@@ -0,0 +1,146 @@
|
||||
# GigE Camera Image Capture
|
||||
|
||||
This project provides simple Python scripts to connect to a GigE camera and capture images using the provided SDK.
|
||||
|
||||
## Files Overview
|
||||
|
||||
### Demo Files (provided with camera)
|
||||
- `python demo/mvsdk.py` - Main SDK wrapper library
|
||||
- `python demo/grab.py` - Basic image capture example
|
||||
- `python demo/cv_grab.py` - OpenCV-based continuous capture
|
||||
- `python demo/cv_grab_callback.py` - Callback-based capture
|
||||
- `python demo/readme.txt` - Original demo documentation
|
||||
|
||||
### Custom Scripts
|
||||
- `camera_capture.py` - Standalone script to capture 10 images with 200ms intervals
|
||||
- `test.ipynb` - Jupyter notebook with the same functionality
|
||||
- `images/` - Directory where captured images are saved
|
||||
|
||||
## Features
|
||||
|
||||
- **Automatic camera detection** - Finds and connects to available GigE cameras
|
||||
- **Configurable capture** - Currently set to capture 10 images with 200ms intervals
|
||||
- **Both mono and color support** - Automatically detects camera type
|
||||
- **Timestamped filenames** - Images saved with date/time stamps
|
||||
- **Error handling** - Robust error handling for camera operations
|
||||
- **Cross-platform** - Works on Windows and Linux (with appropriate image flipping)
|
||||
|
||||
## Requirements
|
||||
|
||||
- Python 3.x
|
||||
- OpenCV (`cv2`)
|
||||
- NumPy
|
||||
- Matplotlib (for Jupyter notebook display)
|
||||
- GigE camera SDK (MVSDK) - included in `python demo/` directory
|
||||
|
||||
## Usage
|
||||
|
||||
### Option 1: Standalone Script
|
||||
|
||||
Run the standalone Python script:
|
||||
|
||||
```bash
|
||||
python camera_capture.py
|
||||
```
|
||||
|
||||
This will:
|
||||
1. Initialize the camera SDK
|
||||
2. Detect available cameras
|
||||
3. Connect to the first camera found
|
||||
4. Configure camera settings (manual exposure, continuous mode)
|
||||
5. Capture 10 images with 200ms intervals
|
||||
6. Save images to the `images/` directory
|
||||
7. Clean up and close the camera
|
||||
|
||||
### Option 2: Jupyter Notebook
|
||||
|
||||
Open and run the `test.ipynb` notebook:
|
||||
|
||||
```bash
|
||||
jupyter notebook test.ipynb
|
||||
```
|
||||
|
||||
The notebook provides the same functionality but with:
|
||||
- Step-by-step execution
|
||||
- Detailed explanations
|
||||
- Visual display of the last captured image
|
||||
- Better error reporting
|
||||
|
||||
## Camera Configuration
|
||||
|
||||
The scripts are configured with the following default settings:
|
||||
|
||||
- **Trigger Mode**: Continuous capture (mode 0)
|
||||
- **Exposure**: Manual, 30ms
|
||||
- **Output Format**:
|
||||
- Monochrome cameras: MONO8
|
||||
- Color cameras: BGR8
|
||||
- **Image Processing**: Automatic ISP processing from RAW to RGB/MONO
|
||||
|
||||
## Output
|
||||
|
||||
Images are saved in the `images/` directory with the following naming convention:
|
||||
```
|
||||
image_XX_YYYYMMDD_HHMMSS_mmm.jpg
|
||||
```
|
||||
|
||||
Where:
|
||||
- `XX` = Image number (01-10)
|
||||
- `YYYYMMDD_HHMMSS_mmm` = Timestamp with milliseconds
|
||||
|
||||
Example: `image_01_20250722_140530_123.jpg`
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **"No camera was found!"**
|
||||
- Check camera connection (Ethernet cable)
|
||||
- Verify camera power
|
||||
- Check network settings (camera and PC should be on same subnet)
|
||||
- Ensure camera drivers are installed
|
||||
|
||||
2. **"CameraInit Failed"**
|
||||
- Camera might be in use by another application
|
||||
- Check camera permissions
|
||||
- Try restarting the camera or PC
|
||||
|
||||
3. **"Failed to capture image"**
|
||||
- Check camera settings
|
||||
- Verify sufficient lighting
|
||||
- Check exposure settings
|
||||
|
||||
4. **Images appear upside down**
|
||||
- This is handled automatically on Windows
|
||||
- Linux users may need to adjust the flip settings
|
||||
|
||||
### Network Configuration
|
||||
|
||||
For GigE cameras, ensure:
|
||||
- Camera and PC are on the same network segment
|
||||
- PC network adapter supports Jumbo frames (recommended)
|
||||
- Firewall allows camera communication
|
||||
- Sufficient network bandwidth
|
||||
|
||||
## Customization
|
||||
|
||||
You can modify the scripts to:
|
||||
|
||||
- **Change capture count**: Modify the range in the capture loop
|
||||
- **Adjust timing**: Change the `time.sleep(0.2)` value
|
||||
- **Modify exposure**: Change the exposure time parameter
|
||||
- **Change output format**: Modify file format and quality settings
|
||||
- **Add image processing**: Insert processing steps before saving
|
||||
|
||||
## SDK Reference
|
||||
|
||||
The camera SDK (`mvsdk.py`) provides extensive functionality:
|
||||
|
||||
- Camera enumeration and initialization
|
||||
- Image capture and processing
|
||||
- Parameter configuration (exposure, gain, etc.)
|
||||
- Trigger modes and timing
|
||||
- Image format conversion
|
||||
- Error handling
|
||||
|
||||
Refer to the original SDK documentation for advanced features.
|
||||
BIN
tests/legacy_tests/Camera/Data/054012620023.mvdat
Normal file
BIN
tests/legacy_tests/Camera/Data/054012620023.mvdat
Normal file
Binary file not shown.
BIN
tests/legacy_tests/Camera/Data/054052320151.mvdat
Normal file
BIN
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
|
||||
184
tests/legacy_tests/IMPLEMENTATION_SUMMARY.md
Normal file
184
tests/legacy_tests/IMPLEMENTATION_SUMMARY.md
Normal file
@@ -0,0 +1,184 @@
|
||||
# USDA Vision Camera System - Implementation Summary
|
||||
|
||||
## 🎉 Project Completed Successfully!
|
||||
|
||||
The USDA Vision Camera System has been fully implemented and tested. All components are working correctly and the system is ready for deployment.
|
||||
|
||||
## ✅ What Was Built
|
||||
|
||||
### Core Architecture
|
||||
- **Modular Design**: Clean separation of concerns across multiple modules
|
||||
- **Multi-threading**: Concurrent MQTT listening, camera monitoring, and recording
|
||||
- **Event-driven**: Thread-safe communication between components
|
||||
- **Configuration-driven**: JSON-based configuration system
|
||||
|
||||
### Key Components
|
||||
|
||||
1. **MQTT Integration** (`usda_vision_system/mqtt/`)
|
||||
- Listens to two machine topics: `vision/vibratory_conveyor/state` and `vision/blower_separator/state`
|
||||
- Thread-safe message handling with automatic reconnection
|
||||
- State normalization (on/off/error)
|
||||
|
||||
2. **Camera Management** (`usda_vision_system/camera/`)
|
||||
- Automatic GigE camera discovery using python demo library
|
||||
- Periodic status monitoring (every 2 seconds)
|
||||
- Camera initialization and configuration management
|
||||
- **Discovered Cameras**:
|
||||
- Blower-Yield-Cam (192.168.1.165)
|
||||
- Cracker-Cam (192.168.1.167)
|
||||
|
||||
3. **Video Recording** (`usda_vision_system/camera/recorder.py`)
|
||||
- Automatic recording start/stop based on machine states
|
||||
- Timestamp-based file naming: `camera1_recording_20250726_143022.avi`
|
||||
- Configurable FPS, exposure, and gain settings
|
||||
- Thread-safe recording with proper cleanup
|
||||
|
||||
4. **Storage Management** (`usda_vision_system/storage/`)
|
||||
- Organized file storage under `./storage/camera1/` and `./storage/camera2/`
|
||||
- File indexing and metadata tracking
|
||||
- Automatic cleanup of old files
|
||||
- Storage statistics and integrity checking
|
||||
|
||||
5. **REST API Server** (`usda_vision_system/api/`)
|
||||
- FastAPI server on port 8000
|
||||
- Real-time WebSocket updates
|
||||
- Manual recording control endpoints
|
||||
- System status and monitoring endpoints
|
||||
|
||||
6. **Comprehensive Logging** (`usda_vision_system/core/logging_config.py`)
|
||||
- Colored console output
|
||||
- Rotating log files
|
||||
- Component-specific log levels
|
||||
- Performance monitoring and error tracking
|
||||
|
||||
## 🚀 How to Use
|
||||
|
||||
### Quick Start
|
||||
```bash
|
||||
# Run system tests
|
||||
python test_system.py
|
||||
|
||||
# Start the system
|
||||
python main.py
|
||||
|
||||
# Or use the startup script
|
||||
./start_system.sh
|
||||
```
|
||||
|
||||
### Configuration
|
||||
Edit `config.json` to customize:
|
||||
- MQTT broker settings
|
||||
- Camera configurations
|
||||
- Storage paths
|
||||
- System parameters
|
||||
|
||||
### API Access
|
||||
- System status: `http://localhost:8000/system/status`
|
||||
- Camera status: `http://localhost:8000/cameras`
|
||||
- Manual recording: `POST http://localhost:8000/cameras/camera1/start-recording`
|
||||
- Real-time updates: WebSocket at `ws://localhost:8000/ws`
|
||||
|
||||
## 📊 Test Results
|
||||
|
||||
All system tests passed successfully:
|
||||
- ✅ Module imports
|
||||
- ✅ Configuration loading
|
||||
- ✅ Camera discovery (found 2 cameras)
|
||||
- ✅ Storage setup
|
||||
- ✅ MQTT configuration
|
||||
- ✅ System initialization
|
||||
- ✅ API endpoints
|
||||
|
||||
## 🔧 System Behavior
|
||||
|
||||
### Automatic Recording Flow
|
||||
1. **Machine turns ON** → MQTT message received → Recording starts automatically
|
||||
2. **Machine turns OFF** → MQTT message received → Recording stops and saves file
|
||||
3. **Files saved** with timestamp: `camera1_recording_YYYYMMDD_HHMMSS.avi`
|
||||
|
||||
### Manual Control
|
||||
- Start/stop recording via API calls
|
||||
- Monitor system status in real-time
|
||||
- Check camera availability on demand
|
||||
|
||||
### Dashboard Integration
|
||||
The system is designed to integrate with your React + Vite + Tailwind + Supabase dashboard:
|
||||
- REST API for status queries
|
||||
- WebSocket for real-time updates
|
||||
- JSON responses for easy frontend consumption
|
||||
|
||||
## 📁 Project Structure
|
||||
|
||||
```
|
||||
usda_vision_system/
|
||||
├── core/ # Configuration, state management, events, logging
|
||||
├── mqtt/ # MQTT client and message handlers
|
||||
├── camera/ # Camera management, monitoring, recording
|
||||
├── storage/ # File organization and management
|
||||
├── api/ # FastAPI server and WebSocket support
|
||||
└── main.py # Application coordinator
|
||||
|
||||
Supporting Files:
|
||||
├── main.py # Entry point script
|
||||
├── config.json # System configuration
|
||||
├── test_system.py # Test suite
|
||||
├── start_system.sh # Startup script
|
||||
└── README_SYSTEM.md # Comprehensive documentation
|
||||
```
|
||||
|
||||
## 🎯 Key Features Delivered
|
||||
|
||||
- ✅ **Dual MQTT topic listening** for two machines
|
||||
- ✅ **Automatic camera recording** triggered by machine states
|
||||
- ✅ **GigE camera support** using python demo library
|
||||
- ✅ **Thread-safe multi-tasking** (MQTT + camera monitoring + recording)
|
||||
- ✅ **Timestamp-based file naming** in organized directories
|
||||
- ✅ **2-second camera status monitoring** with on-demand checks
|
||||
- ✅ **REST API and WebSocket** for dashboard integration
|
||||
- ✅ **Comprehensive logging** with error tracking
|
||||
- ✅ **Configuration management** via JSON
|
||||
- ✅ **Storage management** with cleanup capabilities
|
||||
- ✅ **Graceful startup/shutdown** with signal handling
|
||||
|
||||
## 🔮 Ready for Dashboard Integration
|
||||
|
||||
The system provides everything needed for your React dashboard:
|
||||
|
||||
```javascript
|
||||
// Example API usage
|
||||
const systemStatus = await fetch('http://localhost:8000/system/status');
|
||||
const cameras = await fetch('http://localhost:8000/cameras');
|
||||
|
||||
// WebSocket for real-time updates
|
||||
const ws = new WebSocket('ws://localhost:8000/ws');
|
||||
ws.onmessage = (event) => {
|
||||
const update = JSON.parse(event.data);
|
||||
// Handle real-time system updates
|
||||
};
|
||||
|
||||
// Manual recording control
|
||||
await fetch('http://localhost:8000/cameras/camera1/start-recording', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ camera_name: 'camera1' })
|
||||
});
|
||||
```
|
||||
|
||||
## 🎊 Next Steps
|
||||
|
||||
The system is production-ready! You can now:
|
||||
|
||||
1. **Deploy** the system on your target hardware
|
||||
2. **Integrate** with your existing React dashboard
|
||||
3. **Configure** MQTT topics and camera settings as needed
|
||||
4. **Monitor** system performance through logs and API endpoints
|
||||
5. **Extend** functionality as requirements evolve
|
||||
|
||||
The modular architecture makes it easy to add new features, cameras, or MQTT topics in the future.
|
||||
|
||||
---
|
||||
|
||||
**System Status**: ✅ **FULLY OPERATIONAL**
|
||||
**Test Results**: ✅ **ALL TESTS PASSING**
|
||||
**Cameras Detected**: ✅ **2 GIGE CAMERAS READY**
|
||||
**Ready for Production**: ✅ **YES**
|
||||
1
tests/legacy_tests/README.md
Normal file
1
tests/legacy_tests/README.md
Normal file
@@ -0,0 +1 @@
|
||||
# USDA-Vision-Cameras
|
||||
249
tests/legacy_tests/README_SYSTEM.md
Normal file
249
tests/legacy_tests/README_SYSTEM.md
Normal file
@@ -0,0 +1,249 @@
|
||||
# USDA Vision Camera System
|
||||
|
||||
A comprehensive system for monitoring machines via MQTT and automatically recording video from GigE cameras when machines are active.
|
||||
|
||||
## Overview
|
||||
|
||||
This system integrates MQTT machine monitoring with automated video recording from GigE cameras. When a machine turns on (detected via MQTT), the system automatically starts recording from the associated camera. When the machine turns off, recording stops and the video is saved with a timestamp.
|
||||
|
||||
## Features
|
||||
|
||||
- **MQTT Integration**: Listens to multiple machine state topics
|
||||
- **Automatic Recording**: Starts/stops recording based on machine states
|
||||
- **GigE Camera Support**: Uses the python demo library (mvsdk) for camera control
|
||||
- **Multi-threading**: Concurrent MQTT listening, camera monitoring, and recording
|
||||
- **REST API**: FastAPI server for dashboard integration
|
||||
- **WebSocket Support**: Real-time status updates
|
||||
- **Storage Management**: Organized file storage with cleanup capabilities
|
||||
- **Comprehensive Logging**: Detailed logging with rotation and error tracking
|
||||
- **Configuration Management**: JSON-based configuration system
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ MQTT Broker │ │ GigE Camera │ │ Dashboard │
|
||||
│ │ │ │ │ (React) │
|
||||
└─────────┬───────┘ └─────────┬───────┘ └─────────┬───────┘
|
||||
│ │ │
|
||||
│ Machine States │ Video Streams │ API Calls
|
||||
│ │ │
|
||||
┌─────────▼──────────────────────▼──────────────────────▼───────┐
|
||||
│ USDA Vision Camera System │
|
||||
├───────────────────────────────────────────────────────────────┤
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ MQTT Client │ │ Camera │ │ API Server │ │
|
||||
│ │ │ │ Manager │ │ │ │
|
||||
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ State │ │ Storage │ │ Event │ │
|
||||
│ │ Manager │ │ Manager │ │ System │ │
|
||||
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
||||
└───────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
1. **Prerequisites**:
|
||||
- Python 3.11+
|
||||
- GigE cameras with python demo library
|
||||
- MQTT broker (e.g., Mosquitto)
|
||||
- uv package manager (recommended)
|
||||
|
||||
2. **Install Dependencies**:
|
||||
```bash
|
||||
uv sync
|
||||
```
|
||||
|
||||
3. **Setup Storage Directory**:
|
||||
```bash
|
||||
sudo mkdir -p /storage
|
||||
sudo chown $USER:$USER /storage
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Edit `config.json` to configure your system:
|
||||
|
||||
```json
|
||||
{
|
||||
"mqtt": {
|
||||
"broker_host": "192.168.1.110",
|
||||
"broker_port": 1883,
|
||||
"topics": {
|
||||
"vibratory_conveyor": "vision/vibratory_conveyor/state",
|
||||
"blower_separator": "vision/blower_separator/state"
|
||||
}
|
||||
},
|
||||
"cameras": [
|
||||
{
|
||||
"name": "camera1",
|
||||
"machine_topic": "vibratory_conveyor",
|
||||
"storage_path": "/storage/camera1",
|
||||
"exposure_ms": 1.0,
|
||||
"gain": 3.5,
|
||||
"target_fps": 3.0,
|
||||
"enabled": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
1. **Start the System**:
|
||||
```bash
|
||||
python main.py
|
||||
```
|
||||
|
||||
2. **With Custom Config**:
|
||||
```bash
|
||||
python main.py --config my_config.json
|
||||
```
|
||||
|
||||
3. **Debug Mode**:
|
||||
```bash
|
||||
python main.py --log-level DEBUG
|
||||
```
|
||||
|
||||
### API Endpoints
|
||||
|
||||
The system provides a REST API on port 8000:
|
||||
|
||||
- `GET /system/status` - Overall system status
|
||||
- `GET /cameras` - All camera statuses
|
||||
- `GET /machines` - All machine states
|
||||
- `POST /cameras/{name}/start-recording` - Manual recording start
|
||||
- `POST /cameras/{name}/stop-recording` - Manual recording stop
|
||||
- `GET /storage/stats` - Storage statistics
|
||||
- `WebSocket /ws` - Real-time updates
|
||||
|
||||
### Dashboard Integration
|
||||
|
||||
The system is designed to integrate with your existing React + Vite + Tailwind + Supabase dashboard:
|
||||
|
||||
1. **API Integration**: Use the REST endpoints to display system status
|
||||
2. **WebSocket**: Connect to `/ws` for real-time updates
|
||||
3. **Supabase Storage**: Store recording metadata and system logs
|
||||
|
||||
## File Organization
|
||||
|
||||
```
|
||||
/storage/
|
||||
├── camera1/
|
||||
│ ├── camera1_recording_20250726_143022.avi
|
||||
│ └── camera1_recording_20250726_143155.avi
|
||||
├── camera2/
|
||||
│ ├── camera2_recording_20250726_143025.avi
|
||||
│ └── camera2_recording_20250726_143158.avi
|
||||
└── file_index.json
|
||||
```
|
||||
|
||||
## Monitoring and Logging
|
||||
|
||||
### Log Files
|
||||
|
||||
- `usda_vision_system.log` - Main system log (rotated)
|
||||
- Console output with colored formatting
|
||||
- Component-specific log levels
|
||||
|
||||
### Performance Monitoring
|
||||
|
||||
The system includes built-in performance monitoring:
|
||||
- Startup times
|
||||
- Recording session metrics
|
||||
- MQTT message processing rates
|
||||
- Camera status check intervals
|
||||
|
||||
### Error Tracking
|
||||
|
||||
Comprehensive error tracking with:
|
||||
- Error counts per component
|
||||
- Detailed error context
|
||||
- Automatic recovery attempts
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Camera Not Found**:
|
||||
- Check camera connections
|
||||
- Verify python demo library installation
|
||||
- Run camera discovery: Check logs for enumeration results
|
||||
|
||||
2. **MQTT Connection Failed**:
|
||||
- Verify broker IP and port
|
||||
- Check network connectivity
|
||||
- Verify credentials if authentication is enabled
|
||||
|
||||
3. **Recording Fails**:
|
||||
- Check storage permissions
|
||||
- Verify available disk space
|
||||
- Check camera initialization logs
|
||||
|
||||
4. **API Server Won't Start**:
|
||||
- Check if port 8000 is available
|
||||
- Verify FastAPI dependencies
|
||||
- Check firewall settings
|
||||
|
||||
### Debug Commands
|
||||
|
||||
```bash
|
||||
# Check system status
|
||||
curl http://localhost:8000/system/status
|
||||
|
||||
# Check camera status
|
||||
curl http://localhost:8000/cameras
|
||||
|
||||
# Manual recording start
|
||||
curl -X POST http://localhost:8000/cameras/camera1/start-recording \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"camera_name": "camera1"}'
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
usda_vision_system/
|
||||
├── core/ # Core functionality
|
||||
├── mqtt/ # MQTT client and handlers
|
||||
├── camera/ # Camera management and recording
|
||||
├── storage/ # File management
|
||||
├── api/ # FastAPI server
|
||||
└── main.py # Application coordinator
|
||||
```
|
||||
|
||||
### Adding New Features
|
||||
|
||||
1. **New Camera Type**: Extend `camera/recorder.py`
|
||||
2. **New MQTT Topics**: Update `config.json` and `mqtt/handlers.py`
|
||||
3. **New API Endpoints**: Add to `api/server.py`
|
||||
4. **New Events**: Define in `core/events.py`
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
# Run basic system test
|
||||
python -c "from usda_vision_system import USDAVisionSystem; s = USDAVisionSystem(); print('OK')"
|
||||
|
||||
# Test MQTT connection
|
||||
python -c "from usda_vision_system.mqtt.client import MQTTClient; # ... test code"
|
||||
|
||||
# Test camera discovery
|
||||
python -c "import sys; sys.path.append('python demo'); import mvsdk; print(len(mvsdk.CameraEnumerateDevice()))"
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This project is developed for USDA research purposes.
|
||||
|
||||
## Support
|
||||
|
||||
For issues and questions:
|
||||
1. Check the logs in `usda_vision_system.log`
|
||||
2. Review the troubleshooting section
|
||||
3. Check API status at `http://localhost:8000/health`
|
||||
190
tests/legacy_tests/TIMEZONE_SETUP_SUMMARY.md
Normal file
190
tests/legacy_tests/TIMEZONE_SETUP_SUMMARY.md
Normal file
@@ -0,0 +1,190 @@
|
||||
# Time Synchronization Setup - Atlanta, Georgia
|
||||
|
||||
## ✅ Time Synchronization Complete!
|
||||
|
||||
The USDA Vision Camera System has been configured for proper time synchronization with Atlanta, Georgia (Eastern Time Zone).
|
||||
|
||||
## 🕐 What Was Implemented
|
||||
|
||||
### System-Level Time Configuration
|
||||
- **Timezone**: Set to `America/New_York` (Eastern Time)
|
||||
- **Current Status**: Eastern Daylight Time (EDT, UTC-4)
|
||||
- **NTP Sync**: Configured with multiple reliable time servers
|
||||
- **Hardware Clock**: Synchronized with system time
|
||||
|
||||
### Application-Level Timezone Support
|
||||
- **Timezone-Aware Timestamps**: All recordings use Atlanta time
|
||||
- **Automatic DST Handling**: Switches between EST/EDT automatically
|
||||
- **Time Sync Monitoring**: Built-in time synchronization checking
|
||||
- **Consistent Formatting**: Standardized timestamp formats throughout
|
||||
|
||||
## 🔧 Key Features
|
||||
|
||||
### 1. Automatic Time Synchronization
|
||||
```bash
|
||||
# NTP servers configured:
|
||||
- time.nist.gov (NIST atomic clock)
|
||||
- pool.ntp.org (NTP pool)
|
||||
- time.google.com (Google time)
|
||||
- time.cloudflare.com (Cloudflare time)
|
||||
```
|
||||
|
||||
### 2. Timezone-Aware Recording Filenames
|
||||
```
|
||||
Example: camera1_recording_20250725_213241.avi
|
||||
Format: {camera}_{type}_{YYYYMMDD_HHMMSS}.avi
|
||||
Time: Atlanta local time (EDT/EST)
|
||||
```
|
||||
|
||||
### 3. Time Verification Tools
|
||||
- **Startup Check**: Automatic time sync verification on system start
|
||||
- **Manual Check**: `python check_time.py` for on-demand verification
|
||||
- **API Integration**: Time sync status available via REST API
|
||||
|
||||
### 4. Comprehensive Logging
|
||||
```
|
||||
=== TIME SYNCHRONIZATION STATUS ===
|
||||
System time: 2025-07-25 21:32:41 EDT
|
||||
Timezone: EDT (-0400)
|
||||
Daylight Saving: Yes
|
||||
Sync status: synchronized
|
||||
Time difference: 0.10 seconds
|
||||
=====================================
|
||||
```
|
||||
|
||||
## 🚀 Usage
|
||||
|
||||
### Automatic Operation
|
||||
The system automatically:
|
||||
- Uses Atlanta time for all timestamps
|
||||
- Handles daylight saving time transitions
|
||||
- Monitors time synchronization status
|
||||
- Logs time-related events
|
||||
|
||||
### Manual Verification
|
||||
```bash
|
||||
# Check time synchronization
|
||||
python check_time.py
|
||||
|
||||
# Test timezone functions
|
||||
python test_timezone.py
|
||||
|
||||
# View system time status
|
||||
timedatectl status
|
||||
```
|
||||
|
||||
### API Endpoints
|
||||
```bash
|
||||
# System status includes time info
|
||||
curl http://localhost:8000/system/status
|
||||
|
||||
# Example response includes:
|
||||
{
|
||||
"system_started": true,
|
||||
"uptime_seconds": 3600,
|
||||
"timestamp": "2025-07-25T21:32:41-04:00"
|
||||
}
|
||||
```
|
||||
|
||||
## 📊 Current Status
|
||||
|
||||
### Time Synchronization
|
||||
- ✅ **System Timezone**: America/New_York (EDT)
|
||||
- ✅ **NTP Sync**: Active and synchronized
|
||||
- ✅ **Time Accuracy**: Within 0.1 seconds of atomic time
|
||||
- ✅ **DST Support**: Automatic EST/EDT switching
|
||||
|
||||
### Application Integration
|
||||
- ✅ **Recording Timestamps**: Atlanta time zone
|
||||
- ✅ **Log Timestamps**: Timezone-aware logging
|
||||
- ✅ **API Responses**: ISO format with timezone
|
||||
- ✅ **File Naming**: Consistent Atlanta time format
|
||||
|
||||
### Monitoring
|
||||
- ✅ **Startup Verification**: Time sync checked on boot
|
||||
- ✅ **Continuous Monitoring**: Built-in sync status tracking
|
||||
- ✅ **Error Detection**: Alerts for time drift issues
|
||||
- ✅ **Manual Tools**: On-demand verification scripts
|
||||
|
||||
## 🔍 Technical Details
|
||||
|
||||
### Timezone Configuration
|
||||
```json
|
||||
{
|
||||
"system": {
|
||||
"timezone": "America/New_York"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Time Sources
|
||||
1. **Primary**: NIST atomic clock (time.nist.gov)
|
||||
2. **Secondary**: NTP pool servers (pool.ntp.org)
|
||||
3. **Backup**: Google/Cloudflare time servers
|
||||
4. **Fallback**: Local system clock
|
||||
|
||||
### File Naming Convention
|
||||
```
|
||||
Pattern: {camera_name}_recording_{YYYYMMDD_HHMMSS}.avi
|
||||
Example: camera1_recording_20250725_213241.avi
|
||||
Timezone: Always Atlanta local time (EST/EDT)
|
||||
```
|
||||
|
||||
## 🎯 Benefits
|
||||
|
||||
### For Operations
|
||||
- **Consistent Timestamps**: All recordings use Atlanta time
|
||||
- **Easy Correlation**: Timestamps match local business hours
|
||||
- **Automatic DST**: No manual timezone adjustments needed
|
||||
- **Reliable Sync**: Multiple time sources ensure accuracy
|
||||
|
||||
### For Analysis
|
||||
- **Local Time Context**: Recordings timestamped in business timezone
|
||||
- **Accurate Sequencing**: Precise timing for event correlation
|
||||
- **Standard Format**: Consistent naming across all recordings
|
||||
- **Audit Trail**: Complete time synchronization logging
|
||||
|
||||
### For Integration
|
||||
- **Dashboard Ready**: Timezone-aware API responses
|
||||
- **Database Compatible**: ISO format timestamps with timezone
|
||||
- **Log Analysis**: Structured time information in logs
|
||||
- **Monitoring**: Built-in time sync health checks
|
||||
|
||||
## 🔧 Maintenance
|
||||
|
||||
### Regular Checks
|
||||
The system automatically:
|
||||
- Verifies time sync on startup
|
||||
- Logs time synchronization status
|
||||
- Monitors for time drift
|
||||
- Alerts on sync failures
|
||||
|
||||
### Manual Maintenance
|
||||
```bash
|
||||
# Force time sync
|
||||
sudo systemctl restart systemd-timesyncd
|
||||
|
||||
# Check NTP status
|
||||
timedatectl show-timesync --all
|
||||
|
||||
# Verify timezone
|
||||
timedatectl status
|
||||
```
|
||||
|
||||
## 📈 Next Steps
|
||||
|
||||
The time synchronization is now fully operational. The system will:
|
||||
|
||||
1. **Automatically maintain** accurate Atlanta time
|
||||
2. **Generate timestamped recordings** with local time
|
||||
3. **Monitor sync status** and alert on issues
|
||||
4. **Provide timezone-aware** API responses for dashboard integration
|
||||
|
||||
All recording files will now have accurate Atlanta timestamps, making it easy to correlate with local business operations and machine schedules.
|
||||
|
||||
---
|
||||
|
||||
**Time Sync Status**: ✅ **SYNCHRONIZED**
|
||||
**Timezone**: ✅ **America/New_York (EDT)**
|
||||
**Accuracy**: ✅ **±0.1 seconds**
|
||||
**Ready for Production**: ✅ **YES**
|
||||
191
tests/legacy_tests/VIDEO_RECORDER_README.md
Normal file
191
tests/legacy_tests/VIDEO_RECORDER_README.md
Normal file
@@ -0,0 +1,191 @@
|
||||
# Camera Video Recorder
|
||||
|
||||
A Python script for recording videos from GigE cameras using the provided SDK with custom exposure and gain settings.
|
||||
|
||||
## Features
|
||||
|
||||
- **List all available cameras** - Automatically detects and displays all connected cameras
|
||||
- **Custom camera settings** - Set exposure time to 1ms and gain to 3.5x (or custom values)
|
||||
- **Video recording** - Record videos in AVI format with timestamp filenames
|
||||
- **Live preview** - Test camera functionality with live preview mode
|
||||
- **Interactive menu** - User-friendly menu system for all operations
|
||||
- **Automatic cleanup** - Proper resource management and cleanup
|
||||
|
||||
## Requirements
|
||||
|
||||
- Python 3.x
|
||||
- OpenCV (`cv2`)
|
||||
- NumPy
|
||||
- Camera SDK (mvsdk) - included in `python demo` directory
|
||||
- GigE camera connected to the system
|
||||
|
||||
## Installation
|
||||
|
||||
1. Ensure your GigE camera is connected and properly configured
|
||||
2. Make sure the `python demo` directory with `mvsdk.py` is present
|
||||
3. Install required Python packages:
|
||||
```bash
|
||||
pip install opencv-python numpy
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
Run the script:
|
||||
```bash
|
||||
python camera_video_recorder.py
|
||||
```
|
||||
|
||||
The script will:
|
||||
1. Display a welcome message and feature overview
|
||||
2. List all available cameras
|
||||
3. Let you select a camera (if multiple are available)
|
||||
4. Allow you to set custom exposure and gain values
|
||||
5. Present an interactive menu with options
|
||||
|
||||
### Menu Options
|
||||
|
||||
1. **Start Recording** - Begin video recording with timestamp filename
|
||||
2. **List Camera Info** - Display detailed camera information
|
||||
3. **Test Camera (Live Preview)** - View live camera feed without recording
|
||||
4. **Exit** - Clean up and exit the program
|
||||
|
||||
### Default Settings
|
||||
|
||||
- **Exposure Time**: 1.0ms (1000 microseconds)
|
||||
- **Gain**: 3.5x
|
||||
- **Video Format**: AVI with XVID codec
|
||||
- **Frame Rate**: 30 FPS
|
||||
- **Output Directory**: `videos/` (created automatically)
|
||||
|
||||
### Recording Controls
|
||||
|
||||
- **Start Recording**: Select option 1 from the menu
|
||||
- **Stop Recording**: Press 'q' in the preview window
|
||||
- **Video Files**: Saved as `videos/camera_recording_YYYYMMDD_HHMMSS.avi`
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
camera_video_recorder.py # Main script
|
||||
python demo/
|
||||
mvsdk.py # Camera SDK wrapper
|
||||
(other demo files)
|
||||
videos/ # Output directory (created automatically)
|
||||
camera_recording_*.avi # Recorded video files
|
||||
```
|
||||
|
||||
## Script Features
|
||||
|
||||
### CameraVideoRecorder Class
|
||||
|
||||
- `list_cameras()` - Enumerate and display available cameras
|
||||
- `initialize_camera()` - Set up camera with custom exposure and gain
|
||||
- `start_recording()` - Initialize video writer and begin recording
|
||||
- `stop_recording()` - Stop recording and save video file
|
||||
- `record_loop()` - Main recording loop with live preview
|
||||
- `cleanup()` - Proper resource cleanup
|
||||
|
||||
### Key Functions
|
||||
|
||||
- **Camera Detection**: Automatically finds all connected GigE cameras
|
||||
- **Settings Validation**: Checks and clamps exposure/gain values to camera limits
|
||||
- **Frame Processing**: Handles both monochrome and color cameras
|
||||
- **Windows Compatibility**: Handles frame flipping for Windows systems
|
||||
- **Error Handling**: Comprehensive error handling and user feedback
|
||||
|
||||
## Example Output
|
||||
|
||||
```
|
||||
Camera Video Recorder
|
||||
====================
|
||||
This script allows you to:
|
||||
- List all available cameras
|
||||
- Record videos with custom exposure (1ms) and gain (3.5x) settings
|
||||
- Save videos with timestamps
|
||||
- Stop recording anytime with 'q' key
|
||||
|
||||
Found 1 camera(s):
|
||||
0: GigE Camera Model (GigE) - SN: 12345678
|
||||
|
||||
Using camera: GigE Camera Model
|
||||
|
||||
Camera Settings:
|
||||
Enter exposure time in ms (default 1.0): 1.0
|
||||
Enter gain value (default 3.5): 3.5
|
||||
|
||||
Initializing camera with:
|
||||
- Exposure: 1.0ms
|
||||
- Gain: 3.5x
|
||||
|
||||
Camera type: Color
|
||||
Set exposure time: 1000.0μs
|
||||
Set analog gain: 3.50x (range: 1.00 - 16.00)
|
||||
Camera started successfully
|
||||
|
||||
==================================================
|
||||
Camera Video Recorder Menu
|
||||
==================================================
|
||||
1. Start Recording
|
||||
2. List Camera Info
|
||||
3. Test Camera (Live Preview)
|
||||
4. Exit
|
||||
|
||||
Select option (1-4): 1
|
||||
|
||||
Started recording to: videos/camera_recording_20241223_143022.avi
|
||||
Frame size: (1920, 1080), FPS: 30.0
|
||||
Press 'q' to stop recording...
|
||||
Recording... Press 'q' in the preview window to stop
|
||||
|
||||
Recording stopped!
|
||||
Saved: videos/camera_recording_20241223_143022.avi
|
||||
Frames recorded: 450
|
||||
Duration: 15.2 seconds
|
||||
Average FPS: 29.6
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **"No cameras found!"**
|
||||
- Check camera connection
|
||||
- Verify camera power
|
||||
- Ensure network configuration for GigE cameras
|
||||
|
||||
2. **"SDK initialization failed"**
|
||||
- Verify `python demo/mvsdk.py` exists
|
||||
- Check camera drivers are installed
|
||||
|
||||
3. **"Camera initialization failed"**
|
||||
- Camera may be in use by another application
|
||||
- Try disconnecting and reconnecting the camera
|
||||
|
||||
4. **Recording issues**
|
||||
- Ensure sufficient disk space
|
||||
- Check write permissions in the output directory
|
||||
|
||||
### Performance Tips
|
||||
|
||||
- Close other applications using the camera
|
||||
- Ensure adequate system resources (CPU, RAM)
|
||||
- Use SSD storage for better write performance
|
||||
- Adjust frame rate if experiencing dropped frames
|
||||
|
||||
## Customization
|
||||
|
||||
You can modify the script to:
|
||||
- Change video codec (currently XVID)
|
||||
- Adjust target frame rate
|
||||
- Modify output filename format
|
||||
- Add additional camera settings
|
||||
- Change preview window size
|
||||
|
||||
## Notes
|
||||
|
||||
- Videos are saved in the `videos/` directory with timestamp filenames
|
||||
- The script handles both monochrome and color cameras automatically
|
||||
- Frame flipping is handled automatically for Windows systems
|
||||
- All resources are properly cleaned up on exit
|
||||
291
tests/legacy_tests/camera_capture.py
Normal file
291
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
tests/legacy_tests/camera_video_recorder.py
Normal file
439
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
tests/legacy_tests/main.py
Normal file
6
tests/legacy_tests/main.py
Normal file
@@ -0,0 +1,6 @@
|
||||
def main():
|
||||
print("Hello from usda-vision-cameras!")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
197
tests/legacy_tests/test_exposure.py
Normal file
197
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...")
|
||||
173
tests/test_api_changes.py
Normal file
173
tests/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
tests/test_camera_recovery_api.py
Normal file
92
tests/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()
|
||||
131
tests/test_max_fps.py
Normal file
131
tests/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()
|
||||
168
tests/test_mqtt_events_api.py
Normal file
168
tests/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()
|
||||
117
tests/test_mqtt_logging.py
Normal file
117
tests/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()
|
||||
225
tests/test_system.py
Normal file
225
tests/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())
|
||||
56
tests/test_timezone.py
Normal file
56
tests/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()
|
||||
Reference in New Issue
Block a user