From 27734995b61e9240d0e455b0fc203c240416bc17 Mon Sep 17 00:00:00 2001 From: Eduard Gerlitz Date: Fri, 5 Sep 2025 10:01:01 +0200 Subject: [PATCH] v2.0 --- EXEC_DASHBOARD.bat | 29 ++ README.md | 135 ++++++++- EXEC_INIT.bat => _OLD/EXEC_INIT.bat | 0 EXEC_INSTALL.bat => _OLD/EXEC_INSTALL.bat | 2 +- .../EXEC_PRESS_RESET.bat | 0 .../EXEC_PRESS_START.bat | 0 .../EXEC_PRESS_STOP.bat | 0 RUN_BTN.py => _OLD/RUN_BTN.py | 0 app.py | 280 ++++++++++++++++++ cfg.yaml | 2 + condaenv.yml => environment.yml | 3 +- 11 files changed, 446 insertions(+), 5 deletions(-) create mode 100644 EXEC_DASHBOARD.bat rename EXEC_INIT.bat => _OLD/EXEC_INIT.bat (100%) rename EXEC_INSTALL.bat => _OLD/EXEC_INSTALL.bat (87%) rename EXEC_PRESS_RESET.bat => _OLD/EXEC_PRESS_RESET.bat (100%) rename EXEC_PRESS_START.bat => _OLD/EXEC_PRESS_START.bat (100%) rename EXEC_PRESS_STOP.bat => _OLD/EXEC_PRESS_STOP.bat (100%) rename RUN_BTN.py => _OLD/RUN_BTN.py (100%) create mode 100644 app.py rename condaenv.yml => environment.yml (86%) diff --git a/EXEC_DASHBOARD.bat b/EXEC_DASHBOARD.bat new file mode 100644 index 0000000..a562816 --- /dev/null +++ b/EXEC_DASHBOARD.bat @@ -0,0 +1,29 @@ +@echo off +cd /d %~dp0 +echo Starting OPC UA Robot Control Dashboard... +echo. + +REM Check if conda is available +where conda >nul 2>nul +if %ERRORLEVEL% NEQ 0 ( + echo ERROR: Conda is not installed or not in PATH + echo Please install Anaconda or Miniconda first + pause + exit /b 1 +) + +echo Setting up conda environment... + +REM Try to create environment, if it exists, update it +conda env create -f environment.yml 2>nul +if %ERRORLEVEL% NEQ 0 ( + echo Environment already exists, updating... + conda env update -f environment.yml +) + +echo. +echo Starting Streamlit app on port 8505 using conda run... +conda run -n opcua_com --no-capture-output streamlit run app.py --server.port 8505 +echo. +echo Streamlit exited with code %ERRORLEVEL%. +pause diff --git a/README.md b/README.md index 459b7c8..ee92f1b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,134 @@ -v0.9 +# 🤖 OPC UA Robot Control Dashboard -# opcuaCOM_PY +**Version 2.0** - Modernes Streamlit-Dashboard für OPC UA Robotersteuerung -Simuliertes Drücken von definierten Tasten (START, STOP...) über OPCUA \ No newline at end of file +## 📋 Übersicht + +Dieses Projekt ermöglicht die Steuerung eines Roboters über OPC UA (OPC Unified Architecture) durch ein benutzerfreundliches Web-Dashboard. Das Dashboard ersetzt die ursprünglichen Batch-Dateien durch eine moderne, interaktive Oberfläche. + +## ✨ Features + +### 🎮 **Control Buttons** +- **🔧 INIT** - Adaptiert Zugriffsrechte für OPC UA +- **▶️ START** - Startet den Roboterprozess +- **⏹️ STOP** - Stoppt den Roboterprozess +- **🔄 RESET** - Setzt das System zurück + +### 🖥️ **Dashboard Features** +- **Moderne Web-Oberfläche** mit Streamlit +- **Echtzeit-Status** der OPC UA Verbindung +- **Activity Log** mit Zeitstempel +- **Responsive Design** für verschiedene Bildschirmgrößen +- **Schöne Icons** und Hover-Effekte + +## 🚀 Installation & Start + +### Voraussetzungen +- **Anaconda** oder **Miniconda** installiert +- **Python 3.10+** + +### Automatische Installation +1. **Repository klonen:** + ```bash + git clone + cd opcuaCOM_PY + ``` + +2. **Dashboard starten:** + - **Windows:** Doppelklick auf `EXEC_DASHBOARD.bat` + - **Manuell:** + ```bash + conda env create -f environment.yml + conda activate opcua_com + streamlit run app.py --server.port 8505 + ``` + +### Manuelle Installation +```bash +# Conda-Umgebung erstellen +conda env create -f environment.yml + +# Umgebung aktivieren +conda activate opcua_com + +# Dashboard starten +streamlit run app.py --server.port 8505 +``` + +## ⚙️ Konfiguration + +### OPC UA Server +Bearbeiten Sie `cfg.yaml` für Ihre OPC UA Server-Einstellungen: + +```yaml +cred: + robot: + url: "opc.tcp://192.168.214.1:4840" + username: "OpcUaClient" + password: "OPCUACLIENT" +``` + +### Button-Mapping +Die Button-Zuordnungen sind in `cfg_varlist.csv` definiert: +```csv +BTN_START ns=2;s=LBP_Chan1.A_NCStart +BTN_STOP ns=2;s=LBP_Chan1.A_NCStop +BTN_RESET ns=2;s=LBP_Chan1.A_Reset +``` + +## 🎯 Verwendung + +1. **Dashboard öffnen:** `http://localhost:8505` +2. **Verbindung initialisieren:** "🔌 Initialize Connection" klicken +3. **Status prüfen:** Sollte "Connected" anzeigen +4. **Buttons verwenden:** INIT, START, STOP, RESET + +## 📁 Projektstruktur + +``` +opcuaCOM_PY/ +├── app.py # Streamlit Dashboard +├── opcua_connector.py # OPC UA Verbindungslogik +├── environment.yml # Conda-Umgebung +├── cfg.yaml # Konfiguration +├── cfg_varlist.csv # Button-Mapping +├── EXEC_DASHBOARD.bat # Start-Script +└── README.md # Diese Datei +``` + +## 🔧 Technische Details + +### Abhängigkeiten +- **streamlit** - Web-Dashboard +- **opcua** - OPC UA Client +- **pandas** - Datenverarbeitung +- **influxdb-client** - Zeitreihen-Datenbank +- **pyyaml** - Konfigurationsdateien + +### Architektur +- **Frontend:** Streamlit Web-Interface +- **Backend:** OPC UA Client mit Threading +- **Datenbank:** InfluxDB für Logging +- **Konfiguration:** YAML-basiert + +## 🆚 Version History + +### v2.0 (Aktuell) +- ✅ Streamlit Dashboard hinzugefügt +- ✅ Moderne Web-Oberfläche +- ✅ Automatische Conda-Umgebungsverwaltung +- ✅ Verbesserte Fehlerbehandlung + +### v1.0 (Legacy) +- Batch-Dateien für Button-Steuerung +- Kommandozeilen-basierte Ausführung + +## 📞 Support + +Bei Problemen oder Fragen: +1. Prüfen Sie die Activity Logs im Dashboard +2. Überprüfen Sie die OPC UA Server-Verbindung +3. Stellen Sie sicher, dass alle Abhängigkeiten installiert sind + +--- +**Entwickelt für OPC UA Robotersteuerung** 🤖 \ No newline at end of file diff --git a/EXEC_INIT.bat b/_OLD/EXEC_INIT.bat similarity index 100% rename from EXEC_INIT.bat rename to _OLD/EXEC_INIT.bat diff --git a/EXEC_INSTALL.bat b/_OLD/EXEC_INSTALL.bat similarity index 87% rename from EXEC_INSTALL.bat rename to _OLD/EXEC_INSTALL.bat index b01c084..03a4193 100644 --- a/EXEC_INSTALL.bat +++ b/_OLD/EXEC_INSTALL.bat @@ -2,7 +2,7 @@ setlocal :: ---------- USER DEFINE -------------- -set "ymlPath=condaenv.yml" +set "ymlPath=environment.yml" :: ------------------------------------- :: Execute the Python script diff --git a/EXEC_PRESS_RESET.bat b/_OLD/EXEC_PRESS_RESET.bat similarity index 100% rename from EXEC_PRESS_RESET.bat rename to _OLD/EXEC_PRESS_RESET.bat diff --git a/EXEC_PRESS_START.bat b/_OLD/EXEC_PRESS_START.bat similarity index 100% rename from EXEC_PRESS_START.bat rename to _OLD/EXEC_PRESS_START.bat diff --git a/EXEC_PRESS_STOP.bat b/_OLD/EXEC_PRESS_STOP.bat similarity index 100% rename from EXEC_PRESS_STOP.bat rename to _OLD/EXEC_PRESS_STOP.bat diff --git a/RUN_BTN.py b/_OLD/RUN_BTN.py similarity index 100% rename from RUN_BTN.py rename to _OLD/RUN_BTN.py diff --git a/app.py b/app.py new file mode 100644 index 0000000..633472a --- /dev/null +++ b/app.py @@ -0,0 +1,280 @@ +import streamlit as st +import yaml +import time +import os +from opcua_connector import opcua_connector + +# Load config early for password if present +try: + _APP_CFG = yaml.safe_load(open('./cfg.yaml')) or {} +except Exception: + _APP_CFG = {} + +# Page configuration +st.set_page_config( + page_title="OPC UA Robot Control Dashboard", + page_icon="🤖", + layout="wide", + initial_sidebar_state="expanded" +) + +# Custom CSS for better styling +st.markdown(""" + +""", unsafe_allow_html=True) + +# Initialize session state +if 'opcua_connector' not in st.session_state: + st.session_state.opcua_connector = None +if 'connection_status' not in st.session_state: + st.session_state.connection_status = "Disconnected" +if 'authenticated' not in st.session_state: + st.session_state.authenticated = False + +# Re-validate connection status each render (do this before rendering UI) +def is_opcua_connected(OPCcon) -> bool: + try: + if OPCcon is None: + return False + client = OPCcon.opcuaclient + return hasattr(client, 'uaclient') and client.uaclient and getattr(client.uaclient, 'session', None) is not None + except Exception: + return False + +if st.session_state.opcua_connector and is_opcua_connected(st.session_state.opcua_connector): + if st.session_state.connection_status != "Connected": + st.session_state.connection_status = "Connected" +else: + if st.session_state.connection_status != "Disconnected": + st.session_state.connection_status = "Disconnected" + +def add_log(message): + """No-op logger (logs disabled for minimal UI).""" + return None + +def check_opcua_server(OPCcon): + """Check if OPC UA server is reachable""" + try: + # Check if already connected + if hasattr(OPCcon.opcuaclient, 'uaclient') and OPCcon.opcuaclient.uaclient: + add_log("✅ OPC UA server already connected") + return True + + # Try to connect + OPCcon.opcuaclient.connect() + add_log("✅ OPC UA server is reachable") + return True + except Exception as e: + add_log(f"❌ Error connecting to OPC UA server: {e}") + return False + +def establish_connection(OPCcon): + """Establish connection to OPC UA server""" + try: + if OPCcon.opcuaclient.uaclient: + add_log("✅ OPC UA connection established") + return True + else: + add_log("❌ Error establishing connection") + return False + except Exception as e: + add_log(f"❌ Error establishing connection: {e}") + return False + +def push_button(OPCcon, btn_name): + """Push a button on the OPC UA server""" + try: + if btn_name == 'INIT': + OPCcon.adapt_access_rights() + add_log(f"🔧 INIT button pressed - Access rights adapted") + else: + OPCcon.press_btn(btn_name) + add_log(f"🔘 {btn_name} button pressed") + return True + except Exception as e: + add_log(f"❌ Error pressing {btn_name} button: {e}") + return False + +def initialize_connection(): + """Initialize OPC UA connection""" + try: + config = yaml.safe_load(open('./cfg.yaml')) + OPCcon = opcua_connector(config) + + # Check server + if check_opcua_server(OPCcon): + # Establish connection + if establish_connection(OPCcon): + st.session_state.opcua_connector = OPCcon + st.session_state.connection_status = "Connected" + add_log("🎉 Connection initialized successfully") + return True + + st.session_state.connection_status = "Disconnected" + return False + except Exception as e: + add_log(f"❌ Failed to initialize connection: {e}") + st.session_state.connection_status = "Disconnected" + return False + +def execute_button_action(button_name): + """Execute button action with proper error handling""" + if st.session_state.opcua_connector is None: + add_log("❌ No connection available. Please initialize first.") + return + + # Execute directly instead of threading to avoid race conditions + try: + push_button(st.session_state.opcua_connector, button_name) + except Exception as e: + add_log(f"❌ Critical error executing {button_name}: {e}") + + +# Sidebar +with st.sidebar: + st.write(f"Status: {st.session_state.connection_status}") + + # Initialize button + if st.button("🔌 Initialize Connection", use_container_width=True): + with st.spinner("Initializing connection..."): + initialize_connection() + st.rerun() + + # Disconnect button + if st.button("🔌 Disconnect", use_container_width=True): + if st.session_state.opcua_connector: + try: + # Close InfluxDB connection + if hasattr(st.session_state.opcua_connector, 'influxclient'): + st.session_state.opcua_connector.influxclient.close() + add_log("📊 InfluxDB connection closed") + + # Disconnect OPC UA + if hasattr(st.session_state.opcua_connector, 'opcuaclient'): + st.session_state.opcua_connector.opcuaclient.disconnect() + add_log("🔌 Disconnected from OPC UA server") + + except Exception as e: + add_log(f"⚠️ Warning during disconnect: {e}") + + st.session_state.opcua_connector = None + st.session_state.connection_status = "Disconnected" + add_log("🔌 Connection reset") + st.rerun() + + st.divider() + + + +# Password gate +# Prefer cfg.yaml cred.dashboard.password, else env DASHBOARD_PASSWORD, else 'admin' +expected_password = ( + (_APP_CFG.get('cred', {}).get('dashboard', {}) or {}).get('password') + or os.getenv('DASHBOARD_PASSWORD') + or 'admin' +) +ui_locked = not st.session_state.authenticated +if ui_locked: + pwd = st.text_input("Enter Dashboard Password", type="password") + if st.button("Login", use_container_width=True): + if pwd == expected_password: + st.session_state.authenticated = True + st.rerun() + else: + st.error("Wrong password") + +if not ui_locked: + # Button container + st.markdown('
', unsafe_allow_html=True) + + # Create columns for buttons + btn_col1, btn_col2, btn_col3, btn_col4 = st.columns(4) + + buttons_disabled = st.session_state.connection_status != "Connected" + + with btn_col1: + if st.button("🔧\n\nINIT", key="init_btn", use_container_width=True, disabled=buttons_disabled): + execute_button_action('INIT') + + with btn_col2: + if st.button("▶️\n\nSTART", key="start_btn", use_container_width=True, disabled=buttons_disabled): + execute_button_action('BTN_START') + + with btn_col3: + if st.button("⏹️\n\nSTOP", key="stop_btn", use_container_width=True, disabled=buttons_disabled): + execute_button_action('BTN_STOP') + + with btn_col4: + if st.button("🔄\n\nRESET", key="reset_btn", use_container_width=True, disabled=buttons_disabled): + execute_button_action('BTN_RESET') + + st.markdown('
', unsafe_allow_html=True) + +# Footer removed for minimal UI diff --git a/cfg.yaml b/cfg.yaml index 16d0819..a07d965 100644 --- a/cfg.yaml +++ b/cfg.yaml @@ -5,6 +5,8 @@ data: cfg_varlist: "cfg_varlist.csv" cred: + dashboard: + password: "admin" robot: url: "opc.tcp://192.168.214.1:4840" url_debug: "opc.tcp://192.168.214.250:4840" diff --git a/condaenv.yml b/environment.yml similarity index 86% rename from condaenv.yml rename to environment.yml index f9e2e3a..515f0d5 100644 --- a/condaenv.yml +++ b/environment.yml @@ -10,6 +10,7 @@ dependencies: - matplotlib - pandas - influxdb-client + - streamlit - pip - pip: - - pyyaml \ No newline at end of file + - pyyaml