Massive update - API and other modules added

This commit is contained in:
Alireza Vaezi
2025-07-25 21:39:07 -04:00
parent 172f46d44d
commit f6d6ba612e
70 changed files with 7276 additions and 15 deletions

View File

@@ -0,0 +1,328 @@
"""
State management for the USDA Vision Camera System.
This module manages the current state of machines, cameras, and recordings
in a thread-safe manner.
"""
import threading
import logging
from typing import Dict, Optional, List, Any
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
class MachineState(Enum):
"""Machine states"""
UNKNOWN = "unknown"
ON = "on"
OFF = "off"
ERROR = "error"
class CameraStatus(Enum):
"""Camera status"""
UNKNOWN = "unknown"
AVAILABLE = "available"
BUSY = "busy"
ERROR = "error"
DISCONNECTED = "disconnected"
class RecordingState(Enum):
"""Recording states"""
IDLE = "idle"
RECORDING = "recording"
STOPPING = "stopping"
ERROR = "error"
@dataclass
class MachineInfo:
"""Machine state information"""
name: str
state: MachineState = MachineState.UNKNOWN
last_updated: datetime = field(default_factory=datetime.now)
last_message: Optional[str] = None
mqtt_topic: Optional[str] = None
@dataclass
class CameraInfo:
"""Camera state information"""
name: str
status: CameraStatus = CameraStatus.UNKNOWN
last_checked: datetime = field(default_factory=datetime.now)
last_error: Optional[str] = None
device_info: Optional[Dict[str, Any]] = None
is_recording: bool = False
current_recording_file: Optional[str] = None
recording_start_time: Optional[datetime] = None
@dataclass
class RecordingInfo:
"""Recording session information"""
camera_name: str
filename: str
start_time: datetime
state: RecordingState = RecordingState.RECORDING
end_time: Optional[datetime] = None
file_size_bytes: Optional[int] = None
frame_count: Optional[int] = None
error_message: Optional[str] = None
class StateManager:
"""Thread-safe state manager for the entire system"""
def __init__(self):
self.logger = logging.getLogger(__name__)
self._lock = threading.RLock()
# State dictionaries
self._machines: Dict[str, MachineInfo] = {}
self._cameras: Dict[str, CameraInfo] = {}
self._recordings: Dict[str, RecordingInfo] = {} # Key: recording_id (filename)
# System state
self._mqtt_connected = False
self._system_started = False
self._last_mqtt_message_time: Optional[datetime] = None
# Machine state management
def update_machine_state(self, name: str, state: str, message: Optional[str] = None, topic: Optional[str] = None) -> bool:
"""Update machine state"""
try:
machine_state = MachineState(state.lower())
except ValueError:
self.logger.warning(f"Invalid machine state: {state}")
machine_state = MachineState.UNKNOWN
with self._lock:
if name not in self._machines:
self._machines[name] = MachineInfo(name=name, mqtt_topic=topic)
machine = self._machines[name]
old_state = machine.state
machine.state = machine_state
machine.last_updated = datetime.now()
machine.last_message = message
if topic:
machine.mqtt_topic = topic
self.logger.info(f"Machine {name} state: {old_state.value} -> {machine_state.value}")
return old_state != machine_state
def get_machine_state(self, name: str) -> Optional[MachineInfo]:
"""Get machine state"""
with self._lock:
return self._machines.get(name)
def get_all_machines(self) -> Dict[str, MachineInfo]:
"""Get all machine states"""
with self._lock:
return self._machines.copy()
# Camera state management
def update_camera_status(self, name: str, status: str, error: Optional[str] = None, device_info: Optional[Dict] = None) -> bool:
"""Update camera status"""
try:
camera_status = CameraStatus(status.lower())
except ValueError:
self.logger.warning(f"Invalid camera status: {status}")
camera_status = CameraStatus.UNKNOWN
with self._lock:
if name not in self._cameras:
self._cameras[name] = CameraInfo(name=name)
camera = self._cameras[name]
old_status = camera.status
camera.status = camera_status
camera.last_checked = datetime.now()
camera.last_error = error
if device_info:
camera.device_info = device_info
if old_status != camera_status:
self.logger.info(f"Camera {name} status: {old_status.value} -> {camera_status.value}")
return True
return False
def set_camera_recording(self, name: str, recording: bool, filename: Optional[str] = None) -> None:
"""Set camera recording state"""
with self._lock:
if name not in self._cameras:
self._cameras[name] = CameraInfo(name=name)
camera = self._cameras[name]
camera.is_recording = recording
camera.current_recording_file = filename
if recording and filename:
camera.recording_start_time = datetime.now()
self.logger.info(f"Camera {name} started recording: {filename}")
elif not recording:
camera.recording_start_time = None
self.logger.info(f"Camera {name} stopped recording")
def get_camera_status(self, name: str) -> Optional[CameraInfo]:
"""Get camera status"""
with self._lock:
return self._cameras.get(name)
def get_all_cameras(self) -> Dict[str, CameraInfo]:
"""Get all camera statuses"""
with self._lock:
return self._cameras.copy()
# Recording management
def start_recording(self, camera_name: str, filename: str) -> str:
"""Start a new recording session"""
recording_id = filename # Use filename as recording ID
with self._lock:
recording = RecordingInfo(
camera_name=camera_name,
filename=filename,
start_time=datetime.now()
)
self._recordings[recording_id] = recording
# Update camera state
self.set_camera_recording(camera_name, True, filename)
self.logger.info(f"Started recording session: {recording_id}")
return recording_id
def stop_recording(self, recording_id: str, file_size: Optional[int] = None, frame_count: Optional[int] = None) -> bool:
"""Stop a recording session"""
with self._lock:
if recording_id not in self._recordings:
self.logger.warning(f"Recording session not found: {recording_id}")
return False
recording = self._recordings[recording_id]
recording.state = RecordingState.IDLE
recording.end_time = datetime.now()
recording.file_size_bytes = file_size
recording.frame_count = frame_count
# Update camera state
self.set_camera_recording(recording.camera_name, False)
duration = (recording.end_time - recording.start_time).total_seconds()
self.logger.info(f"Stopped recording session: {recording_id} (duration: {duration:.1f}s)")
return True
def set_recording_error(self, recording_id: str, error_message: str) -> bool:
"""Set recording error state"""
with self._lock:
if recording_id not in self._recordings:
return False
recording = self._recordings[recording_id]
recording.state = RecordingState.ERROR
recording.error_message = error_message
recording.end_time = datetime.now()
# Update camera state
self.set_camera_recording(recording.camera_name, False)
self.logger.error(f"Recording error for {recording_id}: {error_message}")
return True
def get_recording(self, recording_id: str) -> Optional[RecordingInfo]:
"""Get recording information"""
with self._lock:
return self._recordings.get(recording_id)
def get_all_recordings(self) -> Dict[str, RecordingInfo]:
"""Get all recording sessions"""
with self._lock:
return self._recordings.copy()
def get_active_recordings(self) -> Dict[str, RecordingInfo]:
"""Get currently active recordings"""
with self._lock:
return {
rid: recording for rid, recording in self._recordings.items()
if recording.state == RecordingState.RECORDING
}
# System state management
def set_mqtt_connected(self, connected: bool) -> None:
"""Set MQTT connection state"""
with self._lock:
old_state = self._mqtt_connected
self._mqtt_connected = connected
if connected:
self._last_mqtt_message_time = datetime.now()
if old_state != connected:
self.logger.info(f"MQTT connection: {'connected' if connected else 'disconnected'}")
def is_mqtt_connected(self) -> bool:
"""Check if MQTT is connected"""
with self._lock:
return self._mqtt_connected
def update_mqtt_activity(self) -> None:
"""Update last MQTT message time"""
with self._lock:
self._last_mqtt_message_time = datetime.now()
def set_system_started(self, started: bool) -> None:
"""Set system started state"""
with self._lock:
self._system_started = started
self.logger.info(f"System {'started' if started else 'stopped'}")
def is_system_started(self) -> bool:
"""Check if system is started"""
with self._lock:
return self._system_started
# Utility methods
def get_system_summary(self) -> Dict[str, Any]:
"""Get a summary of the entire system state"""
with self._lock:
return {
"system_started": self._system_started,
"mqtt_connected": self._mqtt_connected,
"last_mqtt_message": self._last_mqtt_message_time.isoformat() if self._last_mqtt_message_time else None,
"machines": {name: {
"state": machine.state.value,
"last_updated": machine.last_updated.isoformat()
} for name, machine in self._machines.items()},
"cameras": {name: {
"status": camera.status.value,
"is_recording": camera.is_recording,
"last_checked": camera.last_checked.isoformat()
} for name, camera in self._cameras.items()},
"active_recordings": len(self.get_active_recordings()),
"total_recordings": len(self._recordings)
}
def cleanup_old_recordings(self, max_age_hours: int = 24) -> int:
"""Clean up old recording entries from memory"""
cutoff_time = datetime.now() - datetime.timedelta(hours=max_age_hours)
removed_count = 0
with self._lock:
to_remove = []
for recording_id, recording in self._recordings.items():
if (recording.state != RecordingState.RECORDING and
recording.end_time and recording.end_time < cutoff_time):
to_remove.append(recording_id)
for recording_id in to_remove:
del self._recordings[recording_id]
removed_count += 1
if removed_count > 0:
self.logger.info(f"Cleaned up {removed_count} old recording entries")
return removed_count