fix postgresql
This commit is contained in:
parent
80d9c925de
commit
85b49ddf0f
2 changed files with 1468 additions and 75 deletions
199
app.py
199
app.py
|
@ -600,22 +600,7 @@ async def detect(websocket: WebSocket):
|
||||||
"timestamp": time.time()
|
"timestamp": time.time()
|
||||||
}
|
}
|
||||||
|
|
||||||
# Cache the detection dict for lightweight mode reuse
|
# Note: Will cache detection_dict after branch processing completes
|
||||||
branch_results = detection_result.get("branch_results", {})
|
|
||||||
cached_dict = {
|
|
||||||
"carModel": branch_results.get("car_brand_cls_v1", {}).get("model"),
|
|
||||||
"carBrand": branch_results.get("car_brand_cls_v1", {}).get("brand"),
|
|
||||||
"carYear": None,
|
|
||||||
"bodyType": branch_results.get("car_bodytype_cls_v1", {}).get("body_type"),
|
|
||||||
"licensePlateText": None,
|
|
||||||
"licensePlateConfidence": None
|
|
||||||
}
|
|
||||||
pipeline_state["cached_detection_dict"] = cached_dict
|
|
||||||
|
|
||||||
# Log what was cached for debugging
|
|
||||||
logger.info(f"💾 Camera {camera_id}: CACHING DETECTION DICT:")
|
|
||||||
logger.info(f"💾 Camera {camera_id}: - Full branch_results: {branch_results}")
|
|
||||||
logger.info(f"💾 Camera {camera_id}: - Cached dict: {cached_dict}")
|
|
||||||
|
|
||||||
# Store the stable track ID for lightweight monitoring
|
# Store the stable track ID for lightweight monitoring
|
||||||
track_id = detection_result.get("track_id") or detection_result.get("id")
|
track_id = detection_result.get("track_id") or detection_result.get("id")
|
||||||
|
@ -768,27 +753,30 @@ async def detect(websocket: WebSocket):
|
||||||
detection_dict = None
|
detection_dict = None
|
||||||
logger.debug(f"📤 SENDING 'NONE' - send_detections mode (no car) for camera {camera_id}")
|
logger.debug(f"📤 SENDING 'NONE' - send_detections mode (no car) for camera {camera_id}")
|
||||||
else:
|
else:
|
||||||
# Car detected - check if we have sessionId to determine what to send
|
# Car detected in send_detections mode - ALWAYS send empty dict to trigger backend sessionId
|
||||||
|
# Purpose: Tell backend "car is here, please create sessionId"
|
||||||
|
detection_dict = {}
|
||||||
|
logger.info(f"📤 SENDING EMPTY DETECTION_DICT - send_detections mode, requesting backend to create sessionId (conf={detection_result.get('confidence', 0):.3f}) for camera {camera_id}")
|
||||||
|
|
||||||
if backend_session_id:
|
if backend_session_id:
|
||||||
# Have sessionId - send full detection_dict for database updates
|
logger.debug(f"🔄 Camera {camera_id}: Note - sessionId {backend_session_id} exists but still in send_detections mode (transition pending)")
|
||||||
detection_dict = {
|
|
||||||
"carModel": None,
|
|
||||||
"carBrand": None,
|
|
||||||
"carYear": None,
|
|
||||||
"bodyType": None,
|
|
||||||
"licensePlateText": None,
|
|
||||||
"licensePlateConfidence": None
|
|
||||||
}
|
|
||||||
logger.info(f"📤 SENDING FULL DETECTION_DICT - send_detections mode with sessionId {backend_session_id} (conf={detection_result.get('confidence', 0):.3f}) for camera {camera_id}")
|
|
||||||
else:
|
|
||||||
# No sessionId - send empty detection_dict to trigger backend to generate sessionId
|
|
||||||
detection_dict = {}
|
|
||||||
logger.info(f"📤 SENDING EMPTY DETECTION_DICT - send_detections mode without sessionId, triggering backend to generate sessionId (conf={detection_result.get('confidence', 0):.3f}) for camera {camera_id}")
|
|
||||||
|
|
||||||
elif detection_result.get("class") == "none":
|
elif detection_result.get("class") == "none":
|
||||||
# "None" detection in other modes (lightweight) - car left or absent for 3 frames
|
# "None" detection - skip override if lightweight mode already made the decision
|
||||||
detection_dict = None
|
if current_mode == "lightweight":
|
||||||
logger.info(f"📤 SENDING 'NONE' (detection: null) - Car absent, expecting backend to clear session for camera {camera_id}")
|
# Lightweight mode already set detection_result correctly, don't override
|
||||||
|
logger.debug(f"🪶 Camera {camera_id}: Lightweight mode - respecting detection_result decision")
|
||||||
|
if detection_result is None:
|
||||||
|
detection_dict = None
|
||||||
|
logger.info(f"📤 LIGHTWEIGHT SENDING 'NONE' - Reset conditions met for camera {camera_id}")
|
||||||
|
else:
|
||||||
|
# detection_result should be the cached_detection_dict
|
||||||
|
detection_dict = detection_result
|
||||||
|
logger.info(f"💾 LIGHTWEIGHT SENDING CACHED - Maintaining session for camera {camera_id}")
|
||||||
|
else:
|
||||||
|
# Other modes - send null to clear session
|
||||||
|
detection_dict = None
|
||||||
|
logger.info(f"📤 SENDING 'NONE' (detection: null) - Car absent, expecting backend to clear session for camera {camera_id}")
|
||||||
elif detection_result.get("cached_mode", False):
|
elif detection_result.get("cached_mode", False):
|
||||||
# Cached mode in lightweight - use cached detection dict directly
|
# Cached mode in lightweight - use cached detection dict directly
|
||||||
cached_dict = detection_result.get("branch_results", {})
|
cached_dict = detection_result.get("branch_results", {})
|
||||||
|
@ -800,10 +788,11 @@ async def detect(websocket: WebSocket):
|
||||||
"licensePlateText": None,
|
"licensePlateText": None,
|
||||||
"licensePlateConfidence": None
|
"licensePlateConfidence": None
|
||||||
}
|
}
|
||||||
logger.info(f"💾 Camera {camera_id}: SENDING CACHED DETECTION_DICT to backend:")
|
elif detection_result and "carBrand" in detection_result:
|
||||||
logger.info(f"💾 Camera {camera_id}: - Cached branch_results: {cached_dict}")
|
# Lightweight mode - detection_result IS the cached detection dict
|
||||||
logger.info(f"💾 Camera {camera_id}: - Final detection_dict: {detection_dict}")
|
detection_dict = detection_result
|
||||||
logger.info(f"💾 Camera {camera_id}: - Track ID: {detection_result.get('track_id')} (lightweight mode)")
|
logger.info(f"💾 Camera {camera_id}: LIGHTWEIGHT MODE - using detection_result as detection_dict:")
|
||||||
|
logger.info(f"💾 Camera {camera_id}: - detection_dict: {detection_dict}")
|
||||||
else:
|
else:
|
||||||
# Valid detection - convert to backend format (will be populated by branch processing)
|
# Valid detection - convert to backend format (will be populated by branch processing)
|
||||||
detection_dict = {
|
detection_dict = {
|
||||||
|
@ -855,6 +844,13 @@ async def detect(websocket: WebSocket):
|
||||||
logger.debug(f"Processing branch results: {branch_results}")
|
logger.debug(f"Processing branch results: {branch_results}")
|
||||||
process_branch_results(branch_results)
|
process_branch_results(branch_results)
|
||||||
logger.info(f"Detection payload after branch processing: {detection_dict}")
|
logger.info(f"Detection payload after branch processing: {detection_dict}")
|
||||||
|
|
||||||
|
# Cache the detection_dict for lightweight mode (after branch processing completes)
|
||||||
|
if current_mode == "full_pipeline":
|
||||||
|
pipeline_state = get_or_init_session_pipeline_state(camera_id)
|
||||||
|
pipeline_state["cached_detection_dict"] = detection_dict.copy()
|
||||||
|
logger.info(f"💾 Camera {camera_id}: CACHED DETECTION DICT after branch processing: {detection_dict}")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logger.debug("No branch results found in detection result")
|
logger.debug("No branch results found in detection result")
|
||||||
|
|
||||||
|
@ -870,17 +866,17 @@ async def detect(websocket: WebSocket):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Add session ID to detection data (NOT for "none" detections - backend uses absence of sessionId to know to clear the session)
|
# SessionId should NEVER be sent from worker to backend - it's uni-directional (backend -> worker only)
|
||||||
if session_id and detection_result.get("class") != "none":
|
# Backend manages sessionIds independently based on detection content
|
||||||
detection_data["sessionId"] = session_id
|
logger.debug(f"TX message prepared (no sessionId) - detection_dict type: {type(detection_dict)}")
|
||||||
logger.debug(f"Including sessionId {session_id} in WebSocket message")
|
|
||||||
elif detection_result.get("class") == "none":
|
|
||||||
logger.debug(f"NOT including sessionId in 'none' detection - backend should clear session")
|
|
||||||
|
|
||||||
# Log detection details
|
# Log detection details
|
||||||
if detection_result.get("class") != "none":
|
if detection_result and "class" in detection_result and detection_result.get("class") != "none":
|
||||||
confidence = detection_result.get("confidence", 0.0)
|
confidence = detection_result.get("confidence", 0.0)
|
||||||
logger.info(f"Camera {camera_id}: Detected {detection_result['class']} with confidence {confidence:.2f} using model {stream['modelName']}")
|
logger.info(f"Camera {camera_id}: Detected {detection_result['class']} with confidence {confidence:.2f} using model {stream['modelName']}")
|
||||||
|
elif detection_result and "carBrand" in detection_result:
|
||||||
|
# Lightweight mode cached detection dict - different format
|
||||||
|
logger.info(f"Camera {camera_id}: Using cached detection dict (lightweight mode) - {detection_result.get('carBrand', 'Unknown')} {detection_result.get('bodyType', '')}")
|
||||||
|
|
||||||
# Send detection data to backend (session gating handled above in processing logic)
|
# Send detection data to backend (session gating handled above in processing logic)
|
||||||
logger.debug(f"📤 SENDING TO BACKEND for camera {camera_id}: {json.dumps(detection_data, indent=2)}")
|
logger.debug(f"📤 SENDING TO BACKEND for camera {camera_id}: {json.dumps(detection_data, indent=2)}")
|
||||||
|
@ -888,6 +884,16 @@ async def detect(websocket: WebSocket):
|
||||||
ws_logger.info(f"TX -> {json.dumps(detection_data, separators=(',', ':'))}")
|
ws_logger.info(f"TX -> {json.dumps(detection_data, separators=(',', ':'))}")
|
||||||
await websocket.send_json(detection_data)
|
await websocket.send_json(detection_data)
|
||||||
logger.debug(f"Sent detection data to client for camera {camera_id}")
|
logger.debug(f"Sent detection data to client for camera {camera_id}")
|
||||||
|
|
||||||
|
# Cache the detection data for potential resubscriptions (only if not null detection)
|
||||||
|
if detection_dict is not None and detection_result.get("class") != "none":
|
||||||
|
cached_detections[camera_id] = detection_data.copy()
|
||||||
|
logger.debug(f"Cached detection for camera {camera_id}: {detection_dict}")
|
||||||
|
else:
|
||||||
|
# Don't cache null/none detections - let them reset properly
|
||||||
|
cached_detections.pop(camera_id, None)
|
||||||
|
logger.debug(f"Not caching null/none detection for camera {camera_id}")
|
||||||
|
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
if "websocket.close" in str(e):
|
if "websocket.close" in str(e):
|
||||||
logger.warning(f"WebSocket connection closed - cannot send detection data for camera {camera_id}")
|
logger.warning(f"WebSocket connection closed - cannot send detection data for camera {camera_id}")
|
||||||
|
@ -895,13 +901,13 @@ async def detect(websocket: WebSocket):
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
# Log status after sending
|
# Log status after sending (no sessionId sent to backend)
|
||||||
if session_id and detection_result.get("class") != "none":
|
if detection_dict is None:
|
||||||
logger.info(f"📤 WEBSOCKET RESPONSE with sessionId: {session_id} for camera {camera_id}")
|
|
||||||
elif detection_result.get("class") == "none":
|
|
||||||
logger.info(f"📡 SENT 'none' detection - backend should clear session for camera {camera_id}")
|
logger.info(f"📡 SENT 'none' detection - backend should clear session for camera {camera_id}")
|
||||||
|
elif detection_dict == {}:
|
||||||
|
logger.info(f"📡 SENT empty detection - backend should create sessionId for camera {camera_id}")
|
||||||
else:
|
else:
|
||||||
logger.info(f"📡 Detection data sent for camera {camera_id}")
|
logger.info(f"📡 SENT detection data - backend manages sessionId independently for camera {camera_id}")
|
||||||
return persistent_data
|
return persistent_data
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in handle_detection for camera {camera_id}: {str(e)}", exc_info=True)
|
logger.error(f"Error in handle_detection for camera {camera_id}: {str(e)}", exc_info=True)
|
||||||
|
@ -1145,16 +1151,46 @@ async def detect(websocket: WebSocket):
|
||||||
# Check if parameters changed
|
# Check if parameters changed
|
||||||
if has_subscription_changed(desired_sub, current_stream):
|
if has_subscription_changed(desired_sub, current_stream):
|
||||||
logger.info(f"Parameters changed for {subscription_id}, resubscribing")
|
logger.info(f"Parameters changed for {subscription_id}, resubscribing")
|
||||||
await unsubscribe_internal(subscription_id)
|
logger.debug(f"Parameter comparison for {subscription_id}:")
|
||||||
await subscribe_internal(desired_sub, websocket)
|
logger.debug(f" rtspUrl: '{desired_sub.get('rtspUrl')}' vs '{current_stream.get('rtsp_url')}'")
|
||||||
|
logger.debug(f" snapshotUrl: '{desired_sub.get('snapshotUrl')}' vs '{current_stream.get('snapshot_url')}'")
|
||||||
|
logger.debug(f" modelUrl: '{extract_model_file_identifier(desired_sub.get('modelUrl'))}' vs '{extract_model_file_identifier(current_stream.get('modelUrl'))}'")
|
||||||
|
logger.debug(f" modelId: {desired_sub.get('modelId')} vs {current_stream.get('modelId')}")
|
||||||
|
|
||||||
|
# Preserve detection state for resubscription
|
||||||
|
cached_detection = cached_detections.get(subscription_id)
|
||||||
|
logger.debug(f"Preserving detection state for resubscription: {cached_detection is not None}")
|
||||||
|
|
||||||
|
await unsubscribe_internal(subscription_id, preserve_detection=True)
|
||||||
|
await subscribe_internal(desired_sub, websocket, cached_detection=cached_detection)
|
||||||
|
|
||||||
# Add new subscriptions
|
# Add new subscriptions
|
||||||
for subscription_id in to_add:
|
for subscription_id in to_add:
|
||||||
desired_sub = next(sub for sub in desired_subscriptions if sub["subscriptionIdentifier"] == subscription_id)
|
desired_sub = next(sub for sub in desired_subscriptions if sub["subscriptionIdentifier"] == subscription_id)
|
||||||
await subscribe_internal(desired_sub, websocket)
|
await subscribe_internal(desired_sub, websocket)
|
||||||
|
|
||||||
|
def extract_model_file_identifier(model_url):
|
||||||
|
"""Extract the core model file identifier from S3 URLs, ignoring timestamp parameters"""
|
||||||
|
if not model_url:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# For S3 URLs, extract just the path portion before query parameters
|
||||||
|
try:
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
parsed = urlparse(model_url)
|
||||||
|
# Return the path which contains the actual model file identifier
|
||||||
|
# e.g. "/adsist-cms-staging/models/bangchak_poc-1756312318569.mpta"
|
||||||
|
return parsed.path
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Failed to parse model URL {model_url}: {e}")
|
||||||
|
return model_url
|
||||||
|
|
||||||
def has_subscription_changed(desired_sub, current_stream):
|
def has_subscription_changed(desired_sub, current_stream):
|
||||||
"""Check if subscription parameters have changed"""
|
"""Check if subscription parameters have changed"""
|
||||||
|
# Smart model URL comparison - ignore timestamp changes in signed URLs
|
||||||
|
desired_model_id = extract_model_file_identifier(desired_sub.get("modelUrl"))
|
||||||
|
current_model_id = extract_model_file_identifier(current_stream.get("modelUrl"))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
desired_sub.get("rtspUrl") != current_stream.get("rtsp_url") or
|
desired_sub.get("rtspUrl") != current_stream.get("rtsp_url") or
|
||||||
desired_sub.get("snapshotUrl") != current_stream.get("snapshot_url") or
|
desired_sub.get("snapshotUrl") != current_stream.get("snapshot_url") or
|
||||||
|
@ -1164,10 +1200,11 @@ async def detect(websocket: WebSocket):
|
||||||
desired_sub.get("cropX2") != current_stream.get("cropX2") or
|
desired_sub.get("cropX2") != current_stream.get("cropX2") or
|
||||||
desired_sub.get("cropY2") != current_stream.get("cropY2") or
|
desired_sub.get("cropY2") != current_stream.get("cropY2") or
|
||||||
desired_sub.get("modelId") != current_stream.get("modelId") or
|
desired_sub.get("modelId") != current_stream.get("modelId") or
|
||||||
desired_sub.get("modelName") != current_stream.get("modelName")
|
desired_sub.get("modelName") != current_stream.get("modelName") or
|
||||||
|
desired_model_id != current_model_id
|
||||||
)
|
)
|
||||||
|
|
||||||
async def subscribe_internal(subscription, websocket):
|
async def subscribe_internal(subscription, websocket, cached_detection=None):
|
||||||
"""Internal subscription logic extracted from original subscribe handler"""
|
"""Internal subscription logic extracted from original subscribe handler"""
|
||||||
subscriptionIdentifier = subscription.get("subscriptionIdentifier")
|
subscriptionIdentifier = subscription.get("subscriptionIdentifier")
|
||||||
rtsp_url = subscription.get("rtspUrl")
|
rtsp_url = subscription.get("rtspUrl")
|
||||||
|
@ -1274,38 +1311,49 @@ async def detect(websocket: WebSocket):
|
||||||
"buffer": buffer, "thread": thread, "stop_event": stop_event,
|
"buffer": buffer, "thread": thread, "stop_event": stop_event,
|
||||||
"modelId": modelId, "modelName": modelName, "subscriptionIdentifier": subscriptionIdentifier,
|
"modelId": modelId, "modelName": modelName, "subscriptionIdentifier": subscriptionIdentifier,
|
||||||
"cropX1": cropX1, "cropY1": cropY1, "cropX2": cropX2, "cropY2": cropY2,
|
"cropX1": cropX1, "cropY1": cropY1, "cropX2": cropX2, "cropY2": cropY2,
|
||||||
"mode": mode, "camera_url": camera_url, "modelUrl": model_url
|
"mode": mode, "camera_url": camera_url, "modelUrl": model_url,
|
||||||
|
# Always store both URLs for comparison consistency
|
||||||
|
"rtsp_url": rtsp_url,
|
||||||
|
"snapshot_url": snapshot_url,
|
||||||
|
"snapshot_interval": snapshot_interval
|
||||||
}
|
}
|
||||||
|
|
||||||
if mode == "snapshot":
|
if mode == "rtsp":
|
||||||
stream_info["snapshot_url"] = snapshot_url
|
|
||||||
stream_info["snapshot_interval"] = snapshot_interval
|
|
||||||
elif mode == "rtsp":
|
|
||||||
stream_info["rtsp_url"] = rtsp_url
|
|
||||||
stream_info["cap"] = shared_stream["cap"]
|
stream_info["cap"] = shared_stream["cap"]
|
||||||
|
|
||||||
streams[camera_id] = stream_info
|
streams[camera_id] = stream_info
|
||||||
subscription_to_camera[camera_id] = camera_url
|
subscription_to_camera[camera_id] = camera_url
|
||||||
logger.info(f"Subscribed to camera {camera_id}")
|
logger.info(f"Subscribed to camera {camera_id}")
|
||||||
|
|
||||||
# Send initial "none" detection to backend on camera connect
|
# Send initial detection to backend - use cached if available, otherwise "none"
|
||||||
initial_detection_data = {
|
if cached_detection:
|
||||||
"type": "imageDetection",
|
# Restore cached detection with updated timestamp (RESUBSCRIPTION STATUS UPDATE)
|
||||||
"subscriptionIdentifier": subscriptionIdentifier,
|
initial_detection_data = cached_detection.copy()
|
||||||
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
initial_detection_data["timestamp"] = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
||||||
"data": {
|
logger.info(f"📡 RESUBSCRIPTION: Restoring cached detection for camera {camera_id}")
|
||||||
"detection": None,
|
logger.debug(f"📡 RESUBSCRIPTION: Cached detection has sessionId: {initial_detection_data.get('sessionId', 'None')}")
|
||||||
"modelId": modelId,
|
else:
|
||||||
"modelName": modelName
|
# Send "none" detection for new subscriptions
|
||||||
|
initial_detection_data = {
|
||||||
|
"type": "imageDetection",
|
||||||
|
"subscriptionIdentifier": subscriptionIdentifier,
|
||||||
|
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
||||||
|
"data": {
|
||||||
|
"detection": None,
|
||||||
|
"modelId": modelId,
|
||||||
|
"modelName": modelName
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
logger.info(f"📡 NEW SUBSCRIPTION: Sending initial 'none' detection for camera {camera_id}")
|
||||||
|
|
||||||
ws_logger.info(f"TX -> {json.dumps(initial_detection_data, separators=(',', ':'))}")
|
ws_logger.info(f"TX -> {json.dumps(initial_detection_data, separators=(',', ':'))}")
|
||||||
await websocket.send_json(initial_detection_data)
|
await websocket.send_json(initial_detection_data)
|
||||||
logger.info(f"📡 Sent initial 'none' detection to backend for camera {camera_id}")
|
logger.debug(f"Initial detection data sent (resubscription={cached_detection is not None}): {initial_detection_data}")
|
||||||
logger.debug(f"Initial detection data: {initial_detection_data}")
|
|
||||||
|
|
||||||
async def unsubscribe_internal(subscription_id):
|
# This cached detection was just a one-time status update for resubscription
|
||||||
|
# Normal frame processing will continue independently
|
||||||
|
|
||||||
|
async def unsubscribe_internal(subscription_id, preserve_detection=False):
|
||||||
"""Internal unsubscription logic"""
|
"""Internal unsubscription logic"""
|
||||||
if subscription_id in streams:
|
if subscription_id in streams:
|
||||||
stream = streams.pop(subscription_id)
|
stream = streams.pop(subscription_id)
|
||||||
|
@ -1323,13 +1371,14 @@ async def detect(websocket: WebSocket):
|
||||||
del camera_streams[camera_url]
|
del camera_streams[camera_url]
|
||||||
|
|
||||||
latest_frames.pop(subscription_id, None)
|
latest_frames.pop(subscription_id, None)
|
||||||
cached_detections.pop(subscription_id, None) # Clear cached detection
|
if not preserve_detection:
|
||||||
|
cached_detections.pop(subscription_id, None) # Clear cached detection only if not preserving
|
||||||
frame_skip_flags.pop(subscription_id, None) # Clear frame skip flag
|
frame_skip_flags.pop(subscription_id, None) # Clear frame skip flag
|
||||||
camera_states.pop(subscription_id, None) # Clear camera state
|
camera_states.pop(subscription_id, None) # Clear camera state
|
||||||
cached_full_pipeline_results.pop(subscription_id, None) # Clear cached pipeline results
|
cached_full_pipeline_results.pop(subscription_id, None) # Clear cached pipeline results
|
||||||
session_pipeline_states.pop(subscription_id, None) # Clear session pipeline state
|
session_pipeline_states.pop(subscription_id, None) # Clear session pipeline state
|
||||||
cleanup_camera_stability(subscription_id)
|
cleanup_camera_stability(subscription_id)
|
||||||
logger.info(f"Unsubscribed from camera {subscription_id}")
|
logger.info(f"Unsubscribed from camera {subscription_id} (preserve_detection={preserve_detection})")
|
||||||
|
|
||||||
async def process_streams():
|
async def process_streams():
|
||||||
logger.info("Started processing streams")
|
logger.info("Started processing streams")
|
||||||
|
|
1344
websocket_comm.log
1344
websocket_comm.log
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue