فهرست منبع

add proper logging and more error handling to backend

Fabio De Simone 1 سال پیش
والد
کامیت
f7a45c6bc1

+ 14 - 2
dune-weaver-flask/app.py

@@ -43,6 +43,7 @@ def api_list_ports():
 def api_connect_serial():
 def api_connect_serial():
     port = request.json.get('port')
     port = request.json.get('port')
     if not port:
     if not port:
+        app.logger.error("No port provided in connect_serial request")
         return jsonify({'error': 'No port provided'}), 400
         return jsonify({'error': 'No port provided'}), 400
 
 
     try:
     try:
@@ -58,18 +59,21 @@ def api_disconnect():
         disconnect_serial()
         disconnect_serial()
         return jsonify({'success': True})
         return jsonify({'success': True})
     except Exception as e:
     except Exception as e:
+        app.logger.error(f"Error disconnecting serial port: {str(e)}", exc_info=True)
         return jsonify({'error': str(e)}), 500
         return jsonify({'error': str(e)}), 500
 
 
 @app.route('/restart_serial', methods=['POST'])
 @app.route('/restart_serial', methods=['POST'])
 def api_restart():
 def api_restart():
     port = request.json.get('port')
     port = request.json.get('port')
     if not port:
     if not port:
+        app.logger.error("No port provided in restart_serial request")
         return jsonify({'error': 'No port provided'}), 400
         return jsonify({'error': 'No port provided'}), 400
 
 
     try:
     try:
         success = restart_serial(port)
         success = restart_serial(port)
         return jsonify({'success': success})
         return jsonify({'success': success})
     except Exception as e:
     except Exception as e:
+        app.logger.error(f"Error restarting serial port: {str(e)}", exc_info=True)
         return jsonify({'error': str(e)}), 500
         return jsonify({'error': str(e)}), 500
 
 
 @app.route('/serial_status', methods=['GET'])
 @app.route('/serial_status', methods=['GET'])
@@ -103,11 +107,12 @@ def api_run_theta_rho():
     pre_execution = request.json.get('pre_execution')
     pre_execution = request.json.get('pre_execution')
 
 
     if not file_name:
     if not file_name:
+        app.logger.error("No file name provided in run_theta_rho request")
         return jsonify({'error': 'No file name provided'}), 400
         return jsonify({'error': 'No file name provided'}), 400
 
 
     file_path = os.path.join(THETA_RHO_DIR, file_name)
     file_path = os.path.join(THETA_RHO_DIR, file_name)
     if not os.path.exists(file_path):
     if not os.path.exists(file_path):
-        app.logger.error("aaaaaaaaaaaaaaaaaaaaaaaaaa")
+        app.logger.error(f"File not found: {file_path}")
         return jsonify({'error': 'File not found'}), 404
         return jsonify({'error': 'File not found'}), 404
 
 
     try:
     try:
@@ -123,6 +128,7 @@ def api_run_theta_rho():
         ).start()
         ).start()
         return jsonify({'success': True})
         return jsonify({'success': True})
     except Exception as e:
     except Exception as e:
+        app.logger.error(f"Error running theta rho file: {str(e)}", exc_info=True)
         return jsonify({'error': str(e)}), 500
         return jsonify({'error': str(e)}), 500
 
 
 @app.route('/run_theta_rho_file/<file_name>', methods=['POST'])
 @app.route('/run_theta_rho_file/<file_name>', methods=['POST'])
@@ -139,22 +145,24 @@ def api_run_specific_theta_rho_file(file_name):
 def api_preview_thr():
 def api_preview_thr():
     file_name = request.json.get('file_name')
     file_name = request.json.get('file_name')
     if not file_name:
     if not file_name:
+        app.logger.error("No file name provided in preview_thr request")
         return jsonify({'error': 'No file name provided'}), 400
         return jsonify({'error': 'No file name provided'}), 400
     
     
     # sometimes the frontend sends the complete path, not just the file name
     # sometimes the frontend sends the complete path, not just the file name
     if file_name.startswith("./patterns"):
     if file_name.startswith("./patterns"):
-        
         file_name = file_name.split('/')[-1].split('\\')[-1]
         file_name = file_name.split('/')[-1].split('\\')[-1]
     
     
     file_path = os.path.join(THETA_RHO_DIR, file_name)
     file_path = os.path.join(THETA_RHO_DIR, file_name)
         
         
     if not os.path.exists(file_path):
     if not os.path.exists(file_path):
