From 192c83ebce50b5e0f2a32fab2b6df69e4fed8e98 Mon Sep 17 00:00:00 2001 From: Miguel Date: Fri, 22 Aug 2025 12:07:14 +0200 Subject: [PATCH] feat: Refactor state file path handling and update application events in JSON --- application_events.json | 359 +++++++++++-------------------------- core/config_manager.py | 2 +- main.py | 44 ++++- readme.md | 2 +- system_state.json | 2 +- test_plotjuggler_search.py | 67 +++++++ test_system_state_paths.py | 77 ++++++++ 7 files changed, 292 insertions(+), 261 deletions(-) create mode 100644 test_plotjuggler_search.py create mode 100644 test_system_state_paths.py diff --git a/application_events.json b/application_events.json index 5009fcf..c85e9e9 100644 --- a/application_events.json +++ b/application_events.json @@ -1,258 +1,5 @@ { "events": [ - { - "timestamp": "2025-08-18T16:50:04.346871", - "level": "info", - "event_type": "performance_report", - "message": "Performance report: 0 points saved, 0 lost, 0.2% CPU", - "details": { - "duration": 10.028072118759155, - "points_saved": 0, - "points_rate": 0.0, - "variables_saved": 0, - "udp_points_sent": 0, - "points_lost": 0, - "cpu_average": 0.2, - "cpu_max": 0.2, - "delay_average": 0.0, - "delay_max": 0.0, - "read_errors": 0, - "csv_errors": 0, - "udp_errors": 0, - "read_time_avg": 0.0, - "csv_write_time_avg": 0.0 - } - }, - { - "timestamp": "2025-08-18T16:50:14.373547", - "level": "info", - "event_type": "performance_report", - "message": "Performance report: 0 points saved, 0 lost, 0.2% CPU", - "details": { - "duration": 10.026675939559937, - "points_saved": 0, - "points_rate": 0.0, - "variables_saved": 0, - "udp_points_sent": 0, - "points_lost": 0, - "cpu_average": 0.2, - "cpu_max": 0.2, - "delay_average": 0.0, - "delay_max": 0.0, - "read_errors": 0, - "csv_errors": 0, - "udp_errors": 0, - "read_time_avg": 0.0, - "csv_write_time_avg": 0.0 - } - }, - { - "timestamp": "2025-08-18T16:50:24.402562", - "level": "info", - "event_type": "performance_report", - "message": "Performance report: 0 points saved, 0 lost, 0.0% CPU", - "details": { - "duration": 10.029014587402344, - "points_saved": 0, - "points_rate": 0.0, - "variables_saved": 0, - "udp_points_sent": 0, - "points_lost": 0, - "cpu_average": 0.0, - "cpu_max": 0.0, - "delay_average": 0.0, - "delay_max": 0.0, - "read_errors": 0, - "csv_errors": 0, - "udp_errors": 0, - "read_time_avg": 0.0, - "csv_write_time_avg": 0.0 - } - }, - { - "timestamp": "2025-08-18T16:50:34.431298", - "level": "info", - "event_type": "performance_report", - "message": "Performance report: 0 points saved, 0 lost, 0.2% CPU", - "details": { - "duration": 10.028736352920532, - "points_saved": 0, - "points_rate": 0.0, - "variables_saved": 0, - "udp_points_sent": 0, - "points_lost": 0, - "cpu_average": 0.2, - "cpu_max": 0.2, - "delay_average": 0.0, - "delay_max": 0.0, - "read_errors": 0, - "csv_errors": 0, - "udp_errors": 0, - "read_time_avg": 0.0, - "csv_write_time_avg": 0.0 - } - }, - { - "timestamp": "2025-08-18T16:50:44.457535", - "level": "info", - "event_type": "performance_report", - "message": "Performance report: 0 points saved, 0 lost, 0.3% CPU", - "details": { - "duration": 10.026236534118652, - "points_saved": 0, - "points_rate": 0.0, - "variables_saved": 0, - "udp_points_sent": 0, - "points_lost": 0, - "cpu_average": 0.3, - "cpu_max": 0.3, - "delay_average": 0.0, - "delay_max": 0.0, - "read_errors": 0, - "csv_errors": 0, - "udp_errors": 0, - "read_time_avg": 0.0, - "csv_write_time_avg": 0.0 - } - }, - { - "timestamp": "2025-08-18T16:50:54.486134", - "level": "info", - "event_type": "performance_report", - "message": "Performance report: 0 points saved, 0 lost, 0.0% CPU", - "details": { - "duration": 10.028599500656128, - "points_saved": 0, - "points_rate": 0.0, - "variables_saved": 0, - "udp_points_sent": 0, - "points_lost": 0, - "cpu_average": 0.0, - "cpu_max": 0.0, - "delay_average": 0.0, - "delay_max": 0.0, - "read_errors": 0, - "csv_errors": 0, - "udp_errors": 0, - "read_time_avg": 0.0, - "csv_write_time_avg": 0.0 - } - }, - { - "timestamp": "2025-08-18T16:51:04.515974", - "level": "info", - "event_type": "performance_report", - "message": "Performance report: 0 points saved, 0 lost, 0.2% CPU", - "details": { - "duration": 10.029839277267456, - "points_saved": 0, - "points_rate": 0.0, - "variables_saved": 0, - "udp_points_sent": 0, - "points_lost": 0, - "cpu_average": 0.2, - "cpu_max": 0.2, - "delay_average": 0.0, - "delay_max": 0.0, - "read_errors": 0, - "csv_errors": 0, - "udp_errors": 0, - "read_time_avg": 0.0, - "csv_write_time_avg": 0.0 - } - }, - { - "timestamp": "2025-08-18T16:51:14.546153", - "level": "info", - "event_type": "performance_report", - "message": "Performance report: 0 points saved, 0 lost, 0.3% CPU", - "details": { - "duration": 10.030179023742676, - "points_saved": 0, - "points_rate": 0.0, - "variables_saved": 0, - "udp_points_sent": 0, - "points_lost": 0, - "cpu_average": 0.3, - "cpu_max": 0.3, - "delay_average": 0.0, - "delay_max": 0.0, - "read_errors": 0, - "csv_errors": 0, - "udp_errors": 0, - "read_time_avg": 0.0, - "csv_write_time_avg": 0.0 - } - }, - { - "timestamp": "2025-08-18T16:51:24.573333", - "level": "info", - "event_type": "performance_report", - "message": "Performance report: 0 points saved, 0 lost, 0.2% CPU", - "details": { - "duration": 10.027179956436157, - "points_saved": 0, - "points_rate": 0.0, - "variables_saved": 0, - "udp_points_sent": 0, - "points_lost": 0, - "cpu_average": 0.2, - "cpu_max": 0.2, - "delay_average": 0.0, - "delay_max": 0.0, - "read_errors": 0, - "csv_errors": 0, - "udp_errors": 0, - "read_time_avg": 0.0, - "csv_write_time_avg": 0.0 - } - }, - { - "timestamp": "2025-08-18T16:51:34.602793", - "level": "info", - "event_type": "performance_report", - "message": "Performance report: 0 points saved, 0 lost, 0.3% CPU", - "details": { - "duration": 10.029460191726685, - "points_saved": 0, - "points_rate": 0.0, - "variables_saved": 0, - "udp_points_sent": 0, - "points_lost": 0, - "cpu_average": 0.3, - "cpu_max": 0.3, - "delay_average": 0.0, - "delay_max": 0.0, - "read_errors": 0, - "csv_errors": 0, - "udp_errors": 0, - "read_time_avg": 0.0, - "csv_write_time_avg": 0.0 - } - }, - { - "timestamp": "2025-08-18T16:51:44.630302", - "level": "info", - "event_type": "performance_report", - "message": "Performance report: 0 points saved, 0 lost, 0.0% CPU", - "details": { - "duration": 10.027508974075317, - "points_saved": 0, - "points_rate": 0.0, - "variables_saved": 0, - "udp_points_sent": 0, - "points_lost": 0, - "cpu_average": 0.0, - "cpu_max": 0.0, - "delay_average": 0.0, - "delay_max": 0.0, - "read_errors": 0, - "csv_errors": 0, - "udp_errors": 0, - "read_time_avg": 0.0, - "csv_write_time_avg": 0.0 - } - }, { "timestamp": "2025-08-18T16:51:54.662657", "level": "info", @@ -20723,8 +20470,112 @@ "event_type": "plc_disconnection", "message": "Disconnected from PLC 10.1.33.11 (application shutdown (will auto-reconnect on restart))", "details": {} + }, + { + "timestamp": "2025-08-22T11:58:53.643278", + "level": "info", + "event_type": "application_started", + "message": "Application initialization completed successfully", + "details": {} + }, + { + "timestamp": "2025-08-22T11:58:53.764308", + "level": "info", + "event_type": "dataset_activated", + "message": "Dataset activated: DAR", + "details": { + "dataset_id": "DAR", + "variables_count": 3, + "streaming_count": 3, + "prefix": "gateway_phoenix" + } + }, + { + "timestamp": "2025-08-22T11:58:53.788360", + "level": "info", + "event_type": "dataset_activated", + "message": "Dataset activated: Fast", + "details": { + "dataset_id": "Fast", + "variables_count": 3, + "streaming_count": 1, + "prefix": "fast" + } + }, + { + "timestamp": "2025-08-22T11:58:53.811243", + "level": "info", + "event_type": "csv_recording_started", + "message": "šŸ”„ CRITICAL PRIORITY: CSV recording started with MAXIMUM PRIORITY, async buffering, and performance monitoring: 2 datasets activated", + "details": { + "activated_datasets": 2, + "total_datasets": 3, + "priority": "CRITICAL", + "recording_protection": true, + "performance_monitoring": true, + "async_csv_buffering": true, + "csv_flush_interval": 5.0 + } + }, + { + "timestamp": "2025-08-22T11:58:55.345174", + "level": "info", + "event_type": "udp_streaming_stopped", + "message": "UDP streaming to PlotJuggler stopped (CSV recording continues)", + "details": {} + }, + { + "timestamp": "2025-08-22T11:59:00.765560", + "level": "info", + "event_type": "csv_recording_stopped", + "message": "šŸ”„ CRITICAL: CSV recording stopped (dataset threads continue for UDP streaming)", + "details": { + "recording_protection": false, + "performance_monitoring": false + } + }, + { + "timestamp": "2025-08-22T11:59:00.788247", + "level": "info", + "event_type": "udp_streaming_stopped", + "message": "UDP streaming to PlotJuggler stopped (CSV recording continues)", + "details": {} + }, + { + "timestamp": "2025-08-22T11:59:00.809240", + "level": "info", + "event_type": "dataset_deactivated", + "message": "Dataset deactivated: test", + "details": { + "dataset_id": "Test" + } + }, + { + "timestamp": "2025-08-22T11:59:01.270883", + "level": "info", + "event_type": "dataset_deactivated", + "message": "Dataset deactivated: DAR", + "details": { + "dataset_id": "DAR" + } + }, + { + "timestamp": "2025-08-22T11:59:01.294452", + "level": "info", + "event_type": "dataset_deactivated", + "message": "Dataset deactivated: Fast", + "details": { + "dataset_id": "Fast" + } + }, + { + "timestamp": "2025-08-22T11:59:01.315486", + "level": "info", + "event_type": "plc_disconnection", + "message": "Disconnected from PLC 10.1.33.11 (application shutdown (will auto-reconnect on restart))", + "details": {} } ], - "last_updated": "2025-08-22T11:17:50.022501", + "last_updated": "2025-08-22T11:59:01.315486", "total_entries": 1000 } \ No newline at end of file diff --git a/core/config_manager.py b/core/config_manager.py index 8abfe59..13f4624 100644 --- a/core/config_manager.py +++ b/core/config_manager.py @@ -55,7 +55,7 @@ class ConfigManager: # Legacy dataset file for migration self.datasets_file = resource_path(os.path.join(data_dir, "plc_datasets.json")) - self.state_file = resource_path("system_state.json") + self.state_file = external_path("system_state.json") # Default configurations self.plc_config = {"ip": "192.168.1.100", "rack": 0, "slot": 2} diff --git a/main.py b/main.py index 35f1cd9..d2f5311 100644 --- a/main.py +++ b/main.py @@ -130,6 +130,17 @@ def resource_path(relative_path): return os.path.join(base_path, relative_path) +def external_path(relative_path): + """Get path external to PyInstaller bundle (for persistent files like logs, state, etc.)""" + if getattr(sys, "frozen", False): + # Running as PyInstaller executable - use directory next to exe + executable_dir = os.path.dirname(sys.executable) + return os.path.join(executable_dir, relative_path) + else: + # Running as script - use current directory + return os.path.join(os.path.abspath("."), relative_path) + + def project_path(*parts: str) -> str: """Build absolute path from the project root (based on this file).""" base_dir = os.path.abspath(os.path.dirname(__file__)) @@ -3808,7 +3819,7 @@ def get_plotjuggler_path(): """Get PlotJuggler executable path, search if not found""" try: # Load from system state - state_file = resource_path("system_state.json") + state_file = external_path("system_state.json") if os.path.exists(state_file): with open(state_file, "r") as f: state = json.load(f) @@ -3816,18 +3827,43 @@ def get_plotjuggler_path(): if saved_path and os.path.exists(saved_path): return saved_path - # Search for PlotJuggler + # Search for PlotJuggler in common installation paths search_paths = [ + # Standard installation paths for different Windows versions r"C:\Program Files\PlotJuggler\plotjuggler.exe", r"C:\Program Files (x86)\PlotJuggler\plotjuggler.exe", + # Alternative installation locations r"C:\PlotJuggler\plotjuggler.exe", + r"C:\Tools\PlotJuggler\plotjuggler.exe", + # User-specific installations + os.path.expanduser(r"~\AppData\Local\PlotJuggler\plotjuggler.exe"), + os.path.expanduser(r"~\PlotJuggler\plotjuggler.exe"), + # Portable installations in common locations + r"D:\PlotJuggler\plotjuggler.exe", + r"E:\PlotJuggler\plotjuggler.exe", ] - for path in search_paths: + # Also search in PATH environment variable + import shutil + + path_executable = shutil.which("plotjuggler") + if path_executable: + search_paths.insert(0, path_executable) # Give priority to PATH + + # Search through all paths + backend_logger.info( + f"Searching for PlotJuggler in {len(search_paths)} locations..." + ) + for i, path in enumerate(search_paths): + backend_logger.debug(f" [{i+1}] Checking: {path}") if os.path.exists(path): + backend_logger.info(f"āœ… PlotJuggler found at: {path}") save_plotjuggler_path(path) return path + else: + backend_logger.debug(f" āŒ Not found: {path}") + backend_logger.warning("āŒ PlotJuggler not found in any standard location") return None except Exception as e: @@ -3838,7 +3874,7 @@ def get_plotjuggler_path(): def save_plotjuggler_path(path): """Save PlotJuggler path to system state""" try: - state_file = resource_path("system_state.json") + state_file = external_path("system_state.json") state = {} if os.path.exists(state_file): diff --git a/readme.md b/readme.md index b8ad33b..c9177db 100644 --- a/readme.md +++ b/readme.md @@ -8,5 +8,5 @@ cd frontend && npm run build && cd .. rmdir /s /q build dist # 3. Ejecutar PyInstaller -pyinstaller main.spec --clean +conda activate snap7v12; pyinstaller main.spec --clean ``` diff --git a/system_state.json b/system_state.json index 6adeff7..5da780c 100644 --- a/system_state.json +++ b/system_state.json @@ -9,5 +9,5 @@ ] }, "auto_recovery_enabled": true, - "last_update": "2025-08-22T11:17:49.327595" + "last_update": "2025-08-22T11:59:00.787257" } \ No newline at end of file diff --git a/test_plotjuggler_search.py b/test_plotjuggler_search.py new file mode 100644 index 0000000..39e6380 --- /dev/null +++ b/test_plotjuggler_search.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +""" +Test script to verify PlotJuggler path detection +""" + +import os +import shutil + + +def test_plotjuggler_search(): + """Test PlotJuggler search paths""" + print("šŸ” Testing PlotJuggler search paths") + + # Standard installation paths for different Windows versions + search_paths = [ + # Standard installation paths for different Windows versions + r"C:\Program Files\PlotJuggler\plotjuggler.exe", + r"C:\Program Files (x86)\PlotJuggler\plotjuggler.exe", + # Alternative installation locations + r"C:\PlotJuggler\plotjuggler.exe", + r"C:\Tools\PlotJuggler\plotjuggler.exe", + # User-specific installations + os.path.expanduser(r"~\AppData\Local\PlotJuggler\plotjuggler.exe"), + os.path.expanduser(r"~\PlotJuggler\plotjuggler.exe"), + # Portable installations in common locations + r"D:\PlotJuggler\plotjuggler.exe", + r"E:\PlotJuggler\plotjuggler.exe", + ] + + # Also search in PATH environment variable + path_executable = shutil.which("plotjuggler") + if path_executable: + search_paths.insert(0, path_executable) # Give priority to PATH + print(f"šŸŽÆ Found in PATH: {path_executable}") + else: + print("āŒ PlotJuggler not found in PATH") + + print(f"\nšŸ“ Checking {len(search_paths)} locations:") + + found_paths = [] + for i, path in enumerate(search_paths): + exists = os.path.exists(path) + status = "āœ…" if exists else "āŒ" + print(f" [{i+1:2d}] {status} {path}") + if exists: + found_paths.append(path) + + print(f"\nšŸ“Š Summary:") + print(f" Total paths checked: {len(search_paths)}") + print(f" PlotJuggler found: {len(found_paths)}") + + if found_paths: + print(f" šŸ“‚ Available installations:") + for i, path in enumerate(found_paths): + print(f" {i+1}. {path}") + else: + print(f" āš ļø No PlotJuggler installations found") + print(f" šŸ’” You may need to:") + print( + f" - Install PlotJuggler from https://github.com/facontidavide/PlotJuggler" + ) + print(f" - Add PlotJuggler to your PATH") + print(f" - Install to one of the checked locations") + + +if __name__ == "__main__": + test_plotjuggler_search() diff --git a/test_system_state_paths.py b/test_system_state_paths.py new file mode 100644 index 0000000..d62ac64 --- /dev/null +++ b/test_system_state_paths.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +""" +Test script to verify system_state.json path handling +for both development and PyInstaller packaging scenarios +""" + +import sys +import os + + +def external_path(relative_path): + """Get path external to PyInstaller bundle (for persistent files like logs, state, etc.)""" + if getattr(sys, "frozen", False): + # Running as PyInstaller executable - use directory next to exe + executable_dir = os.path.dirname(sys.executable) + return os.path.join(executable_dir, relative_path) + else: + # Running as script - use current directory + return os.path.join(os.path.abspath("."), relative_path) + + +def resource_path(relative_path): + """Get absolute path to resource, works for dev and for PyInstaller""" + try: + # PyInstaller creates a temp folder and stores path in _MEIPASS + base_path = sys._MEIPASS + except Exception: + # Not running in a bundle + base_path = os.path.abspath(".") + return os.path.join(base_path, relative_path) + + +def test_paths(): + """Test path resolution for system_state.json""" + print("šŸ” Testing system_state.json path resolution") + print(f"šŸ“ Current working directory: {os.getcwd()}") + print(f"šŸ Python executable: {sys.executable}") + print(f"šŸ“¦ Frozen (PyInstaller): {getattr(sys, 'frozen', False)}") + + if hasattr(sys, "_MEIPASS"): + print(f"šŸ“‚ PyInstaller temp dir (_MEIPASS): {sys._MEIPASS}") + + print("\nšŸ“ Path comparisons:") + + # Old behavior (would fail in PyInstaller) + old_path = resource_path("system_state.json") + print(f"āŒ OLD (resource_path): {old_path}") + print( + f" šŸ“ Writable: {os.access(os.path.dirname(old_path), os.W_OK) if os.path.exists(os.path.dirname(old_path)) else 'Directory does not exist'}" + ) + + # New behavior (should work everywhere) + new_path = external_path("system_state.json") + print(f"āœ… NEW (external_path): {new_path}") + print( + f" šŸ“ Writable: {os.access(os.path.dirname(new_path), os.W_OK) if os.path.exists(os.path.dirname(new_path)) else 'Directory does not exist'}" + ) + + # Test if current system_state.json exists + current_file = "system_state.json" + if os.path.exists(current_file): + print( + f"\nšŸ“„ Current system_state.json found at: {os.path.abspath(current_file)}" + ) + print(f" šŸ“ Size: {os.path.getsize(current_file)} bytes") + else: + print(f"\nā“ No system_state.json found in current directory") + + # Check if file exists at new path + if os.path.exists(new_path): + print(f"šŸ“„ system_state.json found at NEW path: {new_path}") + else: + print(f"ā“ No system_state.json found at NEW path: {new_path}") + + +if __name__ == "__main__": + test_paths()