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 return OPCcon.check_connection() == 1 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 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(_APP_CFG) OPCcon = opcua_connector(config) # Connect and check status OPCcon.opcuaclient.connect() if OPCcon.check_connection() == 1: 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 using the new method st.session_state.opcua_connector.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