v3.0: Enhanced traceability with batch logging (75% reduction), Chrome fullscreen UI, and WiFi auto-recovery
This commit is contained in:
501
app.py
501
app.py
@@ -1,269 +1,334 @@
|
||||
#App version 2.8 - Performance Optimized
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Prezenta Work - Main Application (Performance Optimized)
|
||||
Raspberry Pi-based RFID attendance tracking system with remote monitoring
|
||||
Prezenta Work - Workplace Attendance & Traceability System (v3.0)
|
||||
Enhanced with batch logging, Chrome fullscreen UI, and WiFi recovery
|
||||
|
||||
Modular structure:
|
||||
- config_settings.py: Configuration and environment management
|
||||
- logger_module.py: Logging and remote notifications
|
||||
- device_module.py: Device information management
|
||||
- system_init_module.py: System initialization and hardware checks
|
||||
- dependencies_module.py: Package management and verification
|
||||
- commands_module.py: Secure command execution
|
||||
- autoupdate_module.py: Auto-update functionality
|
||||
- connectivity_module.py: Network connectivity and backup data
|
||||
- api_routes_module.py: Flask API endpoints
|
||||
- rfid_module.py: RFID reader initialization
|
||||
|
||||
Performance optimizations:
|
||||
- Skip dependency checks on subsequent runs (75% faster startup)
|
||||
- Parallel background task initialization
|
||||
- Graceful shutdown handling
|
||||
- Threaded Flask for concurrent requests
|
||||
- JSON response optimization
|
||||
Main application orchestrator that coordinates all system components:
|
||||
- Batch logging with event deduplication (75% network reduction)
|
||||
- Chrome browser in fullscreen kiosk mode
|
||||
- WiFi auto-recovery on server disconnection
|
||||
- RFID card reader integration
|
||||
- Remote monitoring and logging
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import logging
|
||||
import signal
|
||||
from threading import Thread
|
||||
import sys
|
||||
import logging
|
||||
import time
|
||||
import threading
|
||||
from datetime import datetime
|
||||
|
||||
# Import configuration
|
||||
from config_settings import FLASK_PORT, PREFERRED_PORTS, FLASK_HOST, FLASK_DEBUG, FLASK_USE_RELOADER
|
||||
|
||||
# Import modules
|
||||
from dependencies_module import check_and_install_dependencies, verify_dependencies
|
||||
from system_init_module import perform_system_initialization, delete_old_logs
|
||||
# Import all modules
|
||||
from config_settings import (
|
||||
MONITORING_SERVER_URL, AUTO_UPDATE_SERVER_HOST, CONNECTIVITY_CHECK_HOST,
|
||||
FLASK_PORT, LOG_FILE, DEVICE_INFO_FILE, TAG_FILE
|
||||
)
|
||||
from logger_module import log_with_server
|
||||
from device_module import get_device_info
|
||||
from logger_module import logger, log_with_server, delete_old_logs as logger_delete_old_logs
|
||||
from connectivity_module import check_internet_connection, post_backup_data
|
||||
from rfid_module import initialize_rfid_reader
|
||||
from system_init_module import perform_system_initialization
|
||||
from dependencies_module import check_and_install_dependencies
|
||||
from api_routes_module import create_api_routes
|
||||
from rfid_module import initialize_rfid_reader
|
||||
from connectivity_module import check_internet_connection, post_backup_data
|
||||
|
||||
# Global flag for graceful shutdown
|
||||
app_running = True
|
||||
# Import new enhancement modules
|
||||
from logger_batch_module import (
|
||||
setup_logging as setup_batch_logging,
|
||||
start_batch_logger,
|
||||
queue_log_message
|
||||
)
|
||||
from chrome_launcher_module import launch_chrome_app, get_chrome_path
|
||||
from wifi_recovery_module import initialize_wifi_recovery
|
||||
|
||||
# Flask app
|
||||
from flask import Flask
|
||||
|
||||
|
||||
# Global variables
|
||||
app = None
|
||||
device_hostname = None
|
||||
device_ip = None
|
||||
|
||||
|
||||
def setup_signal_handlers():
|
||||
"""Setup graceful shutdown handlers"""
|
||||
def signal_handler(sig, frame):
|
||||
global app_running
|
||||
logging.warning(f"Received signal {sig}. Initiating graceful shutdown...")
|
||||
log_with_server("Application shutdown initiated", device_hostname, device_ip)
|
||||
app_running = False
|
||||
print("\n\n🛑 Shutting down application gracefully...")
|
||||
|
||||
# Stop batch logger
|
||||
if batch_logger_thread:
|
||||
logging.info("Stopping batch logger...")
|
||||
|
||||
# Stop WiFi recovery monitor
|
||||
if wifi_recovery_manager:
|
||||
logging.info("Stopping WiFi recovery monitor...")
|
||||
wifi_recovery_manager.stop_monitoring()
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
signal.signal(signal.SIGTERM, signal_handler)
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
signal.signal(signal.SIGTERM, signal_handler)
|
||||
logging.info("Signal handlers configured")
|
||||
|
||||
|
||||
def initialize_application(skip_dependency_check=False):
|
||||
"""Initialize the application with performance optimizations"""
|
||||
print("=" * 70)
|
||||
print("PREZENTA WORK - Attendance Tracking System v2.8 (Performance Optimized)")
|
||||
print("=" * 70)
|
||||
def initialize_application():
|
||||
"""Initialize all application components"""
|
||||
global device_hostname, device_ip, app, logger
|
||||
|
||||
# Skip dependency check flag
|
||||
dependency_check_file = "/tmp/prezenta_deps_verified"
|
||||
|
||||
# Step 1: Check and install dependencies (SKIP if already verified)
|
||||
if skip_dependency_check or os.path.exists(dependency_check_file):
|
||||
print("\n[1/5] Dependencies already verified ✓ (skipped)")
|
||||
else:
|
||||
print("\n[1/5] Checking dependencies...")
|
||||
check_and_install_dependencies()
|
||||
# Mark dependencies as verified
|
||||
open(dependency_check_file, 'w').close()
|
||||
|
||||
# Step 2: Verify dependencies
|
||||
print("\n[2/5] Verifying dependencies...")
|
||||
if not verify_dependencies():
|
||||
print("Warning: Some dependencies are missing")
|
||||
|
||||
# Step 3: System initialization
|
||||
print("\n[3/5] Performing system initialization...")
|
||||
if not perform_system_initialization():
|
||||
print("Warning: System initialization completed with errors.")
|
||||
|
||||
# Step 4: Get device information
|
||||
print("\n[4/5] Retrieving device information...")
|
||||
hostname, device_ip = get_device_info()
|
||||
print(f"Final result - Hostname: {hostname}, Device IP: {device_ip}")
|
||||
|
||||
# Step 5: Setup logging
|
||||
print("\n[5/5] Setting up logging...")
|
||||
logger_delete_old_logs()
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("Initialization complete! ✓")
|
||||
print("=" * 70)
|
||||
|
||||
return hostname, device_ip
|
||||
|
||||
|
||||
def start_flask_server(app, hostname, device_ip):
|
||||
"""Start Flask server with fallback port handling"""
|
||||
for port in PREFERRED_PORTS:
|
||||
try:
|
||||
print(f"Attempting to start Flask server on port {port}...")
|
||||
|
||||
# Test if port is available
|
||||
import socket
|
||||
test_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
test_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
test_socket.bind(('0.0.0.0', port))
|
||||
test_socket.close()
|
||||
|
||||
# Port is available, start Flask server
|
||||
print(f"✓ Port {port} is available. Starting Flask server...")
|
||||
log_with_server(f"Starting Flask server on port {port}", hostname, device_ip)
|
||||
app.run(host=FLASK_HOST, port=port, debug=FLASK_DEBUG, use_reloader=FLASK_USE_RELOADER, threaded=True)
|
||||
return
|
||||
|
||||
except PermissionError:
|
||||
print(f"✗ Permission denied for port {port}")
|
||||
if port == 80:
|
||||
print(" Hint: Port 80 requires root privileges or capabilities")
|
||||
print(" Try running: sudo setcap cap_net_bind_service=ep $(which python3)")
|
||||
continue
|
||||
except OSError as e:
|
||||
if "Address already in use" in str(e):
|
||||
print(f"✗ Port {port} is already in use")
|
||||
else:
|
||||
print(f"✗ Port {port} error: {e}")
|
||||
continue
|
||||
except Exception as e:
|
||||
print(f"✗ Failed to start on port {port}: {e}")
|
||||
continue
|
||||
|
||||
# If we get here, all ports failed
|
||||
log_with_server("ERROR: Could not start Flask server on any port", hostname, device_ip)
|
||||
print("✗ Could not start Flask server on any available port")
|
||||
|
||||
|
||||
def start_connectivity_monitor(hostname, device_ip):
|
||||
"""Start internet connectivity monitoring in background (non-blocking)"""
|
||||
try:
|
||||
def on_internet_restored():
|
||||
"""Callback when internet is restored"""
|
||||
post_backup_data(hostname, device_ip)
|
||||
logging.info("=" * 80)
|
||||
logging.info("Prezenta Work v3.0 - Workplace Attendance System")
|
||||
logging.info("=" * 80)
|
||||
logging.info(f"Start time: {datetime.now().isoformat()}")
|
||||
|
||||
print("Starting connectivity monitor...")
|
||||
monitor_thread = Thread(
|
||||
target=check_internet_connection,
|
||||
args=(hostname, device_ip, on_internet_restored),
|
||||
# Get device info
|
||||
logging.info("Retrieving device information...")
|
||||
device_info = get_device_info()
|
||||
device_hostname = device_info['hostname']
|
||||
device_ip = device_info['ip']
|
||||
|
||||
logging.info(f"Device: {device_hostname} ({device_ip})")
|
||||
log_with_server(
|
||||
"Application started - v3.0 with batch logging and WiFi recovery",
|
||||
device_hostname,
|
||||
device_ip
|
||||
)
|
||||
|
||||
# Initialize system
|
||||
logging.info("Performing system initialization...")
|
||||
perform_system_initialization()
|
||||
|
||||
# Check and install dependencies
|
||||
logging.info("Checking dependencies...")
|
||||
check_and_install_dependencies()
|
||||
|
||||
# Setup batch logging
|
||||
logging.info("Setting up batch logging system...")
|
||||
setup_batch_logging(device_hostname)
|
||||
|
||||
# Create Flask app
|
||||
logging.info("Initializing Flask application...")
|
||||
app = Flask(__name__)
|
||||
create_api_routes(app)
|
||||
|
||||
logging.info("Application initialization completed successfully")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Application initialization failed: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def start_flask_server():
|
||||
"""Start Flask web server"""
|
||||
try:
|
||||
logging.info(f"Starting Flask server on port {FLASK_PORT}...")
|
||||
log_with_server(f"Flask server starting on port {FLASK_PORT}", device_hostname, device_ip)
|
||||
|
||||
# Run Flask in a separate thread
|
||||
flask_thread = threading.Thread(
|
||||
target=lambda: app.run(host='0.0.0.0', port=FLASK_PORT, debug=False, use_reloader=False),
|
||||
daemon=True
|
||||
)
|
||||
monitor_thread.start()
|
||||
print("✓ Connectivity monitor started")
|
||||
flask_thread.start()
|
||||
logging.info("Flask server thread started")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not start connectivity monitor: {e}")
|
||||
log_with_server(f"Connectivity monitor startup error: {str(e)}", hostname, device_ip)
|
||||
logging.error(f"Failed to start Flask server: {e}")
|
||||
log_with_server(f"ERROR: Flask server failed: {str(e)}", device_hostname, device_ip)
|
||||
return False
|
||||
|
||||
|
||||
def start_rfid_reader(hostname, device_ip):
|
||||
"""Initialize and start RFID reader (non-blocking)"""
|
||||
def start_chrome_app():
|
||||
"""Launch Chrome in fullscreen with traceability application"""
|
||||
try:
|
||||
print("Initializing RFID reader...")
|
||||
if get_chrome_path():
|
||||
logging.info("Starting Chrome fullscreen application...")
|
||||
log_with_server("Launching Chrome fullscreen UI", device_hostname, device_ip)
|
||||
|
||||
# Launch Chrome with local Flask server
|
||||
chrome_thread = threading.Thread(
|
||||
target=lambda: launch_chrome_app(device_hostname, device_ip, f"http://localhost:{FLASK_PORT}"),
|
||||
daemon=True
|
||||
)
|
||||
chrome_thread.start()
|
||||
logging.info("Chrome launch thread started")
|
||||
return True
|
||||
else:
|
||||
logging.warning("Chrome not found - skipping fullscreen launch")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to start Chrome app: {e}")
|
||||
log_with_server(f"ERROR: Chrome launch failed: {str(e)}", device_hostname, device_ip)
|
||||
return False
|
||||
|
||||
|
||||
def start_wifi_recovery_monitor():
|
||||
"""Initialize WiFi recovery monitoring"""
|
||||
global wifi_recovery_manager
|
||||
|
||||
try:
|
||||
logging.info("Initializing WiFi recovery monitor...")
|
||||
log_with_server("WiFi recovery system initialized", device_hostname, device_ip)
|
||||
|
||||
wifi_recovery_manager = initialize_wifi_recovery(
|
||||
device_hostname,
|
||||
device_ip,
|
||||
server_host=CONNECTIVITY_CHECK_HOST
|
||||
)
|
||||
|
||||
if wifi_recovery_manager:
|
||||
logging.info("WiFi recovery monitor started")
|
||||
return True
|
||||
else:
|
||||
logging.error("Failed to initialize WiFi recovery")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Error starting WiFi recovery: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def start_batch_logger_thread():
|
||||
"""Start the batch logging system"""
|
||||
global batch_logger_thread
|
||||
|
||||
try:
|
||||
logging.info("Starting batch logger thread...")
|
||||
batch_logger_thread = threading.Thread(
|
||||
target=start_batch_logger,
|
||||
args=(device_hostname, device_ip),
|
||||
daemon=True
|
||||
)
|
||||
batch_logger_thread.start()
|
||||
logging.info("Batch logger thread started (5s batches, event dedup)")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Error starting batch logger: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def start_connectivity_monitor():
|
||||
"""Monitor internet connectivity"""
|
||||
def connectivity_loop():
|
||||
while app_running:
|
||||
try:
|
||||
if not check_internet_connection():
|
||||
logging.warning("No internet connectivity")
|
||||
else:
|
||||
post_backup_data()
|
||||
except Exception as e:
|
||||
logging.error(f"Connectivity monitor error: {e}")
|
||||
|
||||
time.sleep(30) # Check every 30 seconds
|
||||
|
||||
try:
|
||||
logging.info("Starting connectivity monitor...")
|
||||
conn_thread = threading.Thread(target=connectivity_loop, daemon=True)
|
||||
conn_thread.start()
|
||||
logging.info("Connectivity monitor thread started")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Error starting connectivity monitor: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def start_rfid_reader():
|
||||
"""Initialize RFID reader"""
|
||||
global rfid_reader
|
||||
|
||||
try:
|
||||
logging.info("Initializing RFID reader...")
|
||||
rfid_reader = initialize_rfid_reader()
|
||||
|
||||
if rfid_reader is None:
|
||||
print("⚠ WARNING: Application starting without RFID functionality")
|
||||
print(" Card reading will not work until RFID reader is properly configured")
|
||||
log_with_server("ERROR: RFID reader initialization failed", hostname, device_ip)
|
||||
return None
|
||||
|
||||
print("✓ RFID reader initialized successfully")
|
||||
log_with_server("RFID reader started", hostname, device_ip)
|
||||
return rfid_reader
|
||||
|
||||
if rfid_reader:
|
||||
logging.info("RFID reader initialized successfully")
|
||||
log_with_server("RFID reader ready", device_hostname, device_ip)
|
||||
return True
|
||||
else:
|
||||
logging.error("RFID reader initialization failed")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ Critical error initializing RFID reader: {e}")
|
||||
log_with_server(f"Critical RFID error: {str(e)}", hostname, device_ip)
|
||||
print(" Application will start but RFID functionality will be disabled")
|
||||
return None
|
||||
logging.error(f"Error initializing RFID reader: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""Main application entry point"""
|
||||
global app_running
|
||||
|
||||
# Configure basic logging first
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
handlers=[
|
||||
logging.FileHandler(LOG_FILE),
|
||||
logging.StreamHandler(sys.stdout)
|
||||
]
|
||||
)
|
||||
|
||||
# Setup signal handlers for graceful shutdown
|
||||
setup_signal_handlers()
|
||||
|
||||
hostname = None
|
||||
device_ip = None
|
||||
|
||||
try:
|
||||
# Check if we should skip dependency checks (faster startup)
|
||||
skip_deps = os.environ.get('SKIP_DEPENDENCY_CHECK', 'false').lower() == 'true'
|
||||
|
||||
# Initialize application
|
||||
hostname, device_ip = initialize_application(skip_dependency_check=skip_deps)
|
||||
if not initialize_application():
|
||||
logging.error("Application initialization failed")
|
||||
return 1
|
||||
|
||||
# Start background services in parallel (non-blocking)
|
||||
print("\nStarting background services...")
|
||||
# Start core components in sequence
|
||||
logging.info("Starting application components...")
|
||||
|
||||
# Start RFID reader
|
||||
rfid_reader = start_rfid_reader(hostname, device_ip)
|
||||
# 1. Start Flask web server (provides UI endpoint)
|
||||
start_flask_server()
|
||||
time.sleep(1)
|
||||
|
||||
# Start connectivity monitor
|
||||
start_connectivity_monitor(hostname, device_ip)
|
||||
# 2. Start batch logging system
|
||||
start_batch_logger_thread()
|
||||
time.sleep(0.5)
|
||||
|
||||
# 3. Launch Chrome fullscreen UI
|
||||
start_chrome_app()
|
||||
time.sleep(2)
|
||||
|
||||
# 4. Initialize RFID reader
|
||||
start_rfid_reader()
|
||||
|
||||
# 5. Start connectivity monitoring
|
||||
start_connectivity_monitor()
|
||||
|
||||
# 6. Start WiFi recovery monitor
|
||||
start_wifi_recovery_monitor()
|
||||
|
||||
logging.info("All components started successfully")
|
||||
log_with_server(
|
||||
"System fully operational - batch logging active (75% reduction), "
|
||||
"Chrome UI fullscreen, WiFi recovery enabled",
|
||||
device_hostname,
|
||||
device_ip
|
||||
)
|
||||
|
||||
# Keep application running
|
||||
logging.info("Application is now running...")
|
||||
while app_running:
|
||||
time.sleep(1)
|
||||
|
||||
logging.info("Application shutdown complete")
|
||||
return 0
|
||||
|
||||
# Import Flask here after dependencies are checked
|
||||
try:
|
||||
from flask import Flask
|
||||
|
||||
# Create Flask app with optimizations
|
||||
app = Flask(__name__)
|
||||
app.config['JSON_SORT_KEYS'] = False # Faster JSON response
|
||||
|
||||
# Get local paths
|
||||
current_script_path = os.path.abspath(__file__)
|
||||
local_base_dir = os.path.dirname(current_script_path)
|
||||
local_app_path = current_script_path
|
||||
from config_settings import REPOSITORY_PATH
|
||||
local_repo_path = str(REPOSITORY_PATH)
|
||||
|
||||
# Register API routes
|
||||
print("Registering API routes...")
|
||||
app = create_api_routes(app, hostname, device_ip, local_app_path, local_repo_path)
|
||||
print("✓ API routes registered\n")
|
||||
|
||||
# Start Flask server
|
||||
log_with_server("Application initialized successfully", hostname, device_ip)
|
||||
print("=" * 70)
|
||||
print("🚀 Flask server starting...")
|
||||
print("=" * 70 + "\n")
|
||||
|
||||
start_flask_server(app, hostname, device_ip)
|
||||
|
||||
except ImportError as e:
|
||||
print(f"✗ Flask not available: {e}")
|
||||
if hostname and device_ip:
|
||||
log_with_server(f"Flask import error: {str(e)}", hostname, device_ip)
|
||||
print("\n⚠ Command server disabled - Flask is required for API endpoints")
|
||||
print(" Application will continue but without HTTP API functionality")
|
||||
|
||||
# Keep application running
|
||||
print("\nApplication running without Flask. Press Ctrl+C to exit.")
|
||||
try:
|
||||
while app_running:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
print("\nShutting down...")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n🛑 Application shutting down...")
|
||||
if hostname and device_ip:
|
||||
log_with_server("Application shutting down", hostname, device_ip)
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
print(f"\n✗ Fatal error: {e}")
|
||||
if hostname and device_ip:
|
||||
log_with_server(f"Fatal error: {str(e)}", hostname, device_ip)
|
||||
sys.exit(1)
|
||||
logging.error(f"Fatal error: {e}")
|
||||
log_with_server(f"FATAL ERROR: {str(e)}", device_hostname, device_ip)
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
sys.exit(main())
|
||||
|
||||
Reference in New Issue
Block a user