update sessionID backend

This commit is contained in:
Pongsatorn 2025-08-23 18:38:50 +07:00
parent 5873945115
commit 07eddd3f0d
2 changed files with 137 additions and 81 deletions

View file

@ -22,8 +22,8 @@ logger = logging.getLogger("detector_worker.pympta")
# Structure: {camera_id: {model_id: {"track_stability_counters": {track_id: count}, "stable_tracks": set(), "session_state": {...}}}}
_camera_stability_tracking = {}
# Timer-based cooldown configuration (for testing)
_cooldown_duration_seconds = 30
# Session timeout configuration (waiting for backend sessionId)
_session_timeout_seconds = 15
def validate_redis_config(redis_config: dict) -> bool:
"""Validate Redis configuration parameters."""
@ -759,7 +759,8 @@ def get_camera_stability_data(camera_id, model_id):
"stable_tracks": set(),
"session_state": {
"active": True,
"cooldown_until": 0.0,
"waiting_for_backend_session": False,
"wait_start_time": 0.0,
"reset_tracker_on_resume": False
}
}
@ -822,70 +823,50 @@ def check_stable_tracks(camera_id, model_id, regions_dict):
has_stable_tracks = len(stable_detections) > 0
return has_stable_tracks, stable_detections
def start_cooldown_timer(camera_id, model_id):
"""Start 30-second cooldown timer after successful pipeline completion."""
def reset_tracking_state(camera_id, model_id, reason="session ended"):
"""Reset tracking state after session completion or timeout."""
stability_data = get_camera_stability_data(camera_id, model_id)
session_state = stability_data["session_state"]
# Start timer-based cooldown
cooldown_until = time.time() + _cooldown_duration_seconds
session_state["cooldown_until"] = cooldown_until
session_state["active"] = False
session_state["reset_tracker_on_resume"] = True # Flag to reset YOLO tracker
# Clear all tracking data for fresh start
stability_data["track_stability_counters"].clear()
stability_data["stable_tracks"].clear()
session_state["active"] = True
session_state["waiting_for_backend_session"] = False
session_state["wait_start_time"] = 0.0
session_state["reset_tracker_on_resume"] = True
logger.info(f"Camera {camera_id}: 🛑 Starting {_cooldown_duration_seconds}s cooldown timer (until: {cooldown_until:.2f})")
# DO NOT clear tracking state here - preserve it during cooldown
# Tracking state will be cleared when cooldown expires and new session starts
logger.info(f"Camera {camera_id}: 🔄 Reset tracking state - {reason}")
logger.info(f"Camera {camera_id}: 🧹 Cleared stability counters and stable tracks for fresh session")
def is_camera_active(camera_id, model_id):
"""Check if camera should be processing detections (timer-based cooldown)."""
"""Check if camera should be processing detections."""
stability_data = get_camera_stability_data(camera_id, model_id)
session_state = stability_data["session_state"]
# Check if cooldown timer has expired
if not session_state["active"]:
# Check if waiting for backend sessionId has timed out
if session_state.get("waiting_for_backend_session", False):
current_time = time.time()
cooldown_until = session_state["cooldown_until"]
remaining_time = cooldown_until - current_time
wait_start_time = session_state.get("wait_start_time", 0)
elapsed_time = current_time - wait_start_time
if current_time >= cooldown_until:
session_state["active"] = True
session_state["reset_tracker_on_resume"] = True # Ensure tracker reset flag is set
# Clear tracking state NOW - before new detection session starts
stability_data["track_stability_counters"].clear()
stability_data["stable_tracks"].clear()
logger.info(f"Camera {camera_id}: 📢 Cooldown timer ended, resuming detection with fresh track IDs")
logger.info(f"Camera {camera_id}: 🧹 Cleared stability counters and stable tracks for fresh session")
if elapsed_time >= _session_timeout_seconds:
logger.warning(f"Camera {camera_id}: Backend sessionId timeout ({_session_timeout_seconds}s) - resetting tracking")
reset_tracking_state(camera_id, model_id, "backend sessionId timeout")
return True
else:
logger.debug(f"Camera {camera_id}: Still in cooldown - {remaining_time:.1f}s remaining")
remaining_time = _session_timeout_seconds - elapsed_time
logger.debug(f"Camera {camera_id}: Still waiting for backend sessionId - {remaining_time:.1f}s remaining")
return False
return session_state["active"]
return session_state.get("active", True)
def cleanup_camera_stability(camera_id):
"""Clean up stability tracking data when a camera is disconnected, preserving cooldown timers."""
"""Clean up stability tracking data when a camera is disconnected."""
global _camera_stability_tracking
if camera_id in _camera_stability_tracking:
# Check if any models are still in cooldown before cleanup
models_in_cooldown = []
for model_id, model_data in _camera_stability_tracking[camera_id].items():
session_state = model_data.get("session_state", {})
if not session_state.get("active", True) and time.time() < session_state.get("cooldown_until", 0):
cooldown_remaining = session_state["cooldown_until"] - time.time()
models_in_cooldown.append((model_id, cooldown_remaining))
logger.warning(f"⚠️ Camera {camera_id}: Model {model_id} is in cooldown ({cooldown_remaining:.1f}s remaining) - preserving timer!")
if models_in_cooldown:
# DO NOT clear any tracking data during cooldown - preserve everything
logger.warning(f"⚠️ Camera {camera_id}: PRESERVING ALL data during cooldown - no cleanup performed!")
logger.warning(f" - Track IDs will reset only AFTER cooldown expires")
logger.warning(f" - Stability counters preserved until cooldown ends")
else:
# Safe to delete everything - no active cooldowns
del _camera_stability_tracking[camera_id]
logger.info(f"Cleaned up stability tracking data for camera {camera_id} (no active cooldowns)")
del _camera_stability_tracking[camera_id]
logger.info(f"Cleaned up stability tracking data for camera {camera_id}")
def validate_pipeline_execution(node, regions_dict):
"""
@ -955,6 +936,16 @@ def run_pipeline(frame, node: dict, return_bbox: bool=False, context=None):
- Context passing for session/camera information
"""
try:
# Extract backend sessionId from context at the start of function
backend_session_id = context.get("backend_session_id") if context else None
camera_id = context.get("camera_id", "unknown") if context else "unknown"
model_id = node.get("modelId", "unknown")
if backend_session_id:
logger.info(f"🔑 PIPELINE USING BACKEND SESSION_ID: {backend_session_id} for camera {camera_id}")
else:
logger.debug(f"❌ No backend session_id in pipeline context for camera {camera_id}")
task = getattr(node["model"], "task", None)
# ─── Classification stage ───────────────────────────────────
@ -992,11 +983,8 @@ def run_pipeline(frame, node: dict, return_bbox: bool=False, context=None):
return (det, None) if return_bbox else det
# ─── Session management check ───────────────────────────────────────
camera_id = context.get("camera_id", "unknown") if context else "unknown"
model_id = node.get("modelId", "unknown")
if not is_camera_active(camera_id, model_id):
logger.info(f"⏰ Camera {camera_id}: Tracker stopped - in cooldown period, skipping all detection")
logger.debug(f"⏰ Camera {camera_id}: Waiting for backend sessionId, skipping pipeline")
return (None, None) if return_bbox else None
# ─── Detection stage - Using structured detection function ──────────────────
@ -1026,7 +1014,27 @@ def run_pipeline(frame, node: dict, return_bbox: bool=False, context=None):
primary_bbox = primary_detection.get("bbox", [0, 0, 0, 0])
return (primary_detection, primary_bbox) if return_bbox else primary_detection
else:
logger.info(f"Camera {camera_id}: Stable tracks {[det[1] for det in stable_detections]} detected - proceeding with full pipeline")
logger.info(f"Camera {camera_id}: Stable tracks {[det[1] for det in stable_detections]} detected - checking backend sessionId")
# Check if we need to wait for backend sessionId
if not backend_session_id:
logger.info(f"Camera {camera_id}: Stable car detected, waiting for backend sessionId...")
stability_data = get_camera_stability_data(camera_id, model_id)
session_state = stability_data["session_state"]
if not session_state.get("waiting_for_backend_session", False):
# Start waiting for backend sessionId
session_state["waiting_for_backend_session"] = True
session_state["wait_start_time"] = time.time()
logger.info(f"⏳ Camera {camera_id}: WAITING FOR BACKEND SESSION_ID (timeout: {_session_timeout_seconds}s)")
logger.info(f"📡 Stable car detected - sending imageDetection to trigger backend session creation")
# Return detection to signal backend, but don't proceed with pipeline
primary_detection = max(all_detections, key=lambda x: x["confidence"]) if all_detections else {"class": "none", "confidence": 0.0, "bbox": [0, 0, 0, 0]}
primary_bbox = primary_detection.get("bbox", [0, 0, 0, 0])
return (primary_detection, primary_bbox) if return_bbox else primary_detection
logger.info(f"🚀 Camera {camera_id}: BACKEND SESSION_ID AVAILABLE ({backend_session_id}) - proceeding with full pipeline")
# ─── Pre-validate pipeline execution ────────────────────────
pipeline_valid, missing_branches = validate_pipeline_execution(node, regions_dict)
@ -1043,36 +1051,40 @@ def run_pipeline(frame, node: dict, return_bbox: bool=False, context=None):
**(context or {})
}
# ─── Create initial database record when valid detection found ────
# ─── Check backend sessionId before database operations ────
if node.get("db_manager") and regions_dict:
# Create database record if we have any valid detection (after class mapping and filtering)
detected_classes = list(regions_dict.keys())
logger.debug(f"Valid detections found for database record: {detected_classes}")
logger.debug(f"Valid detections found - checking for backend sessionId: {detected_classes}")
# Always create record if we have valid detections that passed all filters
if not backend_session_id:
logger.error(f"🚫 Camera {camera_id}: No backend sessionId available - cannot proceed with database operations")
logger.error(f"🚫 Camera {camera_id}: Pipeline requires backend sessionId for Redis/PostgreSQL operations")
# Reset tracking and wait for new stable car
reset_tracking_state(camera_id, model_id, "missing backend sessionId")
return (None, None) if return_bbox else None
# Use backend sessionId for database operations
if detected_classes:
# Generate UUID session_id since client session is None for now
import uuid as uuid_lib
from datetime import datetime
generated_session_id = str(uuid_lib.uuid4())
# Insert initial detection record
display_id = detection_result.get("display_id", "unknown")
timestamp = datetime.now().strftime("%Y-%m-%dT%H-%M-%S")
inserted_session_id = node["db_manager"].insert_initial_detection(
display_id=display_id,
captured_timestamp=timestamp,
session_id=generated_session_id
session_id=backend_session_id # Use backend sessionId
)
if inserted_session_id:
# Update detection_result with the generated session_id for actions and branches
detection_result["session_id"] = inserted_session_id
detection_result["timestamp"] = timestamp # Update with proper timestamp
logger.info(f"Created initial database record with session_id: {inserted_session_id}")
else:
logger.debug("Database record not created - no valid detections found after filtering")
detection_result["timestamp"] = timestamp
logger.info(f"💾 DATABASE RECORD CREATED with backend session_id: {inserted_session_id}")
logger.debug(f"Database record: display_id={display_id}, timestamp={timestamp}")
else:
logger.error(f"Failed to create database record with backend session_id: {backend_session_id}")
reset_tracking_state(camera_id, model_id, "database insertion failed")
return (None, None) if return_bbox else None
# Execute actions for root node only if it doesn't have branches
# Branch nodes with actions will execute them after branch processing
@ -1194,9 +1206,8 @@ def run_pipeline(frame, node: dict, return_bbox: bool=False, context=None):
if node.get("parallelActions") and "branch_results" in detection_result:
execute_parallel_actions(node, frame, detection_result, regions_dict)
# ─── Start 30s cooldown timer after successful pipeline completion ─────────────────
start_cooldown_timer(camera_id, model_id)
logger.info(f"Camera {camera_id}: Pipeline completed successfully, starting 30s cooldown")
# ─── Note: Tracking will be reset when backend sends setSessionId: null ─────────────────
logger.info(f"Camera {camera_id}: Pipeline completed successfully - waiting for backend to end session")
# ─── Execute actions after successful detection AND branch processing ──────────
# This ensures detection nodes (like frontal_detection_v1) execute their actions
@ -1220,5 +1231,6 @@ def run_pipeline(frame, node: dict, return_bbox: bool=False, context=None):
except Exception as e:
logger.error(f"Error in node {node.get('modelId')}: {e}")
import traceback
traceback.print_exc()
return (None, None) if return_bbox else None