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",
|
# "machine_topic": "vibratory_conveyor",
|
||||||
# "storage_path": "/storage/camera1",
|
# "storage_path": "/storage/camera1",
|
||||||
# "enabled": true,
|
# "enabled": true,
|
||||||
|
# "auto_start_recording_enabled": true,
|
||||||
|
# "auto_recording_max_retries": 3,
|
||||||
|
# "auto_recording_retry_delay_seconds": 2,
|
||||||
# "exposure_ms": 1.0,
|
# "exposure_ms": 1.0,
|
||||||
# "gain": 3.5,
|
# "gain": 3.5,
|
||||||
# "target_fps": 0,
|
# "target_fps": 0,
|
||||||
@@ -329,6 +332,9 @@ GET {{baseUrl}}/cameras/camera1/config
|
|||||||
# "denoise_3d_enabled": false,
|
# "denoise_3d_enabled": false,
|
||||||
# "auto_white_balance": true,
|
# "auto_white_balance": true,
|
||||||
# "color_temperature_preset": 0,
|
# "color_temperature_preset": 0,
|
||||||
|
# "wb_red_gain": 1.0,
|
||||||
|
# "wb_green_gain": 1.0,
|
||||||
|
# "wb_blue_gain": 1.0,
|
||||||
# "anti_flicker_enabled": true,
|
# "anti_flicker_enabled": true,
|
||||||
# "light_frequency": 1,
|
# "light_frequency": 1,
|
||||||
# "bit_depth": 8,
|
# "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
|
### Enable HDR mode
|
||||||
PUT {{baseUrl}}/cameras/camera1/config
|
PUT {{baseUrl}}/cameras/camera1/config
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|||||||
34
config.json
34
config.json
@@ -28,10 +28,10 @@
|
|||||||
"cameras": [
|
"cameras": [
|
||||||
{
|
{
|
||||||
"name": "camera1",
|
"name": "camera1",
|
||||||
"machine_topic": "vibratory_conveyor",
|
"machine_topic": "blower_separator",
|
||||||
"storage_path": "/storage/camera1",
|
"storage_path": "/storage/camera1",
|
||||||
"exposure_ms": 0.5,
|
"exposure_ms": 0.3,
|
||||||
"gain": 0.5,
|
"gain": 4.0,
|
||||||
"target_fps": 0,
|
"target_fps": 0,
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"auto_start_recording_enabled": true,
|
"auto_start_recording_enabled": true,
|
||||||
@@ -40,23 +40,26 @@
|
|||||||
"sharpness": 100,
|
"sharpness": 100,
|
||||||
"contrast": 100,
|
"contrast": 100,
|
||||||
"saturation": 100,
|
"saturation": 100,
|
||||||
"gamma": 110,
|
"gamma": 100,
|
||||||
"noise_filter_enabled": false,
|
"noise_filter_enabled": false,
|
||||||
"denoise_3d_enabled": false,
|
"denoise_3d_enabled": false,
|
||||||
"auto_white_balance": true,
|
"auto_white_balance": false,
|
||||||
"color_temperature_preset": 0,
|
"color_temperature_preset": 0,
|
||||||
|
"wb_red_gain": 0.94,
|
||||||
|
"wb_green_gain": 1.0,
|
||||||
|
"wb_blue_gain": 0.87,
|
||||||
"anti_flicker_enabled": false,
|
"anti_flicker_enabled": false,
|
||||||
"light_frequency": 1,
|
"light_frequency": 0,
|
||||||
"bit_depth": 8,
|
"bit_depth": 8,
|
||||||
"hdr_enabled": false,
|
"hdr_enabled": false,
|
||||||
"hdr_gain_mode": 0
|
"hdr_gain_mode": 2
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "camera2",
|
"name": "camera2",
|
||||||
"machine_topic": "blower_separator",
|
"machine_topic": "vibratory_conveyor",
|
||||||
"storage_path": "/storage/camera2",
|
"storage_path": "/storage/camera2",
|
||||||
"exposure_ms": 0.5,
|
"exposure_ms": 0.2,
|
||||||
"gain": 0.3,
|
"gain": 2.0,
|
||||||
"target_fps": 0,
|
"target_fps": 0,
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"auto_start_recording_enabled": true,
|
"auto_start_recording_enabled": true,
|
||||||
@@ -64,14 +67,17 @@
|
|||||||
"auto_recording_retry_delay_seconds": 2,
|
"auto_recording_retry_delay_seconds": 2,
|
||||||
"sharpness": 100,
|
"sharpness": 100,
|
||||||
"contrast": 100,
|
"contrast": 100,
|
||||||
"saturation": 75,
|
"saturation": 100,
|
||||||
"gamma": 110,
|
"gamma": 100,
|
||||||
"noise_filter_enabled": false,
|
"noise_filter_enabled": false,
|
||||||
"denoise_3d_enabled": false,
|
"denoise_3d_enabled": false,
|
||||||
"auto_white_balance": true,
|
"auto_white_balance": false,
|
||||||
"color_temperature_preset": 0,
|
"color_temperature_preset": 0,
|
||||||
|
"wb_red_gain": 1.01,
|
||||||
|
"wb_green_gain": 1.0,
|
||||||
|
"wb_blue_gain": 0.87,
|
||||||
"anti_flicker_enabled": false,
|
"anti_flicker_enabled": false,
|
||||||
"light_frequency": 1,
|
"light_frequency": 0,
|
||||||
"bit_depth": 8,
|
"bit_depth": 8,
|
||||||
"hdr_enabled": false,
|
"hdr_enabled": false,
|
||||||
"hdr_gain_mode": 0
|
"hdr_gain_mode": 0
|
||||||
|
|||||||
@@ -197,10 +197,12 @@ GET /cameras/{camera_name}/config
|
|||||||
"machine_topic": "vibratory_conveyor",
|
"machine_topic": "vibratory_conveyor",
|
||||||
"storage_path": "/storage/camera1",
|
"storage_path": "/storage/camera1",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
"auto_start_recording_enabled": true,
|
||||||
|
"auto_recording_max_retries": 3,
|
||||||
|
"auto_recording_retry_delay_seconds": 2,
|
||||||
"exposure_ms": 1.0,
|
"exposure_ms": 1.0,
|
||||||
"gain": 3.5,
|
"gain": 3.5,
|
||||||
"target_fps": 3.0,
|
"target_fps": 3.0,
|
||||||
"auto_start_recording_enabled": true,
|
|
||||||
"sharpness": 120,
|
"sharpness": 120,
|
||||||
"contrast": 110,
|
"contrast": 110,
|
||||||
"saturation": 100,
|
"saturation": 100,
|
||||||
@@ -209,6 +211,9 @@ GET /cameras/{camera_name}/config
|
|||||||
"denoise_3d_enabled": false,
|
"denoise_3d_enabled": false,
|
||||||
"auto_white_balance": true,
|
"auto_white_balance": true,
|
||||||
"color_temperature_preset": 0,
|
"color_temperature_preset": 0,
|
||||||
|
"wb_red_gain": 1.0,
|
||||||
|
"wb_green_gain": 1.0,
|
||||||
|
"wb_blue_gain": 1.0,
|
||||||
"anti_flicker_enabled": true,
|
"anti_flicker_enabled": true,
|
||||||
"light_frequency": 1,
|
"light_frequency": 1,
|
||||||
"bit_depth": 8,
|
"bit_depth": 8,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ These settings can be changed while the camera is active:
|
|||||||
- **Basic**: `exposure_ms`, `gain`, `target_fps`
|
- **Basic**: `exposure_ms`, `gain`, `target_fps`
|
||||||
- **Image Quality**: `sharpness`, `contrast`, `saturation`, `gamma`
|
- **Image Quality**: `sharpness`, `contrast`, `saturation`, `gamma`
|
||||||
- **Color**: `auto_white_balance`, `color_temperature_preset`
|
- **Color**: `auto_white_balance`, `color_temperature_preset`
|
||||||
|
- **White Balance**: `wb_red_gain`, `wb_green_gain`, `wb_blue_gain`
|
||||||
- **Advanced**: `anti_flicker_enabled`, `light_frequency`
|
- **Advanced**: `anti_flicker_enabled`, `light_frequency`
|
||||||
- **HDR**: `hdr_enabled`, `hdr_gain_mode`
|
- **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`
|
- **Noise Reduction**: `noise_filter_enabled`, `denoise_3d_enabled`
|
||||||
- **System**: `machine_topic`, `storage_path`, `enabled`, `bit_depth`
|
- **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
|
## 🔌 API Endpoints
|
||||||
|
|
||||||
### 1. Get Camera Configuration
|
### 1. Get Camera Configuration
|
||||||
@@ -35,6 +42,9 @@ GET /cameras/{camera_name}/config
|
|||||||
"machine_topic": "vibratory_conveyor",
|
"machine_topic": "vibratory_conveyor",
|
||||||
"storage_path": "/storage/camera1",
|
"storage_path": "/storage/camera1",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
"auto_start_recording_enabled": true,
|
||||||
|
"auto_recording_max_retries": 3,
|
||||||
|
"auto_recording_retry_delay_seconds": 2,
|
||||||
"exposure_ms": 1.0,
|
"exposure_ms": 1.0,
|
||||||
"gain": 3.5,
|
"gain": 3.5,
|
||||||
"target_fps": 0,
|
"target_fps": 0,
|
||||||
@@ -46,6 +56,9 @@ GET /cameras/{camera_name}/config
|
|||||||
"denoise_3d_enabled": false,
|
"denoise_3d_enabled": false,
|
||||||
"auto_white_balance": true,
|
"auto_white_balance": true,
|
||||||
"color_temperature_preset": 0,
|
"color_temperature_preset": 0,
|
||||||
|
"wb_red_gain": 1.0,
|
||||||
|
"wb_green_gain": 1.0,
|
||||||
|
"wb_blue_gain": 1.0,
|
||||||
"anti_flicker_enabled": true,
|
"anti_flicker_enabled": true,
|
||||||
"light_frequency": 1,
|
"light_frequency": 1,
|
||||||
"bit_depth": 8,
|
"bit_depth": 8,
|
||||||
@@ -74,6 +87,9 @@ Content-Type: application/json
|
|||||||
"denoise_3d_enabled": false,
|
"denoise_3d_enabled": false,
|
||||||
"auto_white_balance": false,
|
"auto_white_balance": false,
|
||||||
"color_temperature_preset": 1,
|
"color_temperature_preset": 1,
|
||||||
|
"wb_red_gain": 1.2,
|
||||||
|
"wb_green_gain": 1.0,
|
||||||
|
"wb_blue_gain": 0.8,
|
||||||
"anti_flicker_enabled": true,
|
"anti_flicker_enabled": true,
|
||||||
"light_frequency": 1,
|
"light_frequency": 1,
|
||||||
"hdr_enabled": false,
|
"hdr_enabled": false,
|
||||||
@@ -86,7 +102,7 @@ Content-Type: application/json
|
|||||||
{
|
{
|
||||||
"success": true,
|
"success": true,
|
||||||
"message": "Camera camera1 configuration updated",
|
"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
|
## 📊 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
|
### Basic Settings
|
||||||
| Setting | Range | Default | Description |
|
| Setting | Range | Default | Description |
|
||||||
|---------|-------|---------|-------------|
|
|---------|-------|---------|-------------|
|
||||||
@@ -126,6 +157,13 @@ POST /cameras/{camera_name}/apply-config
|
|||||||
| `auto_white_balance` | true/false | true | Automatic white balance |
|
| `auto_white_balance` | true/false | true | Automatic white balance |
|
||||||
| `color_temperature_preset` | 0-10 | 0 | Color temperature preset (0=auto) |
|
| `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
|
### Advanced Settings
|
||||||
| Setting | Values | Default | Description |
|
| Setting | Values | Default | Description |
|
||||||
|---------|--------|---------|-------------|
|
|---------|--------|---------|-------------|
|
||||||
@@ -248,7 +286,21 @@ const CameraConfig = ({ cameraName, apiBaseUrl = 'http://localhost:8000' }) => {
|
|||||||
return (
|
return (
|
||||||
<div className="camera-config">
|
<div className="camera-config">
|
||||||
<h3>Camera Configuration: {cameraName}</h3>
|
<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 */}
|
{/* Basic Settings */}
|
||||||
<div className="config-section">
|
<div className="config-section">
|
||||||
<h4>Basic Settings</h4>
|
<h4>Basic Settings</h4>
|
||||||
@@ -328,6 +380,47 @@ const CameraConfig = ({ cameraName, apiBaseUrl = 'http://localhost:8000' }) => {
|
|||||||
</div>
|
</div>
|
||||||
</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 */}
|
{/* Advanced Settings */}
|
||||||
<div className="config-section">
|
<div className="config-section">
|
||||||
<h4>Advanced Settings</h4>
|
<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
|
# Add the current directory to Python path
|
||||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
|
||||||
def test_config_structure():
|
def test_config_structure():
|
||||||
"""Test that config.json has the required auto-recording fields"""
|
"""Test that config.json has the required auto-recording fields"""
|
||||||
print("🔍 Testing configuration structure...")
|
print("🔍 Testing configuration structure...")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open("config.json", "r") as f:
|
with open("config.json", "r") as f:
|
||||||
config = json.load(f)
|
config = json.load(f)
|
||||||
|
|
||||||
# Check system-level auto-recording setting
|
# Check system-level auto-recording setting
|
||||||
system_config = config.get("system", {})
|
system_config = config.get("system", {})
|
||||||
if "auto_recording_enabled" not in system_config:
|
if "auto_recording_enabled" not in system_config:
|
||||||
print("❌ Missing 'auto_recording_enabled' in system config")
|
print("❌ Missing 'auto_recording_enabled' in system config")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
print(f"✅ System auto-recording enabled: {system_config['auto_recording_enabled']}")
|
print(f"✅ System auto-recording enabled: {system_config['auto_recording_enabled']}")
|
||||||
|
|
||||||
# Check camera-level auto-recording settings
|
# Check camera-level auto-recording settings
|
||||||
cameras = config.get("cameras", [])
|
cameras = config.get("cameras", [])
|
||||||
if not cameras:
|
if not cameras:
|
||||||
print("❌ No cameras found in config")
|
print("❌ No cameras found in config")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
for camera in cameras:
|
for camera in cameras:
|
||||||
camera_name = camera.get("name", "unknown")
|
camera_name = camera.get("name", "unknown")
|
||||||
required_fields = [
|
required_fields = ["auto_start_recording_enabled", "auto_recording_max_retries", "auto_recording_retry_delay_seconds"]
|
||||||
"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]
|
missing_fields = [field for field in required_fields if field not in camera]
|
||||||
if missing_fields:
|
if missing_fields:
|
||||||
print(f"❌ Camera {camera_name} missing fields: {missing_fields}")
|
print(f"❌ Camera {camera_name} missing fields: {missing_fields}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
print(f"✅ Camera {camera_name} auto-recording config:")
|
print(f"✅ Camera {camera_name} auto-recording config:")
|
||||||
print(f" - Enabled: {camera['auto_start_recording_enabled']}")
|
print(f" - Enabled: {camera['auto_start_recording_enabled']}")
|
||||||
print(f" - Max retries: {camera['auto_recording_max_retries']}")
|
print(f" - Max retries: {camera['auto_recording_max_retries']}")
|
||||||
print(f" - Retry delay: {camera['auto_recording_retry_delay_seconds']}s")
|
print(f" - Retry delay: {camera['auto_recording_retry_delay_seconds']}s")
|
||||||
print(f" - Machine topic: {camera.get('machine_topic', 'unknown')}")
|
print(f" - Machine topic: {camera.get('machine_topic', 'unknown')}")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"❌ Error reading config: {e}")
|
print(f"❌ Error reading config: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def test_module_imports():
|
def test_module_imports():
|
||||||
"""Test that all required modules can be imported"""
|
"""Test that all required modules can be imported"""
|
||||||
print("\n🔍 Testing module imports...")
|
print("\n🔍 Testing module imports...")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from usda_vision_system.recording.auto_manager import AutoRecordingManager
|
from usda_vision_system.recording.auto_manager import AutoRecordingManager
|
||||||
|
|
||||||
print("✅ AutoRecordingManager imported successfully")
|
print("✅ AutoRecordingManager imported successfully")
|
||||||
|
|
||||||
from usda_vision_system.core.config import Config
|
from usda_vision_system.core.config import Config
|
||||||
|
|
||||||
config = Config("config.json")
|
config = Config("config.json")
|
||||||
print("✅ Config loaded successfully")
|
print("✅ Config loaded successfully")
|
||||||
|
|
||||||
from usda_vision_system.core.state_manager import StateManager
|
from usda_vision_system.core.state_manager import StateManager
|
||||||
|
|
||||||
state_manager = StateManager()
|
state_manager = StateManager()
|
||||||
print("✅ StateManager created successfully")
|
print("✅ StateManager created successfully")
|
||||||
|
|
||||||
from usda_vision_system.core.events import EventSystem
|
from usda_vision_system.core.events import EventSystem
|
||||||
|
|
||||||
event_system = EventSystem()
|
event_system = EventSystem()
|
||||||
print("✅ EventSystem created successfully")
|
print("✅ EventSystem created successfully")
|
||||||
|
|
||||||
# Test creating AutoRecordingManager (without camera_manager for now)
|
# Test creating AutoRecordingManager (without camera_manager for now)
|
||||||
auto_manager = AutoRecordingManager(config, state_manager, event_system, None)
|
auto_manager = AutoRecordingManager(config, state_manager, event_system, None)
|
||||||
print("✅ AutoRecordingManager created successfully")
|
print("✅ AutoRecordingManager created successfully")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"❌ Import error: {e}")
|
print(f"❌ Import error: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def test_camera_mapping():
|
def test_camera_mapping():
|
||||||
"""Test camera to machine topic mapping"""
|
"""Test camera to machine topic mapping"""
|
||||||
print("\n🔍 Testing camera to machine mapping...")
|
print("\n🔍 Testing camera to machine mapping...")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open("config.json", "r") as f:
|
with open("config.json", "r") as f:
|
||||||
config = json.load(f)
|
config = json.load(f)
|
||||||
|
|
||||||
cameras = config.get("cameras", [])
|
cameras = config.get("cameras", [])
|
||||||
expected_mappings = {
|
expected_mappings = {"camera1": "blower_separator", "camera2": "vibratory_conveyor"} # Blower separator # Conveyor/cracker cam
|
||||||
"camera1": "vibratory_conveyor", # Conveyor/cracker cam
|
|
||||||
"camera2": "blower_separator" # Blower separator
|
|
||||||
}
|
|
||||||
|
|
||||||
for camera in cameras:
|
for camera in cameras:
|
||||||
camera_name = camera.get("name")
|
camera_name = camera.get("name")
|
||||||
machine_topic = camera.get("machine_topic")
|
machine_topic = camera.get("machine_topic")
|
||||||
|
|
||||||
if camera_name in expected_mappings:
|
if camera_name in expected_mappings:
|
||||||
expected_topic = expected_mappings[camera_name]
|
expected_topic = expected_mappings[camera_name]
|
||||||
if machine_topic == expected_topic:
|
if machine_topic == expected_topic:
|
||||||
@@ -118,38 +118,25 @@ def test_camera_mapping():
|
|||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
print(f"⚠️ Unknown camera: {camera_name}")
|
print(f"⚠️ Unknown camera: {camera_name}")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"❌ Error checking mappings: {e}")
|
print(f"❌ Error checking mappings: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def test_api_models():
|
def test_api_models():
|
||||||
"""Test that API models include auto-recording fields"""
|
"""Test that API models include auto-recording fields"""
|
||||||
print("\n🔍 Testing API models...")
|
print("\n🔍 Testing API models...")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from usda_vision_system.api.models import (
|
from usda_vision_system.api.models import CameraStatusResponse, CameraConfigResponse, AutoRecordingConfigRequest, AutoRecordingConfigResponse, AutoRecordingStatusResponse
|
||||||
CameraStatusResponse,
|
|
||||||
CameraConfigResponse,
|
|
||||||
AutoRecordingConfigRequest,
|
|
||||||
AutoRecordingConfigResponse,
|
|
||||||
AutoRecordingStatusResponse
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check CameraStatusResponse has auto-recording fields
|
# Check CameraStatusResponse has auto-recording fields
|
||||||
camera_response = CameraStatusResponse(
|
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)
|
||||||
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")
|
print("✅ CameraStatusResponse includes auto-recording fields")
|
||||||
|
|
||||||
# Check CameraConfigResponse has auto-recording fields
|
# Check CameraConfigResponse has auto-recording fields
|
||||||
config_response = CameraConfigResponse(
|
config_response = CameraConfigResponse(
|
||||||
name="test",
|
name="test",
|
||||||
@@ -170,46 +157,45 @@ def test_api_models():
|
|||||||
denoise_3d_enabled=False,
|
denoise_3d_enabled=False,
|
||||||
auto_white_balance=True,
|
auto_white_balance=True,
|
||||||
color_temperature_preset=0,
|
color_temperature_preset=0,
|
||||||
|
wb_red_gain=1.0,
|
||||||
|
wb_green_gain=1.0,
|
||||||
|
wb_blue_gain=1.0,
|
||||||
anti_flicker_enabled=False,
|
anti_flicker_enabled=False,
|
||||||
light_frequency=1,
|
light_frequency=1,
|
||||||
bit_depth=8,
|
bit_depth=8,
|
||||||
hdr_enabled=False,
|
hdr_enabled=False,
|
||||||
hdr_gain_mode=0
|
hdr_gain_mode=0,
|
||||||
)
|
)
|
||||||
print("✅ CameraConfigResponse includes auto-recording fields")
|
print("✅ CameraConfigResponse includes auto-recording fields")
|
||||||
|
|
||||||
print("✅ All auto-recording API models available")
|
print("✅ All auto-recording API models available")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"❌ API model error: {e}")
|
print(f"❌ API model error: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Run all basic tests"""
|
"""Run all basic tests"""
|
||||||
print("🧪 Auto-Recording Integration Test")
|
print("🧪 Auto-Recording Integration Test")
|
||||||
print("=" * 40)
|
print("=" * 40)
|
||||||
|
|
||||||
tests = [
|
tests = [test_config_structure, test_module_imports, test_camera_mapping, test_api_models]
|
||||||
test_config_structure,
|
|
||||||
test_module_imports,
|
|
||||||
test_camera_mapping,
|
|
||||||
test_api_models
|
|
||||||
]
|
|
||||||
|
|
||||||
passed = 0
|
passed = 0
|
||||||
total = len(tests)
|
total = len(tests)
|
||||||
|
|
||||||
for test in tests:
|
for test in tests:
|
||||||
try:
|
try:
|
||||||
if test():
|
if test():
|
||||||
passed += 1
|
passed += 1
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"❌ Test {test.__name__} failed with exception: {e}")
|
print(f"❌ Test {test.__name__} failed with exception: {e}")
|
||||||
|
|
||||||
print("\n" + "=" * 40)
|
print("\n" + "=" * 40)
|
||||||
print(f"📊 Results: {passed}/{total} tests passed")
|
print(f"📊 Results: {passed}/{total} tests passed")
|
||||||
|
|
||||||
if passed == total:
|
if passed == total:
|
||||||
print("🎉 All integration tests passed!")
|
print("🎉 All integration tests passed!")
|
||||||
print("\n📝 Next steps:")
|
print("\n📝 Next steps:")
|
||||||
@@ -222,6 +208,7 @@ def main():
|
|||||||
print("Please fix the issues before running the full system")
|
print("Please fix the issues before running the full system")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
success = main()
|
success = main()
|
||||||
sys.exit(0 if success else 1)
|
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")
|
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")
|
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
|
# Advanced Settings
|
||||||
anti_flicker_enabled: Optional[bool] = Field(default=None, description="Reduce artificial lighting flicker")
|
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)")
|
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
|
auto_white_balance: bool
|
||||||
color_temperature_preset: int
|
color_temperature_preset: int
|
||||||
|
|
||||||
|
# Manual White Balance RGB Gains
|
||||||
|
wb_red_gain: float
|
||||||
|
wb_green_gain: float
|
||||||
|
wb_blue_gain: float
|
||||||
|
|
||||||
# Advanced Settings
|
# Advanced Settings
|
||||||
anti_flicker_enabled: bool
|
anti_flicker_enabled: bool
|
||||||
light_frequency: int
|
light_frequency: int
|
||||||
|
|||||||
@@ -354,6 +354,10 @@ class APIServer:
|
|||||||
# Color Settings
|
# Color Settings
|
||||||
auto_white_balance=config.auto_white_balance,
|
auto_white_balance=config.auto_white_balance,
|
||||||
color_temperature_preset=config.color_temperature_preset,
|
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
|
# Advanced Settings
|
||||||
anti_flicker_enabled=config.anti_flicker_enabled,
|
anti_flicker_enabled=config.anti_flicker_enabled,
|
||||||
light_frequency=config.light_frequency,
|
light_frequency=config.light_frequency,
|
||||||
@@ -512,7 +516,7 @@ class APIServer:
|
|||||||
self.config.save_config()
|
self.config.save_config()
|
||||||
|
|
||||||
# Update camera status in state manager
|
# 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:
|
if camera_info:
|
||||||
camera_info.auto_recording_enabled = True
|
camera_info.auto_recording_enabled = True
|
||||||
|
|
||||||
@@ -539,7 +543,7 @@ class APIServer:
|
|||||||
self.config.save_config()
|
self.config.save_config()
|
||||||
|
|
||||||
# Update camera status in state manager
|
# 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:
|
if camera_info:
|
||||||
camera_info.auto_recording_enabled = False
|
camera_info.auto_recording_enabled = False
|
||||||
camera_info.auto_recording_active = False
|
camera_info.auto_recording_active = False
|
||||||
|
|||||||
@@ -260,7 +260,13 @@ class CameraRecorder:
|
|||||||
if not self.camera_config.auto_white_balance:
|
if not self.camera_config.auto_white_balance:
|
||||||
mvsdk.CameraSetPresetClrTemp(self.hCamera, self.camera_config.color_temperature_preset)
|
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:
|
except Exception as e:
|
||||||
self.logger.warning(f"Error configuring color settings: {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"]
|
self.camera_config.color_temperature_preset = kwargs["color_temperature_preset"]
|
||||||
settings_updated = True
|
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
|
# Update advanced settings
|
||||||
if "anti_flicker_enabled" in kwargs and kwargs["anti_flicker_enabled"] is not None:
|
if "anti_flicker_enabled" in kwargs and kwargs["anti_flicker_enabled"] is not None:
|
||||||
mvsdk.CameraSetAntiFlick(self.hCamera, kwargs["anti_flicker_enabled"])
|
mvsdk.CameraSetAntiFlick(self.hCamera, kwargs["anti_flicker_enabled"])
|
||||||
|
|||||||
@@ -205,22 +205,37 @@ class CameraStreamer:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def _configure_streaming_settings(self):
|
def _configure_streaming_settings(self):
|
||||||
"""Configure camera settings optimized for streaming"""
|
"""Configure camera settings from config.json for streaming"""
|
||||||
try:
|
try:
|
||||||
# Set trigger mode to free run for continuous streaming
|
# Set trigger mode to free run for continuous streaming
|
||||||
mvsdk.CameraSetTriggerMode(self.hCamera, 0)
|
mvsdk.CameraSetTriggerMode(self.hCamera, 0)
|
||||||
|
|
||||||
# Set exposure (use a reasonable default for preview)
|
# Set manual exposure
|
||||||
exposure_us = int(self.camera_config.exposure_ms * 1000)
|
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)
|
mvsdk.CameraSetExposureTime(self.hCamera, exposure_us)
|
||||||
|
|
||||||
# Set gain
|
# Set analog gain
|
||||||
mvsdk.CameraSetAnalogGain(self.hCamera, int(self.camera_config.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)
|
# Set frame rate for streaming (lower than recording)
|
||||||
if hasattr(mvsdk, "CameraSetFrameSpeed"):
|
if hasattr(mvsdk, "CameraSetFrameSpeed"):
|
||||||
mvsdk.CameraSetFrameSpeed(self.hCamera, int(self.preview_fps))
|
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}")
|
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:
|
except Exception as e:
|
||||||
@@ -314,6 +329,83 @@ class CameraStreamer:
|
|||||||
"""Check if streaming is active"""
|
"""Check if streaming is active"""
|
||||||
return self.streaming
|
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):
|
def __del__(self):
|
||||||
"""Destructor to ensure cleanup"""
|
"""Destructor to ensure cleanup"""
|
||||||
if self.streaming:
|
if self.streaming:
|
||||||
|
|||||||
@@ -59,6 +59,11 @@ class CameraConfig:
|
|||||||
auto_white_balance: bool = True # Enable automatic white balance
|
auto_white_balance: bool = True # Enable automatic white balance
|
||||||
color_temperature_preset: int = 0 # 0=auto, 1=daylight, 2=fluorescent, etc.
|
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
|
# Advanced Settings
|
||||||
anti_flicker_enabled: bool = True # Reduce artificial lighting flicker
|
anti_flicker_enabled: bool = True # Reduce artificial lighting flicker
|
||||||
light_frequency: int = 1 # 0=50Hz, 1=60Hz (match local power frequency)
|
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:
|
for camera_config in self.config.cameras:
|
||||||
if camera_config.enabled and camera_config.auto_start_recording_enabled:
|
if camera_config.enabled and camera_config.auto_start_recording_enabled:
|
||||||
# Update camera status in state manager
|
# 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:
|
if camera_info:
|
||||||
camera_info.auto_recording_enabled = True
|
camera_info.auto_recording_enabled = True
|
||||||
self.logger.info(f"Auto-recording enabled for camera {camera_config.name}")
|
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:
|
def _on_machine_state_changed(self, event: Event) -> None:
|
||||||
"""Handle machine state change events"""
|
"""Handle machine state change events"""
|
||||||
@@ -96,15 +103,17 @@ class AutoRecordingManager:
|
|||||||
new_state = event.data.get("state")
|
new_state = event.data.get("state")
|
||||||
|
|
||||||
if not machine_name or not new_state:
|
if not machine_name or not new_state:
|
||||||
|
self.logger.warning(f"Invalid event data - machine_name: {machine_name}, state: {new_state}")
|
||||||
return
|
return
|
||||||
|
|
||||||
self.logger.info(f"Machine state changed: {machine_name} -> {new_state}")
|
self.logger.info(f"Machine state changed: {machine_name} -> {new_state}")
|
||||||
|
|
||||||
# Find cameras associated with this machine
|
# Find cameras associated with this machine
|
||||||
associated_cameras = self._get_cameras_for_machine(machine_name)
|
associated_cameras = self._get_cameras_for_machine(machine_name)
|
||||||
|
|
||||||
for camera_config in associated_cameras:
|
for camera_config in associated_cameras:
|
||||||
if not camera_config.enabled or not camera_config.auto_start_recording_enabled:
|
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
|
continue
|
||||||
|
|
||||||
if new_state.lower() == "on":
|
if new_state.lower() == "on":
|
||||||
@@ -118,13 +127,10 @@ class AutoRecordingManager:
|
|||||||
def _get_cameras_for_machine(self, machine_name: str) -> list[CameraConfig]:
|
def _get_cameras_for_machine(self, machine_name: str) -> list[CameraConfig]:
|
||||||
"""Get all cameras associated with a machine topic"""
|
"""Get all cameras associated with a machine topic"""
|
||||||
associated_cameras = []
|
associated_cameras = []
|
||||||
|
|
||||||
# Map machine names to topics
|
# Map machine names to topics
|
||||||
machine_topic_map = {
|
machine_topic_map = {"vibratory_conveyor": "vibratory_conveyor", "blower_separator": "blower_separator"}
|
||||||
"vibratory_conveyor": "vibratory_conveyor",
|
|
||||||
"blower_separator": "blower_separator"
|
|
||||||
}
|
|
||||||
|
|
||||||
machine_topic = machine_topic_map.get(machine_name)
|
machine_topic = machine_topic_map.get(machine_name)
|
||||||
if not machine_topic:
|
if not machine_topic:
|
||||||
return associated_cameras
|
return associated_cameras
|
||||||
@@ -138,23 +144,30 @@ class AutoRecordingManager:
|
|||||||
def _handle_machine_on(self, camera_config: CameraConfig) -> None:
|
def _handle_machine_on(self, camera_config: CameraConfig) -> None:
|
||||||
"""Handle machine turning on - start recording"""
|
"""Handle machine turning on - start recording"""
|
||||||
camera_name = camera_config.name
|
camera_name = camera_config.name
|
||||||
|
|
||||||
# Check if camera is already recording
|
# 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:
|
if camera_info and camera_info.is_recording:
|
||||||
self.logger.info(f"Camera {camera_name} is already recording, skipping auto-start")
|
self.logger.info(f"Camera {camera_name} is already recording, skipping auto-start")
|
||||||
return
|
return
|
||||||
|
|
||||||
self.logger.info(f"Machine turned ON - attempting to start recording for camera {camera_name}")
|
self.logger.info(f"Machine turned ON - attempting to start recording for camera {camera_name}")
|
||||||
|
|
||||||
# Update auto-recording status
|
# Update auto-recording status
|
||||||
if camera_info:
|
if camera_info:
|
||||||
camera_info.auto_recording_active = True
|
camera_info.auto_recording_active = True
|
||||||
camera_info.auto_recording_last_attempt = datetime.now()
|
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
|
# Attempt to start recording
|
||||||
success = self._start_recording_for_camera(camera_config)
|
success = self._start_recording_for_camera(camera_config)
|
||||||
|
|
||||||
if not success:
|
if not success:
|
||||||
# Add to retry queue
|
# Add to retry queue
|
||||||
self._add_to_retry_queue(camera_config, "start")
|
self._add_to_retry_queue(camera_config, "start")
|
||||||
@@ -162,11 +175,11 @@ class AutoRecordingManager:
|
|||||||
def _handle_machine_off(self, camera_config: CameraConfig) -> None:
|
def _handle_machine_off(self, camera_config: CameraConfig) -> None:
|
||||||
"""Handle machine turning off - stop recording"""
|
"""Handle machine turning off - stop recording"""
|
||||||
camera_name = camera_config.name
|
camera_name = camera_config.name
|
||||||
|
|
||||||
self.logger.info(f"Machine turned OFF - attempting to stop recording for camera {camera_name}")
|
self.logger.info(f"Machine turned OFF - attempting to stop recording for camera {camera_name}")
|
||||||
|
|
||||||
# Update auto-recording status
|
# 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:
|
if camera_info:
|
||||||
camera_info.auto_recording_active = False
|
camera_info.auto_recording_active = False
|
||||||
|
|
||||||
@@ -179,57 +192,59 @@ class AutoRecordingManager:
|
|||||||
self._stop_recording_for_camera(camera_config)
|
self._stop_recording_for_camera(camera_config)
|
||||||
|
|
||||||
def _start_recording_for_camera(self, camera_config: CameraConfig) -> bool:
|
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:
|
try:
|
||||||
camera_name = camera_config.name
|
camera_name = camera_config.name
|
||||||
|
|
||||||
# Generate filename with timestamp and machine info
|
# Generate filename with timestamp and machine info
|
||||||
timestamp = format_filename_timestamp()
|
timestamp = format_filename_timestamp()
|
||||||
machine_name = camera_config.machine_topic.replace("_", "-")
|
machine_name = camera_config.machine_topic.replace("_", "-")
|
||||||
filename = f"{camera_name}_auto_{machine_name}_{timestamp}.avi"
|
filename = f"{camera_name}_auto_{machine_name}_{timestamp}.avi"
|
||||||
|
|
||||||
# Use camera manager to start recording
|
# Use camera manager to start recording with the camera's default configuration
|
||||||
success = self.camera_manager.manual_start_recording(camera_name, filename)
|
# 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:
|
if success:
|
||||||
self.logger.info(f"Successfully started auto-recording for camera {camera_name}: {filename}")
|
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
|
# 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:
|
if camera_info:
|
||||||
camera_info.auto_recording_failure_count = 0
|
camera_info.auto_recording_failure_count = 0
|
||||||
camera_info.auto_recording_last_error = None
|
camera_info.auto_recording_last_error = None
|
||||||
|
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
self.logger.error(f"Failed to start auto-recording for camera {camera_name}")
|
self.logger.error(f"Failed to start auto-recording for camera {camera_name}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Error starting auto-recording for camera {camera_config.name}: {e}")
|
self.logger.error(f"Error starting auto-recording for camera {camera_config.name}: {e}")
|
||||||
|
|
||||||
# Update error status
|
# 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:
|
if camera_info:
|
||||||
camera_info.auto_recording_last_error = str(e)
|
camera_info.auto_recording_last_error = str(e)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _stop_recording_for_camera(self, camera_config: CameraConfig) -> bool:
|
def _stop_recording_for_camera(self, camera_config: CameraConfig) -> bool:
|
||||||
"""Stop recording for a specific camera"""
|
"""Stop recording for a specific camera"""
|
||||||
try:
|
try:
|
||||||
camera_name = camera_config.name
|
camera_name = camera_config.name
|
||||||
|
|
||||||
# Use camera manager to stop recording
|
# Use camera manager to stop recording
|
||||||
success = self.camera_manager.manual_stop_recording(camera_name)
|
success = self.camera_manager.manual_stop_recording(camera_name)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
self.logger.info(f"Successfully stopped auto-recording for camera {camera_name}")
|
self.logger.info(f"Successfully stopped auto-recording for camera {camera_name}")
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
self.logger.warning(f"Failed to stop auto-recording for camera {camera_name} (may not have been recording)")
|
self.logger.warning(f"Failed to stop auto-recording for camera {camera_name} (may not have been recording)")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Error stopping auto-recording for camera {camera_config.name}: {e}")
|
self.logger.error(f"Error stopping auto-recording for camera {camera_config.name}: {e}")
|
||||||
return False
|
return False
|
||||||
@@ -238,15 +253,9 @@ class AutoRecordingManager:
|
|||||||
"""Add a camera to the retry queue"""
|
"""Add a camera to the retry queue"""
|
||||||
with self._retry_lock:
|
with self._retry_lock:
|
||||||
camera_name = camera_config.name
|
camera_name = camera_config.name
|
||||||
|
|
||||||
retry_info = {
|
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}
|
||||||
"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._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']})")
|
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:
|
try:
|
||||||
current_time = datetime.now()
|
current_time = datetime.now()
|
||||||
cameras_to_retry = []
|
cameras_to_retry = []
|
||||||
|
|
||||||
# Find cameras ready for retry
|
# Find cameras ready for retry
|
||||||
with self._retry_lock:
|
with self._retry_lock:
|
||||||
for camera_name, retry_info in list(self._retry_queue.items()):
|
for camera_name, retry_info in list(self._retry_queue.items()):
|
||||||
if current_time >= retry_info["next_retry_time"]:
|
if current_time >= retry_info["next_retry_time"]:
|
||||||
cameras_to_retry.append((camera_name, retry_info))
|
cameras_to_retry.append((camera_name, retry_info))
|
||||||
|
|
||||||
# Process retries
|
# Process retries
|
||||||
for camera_name, retry_info in cameras_to_retry:
|
for camera_name, retry_info in cameras_to_retry:
|
||||||
self._process_retry(camera_name, retry_info)
|
self._process_retry(camera_name, retry_info)
|
||||||
|
|
||||||
# Sleep for a short interval
|
# Sleep for a short interval
|
||||||
self._stop_event.wait(1)
|
self._stop_event.wait(1)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Error in retry loop: {e}")
|
self.logger.error(f"Error in retry loop: {e}")
|
||||||
self._stop_event.wait(5)
|
self._stop_event.wait(5)
|
||||||
@@ -280,20 +289,20 @@ class AutoRecordingManager:
|
|||||||
retry_info["attempt_count"] += 1
|
retry_info["attempt_count"] += 1
|
||||||
camera_config = retry_info["camera_config"]
|
camera_config = retry_info["camera_config"]
|
||||||
action = retry_info["action"]
|
action = retry_info["action"]
|
||||||
|
|
||||||
self.logger.info(f"Retry attempt {retry_info['attempt_count']}/{retry_info['max_retries']} for camera {camera_name} ({action})")
|
self.logger.info(f"Retry attempt {retry_info['attempt_count']}/{retry_info['max_retries']} for camera {camera_name} ({action})")
|
||||||
|
|
||||||
# Update camera status
|
# 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:
|
if camera_info:
|
||||||
camera_info.auto_recording_last_attempt = datetime.now()
|
camera_info.auto_recording_last_attempt = datetime.now()
|
||||||
camera_info.auto_recording_failure_count = retry_info["attempt_count"]
|
camera_info.auto_recording_failure_count = retry_info["attempt_count"]
|
||||||
|
|
||||||
# Attempt the action
|
# Attempt the action
|
||||||
success = False
|
success = False
|
||||||
if action == "start":
|
if action == "start":
|
||||||
success = self._start_recording_for_camera(camera_config)
|
success = self._start_recording_for_camera(camera_config)
|
||||||
|
|
||||||
if success:
|
if success:
|
||||||
# Success - remove from retry queue
|
# Success - remove from retry queue
|
||||||
with self._retry_lock:
|
with self._retry_lock:
|
||||||
@@ -307,10 +316,10 @@ class AutoRecordingManager:
|
|||||||
with self._retry_lock:
|
with self._retry_lock:
|
||||||
if camera_name in self._retry_queue:
|
if camera_name in self._retry_queue:
|
||||||
del self._retry_queue[camera_name]
|
del self._retry_queue[camera_name]
|
||||||
|
|
||||||
error_msg = f"Max retry attempts ({retry_info['max_retries']}) reached for camera {camera_name}"
|
error_msg = f"Max retry attempts ({retry_info['max_retries']}) reached for camera {camera_name}"
|
||||||
self.logger.error(error_msg)
|
self.logger.error(error_msg)
|
||||||
|
|
||||||
# Update camera status
|
# Update camera status
|
||||||
if camera_info:
|
if camera_info:
|
||||||
camera_info.auto_recording_last_error = error_msg
|
camera_info.auto_recording_last_error = error_msg
|
||||||
@@ -319,10 +328,10 @@ class AutoRecordingManager:
|
|||||||
# Schedule next retry
|
# Schedule next retry
|
||||||
retry_info["next_retry_time"] = datetime.now() + timedelta(seconds=camera_config.auto_recording_retry_delay_seconds)
|
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")
|
self.logger.info(f"Scheduling next retry for camera {camera_name} in {camera_config.auto_recording_retry_delay_seconds} seconds")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Error processing retry for camera {camera_name}: {e}")
|
self.logger.error(f"Error processing retry for camera {camera_name}: {e}")
|
||||||
|
|
||||||
# Remove from retry queue on error
|
# Remove from retry queue on error
|
||||||
with self._retry_lock:
|
with self._retry_lock:
|
||||||
if camera_name in self._retry_queue:
|
if camera_name in self._retry_queue:
|
||||||
@@ -331,22 +340,6 @@ class AutoRecordingManager:
|
|||||||
def get_status(self) -> Dict[str, Any]:
|
def get_status(self) -> Dict[str, Any]:
|
||||||
"""Get auto-recording manager status"""
|
"""Get auto-recording manager status"""
|
||||||
with self._retry_lock:
|
with self._retry_lock:
|
||||||
retry_queue_status = {
|
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()}
|
||||||
camera_name: {
|
|
||||||
"action": info["action"],
|
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]}
|
||||||
"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