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:
parent
250748446f
commit
aba83f843a
16099
application_events.json
16099
application_events.json
File diff suppressed because it is too large
Load Diff
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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
256
main.py
|
@ -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.")
|
||||
|
||||
|
||||
# ==============================================================================
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
}
|
Loading…
Reference in New Issue