+        app.logger.error(f"File not found: {file_path}")
         return jsonify({'error': 'File not found'}), 404
         return jsonify({'error': 'File not found'}), 404
 
 
     try:
     try:
         coordinates = parse_theta_rho_file(file_path)
         coordinates = parse_theta_rho_file(file_path)
         return jsonify({'success': True, 'coordinates': coordinates})
         return jsonify({'success': True, 'coordinates': coordinates})
     except Exception as e:
     except Exception as e:
+        app.logger.error(f"Error parsing theta rho file: {str(e)}", exc_info=True)
         return jsonify({'error': str(e)}), 500
         return jsonify({'error': str(e)}), 500
 
 
 @app.route('/send_coordinate', methods=['POST'])
 @app.route('/send_coordinate', methods=['POST'])
@@ -243,6 +251,7 @@ def api_get_playlist():
 def api_create_playlist():
 def api_create_playlist():
     data = request.get_json()
     data = request.get_json()
     if not data or "name" not in data or "files" not in data:
     if not data or "name" not in data or "files" not in data:
+        app.logger.error("Missing required fields in create_playlist request")
         return jsonify({"success": False, "error": "Playlist 'name' and 'files' are required"}), 400
         return jsonify({"success": False, "error": "Playlist 'name' and 'files' are required"}), 400
 
 
     success = create_playlist(data["name"], data["files"])
     success = create_playlist(data["name"], data["files"])
@@ -417,16 +426,19 @@ def api_delete_theta_rho_file():
     file_name = data.get('file_name')
     file_name = data.get('file_name')
 
 
     if not file_name:
     if not file_name:
+        app.logger.error("No file name provided in delete_theta_rho_file request")
         return jsonify({"success": False, "error": "No file name provided"}), 400
         return jsonify({"success": False, "error": "No file name provided"}), 400
 
 
     file_path = os.path.join(THETA_RHO_DIR, file_name)
     file_path = os.path.join(THETA_RHO_DIR, file_name)
     if not os.path.exists(file_path):
     if not os.path.exists(file_path):
+        app.logger.error(f"File not found: {file_path}")
         return jsonify({"success": False, "error": "File not found"}), 404
         return jsonify({"success": False, "error": "File not found"}), 404
 
 
     try:
     try:
         os.remove(file_path)
         os.remove(file_path)
         return jsonify({"success": True})
         return jsonify({"success": True})
     except Exception as e:
     except Exception as e:
+        app.logger.error(f"Error deleting theta rho file: {str(e)}", exc_info=True)
         return jsonify({"success": False, "error": str(e)}), 500
         return jsonify({"success": False, "error": str(e)}), 500
 
 
 if __name__ == '__main__':
 if __name__ == '__main__':

+ 39 - 29
dune-weaver-flask/modules/core/pattern_manager.py

@@ -5,6 +5,9 @@ import threading
 from datetime import datetime
 from datetime import datetime
 import time
 import time
 from ..serial.serial_manager import send_coordinate_batch, reset_theta, send_command
 from ..serial.serial_manager import send_coordinate_batch, reset_theta, send_command
+import logging
+
+logger = logging.getLogger(__name__)
 
 
 # Configuration
 # Configuration
 THETA_RHO_DIR = './patterns'
 THETA_RHO_DIR = './patterns'
@@ -45,10 +48,13 @@ def parse_theta_rho_file(file_path):
                     theta, rho = map(float, line.split())
                     theta, rho = map(float, line.split())
                     coordinates.append((theta, rho))
                     coordinates.append((theta, rho))
                 except ValueError:
                 except ValueError:
-                    print(f"Skipping invalid line: {line}")
+                    logger.warning(f"Skipping invalid line in {file_path}: {line}")
                     continue
                     continue
+    except FileNotFoundError:
+        logger.error(f"Theta-rho file not found: {file_path}")
+        return coordinates
     except Exception as e:
     except Exception as e:
-        print(f"Error reading file: {e}")
+        logger.error(f"Error reading theta-rho file {file_path}: {str(e)}", exc_info=True)
         return coordinates
         return coordinates
 
 
     # Normalize coordinates
     # Normalize coordinates
@@ -113,44 +119,48 @@ def run_theta_rho_file(file_path, schedule_hours=None):
     total_coordinates = len(coordinates)
     total_coordinates = len(coordinates)
 
 
     if total_coordinates < 2:
     if total_coordinates < 2:
