Refactor dataset definitions and variables; update plot configurations and add system tray support

- Updated dataset definitions in `dataset_definitions.json` to change prefix for DAR and removed unused datasets.
- Modified dataset variables in `dataset_variables.json` to update variable names and added a new variable for CTS306_PEW.
- Simplified plot definitions in `plot_definitions.json` by removing old plots and updating the DAR plot configuration.
- Enhanced `main.py` to include system tray functionality using pystray, allowing users to manage the application from the tray.
- Updated `requirements.txt` to include dependencies for system tray support.
- Adjusted `system_state.json` to reflect changes in active datasets and removed references to obsolete datasets.
This commit is contained in:
Miguel 2025-08-25 16:02:15 +02:00
parent 250748446f
commit aba83f843a
7 changed files with 8512 additions and 7934 deletions

File diff suppressed because it is too large Load Diff

View File

@ -5,26 +5,9 @@
"enabled": true,
"id": "DAR",
"name": "DAR",
"prefix": "gateway_phoenix",
"prefix": "dar",
"sampling_interval": 0.5,
"use_optimized_reading": true
},
{
"created": "2025-08-09T02:06:26.840011",
"enabled": true,
"id": "Fast",
"name": "Fast",
"prefix": "fast",
"sampling_interval": 0.5,
"use_optimized_reading": true
},
{
"enabled": true,
"id": "Test",
"name": "test",
"prefix": "test",
"sampling_interval": 1,
"use_optimized_reading": true
}
]
}

View File

@ -4,31 +4,39 @@
"dataset_id": "DAR",
"variables": [
{
"area": "db",
"configType": "manual",
"area": "db",
"db": 1011,
"name": "UR29_Brix",
"name": "HMI_Instrument.QTM307.PVFiltered",
"offset": 1322,
"streaming": true,
"type": "real"
},
{
"area": "db",
"configType": "manual",
"area": "db",
"db": 1011,
"name": "UR29_ma",
"name": "HMI_Instrument.QTM306.PVFiltered",
"offset": 1296,
"streaming": true,
"type": "real"
},
{
"area": "db",
"configType": "manual",
"area": "db",
"db": 1011,
"name": "UR29_max",
"offset": 1296,
"name": "HMI_Instrument.CTS306.PVFiltered",
"offset": 1348,
"streaming": true,
"type": "real"
},
{
"configType": "manual",
"area": "pew",
"type": "word",
"streaming": false,
"name": "CTS306_PEW",
"offset": 256
}
]
},
@ -36,29 +44,29 @@
"dataset_id": "Fast",
"variables": [
{
"configType": "symbol",
"area": "db",
"configType": "symbol",
"streaming": true,
"symbol": "AUX Blink_2.0S",
"type": "real"
},
{
"configType": "manual",
"area": "m",
"bit": 1,
"configType": "manual",
"name": "M50.1",
"offset": 50,
"streaming": false,
"type": "bool"
},
{
"configType": "manual",
"area": "m",
"type": "bool",
"streaming": false,
"offset": 50,
"bit": 2,
"name": "M50.2"
"configType": "manual",
"name": "M50.2",
"offset": 50,
"streaming": false,
"type": "bool"
}
]
}

View File

