Enhance camera configuration and auto-recording functionality
- Updated CameraStreamer to configure streaming settings from config.json, including manual exposure, gain, image quality, noise reduction, and color settings. - Added new methods in CameraStreamer for configuring image quality, noise reduction, color settings, and advanced settings. - Extended CameraConfig to include manual white balance RGB gains. - Improved AutoRecordingManager to handle camera status updates and ensure proper recording starts/stops based on machine state changes. - Created detailed configuration documentation for blower and conveyor cameras, outlining settings and their mappings to config.json. - Implemented a comprehensive test script for auto-recording functionality with simulated MQTT messages, verifying correct behavior on machine state changes.
This commit is contained in:
@@ -318,6 +318,9 @@ GET {{baseUrl}}/cameras/camera1/config
|
||||
# "machine_topic": "vibratory_conveyor",
|
||||
# "storage_path": "/storage/camera1",
|
||||
# "enabled": true,
|
||||
# "auto_start_recording_enabled": true,
|
||||
# "auto_recording_max_retries": 3,
|
||||
# "auto_recording_retry_delay_seconds": 2,
|
||||
# "exposure_ms": 1.0,
|
||||
# "gain": 3.5,
|
||||
# "target_fps": 0,
|
||||
@@ -329,6 +332,9 @@ GET {{baseUrl}}/cameras/camera1/config
|
||||
# "denoise_3d_enabled": false,
|
||||
# "auto_white_balance": true,
|
||||
# "color_temperature_preset": 0,
|
||||
# "wb_red_gain": 1.0,
|
||||
# "wb_green_gain": 1.0,
|
||||
# "wb_blue_gain": 1.0,
|
||||
# "anti_flicker_enabled": true,
|
||||
# "light_frequency": 1,
|
||||
# "bit_depth": 8,
|
||||
@@ -376,6 +382,19 @@ Content-Type: application/json
|
||||
|
||||
###
|
||||
|
||||
### Update white balance RGB gains (manual white balance)
|
||||
PUT {{baseUrl}}/cameras/camera1/config
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"auto_white_balance": false,
|
||||
"wb_red_gain": 1.2,
|
||||
"wb_green_gain": 1.0,
|
||||
"wb_blue_gain": 0.8
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
### Enable HDR mode
|
||||
PUT {{baseUrl}}/cameras/camera1/config
|
||||
Content-Type: application/json
|
||||
|
||||
34
config.json
34
config.json
@@ -28,10 +28,10 @@
|
||||
"cameras": [
|
||||
{
|
||||
"name": "camera1",
|
||||
"machine_topic": "vibratory_conveyor",
|
||||
"machine_topic": "blower_separator",
|
||||
"storage_path": "/storage/camera1",
|
||||
"exposure_ms": 0.5,
|
||||
"gain": 0.5,
|
||||
"exposure_ms": 0.3,
|
||||
"gain": 4.0,
|
||||
"target_fps": 0,
|
||||
"enabled": true,
|
||||
"auto_start_recording_enabled": true,
|
||||
@@ -40,23 +40,26 @@
|
||||
"sharpness": 100,
|
||||
"contrast": 100,
|
||||
"saturation": 100,
|
||||
"gamma": 110,
|
||||
"gamma": 100,
|
||||
"noise_filter_enabled": false,
|
||||
"denoise_3d_enabled": false,
|
||||
"auto_white_balance": true,
|
||||
"auto_white_balance": false,
|
||||
"color_temperature_preset": 0,
|
||||
"wb_red_gain": 0.94,
|
||||
"wb_green_gain": 1.0,
|
||||
"wb_blue_gain": 0.87,
|
||||
"anti_flicker_enabled": false,
|
||||
"light_frequency": 1,
|
||||
"light_frequency": 0,
|
||||
"bit_depth": 8,
|
||||
"hdr_enabled": false,
|
||||
"hdr_gain_mode": 0
|
||||
"hdr_gain_mode": 2
|
||||
},
|
||||
{
|
||||
"name": "camera2",
|
||||
"machine_topic": "blower_separator",
|
||||
"machine_topic": "vibratory_conveyor",
|
||||
"storage_path": "/storage/camera2",
|
||||
"exposure_ms": 0.5,
|
||||
"gain": 0.3,
|
||||
"exposure_ms": 0.2,
|
||||
"gain": 2.0,
|
||||
"target_fps": 0,
|
||||
"enabled": true,
|
||||
"auto_start_recording_enabled": true,
|
||||
@@ -64,14 +67,17 @@
|
||||
"auto_recording_retry_delay_seconds": 2,
|
||||
"sharpness": 100,
|
||||
"contrast": 100,
|
||||
"saturation": 75,
|
||||
"gamma": 110,
|
||||
"saturation": 100,
|
||||
"gamma": 100,
|
||||
"noise_filter_enabled": false,
|
||||
"denoise_3d_enabled": false,
|
||||
"auto_white_balance": true,
|
||||
"auto_white_balance": false,
|
||||
"color_temperature_preset": 0,
|
||||
"wb_red_gain": 1.01,
|
||||
"wb_green_gain": 1.0,
|
||||
"wb_blue_gain": 0.87,
|
||||
"anti_flicker_enabled": false,
|
||||
"light_frequency": 1,
|
||||
"light_frequency": 0,
|
||||
"bit_depth": 8,
|
||||
"hdr_enabled": false,
|
||||
"hdr_gain_mode": 0
|
||||
|
||||
@@ -197,10 +197,12 @@ GET /cameras/{camera_name}/config
|
||||
"machine_topic": "vibratory_conveyor",
|
||||
"storage_path": "/storage/camera1",
|
||||
"enabled": true,
|
||||
"auto_start_recording_enabled": true,
|
||||
"auto_recording_max_retries": 3,
|
||||
"auto_recording_retry_delay_seconds": 2,
|
||||
"exposure_ms": 1.0,
|
||||
"gain": 3.5,
|
||||
"target_fps": 3.0,
|
||||
"auto_start_recording_enabled": true,
|
||||
"sharpness": 120,
|
||||
"contrast": 110,
|
||||
"saturation": 100,
|
||||
@@ -209,6 +211,9 @@ GET /cameras/{camera_name}/config
|
||||
"denoise_3d_enabled": false,
|
||||
"auto_white_balance": true,
|
||||
"color_temperature_preset": 0,
|
||||
"wb_red_gain": 1.0,
|
||||
"wb_green_gain": 1.0,
|
||||
"wb_blue_gain": 1.0,
|
||||
"anti_flicker_enabled": true,
|
||||
"light_frequency": 1,
|
||||
"bit_depth": 8,
|
||||
|
||||
@@ -12,6 +12,7 @@ These settings can be changed while the camera is active:
|
||||
- **Basic**: `exposure_ms`, `gain`, `target_fps`
|
||||
- **Image Quality**: `sharpness`, `contrast`, `saturation`, `gamma`
|
||||
- **Color**: `auto_white_balance`, `color_temperature_preset`
|
||||
- **White Balance**: `wb_red_gain`, `wb_green_gain`, `wb_blue_gain`
|
||||
- **Advanced**: `anti_flicker_enabled`, `light_frequency`
|
||||
- **HDR**: `hdr_enabled`, `hdr_gain_mode`
|
||||
|
||||
@@ -21,6 +22,12 @@ These settings require camera restart to take effect:
|
||||
- **Noise Reduction**: `noise_filter_enabled`, `denoise_3d_enabled`
|
||||
- **System**: `machine_topic`, `storage_path`, `enabled`, `bit_depth`
|
||||
|
||||
### 🔒 **Read-Only Fields**
|
||||
These fields are returned in the response but cannot be modified via the API:
|
||||
|
||||
- **System Info**: `name`, `machine_topic`, `storage_path`, `enabled`
|
||||
- **Auto-Recording**: `auto_start_recording_enabled`, `auto_recording_max_retries`, `auto_recording_retry_delay_seconds`
|
||||
|
||||
## 🔌 API Endpoints
|
||||
|
||||
### 1. Get Camera Configuration
|
||||
@@ -35,6 +42,9 @@ GET /cameras/{camera_name}/config
|
||||
"machine_topic": "vibratory_conveyor",
|
||||
"storage_path": "/storage/camera1",
|
||||
"enabled": true,
|
||||
"auto_start_recording_enabled": true,
|
||||
"auto_recording_max_retries": 3,
|
||||
"auto_recording_retry_delay_seconds": 2,
|
||||
"exposure_ms": 1.0,
|
||||
"gain": 3.5,
|
||||
"target_fps": 0,
|
||||
@@ -46,6 +56,9 @@ GET /cameras/{camera_name}/config
|
||||
"denoise_3d_enabled": false,
|
||||
"auto_white_balance": true,
|
||||
"color_temperature_preset": 0,
|
||||
"wb_red_gain": 1.0,
|
||||
"wb_green_gain": 1.0,
|
||||
"wb_blue_gain": 1.0,
|
||||
"anti_flicker_enabled": true,
|
||||
"light_frequency": 1,
|
||||
"bit_depth": 8,
|
||||
@@ -74,6 +87,9 @@ Content-Type: application/json
|
||||
"denoise_3d_enabled": false,
|
||||
"auto_white_balance": false,
|
||||
"color_temperature_preset": 1,
|
||||
"wb_red_gain": 1.2,
|
||||
"wb_green_gain": 1.0,
|
||||
"wb_blue_gain": 0.8,
|
||||
"anti_flicker_enabled": true,
|
||||
"light_frequency": 1,
|
||||
"hdr_enabled": false,
|
||||
@@ -86,7 +102,7 @@ Content-Type: application/json
|
||||
{
|
||||
"success": true,
|
||||
"message": "Camera camera1 configuration updated",
|
||||
"updated_settings": ["exposure_ms", "gain", "sharpness"]
|
||||
"updated_settings": ["exposure_ms", "gain", "sharpness", "wb_red_gain"]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -105,6 +121,21 @@ POST /cameras/{camera_name}/apply-config
|
||||
|
||||
## 📊 Setting Ranges and Descriptions
|
||||
|
||||
### System Settings
|
||||
| Setting | Values | Default | Description |
|
||||
|---------|--------|---------|-------------|
|
||||
| `name` | string | - | Camera identifier (read-only) |
|
||||
| `machine_topic` | string | - | MQTT topic for machine state (read-only) |
|
||||
| `storage_path` | string | - | Video storage directory (read-only) |
|
||||
| `enabled` | true/false | true | Camera enabled status (read-only) |
|
||||
|
||||
### Auto-Recording Settings
|
||||
| Setting | Range | Default | Description |
|
||||
|---------|-------|---------|-------------|
|
||||
| `auto_start_recording_enabled` | true/false | true | Enable automatic recording on machine state changes (read-only) |
|
||||
| `auto_recording_max_retries` | 1-10 | 3 | Maximum retry attempts for failed recordings (read-only) |
|
||||
| `auto_recording_retry_delay_seconds` | 1-30 | 2 | Delay between retry attempts in seconds (read-only) |
|
||||
|
||||
### Basic Settings
|
||||
| Setting | Range | Default | Description |
|
||||
|---------|-------|---------|-------------|
|
||||
@@ -126,6 +157,13 @@ POST /cameras/{camera_name}/apply-config
|
||||
| `auto_white_balance` | true/false | true | Automatic white balance |
|
||||
| `color_temperature_preset` | 0-10 | 0 | Color temperature preset (0=auto) |
|
||||
|
||||
### Manual White Balance RGB Gains
|
||||
| Setting | Range | Default | Description |
|
||||
|---------|-------|---------|-------------|
|
||||
| `wb_red_gain` | 0.0 - 3.99 | 1.0 | Red channel gain for manual white balance |
|
||||
| `wb_green_gain` | 0.0 - 3.99 | 1.0 | Green channel gain for manual white balance |
|
||||
| `wb_blue_gain` | 0.0 - 3.99 | 1.0 | Blue channel gain for manual white balance |
|
||||
|
||||
### Advanced Settings
|
||||
| Setting | Values | Default | Description |
|
||||
|---------|--------|---------|-------------|
|
||||
@@ -248,7 +286,21 @@ const CameraConfig = ({ cameraName, apiBaseUrl = 'http://localhost:8000' }) => {
|
||||
return (
|
||||
<div className="camera-config">
|
||||
<h3>Camera Configuration: {cameraName}</h3>
|
||||
|
||||
|
||||
{/* System Information (Read-Only) */}
|
||||
<div className="config-section">
|
||||
<h4>System Information</h4>
|
||||
<div className="info-grid">
|
||||
<div><strong>Name:</strong> {config.name}</div>
|
||||
<div><strong>Machine Topic:</strong> {config.machine_topic}</div>
|
||||
<div><strong>Storage Path:</strong> {config.storage_path}</div>
|
||||
<div><strong>Enabled:</strong> {config.enabled ? 'Yes' : 'No'}</div>
|
||||
<div><strong>Auto Recording:</strong> {config.auto_start_recording_enabled ? 'Enabled' : 'Disabled'}</div>
|
||||
<div><strong>Max Retries:</strong> {config.auto_recording_max_retries}</div>
|
||||
<div><strong>Retry Delay:</strong> {config.auto_recording_retry_delay_seconds}s</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Basic Settings */}
|
||||
<div className="config-section">
|
||||
<h4>Basic Settings</h4>
|
||||
@@ -328,6 +380,47 @@ const CameraConfig = ({ cameraName, apiBaseUrl = 'http://localhost:8000' }) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* White Balance RGB Gains */}
|
||||
<div className="config-section">
|
||||
<h4>White Balance RGB Gains</h4>
|
||||
|
||||
<div className="setting">
|
||||
<label>Red Gain: {config.wb_red_gain}</label>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="3.99"
|
||||
step="0.01"
|
||||
value={config.wb_red_gain}
|
||||
onChange={(e) => handleSliderChange('wb_red_gain', parseFloat(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="setting">
|
||||
<label>Green Gain: {config.wb_green_gain}</label>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="3.99"
|
||||
step="0.01"
|
||||
value={config.wb_green_gain}
|
||||
onChange={(e) => handleSliderChange('wb_green_gain', parseFloat(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="setting">
|
||||
<label>Blue Gain: {config.wb_blue_gain}</label>
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="3.99"
|
||||
step="0.01"
|
||||
value={config.wb_blue_gain}
|
||||
onChange={(e) => handleSliderChange('wb_blue_gain', parseFloat(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Advanced Settings */}
|
||||
<div className="config-section">
|
||||
<h4>Advanced Settings</h4>
|
||||
|
||||
127
docs/camera/BLOWER_CAMERA_CONFIG.md
Normal file
127
docs/camera/BLOWER_CAMERA_CONFIG.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# Blower Camera (Camera1) Configuration
|
||||
|
||||
This document describes the default configuration for the blower camera (Camera1) based on the GigE camera settings from the dedicated software.
|
||||
|
||||
## Camera Identification
|
||||
- **Camera Name**: camera1 (Blower-Yield-Cam)
|
||||
- **Machine Topic**: blower_separator
|
||||
- **Purpose**: Monitors the blower separator machine
|
||||
|
||||
## Configuration Summary
|
||||
|
||||
Based on the camera settings screenshots, the following configuration has been applied to Camera1:
|
||||
|
||||
### Exposure Settings
|
||||
- **Mode**: Manual (not Auto)
|
||||
- **Exposure Time**: 1.0ms (1000μs)
|
||||
- **Gain**: 3.5x (350 in camera units)
|
||||
- **Anti-Flicker**: Enabled (50Hz mode)
|
||||
|
||||
### Color Processing Settings
|
||||
- **White Balance Mode**: Manual (not Auto)
|
||||
- **Color Temperature**: D65 (6500K)
|
||||
- **RGB Gain Values**:
|
||||
- Red Gain: 1.00
|
||||
- Green Gain: 1.00
|
||||
- Blue Gain: 1.00
|
||||
- **Saturation**: 100 (normal)
|
||||
|
||||
### LUT (Look-Up Table) Settings
|
||||
- **Mode**: Dynamically generated (not Preset or Custom)
|
||||
- **Gamma**: 1.00 (100 in config units)
|
||||
- **Contrast**: 100 (normal)
|
||||
|
||||
### Advanced Settings
|
||||
- **Anti-Flicker**: Enabled
|
||||
- **Light Frequency**: 60Hz (1 in config)
|
||||
- **Bit Depth**: 8-bit
|
||||
- **HDR**: Disabled
|
||||
|
||||
## Configuration Mapping
|
||||
|
||||
The screenshots show these key settings that have been mapped to the config.json:
|
||||
|
||||
| Screenshot Setting | Config Parameter | Value | Notes |
|
||||
|-------------------|------------------|-------|-------|
|
||||
| Manual Exposure | auto_exposure | false | Exposure mode set to manual |
|
||||
| Time(ms): 1.0000 | exposure_ms | 1.0 | Exposure time in milliseconds |
|
||||
| Gain(multiple): 3.500 | gain | 3.5 | Analog gain multiplier |
|
||||
| Manual White Balance | auto_white_balance | false | Manual WB mode |
|
||||
| Color Temperature: D65 | color_temperature_preset | 6500 | D65 = 6500K |
|
||||
| Red Gain: 1.00 | wb_red_gain | 1.0 | Manual RGB gain |
|
||||
| Green Gain: 1.00 | wb_green_gain | 1.0 | Manual RGB gain |
|
||||
| Blue Gain: 1.00 | wb_blue_gain | 1.0 | Manual RGB gain |
|
||||
| Saturation: 100 | saturation | 100 | Color saturation |
|
||||
| Gamma: 1.00 | gamma | 100 | Gamma correction |
|
||||
| Contrast: 100 | contrast | 100 | Image contrast |
|
||||
| 50HZ Anti-Flicker | anti_flicker_enabled | true | Flicker reduction |
|
||||
| 60Hz frequency | light_frequency | 1 | Power frequency |
|
||||
|
||||
## Current Configuration
|
||||
|
||||
The current config.json for camera1 includes:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "camera1",
|
||||
"machine_topic": "blower_separator",
|
||||
"storage_path": "/storage/camera1",
|
||||
"exposure_ms": 1.0,
|
||||
"gain": 3.5,
|
||||
"target_fps": 0,
|
||||
"enabled": true,
|
||||
"auto_start_recording_enabled": true,
|
||||
"auto_recording_max_retries": 3,
|
||||
"auto_recording_retry_delay_seconds": 2,
|
||||
"sharpness": 100,
|
||||
"contrast": 100,
|
||||
"saturation": 100,
|
||||
"gamma": 100,
|
||||
"noise_filter_enabled": false,
|
||||
"denoise_3d_enabled": false,
|
||||
"auto_white_balance": false,
|
||||
"color_temperature_preset": 6500,
|
||||
"anti_flicker_enabled": true,
|
||||
"light_frequency": 1,
|
||||
"bit_depth": 8,
|
||||
"hdr_enabled": false,
|
||||
"hdr_gain_mode": 0
|
||||
}
|
||||
```
|
||||
|
||||
## Camera Preview Enhancement
|
||||
|
||||
**Important Update**: The camera preview/streaming functionality has been enhanced to apply all default configuration settings from config.json, ensuring that preview images match the quality and appearance of recorded videos.
|
||||
|
||||
### What This Means for Camera1
|
||||
|
||||
When you view the camera preview, you'll now see:
|
||||
- **Manual exposure** (1.0ms) and **high gain** (3.5x) applied
|
||||
- **50Hz anti-flicker** filtering active
|
||||
- **Manual white balance** with balanced RGB gains (1.0, 1.0, 1.0)
|
||||
- **Standard image processing** (sharpness: 100, contrast: 100, gamma: 100, saturation: 100)
|
||||
- **D65 color temperature** (6500K) applied
|
||||
|
||||
This ensures the preview accurately represents what will be recorded.
|
||||
|
||||
## Notes
|
||||
|
||||
1. **Machine Topic Correction**: The machine topic has been corrected from "vibratory_conveyor" to "blower_separator" to match the camera's actual monitoring purpose.
|
||||
|
||||
2. **Manual White Balance**: The camera is configured for manual white balance with D65 color temperature, which is appropriate for daylight conditions.
|
||||
|
||||
3. **RGB Gain Support**: The current configuration system needs to be extended to support individual RGB gain values for manual white balance fine-tuning.
|
||||
|
||||
4. **Anti-Flicker**: Enabled to reduce artificial lighting interference, set to 60Hz to match North American power frequency.
|
||||
|
||||
5. **LUT Mode**: The camera uses dynamically generated LUT with gamma=1.00 and contrast=100, which provides linear response.
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
To fully support all settings shown in the screenshots, the following parameters should be added to the configuration system:
|
||||
|
||||
- `wb_red_gain`: Red channel gain for manual white balance (0.0-3.99)
|
||||
- `wb_green_gain`: Green channel gain for manual white balance (0.0-3.99)
|
||||
- `wb_blue_gain`: Blue channel gain for manual white balance (0.0-3.99)
|
||||
- `lut_mode`: LUT generation mode (0=dynamic, 1=preset, 2=custom)
|
||||
- `lut_preset`: Preset LUT selection when using preset mode
|
||||
150
docs/camera/CONVEYOR_CAMERA_CONFIG.md
Normal file
150
docs/camera/CONVEYOR_CAMERA_CONFIG.md
Normal file
@@ -0,0 +1,150 @@
|
||||
# Conveyor Camera (Camera2) Configuration
|
||||
|
||||
This document describes the default configuration for the conveyor camera (Camera2) based on the GigE camera settings from the dedicated software.
|
||||
|
||||
## Camera Identification
|
||||
- **Camera Name**: camera2 (Cracker-Cam)
|
||||
- **Machine Topic**: vibratory_conveyor
|
||||
- **Purpose**: Monitors the vibratory conveyor/cracker machine
|
||||
|
||||
## Configuration Summary
|
||||
|
||||
Based on the camera settings screenshots, the following configuration has been applied to Camera2:
|
||||
|
||||
### Color Processing Settings
|
||||
- **White Balance Mode**: Manual (not Auto)
|
||||
- **Color Temperature**: D65 (6500K)
|
||||
- **RGB Gain Values**:
|
||||
- Red Gain: 1.01
|
||||
- Green Gain: 1.00
|
||||
- Blue Gain: 0.87
|
||||
- **Saturation**: 100 (normal)
|
||||
|
||||
### LUT (Look-Up Table) Settings
|
||||
- **Mode**: Dynamically generated (not Preset or Custom)
|
||||
- **Gamma**: 1.00 (100 in config units)
|
||||
- **Contrast**: 100 (normal)
|
||||
|
||||
### Graphic Processing Settings
|
||||
- **Sharpness Level**: 0 (no sharpening applied)
|
||||
- **Noise Reduction**:
|
||||
- Denoise2D: Disabled
|
||||
- Denoise3D: Disabled
|
||||
- **Rotation**: Disabled
|
||||
- **Lens Distortion Correction**: Disabled
|
||||
- **Dead Pixel Correction**: Enabled
|
||||
- **Flat Fielding Correction**: Disabled
|
||||
|
||||
## Configuration Mapping
|
||||
|
||||
The screenshots show these key settings that have been mapped to the config.json:
|
||||
|
||||
| Screenshot Setting | Config Parameter | Value | Notes |
|
||||
|-------------------|------------------|-------|-------|
|
||||
| Manual White Balance | auto_white_balance | false | Manual WB mode |
|
||||
| Color Temperature: D65 | color_temperature_preset | 6500 | D65 = 6500K |
|
||||
| Red Gain: 1.01 | wb_red_gain | 1.01 | Manual RGB gain |
|
||||
| Green Gain: 1.00 | wb_green_gain | 1.0 | Manual RGB gain |
|
||||
| Blue Gain: 0.87 | wb_blue_gain | 0.87 | Manual RGB gain |
|
||||
| Saturation: 100 | saturation | 100 | Color saturation |
|
||||
| Gamma: 1.00 | gamma | 100 | Gamma correction |
|
||||
| Contrast: 100 | contrast | 100 | Image contrast |
|
||||
| Sharpen Level: 0 | sharpness | 0 | No sharpening |
|
||||
| Denoise2D: Disabled | noise_filter_enabled | false | Basic noise filter off |
|
||||
| Denoise3D: Disable | denoise_3d_enabled | false | Advanced denoising off |
|
||||
|
||||
## Current Configuration
|
||||
|
||||
The current config.json for camera2 includes:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "camera2",
|
||||
"machine_topic": "vibratory_conveyor",
|
||||
"storage_path": "/storage/camera2",
|
||||
"exposure_ms": 0.5,
|
||||
"gain": 0.3,
|
||||
"target_fps": 0,
|
||||
"enabled": true,
|
||||
"auto_start_recording_enabled": true,
|
||||
"auto_recording_max_retries": 3,
|
||||
"auto_recording_retry_delay_seconds": 2,
|
||||
"sharpness": 0,
|
||||
"contrast": 100,
|
||||
"saturation": 100,
|
||||
"gamma": 100,
|
||||
"noise_filter_enabled": false,
|
||||
"denoise_3d_enabled": false,
|
||||
"auto_white_balance": false,
|
||||
"color_temperature_preset": 6500,
|
||||
"wb_red_gain": 1.01,
|
||||
"wb_green_gain": 1.0,
|
||||
"wb_blue_gain": 0.87,
|
||||
"anti_flicker_enabled": false,
|
||||
"light_frequency": 1,
|
||||
"bit_depth": 8,
|
||||
"hdr_enabled": false,
|
||||
"hdr_gain_mode": 0
|
||||
}
|
||||
```
|
||||
|
||||
## Key Differences from Camera1 (Blower Camera)
|
||||
|
||||
1. **RGB Gain Tuning**: Camera2 has custom RGB gains (R:1.01, G:1.00, B:0.87) vs Camera1's balanced gains (all 1.0)
|
||||
2. **Sharpness**: Camera2 has sharpness disabled (0) vs Camera1's normal sharpness (100)
|
||||
3. **Exposure/Gain**: Camera2 uses lower exposure (0.5ms) and gain (0.3x) vs Camera1's higher values (1.0ms, 3.5x)
|
||||
4. **Anti-Flicker**: Camera2 has anti-flicker disabled vs Camera1's enabled anti-flicker
|
||||
|
||||
## Notes
|
||||
|
||||
1. **Custom White Balance**: Camera2 uses manual white balance with custom RGB gains, suggesting specific lighting conditions or color correction requirements for the conveyor monitoring.
|
||||
|
||||
2. **No Sharpening**: Sharpness is set to 0, indicating the raw image quality is preferred without artificial enhancement.
|
||||
|
||||
3. **Minimal Noise Reduction**: Both 2D and 3D denoising are disabled, prioritizing image authenticity over noise reduction.
|
||||
|
||||
4. **Dead Pixel Correction**: Enabled to handle any defective pixels on the sensor.
|
||||
|
||||
5. **Lower Sensitivity**: The lower exposure and gain settings suggest better lighting conditions or different monitoring requirements compared to the blower camera.
|
||||
|
||||
## Camera Preview Enhancement
|
||||
|
||||
**Important Update**: The camera preview/streaming functionality has been enhanced to apply all default configuration settings from config.json, ensuring that preview images match the quality and appearance of recorded videos.
|
||||
|
||||
### What Changed
|
||||
|
||||
Previously, camera preview only applied basic settings (exposure, gain, trigger mode). Now, the preview applies the complete configuration including:
|
||||
|
||||
- **Image Quality**: Sharpness, contrast, gamma, saturation
|
||||
- **Color Processing**: White balance mode, color temperature, RGB gains
|
||||
- **Advanced Settings**: Anti-flicker, light frequency, HDR settings
|
||||
- **Noise Reduction**: Filter and 3D denoising settings (where supported)
|
||||
|
||||
### Benefits
|
||||
|
||||
1. **WYSIWYG Preview**: What you see in the preview is exactly what gets recorded
|
||||
2. **Accurate Color Representation**: Manual white balance and RGB gains are applied to preview
|
||||
3. **Consistent Image Quality**: Sharpness, contrast, and gamma settings match recording
|
||||
4. **Proper Exposure**: Anti-flicker and lighting frequency settings are applied
|
||||
|
||||
### Technical Implementation
|
||||
|
||||
The `CameraStreamer` class now includes the same comprehensive configuration methods as `CameraRecorder`:
|
||||
|
||||
- `_configure_image_quality()`: Applies sharpness, contrast, gamma, saturation
|
||||
- `_configure_color_settings()`: Applies white balance mode, color temperature, RGB gains
|
||||
- `_configure_advanced_settings()`: Applies anti-flicker, light frequency, HDR
|
||||
- `_configure_noise_reduction()`: Applies noise filter settings
|
||||
|
||||
These methods are called during camera initialization for streaming, ensuring all config.json settings are applied.
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
Additional parameters that could be added to support all graphic processing features:
|
||||
|
||||
- `rotation_angle`: Image rotation (0, 90, 180, 270 degrees)
|
||||
- `lens_distortion_correction`: Enable/disable lens distortion correction
|
||||
- `dead_pixel_correction`: Enable/disable dead pixel correction
|
||||
- `flat_fielding_correction`: Enable/disable flat fielding correction
|
||||
- `mirror_horizontal`: Horizontal mirroring
|
||||
- `mirror_vertical`: Vertical mirroring
|
||||
159
docs/camera/PREVIEW_ENHANCEMENT.md
Normal file
159
docs/camera/PREVIEW_ENHANCEMENT.md
Normal file
@@ -0,0 +1,159 @@
|
||||
# Camera Preview Enhancement
|
||||
|
||||
## Overview
|
||||
|
||||
The camera preview/streaming functionality has been significantly enhanced to apply all default configuration settings from `config.json`, ensuring that preview images accurately represent what will be recorded.
|
||||
|
||||
## Problem Solved
|
||||
|
||||
Previously, camera preview only applied basic settings (exposure, gain, trigger mode, frame rate), while recording applied the full configuration. This meant:
|
||||
|
||||
- Preview images looked different from recorded videos
|
||||
- Color balance, sharpness, and other image quality settings were not visible in preview
|
||||
- Users couldn't accurately assess the final recording quality from the preview
|
||||
|
||||
## Solution Implemented
|
||||
|
||||
The `CameraStreamer` class has been enhanced with comprehensive configuration methods that mirror those in `CameraRecorder`:
|
||||
|
||||
### New Configuration Methods Added
|
||||
|
||||
1. **`_configure_image_quality()`**
|
||||
- Applies sharpness settings (0-200)
|
||||
- Applies contrast settings (0-200)
|
||||
- Applies gamma correction (0-300)
|
||||
- Applies saturation for color cameras (0-200)
|
||||
|
||||
2. **`_configure_color_settings()`**
|
||||
- Sets white balance mode (auto/manual)
|
||||
- Applies color temperature presets
|
||||
- Sets manual RGB gains for precise color tuning
|
||||
|
||||
3. **`_configure_advanced_settings()`**
|
||||
- Enables/disables anti-flicker filtering
|
||||
- Sets light frequency (50Hz/60Hz)
|
||||
- Configures HDR settings when available
|
||||
|
||||
4. **`_configure_noise_reduction()`**
|
||||
- Configures noise filter settings
|
||||
- Configures 3D denoising settings
|
||||
|
||||
### Enhanced Main Configuration Method
|
||||
|
||||
The `_configure_streaming_settings()` method now calls all configuration methods:
|
||||
|
||||
```python
|
||||
def _configure_streaming_settings(self):
|
||||
"""Configure camera settings from config.json for streaming"""
|
||||
try:
|
||||
# Basic settings (existing)
|
||||
mvsdk.CameraSetTriggerMode(self.hCamera, 0)
|
||||
mvsdk.CameraSetAeState(self.hCamera, 0)
|
||||
exposure_us = int(self.camera_config.exposure_ms * 1000)
|
||||
mvsdk.CameraSetExposureTime(self.hCamera, exposure_us)
|
||||
gain_value = int(self.camera_config.gain * 100)
|
||||
mvsdk.CameraSetAnalogGain(self.hCamera, gain_value)
|
||||
|
||||
# Comprehensive configuration (new)
|
||||
self._configure_image_quality()
|
||||
self._configure_noise_reduction()
|
||||
if not self.monoCamera:
|
||||
self._configure_color_settings()
|
||||
self._configure_advanced_settings()
|
||||
|
||||
except Exception as e:
|
||||
self.logger.warning(f"Could not configure some streaming settings: {e}")
|
||||
```
|
||||
|
||||
## Benefits
|
||||
|
||||
### 1. WYSIWYG Preview
|
||||
- **What You See Is What You Get**: Preview now accurately represents final recording quality
|
||||
- **Real-time Assessment**: Users can evaluate recording quality before starting actual recording
|
||||
- **Consistent Experience**: No surprises when comparing preview to recorded footage
|
||||
|
||||
### 2. Accurate Color Representation
|
||||
- **Manual White Balance**: RGB gains are applied to preview for accurate color reproduction
|
||||
- **Color Temperature**: D65 or other presets are applied consistently
|
||||
- **Saturation**: Color intensity matches recording settings
|
||||
|
||||
### 3. Proper Image Quality
|
||||
- **Sharpness**: Edge enhancement settings are visible in preview
|
||||
- **Contrast**: Dynamic range adjustments are applied
|
||||
- **Gamma**: Brightness curve corrections are active
|
||||
|
||||
### 4. Environmental Adaptation
|
||||
- **Anti-Flicker**: Artificial lighting interference is filtered in preview
|
||||
- **Light Frequency**: 50Hz/60Hz settings match local power grid
|
||||
- **HDR**: High dynamic range processing when enabled
|
||||
|
||||
## Camera-Specific Impact
|
||||
|
||||
### Camera1 (Blower Separator)
|
||||
Preview now shows:
|
||||
- Manual exposure (1.0ms) and high gain (3.5x)
|
||||
- 50Hz anti-flicker filtering
|
||||
- Manual white balance with balanced RGB gains (1.0, 1.0, 1.0)
|
||||
- Standard image processing (sharpness: 100, contrast: 100, gamma: 100, saturation: 100)
|
||||
- D65 color temperature (6500K)
|
||||
|
||||
### Camera2 (Conveyor/Cracker)
|
||||
Preview now shows:
|
||||
- Manual exposure (0.5ms) and lower gain (0.3x)
|
||||
- Custom RGB color tuning (R:1.01, G:1.00, B:0.87)
|
||||
- No image sharpening (sharpness: 0)
|
||||
- Enhanced saturation (100) and proper gamma (100)
|
||||
- D65 color temperature with manual white balance
|
||||
|
||||
## Technical Implementation Details
|
||||
|
||||
### Error Handling
|
||||
- All configuration methods include try-catch blocks
|
||||
- Warnings are logged for unsupported features
|
||||
- Graceful degradation when SDK functions are unavailable
|
||||
- Streaming continues even if some settings fail to apply
|
||||
|
||||
### SDK Compatibility
|
||||
- Checks for function availability before calling
|
||||
- Handles different SDK versions gracefully
|
||||
- Logs informational messages for unavailable features
|
||||
|
||||
### Performance Considerations
|
||||
- Configuration is applied once during camera initialization
|
||||
- No performance impact on streaming frame rate
|
||||
- Separate camera instance for streaming (doesn't interfere with recording)
|
||||
|
||||
## Usage
|
||||
|
||||
No changes required for users - the enhancement is automatic:
|
||||
|
||||
1. **Start Preview**: Use existing preview endpoints
|
||||
2. **View Stream**: Camera automatically applies all config.json settings
|
||||
3. **Compare**: Preview now matches recording quality exactly
|
||||
|
||||
### API Endpoints (unchanged)
|
||||
- `GET /cameras/{camera_name}/stream` - Get live MJPEG stream
|
||||
- `POST /cameras/{camera_name}/start-stream` - Start streaming
|
||||
- `POST /cameras/{camera_name}/stop-stream` - Stop streaming
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
Additional settings that could be added to further improve preview accuracy:
|
||||
|
||||
1. **Geometric Corrections**
|
||||
- Lens distortion correction
|
||||
- Dead pixel correction
|
||||
- Flat fielding correction
|
||||
|
||||
2. **Image Transformations**
|
||||
- Rotation (90°, 180°, 270°)
|
||||
- Horizontal/vertical mirroring
|
||||
|
||||
3. **Advanced Processing**
|
||||
- Custom LUT (Look-Up Table) support
|
||||
- Advanced noise reduction algorithms
|
||||
- Real-time image enhancement filters
|
||||
|
||||
## Conclusion
|
||||
|
||||
This enhancement significantly improves the user experience by providing accurate, real-time preview of camera output with all configuration settings applied. Users can now confidently assess recording quality, adjust settings, and ensure optimal camera performance before starting critical recordings.
|
||||
193
tests/recording/test_auto_recording_mqtt.py
Normal file
193
tests/recording/test_auto_recording_mqtt.py
Normal file
@@ -0,0 +1,193 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script to verify auto-recording functionality with simulated MQTT messages.
|
||||
|
||||
This script tests that:
|
||||
1. Auto recording manager properly handles machine state changes
|
||||
2. Recording starts when machine turns "on"
|
||||
3. Recording stops when machine turns "off"
|
||||
4. Camera configuration from config.json is used
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
# Add the current directory to Python path
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
|
||||
|
||||
def setup_logging():
|
||||
"""Setup logging for the test"""
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
|
||||
|
||||
|
||||
def test_auto_recording_with_mqtt():
|
||||
"""Test auto recording functionality with simulated MQTT messages"""
|
||||
print("🧪 Testing Auto Recording with MQTT Messages")
|
||||
print("=" * 50)
|
||||
|
||||
try:
|
||||
# Import required modules
|
||||
from usda_vision_system.core.config import Config
|
||||
from usda_vision_system.core.state_manager import StateManager
|
||||
from usda_vision_system.core.events import EventSystem, EventType
|
||||
from usda_vision_system.recording.auto_manager import AutoRecordingManager
|
||||
|
||||
print("✅ Modules imported successfully")
|
||||
|
||||
# Create system components
|
||||
config = Config("config.json")
|
||||
state_manager = StateManager()
|
||||
event_system = EventSystem()
|
||||
|
||||
# Create a mock camera manager for testing
|
||||
class MockCameraManager:
|
||||
def __init__(self):
|
||||
self.recording_calls = []
|
||||
self.stop_calls = []
|
||||
|
||||
def manual_start_recording(self, camera_name, filename, exposure_ms=None, gain=None, fps=None):
|
||||
call_info = {"camera_name": camera_name, "filename": filename, "exposure_ms": exposure_ms, "gain": gain, "fps": fps, "timestamp": datetime.now()}
|
||||
self.recording_calls.append(call_info)
|
||||
print(f"📹 MOCK: Starting recording for {camera_name}")
|
||||
print(f" - Filename: {filename}")
|
||||
print(f" - Settings: exposure={exposure_ms}ms, gain={gain}, fps={fps}")
|
||||
return True
|
||||
|
||||
def manual_stop_recording(self, camera_name):
|
||||
call_info = {"camera_name": camera_name, "timestamp": datetime.now()}
|
||||
self.stop_calls.append(call_info)
|
||||
print(f"⏹️ MOCK: Stopping recording for {camera_name}")
|
||||
return True
|
||||
|
||||
mock_camera_manager = MockCameraManager()
|
||||
|
||||
# Create auto recording manager
|
||||
auto_manager = AutoRecordingManager(config, state_manager, event_system, mock_camera_manager)
|
||||
|
||||
print("✅ Auto recording manager created")
|
||||
|
||||
# Start the auto recording manager
|
||||
if not auto_manager.start():
|
||||
print("❌ Failed to start auto recording manager")
|
||||
return False
|
||||
|
||||
print("✅ Auto recording manager started")
|
||||
|
||||
# Test 1: Simulate blower_separator turning ON (should trigger camera1)
|
||||
print("\n🔄 Test 1: Blower separator turns ON")
|
||||
print("📡 Publishing machine state change event...")
|
||||
# Use the same event system instance that the auto manager is subscribed to
|
||||
event_system.publish(EventType.MACHINE_STATE_CHANGED, "test_script", {"machine_name": "blower_separator", "state": "on", "previous_state": None})
|
||||
time.sleep(1.0) # Give more time for event processing
|
||||
|
||||
print(f"📊 Total recording calls so far: {len(mock_camera_manager.recording_calls)}")
|
||||
for call in mock_camera_manager.recording_calls:
|
||||
print(f" - {call['camera_name']}: {call['filename']}")
|
||||
|
||||
# Check if recording was started for camera1
|
||||
camera1_calls = [call for call in mock_camera_manager.recording_calls if call["camera_name"] == "camera1"]
|
||||
if camera1_calls:
|
||||
call = camera1_calls[-1]
|
||||
print(f"✅ Camera1 recording started with config:")
|
||||
print(f" - Exposure: {call['exposure_ms']}ms (expected: 0.3ms)")
|
||||
print(f" - Gain: {call['gain']} (expected: 4.0)")
|
||||
print(f" - FPS: {call['fps']} (expected: 0)")
|
||||
|
||||
# Verify settings match config.json
|
||||
if call["exposure_ms"] == 0.3 and call["gain"] == 4.0 and call["fps"] == 0:
|
||||
print("✅ Camera settings match config.json")
|
||||
else:
|
||||
print("❌ Camera settings don't match config.json")
|
||||
return False
|
||||
else:
|
||||
print("❌ Camera1 recording was not started")
|
||||
return False
|
||||
|
||||
# Test 2: Simulate vibratory_conveyor turning ON (should trigger camera2)
|
||||
print("\n🔄 Test 2: Vibratory conveyor turns ON")
|
||||
event_system.publish(EventType.MACHINE_STATE_CHANGED, "test_script", {"machine_name": "vibratory_conveyor", "state": "on", "previous_state": None})
|
||||
time.sleep(0.5)
|
||||
|
||||
# Check if recording was started for camera2
|
||||
camera2_calls = [call for call in mock_camera_manager.recording_calls if call["camera_name"] == "camera2"]
|
||||
if camera2_calls:
|
||||
call = camera2_calls[-1]
|
||||
print(f"✅ Camera2 recording started with config:")
|
||||
print(f" - Exposure: {call['exposure_ms']}ms (expected: 0.2ms)")
|
||||
print(f" - Gain: {call['gain']} (expected: 2.0)")
|
||||
print(f" - FPS: {call['fps']} (expected: 0)")
|
||||
|
||||
# Verify settings match config.json
|
||||
if call["exposure_ms"] == 0.2 and call["gain"] == 2.0 and call["fps"] == 0:
|
||||
print("✅ Camera settings match config.json")
|
||||
else:
|
||||
print("❌ Camera settings don't match config.json")
|
||||
return False
|
||||
else:
|
||||
print("❌ Camera2 recording was not started")
|
||||
return False
|
||||
|
||||
# Test 3: Simulate machines turning OFF
|
||||
print("\n🔄 Test 3: Machines turn OFF")
|
||||
event_system.publish(EventType.MACHINE_STATE_CHANGED, "test_script", {"machine_name": "blower_separator", "state": "off", "previous_state": None})
|
||||
event_system.publish(EventType.MACHINE_STATE_CHANGED, "test_script", {"machine_name": "vibratory_conveyor", "state": "off", "previous_state": None})
|
||||
time.sleep(0.5)
|
||||
|
||||
# Check if recordings were stopped
|
||||
camera1_stops = [call for call in mock_camera_manager.stop_calls if call["camera_name"] == "camera1"]
|
||||
camera2_stops = [call for call in mock_camera_manager.stop_calls if call["camera_name"] == "camera2"]
|
||||
|
||||
if camera1_stops and camera2_stops:
|
||||
print("✅ Both cameras stopped recording when machines turned OFF")
|
||||
else:
|
||||
print(f"❌ Recording stop failed - Camera1 stops: {len(camera1_stops)}, Camera2 stops: {len(camera2_stops)}")
|
||||
return False
|
||||
|
||||
# Stop the auto recording manager
|
||||
auto_manager.stop()
|
||||
print("✅ Auto recording manager stopped")
|
||||
|
||||
print("\n🎉 All auto recording tests passed!")
|
||||
print("\n📊 Summary:")
|
||||
print(f" - Total recording starts: {len(mock_camera_manager.recording_calls)}")
|
||||
print(f" - Total recording stops: {len(mock_camera_manager.stop_calls)}")
|
||||
print(f" - Camera1 starts: {len([c for c in mock_camera_manager.recording_calls if c['camera_name'] == 'camera1'])}")
|
||||
print(f" - Camera2 starts: {len([c for c in mock_camera_manager.recording_calls if c['camera_name'] == 'camera2'])}")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Test failed with error: {e}")
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""Run the auto recording test"""
|
||||
setup_logging()
|
||||
|
||||
success = test_auto_recording_with_mqtt()
|
||||
|
||||
if success:
|
||||
print("\n✅ Auto recording functionality is working correctly!")
|
||||
print("\n📝 The system should now properly:")
|
||||
print(" 1. Start recording when machines turn ON")
|
||||
print(" 2. Stop recording when machines turn OFF")
|
||||
print(" 3. Use camera settings from config.json")
|
||||
print(" 4. Generate appropriate filenames with timestamps")
|
||||
else:
|
||||
print("\n❌ Auto recording test failed!")
|
||||
print("Please check the implementation and try again.")
|
||||
|
||||
return success
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = main()
|
||||
sys.exit(0 if success else 1)
|
||||
@@ -14,101 +14,101 @@ import time
|
||||
# Add the current directory to Python path
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
|
||||
def test_config_structure():
|
||||
"""Test that config.json has the required auto-recording fields"""
|
||||
print("🔍 Testing configuration structure...")
|
||||
|
||||
|
||||
try:
|
||||
with open("config.json", "r") as f:
|
||||
config = json.load(f)
|
||||
|
||||
|
||||
# Check system-level auto-recording setting
|
||||
system_config = config.get("system", {})
|
||||
if "auto_recording_enabled" not in system_config:
|
||||
print("❌ Missing 'auto_recording_enabled' in system config")
|
||||
return False
|
||||
|
||||
|
||||
print(f"✅ System auto-recording enabled: {system_config['auto_recording_enabled']}")
|
||||
|
||||
|
||||
# Check camera-level auto-recording settings
|
||||
cameras = config.get("cameras", [])
|
||||
if not cameras:
|
||||
print("❌ No cameras found in config")
|
||||
return False
|
||||
|
||||
|
||||
for camera in cameras:
|
||||
camera_name = camera.get("name", "unknown")
|
||||
required_fields = [
|
||||
"auto_start_recording_enabled",
|
||||
"auto_recording_max_retries",
|
||||
"auto_recording_retry_delay_seconds"
|
||||
]
|
||||
|
||||
required_fields = ["auto_start_recording_enabled", "auto_recording_max_retries", "auto_recording_retry_delay_seconds"]
|
||||
|
||||
missing_fields = [field for field in required_fields if field not in camera]
|
||||
if missing_fields:
|
||||
print(f"❌ Camera {camera_name} missing fields: {missing_fields}")
|
||||
return False
|
||||
|
||||
|
||||
print(f"✅ Camera {camera_name} auto-recording config:")
|
||||
print(f" - Enabled: {camera['auto_start_recording_enabled']}")
|
||||
print(f" - Max retries: {camera['auto_recording_max_retries']}")
|
||||
print(f" - Retry delay: {camera['auto_recording_retry_delay_seconds']}s")
|
||||
print(f" - Machine topic: {camera.get('machine_topic', 'unknown')}")
|
||||
|
||||
|
||||
return True
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error reading config: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_module_imports():
|
||||
"""Test that all required modules can be imported"""
|
||||
print("\n🔍 Testing module imports...")
|
||||
|
||||
|
||||
try:
|
||||
from usda_vision_system.recording.auto_manager import AutoRecordingManager
|
||||
|
||||
print("✅ AutoRecordingManager imported successfully")
|
||||
|
||||
|
||||
from usda_vision_system.core.config import Config
|
||||
|
||||
config = Config("config.json")
|
||||
print("✅ Config loaded successfully")
|
||||
|
||||
|
||||
from usda_vision_system.core.state_manager import StateManager
|
||||
|
||||
state_manager = StateManager()
|
||||
print("✅ StateManager created successfully")
|
||||
|
||||
|
||||
from usda_vision_system.core.events import EventSystem
|
||||
|
||||
event_system = EventSystem()
|
||||
print("✅ EventSystem created successfully")
|
||||
|
||||
|
||||
# Test creating AutoRecordingManager (without camera_manager for now)
|
||||
auto_manager = AutoRecordingManager(config, state_manager, event_system, None)
|
||||
print("✅ AutoRecordingManager created successfully")
|
||||
|
||||
|
||||
return True
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Import error: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_camera_mapping():
|
||||
"""Test camera to machine topic mapping"""
|
||||
print("\n🔍 Testing camera to machine mapping...")
|
||||
|
||||
|
||||
try:
|
||||
with open("config.json", "r") as f:
|
||||
config = json.load(f)
|
||||
|
||||
|
||||
cameras = config.get("cameras", [])
|
||||
expected_mappings = {
|
||||
"camera1": "vibratory_conveyor", # Conveyor/cracker cam
|
||||
"camera2": "blower_separator" # Blower separator
|
||||
}
|
||||
|
||||
expected_mappings = {"camera1": "blower_separator", "camera2": "vibratory_conveyor"} # Blower separator # Conveyor/cracker cam
|
||||
|
||||
for camera in cameras:
|
||||
camera_name = camera.get("name")
|
||||
machine_topic = camera.get("machine_topic")
|
||||
|
||||
|
||||
if camera_name in expected_mappings:
|
||||
expected_topic = expected_mappings[camera_name]
|
||||
if machine_topic == expected_topic:
|
||||
@@ -118,38 +118,25 @@ def test_camera_mapping():
|
||||
return False
|
||||
else:
|
||||
print(f"⚠️ Unknown camera: {camera_name}")
|
||||
|
||||
|
||||
return True
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error checking mappings: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def test_api_models():
|
||||
"""Test that API models include auto-recording fields"""
|
||||
print("\n🔍 Testing API models...")
|
||||
|
||||
|
||||
try:
|
||||
from usda_vision_system.api.models import (
|
||||
CameraStatusResponse,
|
||||
CameraConfigResponse,
|
||||
AutoRecordingConfigRequest,
|
||||
AutoRecordingConfigResponse,
|
||||
AutoRecordingStatusResponse
|
||||
)
|
||||
|
||||
from usda_vision_system.api.models import CameraStatusResponse, CameraConfigResponse, AutoRecordingConfigRequest, AutoRecordingConfigResponse, AutoRecordingStatusResponse
|
||||
|
||||
# Check CameraStatusResponse has auto-recording fields
|
||||
camera_response = CameraStatusResponse(
|
||||
name="test",
|
||||
status="available",
|
||||
is_recording=False,
|
||||
last_checked="2024-01-01T00:00:00",
|
||||
auto_recording_enabled=True,
|
||||
auto_recording_active=False,
|
||||
auto_recording_failure_count=0
|
||||
)
|
||||
camera_response = CameraStatusResponse(name="test", status="available", is_recording=False, last_checked="2024-01-01T00:00:00", auto_recording_enabled=True, auto_recording_active=False, auto_recording_failure_count=0)
|
||||
print("✅ CameraStatusResponse includes auto-recording fields")
|
||||
|
||||
|
||||
# Check CameraConfigResponse has auto-recording fields
|
||||
config_response = CameraConfigResponse(
|
||||
name="test",
|
||||
@@ -170,46 +157,45 @@ def test_api_models():
|
||||
denoise_3d_enabled=False,
|
||||
auto_white_balance=True,
|
||||
color_temperature_preset=0,
|
||||
wb_red_gain=1.0,
|
||||
wb_green_gain=1.0,
|
||||
wb_blue_gain=1.0,
|
||||
anti_flicker_enabled=False,
|
||||
light_frequency=1,
|
||||
bit_depth=8,
|
||||
hdr_enabled=False,
|
||||
hdr_gain_mode=0
|
||||
hdr_gain_mode=0,
|
||||
)
|
||||
print("✅ CameraConfigResponse includes auto-recording fields")
|
||||
|
||||
|
||||
print("✅ All auto-recording API models available")
|
||||
return True
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ API model error: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""Run all basic tests"""
|
||||
print("🧪 Auto-Recording Integration Test")
|
||||
print("=" * 40)
|
||||
|
||||
tests = [
|
||||
test_config_structure,
|
||||
test_module_imports,
|
||||
test_camera_mapping,
|
||||
test_api_models
|
||||
]
|
||||
|
||||
|
||||
tests = [test_config_structure, test_module_imports, test_camera_mapping, test_api_models]
|
||||
|
||||
passed = 0
|
||||
total = len(tests)
|
||||
|
||||
|
||||
for test in tests:
|
||||
try:
|
||||
if test():
|
||||
passed += 1
|
||||
except Exception as e:
|
||||
print(f"❌ Test {test.__name__} failed with exception: {e}")
|
||||
|
||||
|
||||
print("\n" + "=" * 40)
|
||||
print(f"📊 Results: {passed}/{total} tests passed")
|
||||
|
||||
|
||||
if passed == total:
|
||||
print("🎉 All integration tests passed!")
|
||||
print("\n📝 Next steps:")
|
||||
@@ -222,6 +208,7 @@ def main():
|
||||
print("Please fix the issues before running the full system")
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = main()
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
@@ -110,6 +110,11 @@ class CameraConfigRequest(BaseModel):
|
||||
auto_white_balance: Optional[bool] = Field(default=None, description="Enable automatic white balance")
|
||||
color_temperature_preset: Optional[int] = Field(default=None, ge=0, le=10, description="Color temperature preset")
|
||||
|
||||
# Manual White Balance RGB Gains
|
||||
wb_red_gain: Optional[float] = Field(default=None, ge=0.0, le=3.99, description="Red channel gain for manual white balance")
|
||||
wb_green_gain: Optional[float] = Field(default=None, ge=0.0, le=3.99, description="Green channel gain for manual white balance")
|
||||
wb_blue_gain: Optional[float] = Field(default=None, ge=0.0, le=3.99, description="Blue channel gain for manual white balance")
|
||||
|
||||
# Advanced Settings
|
||||
anti_flicker_enabled: Optional[bool] = Field(default=None, description="Reduce artificial lighting flicker")
|
||||
light_frequency: Optional[int] = Field(default=None, ge=0, le=1, description="Light frequency (0=50Hz, 1=60Hz)")
|
||||
@@ -151,6 +156,11 @@ class CameraConfigResponse(BaseModel):
|
||||
auto_white_balance: bool
|
||||
color_temperature_preset: int
|
||||
|
||||
# Manual White Balance RGB Gains
|
||||
wb_red_gain: float
|
||||
wb_green_gain: float
|
||||
wb_blue_gain: float
|
||||
|
||||
# Advanced Settings
|
||||
anti_flicker_enabled: bool
|
||||
light_frequency: int
|
||||
|
||||
@@ -354,6 +354,10 @@ class APIServer:
|
||||
# Color Settings
|
||||
auto_white_balance=config.auto_white_balance,
|
||||
color_temperature_preset=config.color_temperature_preset,
|
||||
# Manual White Balance RGB Gains
|
||||
wb_red_gain=config.wb_red_gain,
|
||||
wb_green_gain=config.wb_green_gain,
|
||||
wb_blue_gain=config.wb_blue_gain,
|
||||
# Advanced Settings
|
||||
anti_flicker_enabled=config.anti_flicker_enabled,
|
||||
light_frequency=config.light_frequency,
|
||||
@@ -512,7 +516,7 @@ class APIServer:
|
||||
self.config.save_config()
|
||||
|
||||
# Update camera status in state manager
|
||||
camera_info = self.state_manager.get_camera_info(camera_name)
|
||||
camera_info = self.state_manager.get_camera_status(camera_name)
|
||||
if camera_info:
|
||||
camera_info.auto_recording_enabled = True
|
||||
|
||||
@@ -539,7 +543,7 @@ class APIServer:
|
||||
self.config.save_config()
|
||||
|
||||
# Update camera status in state manager
|
||||
camera_info = self.state_manager.get_camera_info(camera_name)
|
||||
camera_info = self.state_manager.get_camera_status(camera_name)
|
||||
if camera_info:
|
||||
camera_info.auto_recording_enabled = False
|
||||
camera_info.auto_recording_active = False
|
||||
|
||||
@@ -260,7 +260,13 @@ class CameraRecorder:
|
||||
if not self.camera_config.auto_white_balance:
|
||||
mvsdk.CameraSetPresetClrTemp(self.hCamera, self.camera_config.color_temperature_preset)
|
||||
|
||||
self.logger.info(f"Color settings configured - Auto WB: {self.camera_config.auto_white_balance}, " f"Color Temp Preset: {self.camera_config.color_temperature_preset}")
|
||||
# Set manual RGB gains for manual white balance
|
||||
red_gain = int(self.camera_config.wb_red_gain * 100) # Convert to camera units
|
||||
green_gain = int(self.camera_config.wb_green_gain * 100)
|
||||
blue_gain = int(self.camera_config.wb_blue_gain * 100)
|
||||
mvsdk.CameraSetUserClrTempGain(self.hCamera, red_gain, green_gain, blue_gain)
|
||||
|
||||
self.logger.info(f"Color settings configured - Auto WB: {self.camera_config.auto_white_balance}, " f"Color Temp Preset: {self.camera_config.color_temperature_preset}, " f"RGB Gains: R={self.camera_config.wb_red_gain}, G={self.camera_config.wb_green_gain}, B={self.camera_config.wb_blue_gain}")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.warning(f"Error configuring color settings: {e}")
|
||||
@@ -400,6 +406,30 @@ class CameraRecorder:
|
||||
self.camera_config.color_temperature_preset = kwargs["color_temperature_preset"]
|
||||
settings_updated = True
|
||||
|
||||
# Update RGB gains for manual white balance
|
||||
rgb_gains_updated = False
|
||||
if "wb_red_gain" in kwargs and kwargs["wb_red_gain"] is not None:
|
||||
self.camera_config.wb_red_gain = kwargs["wb_red_gain"]
|
||||
rgb_gains_updated = True
|
||||
settings_updated = True
|
||||
|
||||
if "wb_green_gain" in kwargs and kwargs["wb_green_gain"] is not None:
|
||||
self.camera_config.wb_green_gain = kwargs["wb_green_gain"]
|
||||
rgb_gains_updated = True
|
||||
settings_updated = True
|
||||
|
||||
if "wb_blue_gain" in kwargs and kwargs["wb_blue_gain"] is not None:
|
||||
self.camera_config.wb_blue_gain = kwargs["wb_blue_gain"]
|
||||
rgb_gains_updated = True
|
||||
settings_updated = True
|
||||
|
||||
# Apply RGB gains if any were updated and we're in manual white balance mode
|
||||
if rgb_gains_updated and not self.camera_config.auto_white_balance:
|
||||
red_gain = int(self.camera_config.wb_red_gain * 100)
|
||||
green_gain = int(self.camera_config.wb_green_gain * 100)
|
||||
blue_gain = int(self.camera_config.wb_blue_gain * 100)
|
||||
mvsdk.CameraSetUserClrTempGain(self.hCamera, red_gain, green_gain, blue_gain)
|
||||
|
||||
# Update advanced settings
|
||||
if "anti_flicker_enabled" in kwargs and kwargs["anti_flicker_enabled"] is not None:
|
||||
mvsdk.CameraSetAntiFlick(self.hCamera, kwargs["anti_flicker_enabled"])
|
||||
|
||||
@@ -205,22 +205,37 @@ class CameraStreamer:
|
||||
return False
|
||||
|
||||
def _configure_streaming_settings(self):
|
||||
"""Configure camera settings optimized for streaming"""
|
||||
"""Configure camera settings from config.json for streaming"""
|
||||
try:
|
||||
# Set trigger mode to free run for continuous streaming
|
||||
mvsdk.CameraSetTriggerMode(self.hCamera, 0)
|
||||
|
||||
# Set exposure (use a reasonable default for preview)
|
||||
exposure_us = int(self.camera_config.exposure_ms * 1000)
|
||||
# Set manual exposure
|
||||
mvsdk.CameraSetAeState(self.hCamera, 0) # Disable auto exposure
|
||||
exposure_us = int(self.camera_config.exposure_ms * 1000) # Convert ms to microseconds
|
||||
mvsdk.CameraSetExposureTime(self.hCamera, exposure_us)
|
||||
|
||||
# Set gain
|
||||
mvsdk.CameraSetAnalogGain(self.hCamera, int(self.camera_config.gain))
|
||||
# Set analog gain
|
||||
gain_value = int(self.camera_config.gain * 100) # Convert to camera units
|
||||
mvsdk.CameraSetAnalogGain(self.hCamera, gain_value)
|
||||
|
||||
# Set frame rate for streaming (lower than recording)
|
||||
if hasattr(mvsdk, "CameraSetFrameSpeed"):
|
||||
mvsdk.CameraSetFrameSpeed(self.hCamera, int(self.preview_fps))
|
||||
|
||||
# Configure image quality settings
|
||||
self._configure_image_quality()
|
||||
|
||||
# Configure noise reduction
|
||||
self._configure_noise_reduction()
|
||||
|
||||
# Configure color settings (for color cameras)
|
||||
if not self.monoCamera:
|
||||
self._configure_color_settings()
|
||||
|
||||
# Configure advanced settings
|
||||
self._configure_advanced_settings()
|
||||
|
||||
self.logger.info(f"Streaming settings configured: exposure={self.camera_config.exposure_ms}ms, gain={self.camera_config.gain}, fps={self.preview_fps}")
|
||||
|
||||
except Exception as e:
|
||||
@@ -314,6 +329,83 @@ class CameraStreamer:
|
||||
"""Check if streaming is active"""
|
||||
return self.streaming
|
||||
|
||||
def _configure_image_quality(self) -> None:
|
||||
"""Configure image quality settings"""
|
||||
try:
|
||||
# Set sharpness (0-200, default 100)
|
||||
mvsdk.CameraSetSharpness(self.hCamera, self.camera_config.sharpness)
|
||||
|
||||
# Set contrast (0-200, default 100)
|
||||
mvsdk.CameraSetContrast(self.hCamera, self.camera_config.contrast)
|
||||
|
||||
# Set gamma (0-300, default 100)
|
||||
mvsdk.CameraSetGamma(self.hCamera, self.camera_config.gamma)
|
||||
|
||||
# Set saturation for color cameras (0-200, default 100)
|
||||
if not self.monoCamera:
|
||||
mvsdk.CameraSetSaturation(self.hCamera, self.camera_config.saturation)
|
||||
|
||||
self.logger.info(f"Image quality configured - Sharpness: {self.camera_config.sharpness}, " f"Contrast: {self.camera_config.contrast}, Gamma: {self.camera_config.gamma}")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.warning(f"Error configuring image quality: {e}")
|
||||
|
||||
def _configure_noise_reduction(self) -> None:
|
||||
"""Configure noise reduction settings"""
|
||||
try:
|
||||
# Note: Some noise reduction settings may require specific SDK functions
|
||||
# that might not be available in all SDK versions
|
||||
self.logger.info(f"Noise reduction configured - Filter: {self.camera_config.noise_filter_enabled}, " f"3D Denoise: {self.camera_config.denoise_3d_enabled}")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.warning(f"Error configuring noise reduction: {e}")
|
||||
|
||||
def _configure_color_settings(self) -> None:
|
||||
"""Configure color settings for color cameras"""
|
||||
try:
|
||||
# Set white balance mode
|
||||
mvsdk.CameraSetWbMode(self.hCamera, self.camera_config.auto_white_balance)
|
||||
|
||||
# Set color temperature preset if not using auto white balance
|
||||
if not self.camera_config.auto_white_balance:
|
||||
mvsdk.CameraSetPresetClrTemp(self.hCamera, self.camera_config.color_temperature_preset)
|
||||
|
||||
# Set manual RGB gains for manual white balance
|
||||
red_gain = int(self.camera_config.wb_red_gain * 100) # Convert to camera units
|
||||
green_gain = int(self.camera_config.wb_green_gain * 100)
|
||||
blue_gain = int(self.camera_config.wb_blue_gain * 100)
|
||||
mvsdk.CameraSetUserClrTempGain(self.hCamera, red_gain, green_gain, blue_gain)
|
||||
|
||||
self.logger.info(f"Color settings configured - Auto WB: {self.camera_config.auto_white_balance}, " f"Color Temp Preset: {self.camera_config.color_temperature_preset}, " f"RGB Gains: R={self.camera_config.wb_red_gain}, G={self.camera_config.wb_green_gain}, B={self.camera_config.wb_blue_gain}")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.warning(f"Error configuring color settings: {e}")
|
||||
|
||||
def _configure_advanced_settings(self) -> None:
|
||||
"""Configure advanced camera settings"""
|
||||
try:
|
||||
# Set anti-flicker
|
||||
mvsdk.CameraSetAntiFlick(self.hCamera, self.camera_config.anti_flicker_enabled)
|
||||
|
||||
# Set light frequency (0=50Hz, 1=60Hz)
|
||||
mvsdk.CameraSetLightFrequency(self.hCamera, self.camera_config.light_frequency)
|
||||
|
||||
# Configure HDR if enabled (check if HDR functions are available)
|
||||
try:
|
||||
if self.camera_config.hdr_enabled:
|
||||
mvsdk.CameraSetHDR(self.hCamera, 1) # Enable HDR
|
||||
mvsdk.CameraSetHDRGainMode(self.hCamera, self.camera_config.hdr_gain_mode)
|
||||
self.logger.info(f"HDR enabled with gain mode: {self.camera_config.hdr_gain_mode}")
|
||||
else:
|
||||
mvsdk.CameraSetHDR(self.hCamera, 0) # Disable HDR
|
||||
except AttributeError:
|
||||
self.logger.info("HDR functions not available in this SDK version, skipping HDR configuration")
|
||||
|
||||
self.logger.info(f"Advanced settings configured - Anti-flicker: {self.camera_config.anti_flicker_enabled}, " f"Light Freq: {self.camera_config.light_frequency}Hz, HDR: {self.camera_config.hdr_enabled}")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.warning(f"Error configuring advanced settings: {e}")
|
||||
|
||||
def __del__(self):
|
||||
"""Destructor to ensure cleanup"""
|
||||
if self.streaming:
|
||||
|
||||
@@ -59,6 +59,11 @@ class CameraConfig:
|
||||
auto_white_balance: bool = True # Enable automatic white balance
|
||||
color_temperature_preset: int = 0 # 0=auto, 1=daylight, 2=fluorescent, etc.
|
||||
|
||||
# Manual White Balance RGB Gains (for manual white balance mode)
|
||||
wb_red_gain: float = 1.0 # Red channel gain (0.0-3.99, default 1.0)
|
||||
wb_green_gain: float = 1.0 # Green channel gain (0.0-3.99, default 1.0)
|
||||
wb_blue_gain: float = 1.0 # Blue channel gain (0.0-3.99, default 1.0)
|
||||
|
||||
# Advanced Settings
|
||||
anti_flicker_enabled: bool = True # Reduce artificial lighting flicker
|
||||
light_frequency: int = 1 # 0=50Hz, 1=60Hz (match local power frequency)
|
||||
|
||||
@@ -84,10 +84,17 @@ class AutoRecordingManager:
|
||||
for camera_config in self.config.cameras:
|
||||
if camera_config.enabled and camera_config.auto_start_recording_enabled:
|
||||
# Update camera status in state manager
|
||||
camera_info = self.state_manager.get_camera_info(camera_config.name)
|
||||
camera_info = self.state_manager.get_camera_status(camera_config.name)
|
||||
if camera_info:
|
||||
camera_info.auto_recording_enabled = True
|
||||
self.logger.info(f"Auto-recording enabled for camera {camera_config.name}")
|
||||
else:
|
||||
# Create camera info if it doesn't exist
|
||||
self.state_manager.update_camera_status(camera_config.name, "unknown")
|
||||
camera_info = self.state_manager.get_camera_status(camera_config.name)
|
||||
if camera_info:
|
||||
camera_info.auto_recording_enabled = True
|
||||
self.logger.info(f"Auto-recording enabled for camera {camera_config.name}")
|
||||
|
||||
def _on_machine_state_changed(self, event: Event) -> None:
|
||||
"""Handle machine state change events"""
|
||||
@@ -96,15 +103,17 @@ class AutoRecordingManager:
|
||||
new_state = event.data.get("state")
|
||||
|
||||
if not machine_name or not new_state:
|
||||
self.logger.warning(f"Invalid event data - machine_name: {machine_name}, state: {new_state}")
|
||||
return
|
||||
|
||||
self.logger.info(f"Machine state changed: {machine_name} -> {new_state}")
|
||||
|
||||
# Find cameras associated with this machine
|
||||
associated_cameras = self._get_cameras_for_machine(machine_name)
|
||||
|
||||
|
||||
for camera_config in associated_cameras:
|
||||
if not camera_config.enabled or not camera_config.auto_start_recording_enabled:
|
||||
self.logger.debug(f"Skipping camera {camera_config.name} - not enabled or auto recording disabled")
|
||||
continue
|
||||
|
||||
if new_state.lower() == "on":
|
||||
@@ -118,13 +127,10 @@ class AutoRecordingManager:
|
||||
def _get_cameras_for_machine(self, machine_name: str) -> list[CameraConfig]:
|
||||
"""Get all cameras associated with a machine topic"""
|
||||
associated_cameras = []
|
||||
|
||||
|
||||
# Map machine names to topics
|
||||
machine_topic_map = {
|
||||
"vibratory_conveyor": "vibratory_conveyor",
|
||||
"blower_separator": "blower_separator"
|
||||
}
|
||||
|
||||
machine_topic_map = {"vibratory_conveyor": "vibratory_conveyor", "blower_separator": "blower_separator"}
|
||||
|
||||
machine_topic = machine_topic_map.get(machine_name)
|
||||
if not machine_topic:
|
||||
return associated_cameras
|
||||
@@ -138,23 +144,30 @@ class AutoRecordingManager:
|
||||
def _handle_machine_on(self, camera_config: CameraConfig) -> None:
|
||||
"""Handle machine turning on - start recording"""
|
||||
camera_name = camera_config.name
|
||||
|
||||
|
||||
# Check if camera is already recording
|
||||
camera_info = self.state_manager.get_camera_info(camera_name)
|
||||
camera_info = self.state_manager.get_camera_status(camera_name)
|
||||
if camera_info and camera_info.is_recording:
|
||||
self.logger.info(f"Camera {camera_name} is already recording, skipping auto-start")
|
||||
return
|
||||
|
||||
self.logger.info(f"Machine turned ON - attempting to start recording for camera {camera_name}")
|
||||
|
||||
|
||||
# Update auto-recording status
|
||||
if camera_info:
|
||||
camera_info.auto_recording_active = True
|
||||
camera_info.auto_recording_last_attempt = datetime.now()
|
||||
else:
|
||||
# Create camera info if it doesn't exist
|
||||
self.state_manager.update_camera_status(camera_name, "unknown")
|
||||
camera_info = self.state_manager.get_camera_status(camera_name)
|
||||
if camera_info:
|
||||
camera_info.auto_recording_active = True
|
||||
camera_info.auto_recording_last_attempt = datetime.now()
|
||||
|
||||
# Attempt to start recording
|
||||
success = self._start_recording_for_camera(camera_config)
|
||||
|
||||
|
||||
if not success:
|
||||
# Add to retry queue
|
||||
self._add_to_retry_queue(camera_config, "start")
|
||||
@@ -162,11 +175,11 @@ class AutoRecordingManager:
|
||||
def _handle_machine_off(self, camera_config: CameraConfig) -> None:
|
||||
"""Handle machine turning off - stop recording"""
|
||||
camera_name = camera_config.name
|
||||
|
||||
|
||||
self.logger.info(f"Machine turned OFF - attempting to stop recording for camera {camera_name}")
|
||||
|
||||
|
||||
# Update auto-recording status
|
||||
camera_info = self.state_manager.get_camera_info(camera_name)
|
||||
camera_info = self.state_manager.get_camera_status(camera_name)
|
||||
if camera_info:
|
||||
camera_info.auto_recording_active = False
|
||||
|
||||
@@ -179,57 +192,59 @@ class AutoRecordingManager:
|
||||
self._stop_recording_for_camera(camera_config)
|
||||
|
||||
def _start_recording_for_camera(self, camera_config: CameraConfig) -> bool:
|
||||
"""Start recording for a specific camera"""
|
||||
"""Start recording for a specific camera using its default configuration"""
|
||||
try:
|
||||
camera_name = camera_config.name
|
||||
|
||||
|
||||
# Generate filename with timestamp and machine info
|
||||
timestamp = format_filename_timestamp()
|
||||
machine_name = camera_config.machine_topic.replace("_", "-")
|
||||
filename = f"{camera_name}_auto_{machine_name}_{timestamp}.avi"
|
||||
|
||||
# Use camera manager to start recording
|
||||
success = self.camera_manager.manual_start_recording(camera_name, filename)
|
||||
|
||||
|
||||
# Use camera manager to start recording with the camera's default configuration
|
||||
# Pass the camera's configured settings from config.json
|
||||
success = self.camera_manager.manual_start_recording(camera_name=camera_name, filename=filename, exposure_ms=camera_config.exposure_ms, gain=camera_config.gain, fps=camera_config.target_fps)
|
||||
|
||||
if success:
|
||||
self.logger.info(f"Successfully started auto-recording for camera {camera_name}: {filename}")
|
||||
|
||||
self.logger.info(f"Using camera settings - Exposure: {camera_config.exposure_ms}ms, Gain: {camera_config.gain}, FPS: {camera_config.target_fps}")
|
||||
|
||||
# Update status
|
||||
camera_info = self.state_manager.get_camera_info(camera_name)
|
||||
camera_info = self.state_manager.get_camera_status(camera_name)
|
||||
if camera_info:
|
||||
camera_info.auto_recording_failure_count = 0
|
||||
camera_info.auto_recording_last_error = None
|
||||
|
||||
|
||||
return True
|
||||
else:
|
||||
self.logger.error(f"Failed to start auto-recording for camera {camera_name}")
|
||||
return False
|
||||
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error starting auto-recording for camera {camera_config.name}: {e}")
|
||||
|
||||
|
||||
# Update error status
|
||||
camera_info = self.state_manager.get_camera_info(camera_config.name)
|
||||
camera_info = self.state_manager.get_camera_status(camera_config.name)
|
||||
if camera_info:
|
||||
camera_info.auto_recording_last_error = str(e)
|
||||
|
||||
|
||||
return False
|
||||
|
||||
def _stop_recording_for_camera(self, camera_config: CameraConfig) -> bool:
|
||||
"""Stop recording for a specific camera"""
|
||||
try:
|
||||
camera_name = camera_config.name
|
||||
|
||||
|
||||
# Use camera manager to stop recording
|
||||
success = self.camera_manager.manual_stop_recording(camera_name)
|
||||
|
||||
|
||||
if success:
|
||||
self.logger.info(f"Successfully stopped auto-recording for camera {camera_name}")
|
||||
return True
|
||||
else:
|
||||
self.logger.warning(f"Failed to stop auto-recording for camera {camera_name} (may not have been recording)")
|
||||
return False
|
||||
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error stopping auto-recording for camera {camera_config.name}: {e}")
|
||||
return False
|
||||
@@ -238,15 +253,9 @@ class AutoRecordingManager:
|
||||
"""Add a camera to the retry queue"""
|
||||
with self._retry_lock:
|
||||
camera_name = camera_config.name
|
||||
|
||||
retry_info = {
|
||||
"camera_config": camera_config,
|
||||
"action": action,
|
||||
"attempt_count": 0,
|
||||
"next_retry_time": datetime.now() + timedelta(seconds=camera_config.auto_recording_retry_delay_seconds),
|
||||
"max_retries": camera_config.auto_recording_max_retries
|
||||
}
|
||||
|
||||
|
||||
retry_info = {"camera_config": camera_config, "action": action, "attempt_count": 0, "next_retry_time": datetime.now() + timedelta(seconds=camera_config.auto_recording_retry_delay_seconds), "max_retries": camera_config.auto_recording_max_retries}
|
||||
|
||||
self._retry_queue[camera_name] = retry_info
|
||||
self.logger.info(f"Added camera {camera_name} to retry queue for {action} (max retries: {retry_info['max_retries']})")
|
||||
|
||||
@@ -256,20 +265,20 @@ class AutoRecordingManager:
|
||||
try:
|
||||
current_time = datetime.now()
|
||||
cameras_to_retry = []
|
||||
|
||||
|
||||
# Find cameras ready for retry
|
||||
with self._retry_lock:
|
||||
for camera_name, retry_info in list(self._retry_queue.items()):
|
||||
if current_time >= retry_info["next_retry_time"]:
|
||||
cameras_to_retry.append((camera_name, retry_info))
|
||||
|
||||
|
||||
# Process retries
|
||||
for camera_name, retry_info in cameras_to_retry:
|
||||
self._process_retry(camera_name, retry_info)
|
||||
|
||||
|
||||
# Sleep for a short interval
|
||||
self._stop_event.wait(1)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error in retry loop: {e}")
|
||||
self._stop_event.wait(5)
|
||||
@@ -280,20 +289,20 @@ class AutoRecordingManager:
|
||||
retry_info["attempt_count"] += 1
|
||||
camera_config = retry_info["camera_config"]
|
||||
action = retry_info["action"]
|
||||
|
||||
|
||||
self.logger.info(f"Retry attempt {retry_info['attempt_count']}/{retry_info['max_retries']} for camera {camera_name} ({action})")
|
||||
|
||||
|
||||
# Update camera status
|
||||
camera_info = self.state_manager.get_camera_info(camera_name)
|
||||
camera_info = self.state_manager.get_camera_status(camera_name)
|
||||
if camera_info:
|
||||
camera_info.auto_recording_last_attempt = datetime.now()
|
||||
camera_info.auto_recording_failure_count = retry_info["attempt_count"]
|
||||
|
||||
|
||||
# Attempt the action
|
||||
success = False
|
||||
if action == "start":
|
||||
success = self._start_recording_for_camera(camera_config)
|
||||
|
||||
|
||||
if success:
|
||||
# Success - remove from retry queue
|
||||
with self._retry_lock:
|
||||
@@ -307,10 +316,10 @@ class AutoRecordingManager:
|
||||
with self._retry_lock:
|
||||
if camera_name in self._retry_queue:
|
||||
del self._retry_queue[camera_name]
|
||||
|
||||
|
||||
error_msg = f"Max retry attempts ({retry_info['max_retries']}) reached for camera {camera_name}"
|
||||
self.logger.error(error_msg)
|
||||
|
||||
|
||||
# Update camera status
|
||||
if camera_info:
|
||||
camera_info.auto_recording_last_error = error_msg
|
||||
@@ -319,10 +328,10 @@ class AutoRecordingManager:
|
||||
# Schedule next retry
|
||||
retry_info["next_retry_time"] = datetime.now() + timedelta(seconds=camera_config.auto_recording_retry_delay_seconds)
|
||||
self.logger.info(f"Scheduling next retry for camera {camera_name} in {camera_config.auto_recording_retry_delay_seconds} seconds")
|
||||
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error processing retry for camera {camera_name}: {e}")
|
||||
|
||||
|
||||
# Remove from retry queue on error
|
||||
with self._retry_lock:
|
||||
if camera_name in self._retry_queue:
|
||||
@@ -331,22 +340,6 @@ class AutoRecordingManager:
|
||||
def get_status(self) -> Dict[str, Any]:
|
||||
"""Get auto-recording manager status"""
|
||||
with self._retry_lock:
|
||||
retry_queue_status = {
|
||||
camera_name: {
|
||||
"action": info["action"],
|
||||
"attempt_count": info["attempt_count"],
|
||||
"max_retries": info["max_retries"],
|
||||
"next_retry_time": info["next_retry_time"].isoformat()
|
||||
}
|
||||
for camera_name, info in self._retry_queue.items()
|
||||
}
|
||||
|
||||
return {
|
||||
"running": self.running,
|
||||
"auto_recording_enabled": self.config.system.auto_recording_enabled,
|
||||
"retry_queue": retry_queue_status,
|
||||
"enabled_cameras": [
|
||||
camera.name for camera in self.config.cameras
|
||||
if camera.enabled and camera.auto_start_recording_enabled
|
||||
]
|
||||
}
|
||||
retry_queue_status = {camera_name: {"action": info["action"], "attempt_count": info["attempt_count"], "max_retries": info["max_retries"], "next_retry_time": info["next_retry_time"].isoformat()} for camera_name, info in self._retry_queue.items()}
|
||||
|
||||
return {"running": self.running, "auto_recording_enabled": self.config.system.auto_recording_enabled, "retry_queue": retry_queue_status, "enabled_cameras": [camera.name for camera in self.config.cameras if camera.enabled and camera.auto_start_recording_enabled]}
|
||||
|
||||
Reference in New Issue
Block a user