-        print("Not enough coordinates for interpolation.")
+        logger.error(f"Not enough coordinates for interpolation in file: {file_path}")
         current_playing_file = None
         current_playing_file = None
         execution_progress = None
         execution_progress = None
         return
         return
 
 
-    execution_progress = (0, total_coordinates)
-    batch_size = 10
+    try:
+        execution_progress = (0, total_coordinates)
+        batch_size = 10
 
 
-    for i in range(0, total_coordinates, batch_size):
-        if stop_requested:
-            print("Execution stopped by user after completing the current batch.")
-            break
+        for i in range(0, total_coordinates, batch_size):
+            if stop_requested:
+                logger.info("Execution stopped by user after completing the current batch.")
+                break
 
 
-        with pause_condition:
-            while pause_requested:
-                print("Execution paused...")
-                pause_condition.wait()
-
-        batch = coordinates[i:i + batch_size]
-        if i == 0:
-            send_coordinate_batch(batch)
-            execution_progress = (i + batch_size, total_coordinates)
-            continue
+            with pause_condition:
+                while pause_requested:
+                    logger.info("Execution paused...")
+                    pause_condition.wait()
 
 
-        while True:
-            schedule_checker(schedule_hours)
-            response = send_command("R")
-            if response == "R":
+            batch = coordinates[i:i + batch_size]
+            if i == 0:
                 send_coordinate_batch(batch)
                 send_coordinate_batch(batch)
                 execution_progress = (i + batch_size, total_coordinates)
                 execution_progress = (i + batch_size, total_coordinates)