@ -1,29 +1,14 @@
{
"plots": [
{
"id": "plot_1",
"line_tension": 0,
"name": "UR29",
"point_hover_radius": 4,
"point_radius": 2.5,
"stacked": true,
"stepped": true,
"time_window": 20,
"trigger_enabled": false,
"trigger_on_true": true,
"trigger_variable": null,
"y_max": null,
"y_min": null
},
{
"id": "Clock",
"line_tension": 0,
"name": "Clock",
"id": "DAR",
"line_tension": 0.4,
"name": "DAR_Brix",
"point_hover_radius": 4,
"point_radius": 1,
"stacked": false,
"stepped": true,
"time_window": 10,
"stepped": false,
"time_window": 60,
"trigger_enabled": false,
"trigger_on_true": true
}

256
main.py
View File

@ -45,6 +45,19 @@ except ImportError:
TKINTER_AVAILABLE = False
print("Warning: tkinter not available. File browse functionality will be limited.")
# System Tray Icon imports
try:
import pystray
from PIL import Image
import threading
TRAY_AVAILABLE = True
except ImportError:
TRAY_AVAILABLE = False
print(
"Warning: pystray/PIL not available. System tray functionality will be disabled."
)
# Import core modules
from core import PLCDataStreamer
from core.historical_cache import HistoricalDataCache
@ -3271,8 +3284,62 @@ def signal_handler(sig, frame):
sys.exit(0)
# Global variables for Flask and tray management
flask_thread = None
tray_icon = None
def open_app_browser(icon, item):
"""Open application in web browser"""
import webbrowser
webbrowser.open("http://localhost:5050")
def shutdown_from_tray(icon, item):
"""Shutdown Flask server from tray menu"""
print("🔄 Shutdown requested from system tray...")
graceful_shutdown()
if tray_icon:
tray_icon.stop()
def exit_application(icon, item):
"""Exit entire application from tray menu"""
print("🚪 Exit requested from system tray...")
graceful_shutdown()
if tray_icon:
tray_icon.stop()
sys.exit(0)
def run_flask_app():
"""Run Flask application in a separate thread"""
global streamer
try:
print("🚀 Starting Flask server for PLC S7-315 Streamer")
print("📊 Web interface available at: http://localhost:5050")
print("🔧 Configure your PLC and variables through the web interface")
# Initialize streamer (this will handle instance locking and auto-recovery)
streamer = PLCDataStreamer()
# Start Flask application
app.run(
debug=False,
host="0.0.0.0",
port=5050,
use_reloader=False,
threaded=True,
)
except Exception as e:
print(f"💥 Flask error: {e}")
def main():
"""Main application entry point with error handling and recovery"""
"""Main application entry point with system tray support"""
global flask_thread, tray_icon, streamer
# Setup signal handlers for graceful shutdown
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
@ -3280,52 +3347,161 @@ def main():
max_retries = 3
retry_count = 0
while retry_count < max_retries:
# Check if tray is available and try to setup system tray
if TRAY_AVAILABLE:
try:
print("🚀 Starting Flask server for PLC S7-315 Streamer")
print("📊 Web interface available at: http://localhost:5050")
print("🔧 Configure your PLC and variables through the web interface")
# Start Flask in a separate thread
flask_thread = threading.Thread(target=run_flask_app, daemon=True)
flask_thread.start()
# Initialize streamer (this will handle instance locking and auto-recovery)
global streamer
# Give Flask time to start
time.sleep(2)
# Start Flask application
app.run(
debug=False,
host="0.0.0.0",
port=5050,
use_reloader=False,
threaded=True,
)
# If we reach here, the server stopped normally
break
except RuntimeError as e:
if "Another instance" in str(e):
print(f"{e}")
print("💡 Tip: Stop the other instance or wait for it to finish")
sys.exit(1)
else:
print(f"⚠️ Runtime error: {e}")
retry_count += 1
except KeyboardInterrupt:
print("\n⏸️ Received interrupt signal...")
graceful_shutdown()
break
# Setup and run the system tray icon
icon_path = project_path("frontend", "src", "assets", "logo", "record.png")
try:
image = Image.open(icon_path)
menu = pystray.Menu(
pystray.MenuItem(
"🌐 Abrir PLC Streamer", open_app_browser, default=True
),
pystray.MenuItem("🛑 Cerrar servidor", shutdown_from_tray),
pystray.MenuItem("🚪 Salir", exit_application),
)
tray_icon = pystray.Icon(
"PLC S7-315 Streamer", image, "PLC S7-315 Streamer & Logger", menu
)
print("🎯 Starting system tray icon...")
tray_icon.run() # This blocks the main thread until icon.stop() is called
except FileNotFoundError:
print(
f"⚠️ Error: Icon not found at '{icon_path}'. System tray will not start."
)
print(
"🔧 The Flask application will continue running in background. Press Ctrl+C to stop."
)
# Keep the main thread alive so the Flask thread doesn't exit immediately
try:
while flask_thread.is_alive():
flask_thread.join(timeout=1.0)
except KeyboardInterrupt:
print("\n⏸️ Ctrl+C detected. Stopping Flask...")
graceful_shutdown()
print("👋 Exiting.")
except Exception as e:
print(f"⚠️ Error starting system tray: {e}")
# Keep Flask running without tray
try:
while flask_thread.is_alive():
flask_thread.join(timeout=1.0)
except KeyboardInterrupt:
print("\n⏸️ Ctrl+C detected. Stopping Flask...")
graceful_shutdown()
print("👋 Exiting.")
except Exception as e:
print(f"💥 Unexpected error: {e}")
retry_count += 1
print(f"💥 Error with threaded execution: {e}")
# Fallback to original single-threaded mode
retry_count = 0
while retry_count < max_retries:
try:
print(
"🚀 Starting Flask server for PLC S7-315 Streamer (fallback mode)"
)
print("📊 Web interface available at: http://localhost:5050")
print(
"🔧 Configure your PLC and variables through the web interface"
)
if retry_count < max_retries:
print(f"🔄 Attempting restart ({retry_count}/{max_retries})...")
time.sleep(2) # Wait before retry
else:
print("❌ Maximum retries reached. Exiting...")
# Initialize streamer
streamer = PLCDataStreamer()
# Start Flask application
app.run(
debug=False,
host="0.0.0.0",
port=5050,
use_reloader=False,
threaded=True,
)
break
except RuntimeError as e:
if "Another instance" in str(e):
print(f"{e}")
print(
"💡 Tip: Stop the other instance or wait for it to finish"
)
sys.exit(1)
else:
print(f"⚠️ Runtime error: {e}")
retry_count += 1
except KeyboardInterrupt:
print("\n⏸️ Received interrupt signal...")
graceful_shutdown()
break
except Exception as e:
print(f"💥 Unexpected error: {e}")
retry_count += 1
if retry_count < max_retries:
print(f"🔄 Attempting restart ({retry_count}/{max_retries})...")
time.sleep(2)
else:
print("❌ Maximum retries reached. Exiting...")
graceful_shutdown()
sys.exit(1)
else:
# Original mode without system tray (when pystray is not available)
print("⚠️ System tray not available. Running in console mode.")
while retry_count < max_retries:
try:
print("🚀 Starting Flask server for PLC S7-315 Streamer")
print("📊 Web interface available at: http://localhost:5050")
print("🔧 Configure your PLC and variables through the web interface")
# Initialize streamer
streamer = PLCDataStreamer()
# Start Flask application
app.run(
debug=False,
host="0.0.0.0",
port=5050,
use_reloader=False,
threaded=True,
)
break
except RuntimeError as e:
if "Another instance" in str(e):
print(f"{e}")
print("💡 Tip: Stop the other instance or wait for it to finish")
sys.exit(1)
else:
print(f"⚠️ Runtime error: {e}")
retry_count += 1
except KeyboardInterrupt:
print("\n⏸️ Received interrupt signal...")
graceful_shutdown()
sys.exit(1)
break
except Exception as e:
print(f"💥 Unexpected error: {e}")
retry_count += 1
if retry_count < max_retries:
print(f"🔄 Attempting restart ({retry_count}/{max_retries})...")
time.sleep(2)
else:
print("❌ Maximum retries reached. Exiting...")
graceful_shutdown()
sys.exit(1)
print("🏁 Application finished.")
# ==============================================================================

View File

@ -15,6 +15,10 @@ numpy==2.2.6
# JSON Schema Validation
jsonschema==4.22.0
# System Tray Icon Support
pystray==0.19.4
Pillow==10.0.1
# Note: The following dependencies are automatically installed with Flask:
# - Werkzeug==3.1.3 (WSGI toolkit)
# - Jinja2==3.1.6 (templating engine)

View File

@ -3,12 +3,9 @@
"should_connect": true,
"should_stream": false,
"active_datasets": [
"Test",
"DAR",
"Fast"
"DAR"
]
},
"auto_recovery_enabled": true,
"last_update": "2025-08-25T12:39:01.367655",
"plotjuggler_path": "C:\\Program Files\\PlotJuggler\\plotjuggler.exe"
"last_update": "2025-08-25T16:01:38.412207"
}