opcuaCOM_PY/app.py
Eduard Gerlitz 24d382e94a v1.0 .
2025-09-08 11:33:29 +02:00

145 lines
4.7 KiB
Python

import streamlit as st
import yaml
import time
import os
from opcua_connector import opcua_connector
# Load config early for password if present
app_cfg = yaml.safe_load(open('./cfg.yaml')) or {}
# 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 (external stylesheet)
with open('styles.css') as f:
st.markdown(f"<style>{f.read()}</style>", unsafe_allow_html=True)
def execute_button_action(button_name):
"""Execute button action with proper error handling"""
if st.session_state.opcon is None:
return False
if button_name == 'INIT':
st.session_state.opcon.adapt_access_rights()
print(f"🔧 INIT button pressed - Access rights adapted")
else:
st.session_state.opcon.press_btn(button_name)
print(f"🔘 {button_name} button pressed")
return True
# Re-validate connection status each render (do this before rendering UI)
def is_opcua_connected(conn) -> bool:
try:
return bool(conn and conn.check_connection() == 1)
except Exception:
return False
# Initialize session state
if 'opcon' not in st.session_state:
st.session_state.opcon = None
if 'connection_status' not in st.session_state:
st.session_state.connection_status = "Disconnected"
# Update connection status on each render
st.session_state.connection_status = (
"Connected" if is_opcua_connected(st.session_state.opcon) else "Disconnected"
)
if 'authenticated' not in st.session_state:
st.session_state.authenticated = False
# 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..."):
try:
if st.session_state.opcon is None:
st.session_state.opcon = opcua_connector(app_cfg)
st.session_state.opcon.connect()
if st.session_state.opcon.check_connection() == 1:
st.session_state.connection_status = "Connected"
print("🎉 Connection initialized successfully")
else:
st.session_state.connection_status = "Disconnected"
print("❌ Connection failed")
except Exception as e:
print(f"❌ Failed to initialize connection: {e}")
st.session_state.connection_status = "Disconnected"
st.rerun()
# Disconnect button
if st.button("🔌 Disconnect", use_container_width=True):
try:
if st.session_state.opcon:
st.session_state.opcon.disconnect()
print("🔌 Disconnected from OPC UA server")
except Exception as e:
print(f"⚠️ Warning during disconnect: {e}")
st.session_state.opcon = None
st.session_state.connection_status = "Disconnected"
print("🔌 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 '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('<div class="button-container">', 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('</div>', unsafe_allow_html=True)
# Footer removed for minimal UI