feat: Refactor state file path handling and update application events in JSON

This commit is contained in:
Miguel 2025-08-22 12:07:14 +02:00
parent c5ed02b4a2
commit 192c83ebce
7 changed files with 292 additions and 261 deletions

View File

@ -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
}

View File

@ -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}

44
main.py
View File

@ -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):

View File

@ -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
```

View File

@ -9,5 +9,5 @@
]
},
"auto_recovery_enabled": true,
"last_update": "2025-08-22T11:17:49.327595"
"last_update": "2025-08-22T11:59:00.787257"
}

View File

@ -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()

View File

@ -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()