-                break
+                continue
 
 
-    reset_theta()
-    send_command("FINISHED")
+            while True:
+                schedule_checker(schedule_hours)
+                response = send_command("R")
+                if response == "R":
+                    send_coordinate_batch(batch)
+                    execution_progress = (i + batch_size, total_coordinates)
+                    break
 
 
-    current_playing_file = None
-    execution_progress = None
-    print("Pattern execution completed.")
+        reset_theta()
+        send_command("FINISHED")
+
+    except Exception as e:
+        logger.error(f"Error executing theta-rho file {file_path}: {str(e)}", exc_info=True)
+    finally:
+        current_playing_file = None
+        execution_progress = None
+        logger.info("Pattern execution completed.")
 
 
 def run_theta_rho_files(
 def run_theta_rho_files(
     file_paths,
     file_paths,

+ 3 - 0
dune-weaver-flask/modules/core/playlist_manager.py

@@ -1,5 +1,8 @@
 import os
 import os
 import json
 import json
+import logging
+
+logger = logging.getLogger(__name__)
 
 
 PLAYLISTS_FILE = os.path.join(os.getcwd(), "playlists.json")
 PLAYLISTS_FILE = os.path.join(os.getcwd(), "playlists.json")
 
 

+ 122 - 61
dune-weaver-flask/modules/serial/serial_manager.py

@@ -2,6 +2,9 @@ import serial
 import serial.tools.list_ports
 import serial.tools.list_ports
 import threading
 import threading
 import time
 import time
+import logging
+
+logger = logging.getLogger(__name__)
 
 
 # Configuration
 # Configuration
 IGNORE_PORTS = ['/dev/cu.debug-console', '/dev/cu.Bluetooth-Incoming-Port']
 IGNORE_PORTS = ['/dev/cu.debug-console', '/dev/cu.Bluetooth-Incoming-Port']
@@ -16,8 +19,12 @@ serial_lock = threading.Lock()
 
 
 def list_serial_ports():
 def list_serial_ports():
     """Return a list of available serial ports."""
     """Return a list of available serial ports."""
-    ports = serial.tools.list_ports.comports()
-    return [port.device for port in ports if port.device not in IGNORE_PORTS]
+    try:
+        ports = serial.tools.list_ports.comports()
+        return [port.device for port in ports if port.device not in IGNORE_PORTS]
+    except Exception as e:
+        logger.error(f"Error listing serial ports: {str(e)}", exc_info=True)
+        return []
 
 
 def connect_to_serial(port=None, baudrate=115200):
 def connect_to_serial(port=None, baudrate=115200):
     """Automatically connect to the first available serial port or a specified port."""
     """Automatically connect to the first available serial port or a specified port."""
@@ -27,7 +34,7 @@ def connect_to_serial(port=None, baudrate=115200):
         if port is None:
         if port is None:
             ports = list_serial_ports()
             ports = list_serial_ports()
             if not ports:
             if not ports:
-                print("No serial port connected")
+                logger.error("No serial port connected")
                 return False
                 return False
             port = ports[0]  # Auto-select the first available port
             port = ports[0]  # Auto-select the first available port
 
 
@@ -37,7 +44,7 @@ def connect_to_serial(port=None, baudrate=115200):
             ser = serial.Serial(port, baudrate, timeout=2)  # Set timeout to avoid infinite waits
             ser = serial.Serial(port, baudrate, timeout=2)  # Set timeout to avoid infinite waits
             ser_port = port  # Store the connected port globally
             ser_port = port  # Store the connected port globally
 
 
-        print(f"Connected to serial port: {port}")
+        logger.info(f"Connected to serial port: {port}")
         time.sleep(2)  # Allow time for the connection to establish
         time.sleep(2)  # Allow time for the connection to establish
 
 
         # Read initial startup messages from Arduino
         # Read initial startup messages from Arduino
@@ -45,86 +52,140 @@ def connect_to_serial(port=None, baudrate=115200):
         arduino_driver_type = None
         arduino_driver_type = None
 
 
         while ser.in_waiting > 0:
         while ser.in_waiting > 0:
-            line = ser.readline().decode().strip()
-            print(f"Arduino: {line}")  # Print the received message
-
-            # Store the device details based on the expected messages
-            if "Table:" in line:
-                arduino_table_name = line.replace("Table: ", "").strip()
-            elif "Drivers:" in line:
-                arduino_driver_type = line.replace("Drivers: ", "").strip()
-            elif "Version:" in line:
-                firmware_version = line.replace("Version: ", "").strip()
-
-        # Display stored values
-        print(f"Detected Table: {arduino_table_name or 'Unknown'}")
-        print(f"Detected Drivers: {arduino_driver_type or 'Unknown'}")
+            try:
+                line = ser.readline().decode().strip()
+                logger.debug(f"Arduino: {line}")  # Print the received message
+
+                # Store the device details based on the expected messages
+                if "Table:" in line:
+                    arduino_table_name = line.replace("Table: ", "").strip()
+                elif "Drivers:" in line:
+                    arduino_driver_type = line.replace("Drivers: ", "").strip()
+                elif "Version:" in line:
+                    firmware_version = line.replace("Version: ", "").strip()
+            except UnicodeDecodeError as e:
+                logger.warning(f"Failed to decode Arduino message: {str(e)}")
+                continue
+
+        logger.info(f"Detected Table: {arduino_table_name or 'Unknown'}")
+        logger.info(f"Detected Drivers: {arduino_driver_type or 'Unknown'}")
 
 
         return True  # Successfully connected
         return True  # Successfully connected
     except serial.SerialException as e:
     except serial.SerialException as e:
-        print(f"Failed to connect to serial port {port}: {e}")
-        port = None  # Reset the port to try the next available one
-
-    print("Max retries reached. Could not connect to a serial port.")
-    return False
+        logger.error(f"Failed to connect to serial port {port}: {str(e)}", exc_info=True)
+        return False
+    except Exception as e:
+        logger.error(f"Unexpected error connecting to serial port: {str(e)}", exc_info=True)
+        return False
 
 
 def disconnect_serial():
 def disconnect_serial():
     """Disconnect the current serial connection."""
     """Disconnect the current serial connection."""
     global ser, ser_port
     global ser, ser_port
-    if ser and ser.is_open:
-        ser.close()
+    try:
+        if ser and ser.is_open:
+            ser.close()
+            logger.info(f"Disconnected from serial port: {ser_port}")
         ser = None
         ser = None
-    ser_port = None  # Reset the port name
+        ser_port = None
+    except Exception as e:
+        logger.error(f"Error disconnecting serial port: {str(e)}", exc_info=True)
 
 
 def restart_serial(port, baudrate=115200):
 def restart_serial(port, baudrate=115200):
     """Restart the serial connection."""
     """Restart the serial connection."""
-    disconnect_serial()
-    return connect_to_serial(port, baudrate)
+    try:
+        disconnect_serial()
+        return connect_to_serial(port, baudrate)
+    except Exception as e:
+        logger.error(f"Error restarting serial connection: {str(e)}", exc_info=True)
+        return False
 
 
 def send_command(command):
 def send_command(command):
     """Send a single command to the Arduino."""
     """Send a single command to the Arduino."""
-    ser.write(f"{command}\n".encode())
-    print(f"Sent: {command}")
-
-    # Wait for "R" acknowledgment from Arduino
-    while True:
-        with serial_lock:
-            if ser.in_waiting > 0:
-                response = ser.readline().decode().strip()
-                print(f"Arduino response: {response}")
-                if response == "R":
-                    print("Command execution completed.")
-                    break
+    try:
+        if not ser or not ser.is_open:
+            logger.error("Cannot send command: Serial port not open")
+            return None
+
+        ser.write(f"{command}\n".encode())
+        logger.debug(f"Sent: {command}")
+
+        # Wait for "R" acknowledgment from Arduino
+        while True:
+            with serial_lock:
+                if ser.in_waiting > 0:
+                    response = ser.readline().decode().strip()
+                    logger.debug(f"Arduino response: {response}")
+                    if response == "R":
+                        logger.debug("Command execution completed.")
+                        return response
+    except serial.SerialException as e:
+        logger.error(f"Serial communication error while sending command: {str(e)}", exc_info=True)
+        return None
+    except Exception as e:
+        logger.error(f"Unexpected error sending command: {str(e)}", exc_info=True)
+        return None
 
 
 def send_coordinate_batch(coordinates):
 def send_coordinate_batch(coordinates):
     """Send a batch of theta-rho pairs to the Arduino."""
     """Send a batch of theta-rho pairs to the Arduino."""
-    batch_str = ";".join(f"{theta:.5f},{rho:.5f}" for theta, rho in coordinates) + ";\n"
-    ser.write(batch_str.encode())
+    try:
+        if not ser or not ser.is_open:
+            logger.error("Cannot send coordinates: Serial port not open")
+            return False
+
+        batch_str = ";".join(f"{theta:.5f},{rho:.5f}" for theta, rho in coordinates) + ";\n"
+        ser.write(batch_str.encode())
+        return True
+    except Exception as e:
+        logger.error(f"Error sending coordinate batch: {str(e)}", exc_info=True)
+        return False
 
 
 def get_serial_status():
 def get_serial_status():
     """Get the current status of the serial connection."""
     """Get the current status of the serial connection."""
-    return {
-        'connected': ser.is_open if ser else False,
-        'port': ser_port
-    }
+    try:
+        return {
+            'connected': ser.is_open if ser else False,
+            'port': ser_port
+        }
+    except Exception as e:
+        logger.error(f"Error getting serial status: {str(e)}", exc_info=True)
+        return {
+            'connected': False,
+            'port': None
+        }
 
 
 def get_device_info():
 def get_device_info():
     """Get information about the connected device."""
     """Get information about the connected device."""
-    return {
-        'table_name': arduino_table_name,
-        'driver_type': arduino_driver_type,
-        'firmware_version': firmware_version
-    }
+    try:
+        return {
+            'table_name': arduino_table_name,
+            'driver_type': arduino_driver_type,
+            'firmware_version': firmware_version
+        }
+    except Exception as e:
+        logger.error(f"Error getting device info: {str(e)}", exc_info=True)
+        return {
+            'table_name': None,
+            'driver_type': None,
+            'firmware_version': None
+        }
 
 
 def reset_theta():
 def reset_theta():
     """Reset theta on the Arduino."""
     """Reset theta on the Arduino."""
-    ser.write("RESET_THETA\n".encode())
-    while True:
-        with serial_lock:
-            if ser.in_waiting > 0:
-                response = ser.readline().decode().strip()
-                print(f"Arduino response: {response}")
-                if response == "THETA_RESET":
-                    print("Theta successfully reset.")
-                    break
-        time.sleep(0.5)  # Small delay to avoid busy waiting 
+    try:
+        if not ser or not ser.is_open:
+            logger.error("Cannot reset theta: Serial port not open")
+            return False
+
+        ser.write("RESET_THETA\n".encode())
+        while True:
+            with serial_lock:
+                if ser.in_waiting > 0:
+                    response = ser.readline().decode().strip()
+                    logger.debug(f"Arduino response: {response}")
+                    if response == "THETA_RESET":
+                        logger.info("Theta successfully reset.")
+                        return True
+            time.sleep(0.5)  # Small delay to avoid busy waiting
+    except Exception as e:
+        logger.error(f"Error resetting theta: {str(e)}", exc_info=True)
+        return False