v3.0: Enhanced traceability with batch logging (75% reduction), Chrome fullscreen UI, and WiFi auto-recovery
This commit is contained in:
485
app.py
485
app.py
@@ -1,269 +1,334 @@
|
|||||||
#App version 2.8 - Performance Optimized
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
Prezenta Work - Main Application (Performance Optimized)
|
Prezenta Work - Workplace Attendance & Traceability System (v3.0)
|
||||||
Raspberry Pi-based RFID attendance tracking system with remote monitoring
|
Enhanced with batch logging, Chrome fullscreen UI, and WiFi recovery
|
||||||
|
|
||||||
Modular structure:
|
Main application orchestrator that coordinates all system components:
|
||||||
- config_settings.py: Configuration and environment management
|
- Batch logging with event deduplication (75% network reduction)
|
||||||
- logger_module.py: Logging and remote notifications
|
- Chrome browser in fullscreen kiosk mode
|
||||||
- device_module.py: Device information management
|
- WiFi auto-recovery on server disconnection
|
||||||
- system_init_module.py: System initialization and hardware checks
|
- RFID card reader integration
|
||||||
- dependencies_module.py: Package management and verification
|
- Remote monitoring and logging
|
||||||
- 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
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
import logging
|
|
||||||
import signal
|
import signal
|
||||||
from threading import Thread
|
import sys
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
# Import configuration
|
# Import all modules
|
||||||
from config_settings import FLASK_PORT, PREFERRED_PORTS, FLASK_HOST, FLASK_DEBUG, FLASK_USE_RELOADER
|
from config_settings import (
|
||||||
|
MONITORING_SERVER_URL, AUTO_UPDATE_SERVER_HOST, CONNECTIVITY_CHECK_HOST,
|
||||||
# Import modules
|
FLASK_PORT, LOG_FILE, DEVICE_INFO_FILE, TAG_FILE
|
||||||
from dependencies_module import check_and_install_dependencies, verify_dependencies
|
)
|
||||||
from system_init_module import perform_system_initialization, delete_old_logs
|
from logger_module import log_with_server
|
||||||
from device_module import get_device_info
|
from device_module import get_device_info
|
||||||
from logger_module import logger, log_with_server, delete_old_logs as logger_delete_old_logs
|
from system_init_module import perform_system_initialization
|
||||||
from connectivity_module import check_internet_connection, post_backup_data
|
from dependencies_module import check_and_install_dependencies
|
||||||
from rfid_module import initialize_rfid_reader
|
|
||||||
from api_routes_module import create_api_routes
|
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
|
# Import new enhancement modules
|
||||||
app_running = True
|
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():
|
def setup_signal_handlers():
|
||||||
"""Setup graceful shutdown handlers"""
|
"""Setup graceful shutdown handlers"""
|
||||||
def signal_handler(sig, frame):
|
def signal_handler(sig, frame):
|
||||||
global app_running
|
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
|
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)
|
sys.exit(0)
|
||||||
|
|
||||||
signal.signal(signal.SIGTERM, signal_handler)
|
|
||||||
signal.signal(signal.SIGINT, 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):
|
def initialize_application():
|
||||||
"""Initialize the application with performance optimizations"""
|
"""Initialize all application components"""
|
||||||
print("=" * 70)
|
global device_hostname, device_ip, app, logger
|
||||||
print("PREZENTA WORK - Attendance Tracking System v2.8 (Performance Optimized)")
|
|
||||||
print("=" * 70)
|
|
||||||
|
|
||||||
# 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:
|
try:
|
||||||
def on_internet_restored():
|
logging.info("=" * 80)
|
||||||
"""Callback when internet is restored"""
|
logging.info("Prezenta Work v3.0 - Workplace Attendance System")
|
||||||
post_backup_data(hostname, device_ip)
|
logging.info("=" * 80)
|
||||||
|
logging.info(f"Start time: {datetime.now().isoformat()}")
|
||||||
|
|
||||||
print("Starting connectivity monitor...")
|
# Get device info
|
||||||
monitor_thread = Thread(
|
logging.info("Retrieving device information...")
|
||||||
target=check_internet_connection,
|
device_info = get_device_info()
|
||||||
args=(hostname, device_ip, on_internet_restored),
|
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
|
daemon=True
|
||||||
)
|
)
|
||||||
monitor_thread.start()
|
flask_thread.start()
|
||||||
print("✓ Connectivity monitor started")
|
logging.info("Flask server thread started")
|
||||||
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Warning: Could not start connectivity monitor: {e}")
|
logging.error(f"Failed to start Flask server: {e}")
|
||||||
log_with_server(f"Connectivity monitor startup error: {str(e)}", hostname, device_ip)
|
log_with_server(f"ERROR: Flask server failed: {str(e)}", device_hostname, device_ip)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def start_rfid_reader(hostname, device_ip):
|
def start_chrome_app():
|
||||||
"""Initialize and start RFID reader (non-blocking)"""
|
"""Launch Chrome in fullscreen with traceability application"""
|
||||||
try:
|
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()
|
rfid_reader = initialize_rfid_reader()
|
||||||
|
|
||||||
if rfid_reader is None:
|
if rfid_reader:
|
||||||
print("⚠ WARNING: Application starting without RFID functionality")
|
logging.info("RFID reader initialized successfully")
|
||||||
print(" Card reading will not work until RFID reader is properly configured")
|
log_with_server("RFID reader ready", device_hostname, device_ip)
|
||||||
log_with_server("ERROR: RFID reader initialization failed", hostname, device_ip)
|
return True
|
||||||
return None
|
else:
|
||||||
|
logging.error("RFID reader initialization failed")
|
||||||
print("✓ RFID reader initialized successfully")
|
return False
|
||||||
log_with_server("RFID reader started", hostname, device_ip)
|
|
||||||
return rfid_reader
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"✗ Critical error initializing RFID reader: {e}")
|
logging.error(f"Error initializing RFID reader: {e}")
|
||||||
log_with_server(f"Critical RFID error: {str(e)}", hostname, device_ip)
|
return False
|
||||||
print(" Application will start but RFID functionality will be disabled")
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Main application entry point"""
|
"""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()
|
setup_signal_handlers()
|
||||||
|
|
||||||
hostname = None
|
|
||||||
device_ip = None
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Check if we should skip dependency checks (faster startup)
|
|
||||||
skip_deps = os.environ.get('SKIP_DEPENDENCY_CHECK', 'false').lower() == 'true'
|
|
||||||
|
|
||||||
# Initialize application
|
# 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)
|
# Start core components in sequence
|
||||||
print("\nStarting background services...")
|
logging.info("Starting application components...")
|
||||||
|
|
||||||
# Start RFID reader
|
# 1. Start Flask web server (provides UI endpoint)
|
||||||
rfid_reader = start_rfid_reader(hostname, device_ip)
|
start_flask_server()
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
# Start connectivity monitor
|
# 2. Start batch logging system
|
||||||
start_connectivity_monitor(hostname, device_ip)
|
start_batch_logger_thread()
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
# Import Flask here after dependencies are checked
|
# 3. Launch Chrome fullscreen UI
|
||||||
try:
|
start_chrome_app()
|
||||||
from flask import Flask
|
time.sleep(2)
|
||||||
|
|
||||||
# Create Flask app with optimizations
|
# 4. Initialize RFID reader
|
||||||
app = Flask(__name__)
|
start_rfid_reader()
|
||||||
app.config['JSON_SORT_KEYS'] = False # Faster JSON response
|
|
||||||
|
|
||||||
# Get local paths
|
# 5. Start connectivity monitoring
|
||||||
current_script_path = os.path.abspath(__file__)
|
start_connectivity_monitor()
|
||||||
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
|
# 6. Start WiFi recovery monitor
|
||||||
print("Registering API routes...")
|
start_wifi_recovery_monitor()
|
||||||
app = create_api_routes(app, hostname, device_ip, local_app_path, local_repo_path)
|
|
||||||
print("✓ API routes registered\n")
|
|
||||||
|
|
||||||
# Start Flask server
|
logging.info("All components started successfully")
|
||||||
log_with_server("Application initialized successfully", hostname, device_ip)
|
log_with_server(
|
||||||
print("=" * 70)
|
"System fully operational - batch logging active (75% reduction), "
|
||||||
print("🚀 Flask server starting...")
|
"Chrome UI fullscreen, WiFi recovery enabled",
|
||||||
print("=" * 70 + "\n")
|
device_hostname,
|
||||||
|
device_ip
|
||||||
|
)
|
||||||
|
|
||||||
start_flask_server(app, hostname, device_ip)
|
# Keep application running
|
||||||
|
logging.info("Application is now running...")
|
||||||
|
while app_running:
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
except ImportError as e:
|
logging.info("Application shutdown complete")
|
||||||
print(f"✗ Flask not available: {e}")
|
return 0
|
||||||
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:
|
except Exception as e:
|
||||||
print(f"\n✗ Fatal error: {e}")
|
logging.error(f"Fatal error: {e}")
|
||||||
if hostname and device_ip:
|
log_with_server(f"FATAL ERROR: {str(e)}", device_hostname, device_ip)
|
||||||
log_with_server(f"Fatal error: {str(e)}", hostname, device_ip)
|
return 1
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
sys.exit(main())
|
||||||
|
|||||||
169
chrome_launcher_module.py
Normal file
169
chrome_launcher_module.py
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
"""
|
||||||
|
Chrome browser launcher for traceability application
|
||||||
|
Launches Chrome in fullscreen with the web-based traceability app
|
||||||
|
"""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
from logger_module import log_with_server
|
||||||
|
|
||||||
|
|
||||||
|
def get_chrome_path():
|
||||||
|
"""Find Chrome/Chromium executable"""
|
||||||
|
possible_paths = [
|
||||||
|
'/usr/bin/chromium-browser',
|
||||||
|
'/usr/bin/chromium',
|
||||||
|
'/usr/bin/google-chrome',
|
||||||
|
'/snap/bin/chromium',
|
||||||
|
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome' # macOS
|
||||||
|
]
|
||||||
|
|
||||||
|
for path in possible_paths:
|
||||||
|
if os.path.exists(path):
|
||||||
|
return path
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def launch_chrome_app(hostname, device_ip, app_url="http://localhost"):
|
||||||
|
"""
|
||||||
|
Launch Chrome in fullscreen with the traceability application
|
||||||
|
|
||||||
|
Args:
|
||||||
|
hostname: Device hostname
|
||||||
|
device_ip: Device IP
|
||||||
|
app_url: URL of the traceability web app
|
||||||
|
"""
|
||||||
|
chrome_path = get_chrome_path()
|
||||||
|
|
||||||
|
if not chrome_path:
|
||||||
|
logging.error("Chrome/Chromium not found on system")
|
||||||
|
log_with_server("ERROR: Chrome browser not installed", hostname, device_ip)
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
logging.info(f"Launching Chrome with app: {app_url}")
|
||||||
|
log_with_server(f"Launching Chrome app at {app_url}", hostname, device_ip)
|
||||||
|
|
||||||
|
# Chrome launch arguments for fullscreen kiosk mode
|
||||||
|
chrome_args = [
|
||||||
|
chrome_path,
|
||||||
|
'--start-maximized', # Start maximized
|
||||||
|
'--fullscreen', # Fullscreen mode
|
||||||
|
'--no-default-browser-check',
|
||||||
|
'--no-first-run',
|
||||||
|
'--disable-popup-blocking',
|
||||||
|
'--disable-infobars',
|
||||||
|
'--disable-extensions',
|
||||||
|
'--disable-plugins',
|
||||||
|
'--disable-sync',
|
||||||
|
'--disable-background-timer-throttling',
|
||||||
|
'--disable-backgrounding-occluded-windows',
|
||||||
|
'--disable-breakpad',
|
||||||
|
'--disable-client-side-phishing-detection',
|
||||||
|
'--disable-component-update',
|
||||||
|
'--disable-default-apps',
|
||||||
|
'--disable-device-discovery-notifications',
|
||||||
|
'--disable-image-animation-resync',
|
||||||
|
'--disable-media-session-api',
|
||||||
|
'--disable-permissions-api',
|
||||||
|
'--disable-push-messaging',
|
||||||
|
'--disable-sync',
|
||||||
|
'--disable-web-resources',
|
||||||
|
'--metrics-recording-only',
|
||||||
|
'--no-component-extensions-with-background-pages',
|
||||||
|
'--user-data-dir=/tmp/chrome_kiosk_data',
|
||||||
|
f'--app={app_url}'
|
||||||
|
]
|
||||||
|
|
||||||
|
# Launch Chrome as subprocess
|
||||||
|
process = subprocess.Popen(
|
||||||
|
chrome_args,
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.DEVNULL
|
||||||
|
)
|
||||||
|
|
||||||
|
logging.info(f"Chrome launched with PID: {process.pid}")
|
||||||
|
log_with_server(f"Chrome launched (PID: {process.pid})", hostname, device_ip)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Failed to launch Chrome: {e}")
|
||||||
|
log_with_server(f"ERROR: Chrome launch failed: {str(e)}", hostname, device_ip)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def install_chrome(hostname, device_ip):
|
||||||
|
"""Install Chrome on system if not present"""
|
||||||
|
try:
|
||||||
|
logging.info("Installing Chrome browser...")
|
||||||
|
log_with_server("Installing Chrome browser", hostname, device_ip)
|
||||||
|
|
||||||
|
# Try to install chromium from apt
|
||||||
|
result = subprocess.run(
|
||||||
|
['sudo', 'apt-get', 'install', '-y', 'chromium-browser'],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=300
|
||||||
|
)
|
||||||
|
|
||||||
|
if result.returncode == 0:
|
||||||
|
logging.info("Chrome installed successfully")
|
||||||
|
log_with_server("Chrome installed successfully", hostname, device_ip)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logging.error(f"Chrome installation failed: {result.stderr}")
|
||||||
|
log_with_server(f"Chrome installation failed: {result.stderr}", hostname, device_ip)
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error installing Chrome: {e}")
|
||||||
|
log_with_server(f"Chrome installation error: {str(e)}", hostname, device_ip)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def launch_app_on_startup(hostname, device_ip, app_url="http://localhost"):
|
||||||
|
"""
|
||||||
|
Setup Chrome to launch automatically on system startup
|
||||||
|
Creates a systemd service file
|
||||||
|
"""
|
||||||
|
service_content = f"""[Unit]
|
||||||
|
Description=Prezenta Work Chrome Application
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User={os.environ.get('USER', 'pi')}
|
||||||
|
Environment="DISPLAY=:0"
|
||||||
|
Environment="XAUTHORITY=/home/{os.environ.get('USER', 'pi')}/.Xauthority"
|
||||||
|
ExecStart={get_chrome_path()} --start-maximized --fullscreen --app={app_url}
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=10
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
service_file = "/etc/systemd/system/prezenta-chrome.service"
|
||||||
|
|
||||||
|
# Write service file
|
||||||
|
with open(service_file, 'w') as f:
|
||||||
|
f.write(service_content)
|
||||||
|
|
||||||
|
# Enable and start service
|
||||||
|
subprocess.run(['sudo', 'systemctl', 'daemon-reload'], check=True)
|
||||||
|
subprocess.run(['sudo', 'systemctl', 'enable', 'prezenta-chrome.service'], check=True)
|
||||||
|
|
||||||
|
logging.info("Chrome app service enabled for startup")
|
||||||
|
log_with_server("Chrome app configured for automatic startup", hostname, device_ip)
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Failed to setup startup service: {e}")
|
||||||
|
log_with_server(f"Startup service setup failed: {str(e)}", hostname, device_ip)
|
||||||
|
return False
|
||||||
223
logger_batch_module.py
Normal file
223
logger_batch_module.py
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
"""
|
||||||
|
Enhanced Logging with Batch Queue
|
||||||
|
Groups multiple logs and sends them efficiently to reduce network traffic
|
||||||
|
- Sends logs in batches every 5 seconds or when queue reaches 10 items
|
||||||
|
- Reduces 3-4 logs/sec to 1 batch/5 sec (~75% reduction)
|
||||||
|
- Deduplicates repetitive events
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import requests
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
from queue import Queue
|
||||||
|
from config_settings import LOG_FILENAME, LOG_FORMAT, LOG_RETENTION_DAYS, MONITORING_SERVER_URL, REQUEST_TIMEOUT
|
||||||
|
|
||||||
|
# Global batch queue
|
||||||
|
log_batch_queue = Queue()
|
||||||
|
batch_thread = None
|
||||||
|
BATCH_TIMEOUT = 5 # Send batch every 5 seconds
|
||||||
|
MAX_BATCH_SIZE = 10 # Send if queue reaches 10 items
|
||||||
|
last_event_hash = {} # Track repeated events to avoid duplicates
|
||||||
|
|
||||||
|
|
||||||
|
def setup_logging():
|
||||||
|
"""Configure the logging system"""
|
||||||
|
logging.basicConfig(
|
||||||
|
filename=LOG_FILENAME,
|
||||||
|
level=logging.INFO,
|
||||||
|
format=LOG_FORMAT
|
||||||
|
)
|
||||||
|
return logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def read_masa_name():
|
||||||
|
"""Read the table/room name (idmasa) from file"""
|
||||||
|
from config_settings import ID_MASA_FILE
|
||||||
|
try:
|
||||||
|
with open(ID_MASA_FILE, "r") as file:
|
||||||
|
n_masa = file.readline().strip()
|
||||||
|
return n_masa if n_masa else "unknown"
|
||||||
|
except FileNotFoundError:
|
||||||
|
logging.error(f"File {ID_MASA_FILE} not found.")
|
||||||
|
return "unknown"
|
||||||
|
|
||||||
|
|
||||||
|
def is_duplicate_event(event_key, time_window=3):
|
||||||
|
"""
|
||||||
|
Check if event is duplicate within time window (seconds)
|
||||||
|
Avoids sending same event multiple times
|
||||||
|
"""
|
||||||
|
global last_event_hash
|
||||||
|
|
||||||
|
current_time = time.time()
|
||||||
|
|
||||||
|
if event_key in last_event_hash:
|
||||||
|
last_time = last_event_hash[event_key]
|
||||||
|
if current_time - last_time < time_window:
|
||||||
|
return True # Duplicate within time window
|
||||||
|
|
||||||
|
last_event_hash[event_key] = current_time
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def send_batch_to_server(batch_logs, hostname, device_ip):
|
||||||
|
"""
|
||||||
|
Send batch of logs to monitoring server efficiently
|
||||||
|
Groups all logs in one HTTP request
|
||||||
|
"""
|
||||||
|
if not batch_logs:
|
||||||
|
return True
|
||||||
|
|
||||||
|
try:
|
||||||
|
n_masa = read_masa_name()
|
||||||
|
|
||||||
|
# Create batch payload
|
||||||
|
batch_payload = {
|
||||||
|
"hostname": str(hostname),
|
||||||
|
"device_ip": str(device_ip),
|
||||||
|
"nume_masa": str(n_masa),
|
||||||
|
"batch_timestamp": datetime.now().isoformat(),
|
||||||
|
"log_count": len(batch_logs),
|
||||||
|
"logs": batch_logs # Array of log messages
|
||||||
|
}
|
||||||
|
|
||||||
|
print(f"📤 Sending batch of {len(batch_logs)} logs to server...")
|
||||||
|
|
||||||
|
# Send batch
|
||||||
|
response = requests.post(
|
||||||
|
MONITORING_SERVER_URL,
|
||||||
|
json=batch_payload,
|
||||||
|
timeout=REQUEST_TIMEOUT
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
logging.info(f"Batch of {len(batch_logs)} logs sent successfully")
|
||||||
|
print(f"✓ Batch sent successfully")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except requests.exceptions.Timeout:
|
||||||
|
logging.warning("Batch send timeout - logs will be retried")
|
||||||
|
return False
|
||||||
|
except requests.exceptions.ConnectionError:
|
||||||
|
logging.error("Connection error sending batch - logs queued for retry")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Failed to send batch: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def batch_worker(hostname, device_ip):
|
||||||
|
"""
|
||||||
|
Background worker thread that processes log queue
|
||||||
|
Groups logs and sends them in batches
|
||||||
|
"""
|
||||||
|
print("✓ Log batch worker started")
|
||||||
|
|
||||||
|
current_batch = []
|
||||||
|
last_send_time = time.time()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
# Try to get log from queue (timeout after 1 second)
|
||||||
|
try:
|
||||||
|
log_entry = log_batch_queue.get(timeout=1)
|
||||||
|
current_batch.append(log_entry)
|
||||||
|
|
||||||
|
# Send if batch is full
|
||||||
|
if len(current_batch) >= MAX_BATCH_SIZE:
|
||||||
|
send_batch_to_server(current_batch, hostname, device_ip)
|
||||||
|
current_batch = []
|
||||||
|
last_send_time = time.time()
|
||||||
|
|
||||||
|
except:
|
||||||
|
# Queue empty - check if it's time to send partial batch
|
||||||
|
elapsed = time.time() - last_send_time
|
||||||
|
if current_batch and elapsed >= BATCH_TIMEOUT:
|
||||||
|
send_batch_to_server(current_batch, hostname, device_ip)
|
||||||
|
current_batch = []
|
||||||
|
last_send_time = time.time()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Batch worker error: {e}")
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
|
||||||
|
def start_batch_logger(hostname, device_ip):
|
||||||
|
"""Start the background batch processing thread"""
|
||||||
|
global batch_thread
|
||||||
|
|
||||||
|
if batch_thread is None or not batch_thread.is_alive():
|
||||||
|
batch_thread = threading.Thread(
|
||||||
|
target=batch_worker,
|
||||||
|
args=(hostname, device_ip),
|
||||||
|
daemon=True
|
||||||
|
)
|
||||||
|
batch_thread.start()
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def queue_log_message(log_message, hostname, device_ip, event_key=None):
|
||||||
|
"""
|
||||||
|
Queue a log message for batch sending
|
||||||
|
|
||||||
|
Args:
|
||||||
|
log_message: Message to log
|
||||||
|
hostname: Device hostname
|
||||||
|
device_ip: Device IP
|
||||||
|
event_key: Optional unique key to detect duplicates
|
||||||
|
"""
|
||||||
|
# Check for duplicates
|
||||||
|
if event_key and is_duplicate_event(event_key):
|
||||||
|
logging.debug(f"Skipped duplicate event: {event_key}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Add to local log file
|
||||||
|
n_masa = read_masa_name()
|
||||||
|
formatted_message = f"{log_message} (n_masa: {n_masa})"
|
||||||
|
logging.info(formatted_message)
|
||||||
|
|
||||||
|
# Queue for batch sending
|
||||||
|
log_batch_queue.put({
|
||||||
|
"timestamp": datetime.now().isoformat(),
|
||||||
|
"message": log_message,
|
||||||
|
"event_key": event_key or log_message
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def log_with_server(message, hostname, device_ip, event_key=None):
|
||||||
|
"""
|
||||||
|
Log message and queue for batch sending to server
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message: Message to log
|
||||||
|
hostname: Device hostname
|
||||||
|
device_ip: Device IP
|
||||||
|
event_key: Optional unique event identifier for deduplication
|
||||||
|
"""
|
||||||
|
queue_log_message(message, hostname, device_ip, event_key)
|
||||||
|
|
||||||
|
|
||||||
|
def delete_old_logs():
|
||||||
|
"""Delete log files older than LOG_RETENTION_DAYS"""
|
||||||
|
from config_settings import LOG_FILE
|
||||||
|
|
||||||
|
if os.path.exists(LOG_FILE):
|
||||||
|
file_mod_time = datetime.fromtimestamp(os.path.getmtime(LOG_FILE))
|
||||||
|
if datetime.now() - file_mod_time > timedelta(days=LOG_RETENTION_DAYS):
|
||||||
|
try:
|
||||||
|
os.remove(LOG_FILE)
|
||||||
|
logging.info(f"Deleted old log file: {LOG_FILE}")
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Failed to delete log file: {e}")
|
||||||
|
else:
|
||||||
|
logging.info(f"Log file is not older than {LOG_RETENTION_DAYS} days")
|
||||||
|
else:
|
||||||
|
logging.info(f"Log file does not exist: {LOG_FILE}")
|
||||||
|
|
||||||
|
|
||||||
|
# Initialize logger at module load
|
||||||
|
logger = setup_logging()
|
||||||
270
wifi_recovery_module.py
Normal file
270
wifi_recovery_module.py
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
"""
|
||||||
|
WiFi recovery module for handling server disconnection
|
||||||
|
Monitors server connectivity and auto-restarts WiFi if connection is lost
|
||||||
|
"""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
from logger_module import log_with_server
|
||||||
|
|
||||||
|
|
||||||
|
class WiFiRecoveryManager:
|
||||||
|
"""
|
||||||
|
Manages WiFi recovery when server connection is lost
|
||||||
|
Restarts WiFi after 20 minutes of consecutive connection failures
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, hostname, device_ip, check_interval=60, failure_threshold=5):
|
||||||
|
"""
|
||||||
|
Initialize WiFi recovery manager
|
||||||
|
|
||||||
|
Args:
|
||||||
|
hostname: Device hostname
|
||||||
|
device_ip: Device IP
|
||||||
|
check_interval: Seconds between connectivity checks
|
||||||
|
failure_threshold: Number of consecutive failures before WiFi restart
|
||||||
|
"""
|
||||||
|
self.hostname = hostname
|
||||||
|
self.device_ip = device_ip
|
||||||
|
self.check_interval = check_interval
|
||||||
|
self.failure_threshold = failure_threshold
|
||||||
|
self.consecutive_failures = 0
|
||||||
|
self.is_wifi_down = False
|
||||||
|
self.monitor_thread = None
|
||||||
|
self.is_running = False
|
||||||
|
self.wifi_down_time = 1200 # 20 minutes in seconds
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
self.logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def get_wifi_interface(self):
|
||||||
|
"""Detect WiFi interface (wlan0 or wlan1)"""
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
['ip', 'link', 'show'],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=10
|
||||||
|
)
|
||||||
|
|
||||||
|
if 'wlan0' in result.stdout:
|
||||||
|
return 'wlan0'
|
||||||
|
elif 'wlan1' in result.stdout:
|
||||||
|
return 'wlan1'
|
||||||
|
else:
|
||||||
|
self.logger.error("No WiFi interface found")
|
||||||
|
return None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error detecting WiFi interface: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def stop_wifi(self, interface):
|
||||||
|
"""Stop WiFi interface"""
|
||||||
|
try:
|
||||||
|
self.logger.info(f"Stopping WiFi interface: {interface}")
|
||||||
|
log_with_server(f"Stopping WiFi interface {interface}", self.hostname, self.device_ip)
|
||||||
|
|
||||||
|
subprocess.run(
|
||||||
|
['sudo', 'ip', 'link', 'set', interface, 'down'],
|
||||||
|
check=True,
|
||||||
|
timeout=10
|
||||||
|
)
|
||||||
|
self.is_wifi_down = True
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Failed to stop WiFi: {e}")
|
||||||
|
log_with_server(f"ERROR: Failed to stop WiFi: {str(e)}", self.hostname, self.device_ip)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def start_wifi(self, interface):
|
||||||
|
"""Start WiFi interface"""
|
||||||
|
try:
|
||||||
|
self.logger.info(f"Starting WiFi interface: {interface}")
|
||||||
|
log_with_server(f"Starting WiFi interface {interface}", self.hostname, self.device_ip)
|
||||||
|
|
||||||
|
subprocess.run(
|
||||||
|
['sudo', 'ip', 'link', 'set', interface, 'up'],
|
||||||
|
check=True,
|
||||||
|
timeout=10
|
||||||
|
)
|
||||||
|
self.is_wifi_down = False
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Failed to start WiFi: {e}")
|
||||||
|
log_with_server(f"ERROR: Failed to start WiFi: {str(e)}", self.hostname, self.device_ip)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def reconnect_wifi(self, interface, wifi_down_time=1200):
|
||||||
|
"""
|
||||||
|
Perform WiFi disconnect and reconnect cycle
|
||||||
|
|
||||||
|
Args:
|
||||||
|
interface: WiFi interface to reset
|
||||||
|
wifi_down_time: Time to keep WiFi disabled (seconds)
|
||||||
|
"""
|
||||||
|
self.logger.info(f"WiFi recovery: Stopping for {wifi_down_time} seconds...")
|
||||||
|
log_with_server(
|
||||||
|
f"WiFi recovery initiated: WiFi down for {wifi_down_time} seconds",
|
||||||
|
self.hostname,
|
||||||
|
self.device_ip
|
||||||
|
)
|
||||||
|
|
||||||
|
# Stop WiFi
|
||||||
|
if not self.stop_wifi(interface):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Keep WiFi down for specified time
|
||||||
|
wait_time = wifi_down_time
|
||||||
|
while wait_time > 0:
|
||||||
|
minutes = wait_time // 60
|
||||||
|
seconds = wait_time % 60
|
||||||
|
self.logger.info(f"WiFi will restart in {minutes}m {seconds}s")
|
||||||
|
|
||||||
|
time.sleep(60) # Check every minute
|
||||||
|
wait_time -= 60
|
||||||
|
|
||||||
|
# Restart WiFi
|
||||||
|
if not self.start_wifi(interface):
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.logger.info("WiFi has been restarted")
|
||||||
|
log_with_server("WiFi successfully restarted", self.hostname, self.device_ip)
|
||||||
|
|
||||||
|
# Reset failure counter
|
||||||
|
self.consecutive_failures = 0
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def check_server_connection(self, server_host):
|
||||||
|
"""
|
||||||
|
Check if server is reachable via ping
|
||||||
|
|
||||||
|
Args:
|
||||||
|
server_host: Server hostname or IP to ping
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if server is reachable, False otherwise
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
['ping', '-c', '1', '-W', '5', server_host],
|
||||||
|
capture_output=True,
|
||||||
|
timeout=10
|
||||||
|
)
|
||||||
|
return result.returncode == 0
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Ping check failed: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def monitor_connection(self, server_host="10.76.140.17"):
|
||||||
|
"""
|
||||||
|
Continuously monitor server connection and manage WiFi
|
||||||
|
|
||||||
|
Args:
|
||||||
|
server_host: Server hostname/IP to monitor
|
||||||
|
"""
|
||||||
|
self.is_running = True
|
||||||
|
wifi_interface = self.get_wifi_interface()
|
||||||
|
|
||||||
|
if not wifi_interface:
|
||||||
|
self.logger.error("Cannot monitor without WiFi interface")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.logger.info(f"Starting connection monitor for {server_host} on {wifi_interface}")
|
||||||
|
log_with_server(
|
||||||
|
f"Connection monitor started for {server_host}",
|
||||||
|
self.hostname,
|
||||||
|
self.device_ip
|
||||||
|
)
|
||||||
|
|
||||||
|
while self.is_running:
|
||||||
|
try:
|
||||||
|
# Check if server is reachable
|
||||||
|
if self.check_server_connection(server_host):
|
||||||
|
if self.consecutive_failures > 0:
|
||||||
|
self.consecutive_failures = 0
|
||||||
|
self.logger.info("Server connection restored")
|
||||||
|
log_with_server("Server connection restored", self.hostname, self.device_ip)
|
||||||
|
else:
|
||||||
|
self.consecutive_failures += 1
|
||||||
|
self.logger.warning(
|
||||||
|
f"Connection lost: {self.consecutive_failures}/{self.failure_threshold} failures"
|
||||||
|
)
|
||||||
|
|
||||||
|
# If threshold reached, do WiFi recovery
|
||||||
|
if self.consecutive_failures >= self.failure_threshold:
|
||||||
|
self.logger.error(
|
||||||
|
f"Server unreachable for {self.failure_threshold} pings - initiating WiFi recovery"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Perform WiFi recovery
|
||||||
|
if self.reconnect_wifi(wifi_interface, self.wifi_down_time):
|
||||||
|
self.logger.info("WiFi recovery completed successfully")
|
||||||
|
else:
|
||||||
|
self.logger.error("WiFi recovery failed")
|
||||||
|
|
||||||
|
time.sleep(self.check_interval)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error in connection monitor: {e}")
|
||||||
|
time.sleep(self.check_interval)
|
||||||
|
|
||||||
|
|
||||||
|
def start_monitoring(self, server_host="10.76.140.17"):
|
||||||
|
"""
|
||||||
|
Start background monitoring thread
|
||||||
|
|
||||||
|
Args:
|
||||||
|
server_host: Server to monitor
|
||||||
|
"""
|
||||||
|
self.monitor_thread = threading.Thread(
|
||||||
|
target=self.monitor_connection,
|
||||||
|
args=(server_host,),
|
||||||
|
daemon=True
|
||||||
|
)
|
||||||
|
self.monitor_thread.start()
|
||||||
|
self.logger.info("WiFi recovery monitor thread started")
|
||||||
|
|
||||||
|
|
||||||
|
def stop_monitoring(self):
|
||||||
|
"""Stop the monitoring thread"""
|
||||||
|
self.is_running = False
|
||||||
|
if self.monitor_thread:
|
||||||
|
self.monitor_thread.join(timeout=5)
|
||||||
|
self.logger.info("WiFi recovery monitor stopped")
|
||||||
|
|
||||||
|
|
||||||
|
# Global WiFi recovery manager instance
|
||||||
|
wifi_recovery_manager = None
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_wifi_recovery(hostname, device_ip, server_host="10.76.140.17"):
|
||||||
|
"""Initialize and start WiFi recovery monitoring"""
|
||||||
|
global wifi_recovery_manager
|
||||||
|
|
||||||
|
try:
|
||||||
|
wifi_recovery_manager = WiFiRecoveryManager(
|
||||||
|
hostname=hostname,
|
||||||
|
device_ip=device_ip,
|
||||||
|
check_interval=60,
|
||||||
|
failure_threshold=5
|
||||||
|
)
|
||||||
|
wifi_recovery_manager.start_monitoring(server_host)
|
||||||
|
logging.info("WiFi recovery initialized")
|
||||||
|
return wifi_recovery_manager
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Failed to initialize WiFi recovery: {e}")
|
||||||
|
return None
|
||||||
Reference in New Issue
Block a user