This commit is contained in:
Eduard Gerlitz 2025-09-08 11:21:36 +02:00
parent 88eb33fde4
commit 9ee5ab82c5
2 changed files with 118 additions and 134 deletions

184
app.py
View File

@ -4,13 +4,38 @@ import time
import os import os
from opcua_connector import opcua_connector from opcua_connector import opcua_connector
# Load config early for password if present class app:
try: def __init__(self):
_APP_CFG = yaml.safe_load(open('./cfg.yaml')) or {} self.app_cfg = yaml.safe_load(open('./cfg.yaml')) or {}
except Exception: self.OPCon = opcua_connector(self.app_cfg)
_APP_CFG = {}
OPCcon = opcua_connector(_APP_CFG) def initialize_connection(self):
"""Initialize OPC UA connection"""
try:
self.OPCon.connect()
if self.OPCon.check_connection() == 1:
st.session_state.connection_status = "Connected"
print("🎉 Connection initialized successfully")
return True
st.session_state.connection_status = "Disconnected"
return False
except Exception as e:
print(f"❌ Failed to initialize connection: {e}")
st.session_state.connection_status = "Disconnected"
return False
def execute_button_action(self, button_name):
"""Execute button action with proper error handling"""
if button_name == 'INIT':
self.OPCon.adapt_access_rights()
print(f"🔧 INIT button pressed - Access rights adapted")
else:
self.OPCon.press_btn(button_name)
print(f"🔘 {button_name} button pressed")
return True
# Load config early for password if present
app = app()
# Page configuration # Page configuration
st.set_page_config( st.set_page_config(
@ -20,130 +45,28 @@ st.set_page_config(
initial_sidebar_state="expanded" initial_sidebar_state="expanded"
) )
# Custom CSS for better styling # Custom CSS for better styling (external stylesheet)
st.markdown(""" with open('styles.css') as f:
<style> st.markdown(f"<style>{f.read()}</style>", unsafe_allow_html=True)
.main-header {
text-align: center;
color: #1f77b4;
margin-bottom: 2rem;
}
.button-container {
display: flex;
justify-content: center;
gap: 2rem;
margin: 2rem 0;
flex-wrap: wrap;
}
.control-button {
display: flex;
flex-direction: column;
align-items: center;
padding: 1.5rem;
border-radius: 15px;
background: linear-gradient(145deg, #f0f2f6, #ffffff);
box-shadow: 5px 5px 15px #d1d9e6, -5px -5px 15px #ffffff;
transition: all 0.3s ease;
min-width: 150px;
cursor: pointer;
}
.control-button:hover {
transform: translateY(-2px);
box-shadow: 8px 8px 20px #d1d9e6, -8px -8px 20px #ffffff;
}
.status-indicator {
padding: 0.5rem 1rem;
border-radius: 20px;
font-weight: bold;
text-align: center;
margin: 1rem 0;
}
.status-connected {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.status-disconnected {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.log-container {
background-color: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 5px;
padding: 1rem;
max-height: 300px;
overflow-y: auto;
}
.stButton > button {
height: 60px !important;
font-size: 14px !important;
font-weight: bold !important;
border-radius: 10px !important;
border: 2px solid #e0e0e0 !important;
transition: all 0.3s ease !important;
}
.stButton > button:hover {
transform: translateY(-2px) !important;
box-shadow: 0 4px 8px rgba(0,0,0,0.2) !important;
border-color: #1f77b4 !important;
}
</style>
""", unsafe_allow_html=True)
# Initialize session state # 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: if 'connection_status' not in st.session_state:
st.session_state.connection_status = "Disconnected" st.session_state.connection_status = "Disconnected"
if 'authenticated' not in st.session_state: if 'authenticated' not in st.session_state:
st.session_state.authenticated = False st.session_state.authenticated = False
# Re-validate connection status each render (do this before rendering UI) # Re-validate connection status each render (do this before rendering UI)
def is_opcua_connected(OPCcon) -> bool: def is_opcua_connected(conn) -> bool:
try: try:
if OPCcon is None: return bool(conn and conn.check_connection() == 1)
return False
return OPCcon.check_connection() == 1
except Exception: except Exception:
return False return False
if st.session_state.opcua_connector and is_opcua_connected(st.session_state.opcua_connector): st.session_state.connection_status = (
if st.session_state.connection_status != "Connected": "Connected" if is_opcua_connected(app.OPCon) else "Disconnected"
st.session_state.connection_status = "Connected" )
else:
if st.session_state.connection_status != "Disconnected":
st.session_state.connection_status = "Disconnected"
def initialize_connection():
"""Initialize OPC UA connection"""
try:
# Connect and check status
OPCcon.connect()
if OPCcon.check_connection() == 1:
st.session_state.opcua_connector = OPCcon
st.session_state.connection_status = "Connected"
print("🎉 Connection initialized successfully")
return True
st.session_state.connection_status = "Disconnected"
return False
except Exception as e:
print(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 button_name == 'INIT':
OPCcon.adapt_access_rights()
print(f"🔧 INIT button pressed - Access rights adapted")
else:
OPCcon.press_btn(button_name)
print(f"🔘 {button_name} button pressed")
return True
# Sidebar # Sidebar
with st.sidebar: with st.sidebar:
@ -152,22 +75,16 @@ with st.sidebar:
# Initialize button # Initialize button
if st.button("🔌 Initialize Connection", use_container_width=True): if st.button("🔌 Initialize Connection", use_container_width=True):
with st.spinner("Initializing connection..."): with st.spinner("Initializing connection..."):
initialize_connection() app.initialize_connection()
st.rerun() st.rerun()
# Disconnect button # Disconnect button
if st.button("🔌 Disconnect", use_container_width=True): if st.button("🔌 Disconnect", use_container_width=True):
if st.session_state.opcua_connector: try:
try: app.OPCon.disconnect()
print("🔌 Disconnected from OPC UA server")
# Disconnect OPC UA using the new method except Exception as e:
st.session_state.opcua_connector.disconnect() print(f"⚠️ Warning during disconnect: {e}")
print("🔌 Disconnected from OPC UA server")
except Exception as e:
print(f"⚠️ Warning during disconnect: {e}")
st.session_state.opcua_connector = None
st.session_state.connection_status = "Disconnected" st.session_state.connection_status = "Disconnected"
print("🔌 Connection reset") print("🔌 Connection reset")
st.rerun() st.rerun()
@ -179,8 +96,7 @@ with st.sidebar:
# Password gate # Password gate
# Prefer cfg.yaml cred.dashboard.password, else env DASHBOARD_PASSWORD, else 'admin' # Prefer cfg.yaml cred.dashboard.password, else env DASHBOARD_PASSWORD, else 'admin'
expected_password = ( expected_password = (
(_APP_CFG.get('cred', {}).get('dashboard', {}) or {}).get('password') (app.app_cfg.get('cred', {}).get('dashboard', {}) or {}).get('password')
or os.getenv('DASHBOARD_PASSWORD')
or 'admin' or 'admin'
) )
ui_locked = not st.session_state.authenticated ui_locked = not st.session_state.authenticated
@ -204,19 +120,19 @@ if not ui_locked:
with btn_col1: with btn_col1:
if st.button("🔧\n\nINIT", key="init_btn", use_container_width=True, disabled=buttons_disabled): if st.button("🔧\n\nINIT", key="init_btn", use_container_width=True, disabled=buttons_disabled):
execute_button_action('INIT') app.execute_button_action('INIT')
with btn_col2: with btn_col2:
if st.button("▶️\n\nSTART", key="start_btn", use_container_width=True, disabled=buttons_disabled): if st.button("▶️\n\nSTART", key="start_btn", use_container_width=True, disabled=buttons_disabled):
execute_button_action('BTN_START') app.execute_button_action('BTN_START')
with btn_col3: with btn_col3:
if st.button("⏹️\n\nSTOP", key="stop_btn", use_container_width=True, disabled=buttons_disabled): if st.button("⏹️\n\nSTOP", key="stop_btn", use_container_width=True, disabled=buttons_disabled):
execute_button_action('BTN_STOP') app.execute_button_action('BTN_STOP')
with btn_col4: with btn_col4:
if st.button("🔄\n\nRESET", key="reset_btn", use_container_width=True, disabled=buttons_disabled): if st.button("🔄\n\nRESET", key="reset_btn", use_container_width=True, disabled=buttons_disabled):
execute_button_action('BTN_RESET') app.execute_button_action('BTN_RESET')
st.markdown('</div>', unsafe_allow_html=True) st.markdown('</div>', unsafe_allow_html=True)

68
styles.css Normal file
View File

@ -0,0 +1,68 @@
.main-header {
text-align: center;
color: #1f77b4;
margin-bottom: 2rem;
}
.button-container {
display: flex;
justify-content: center;
gap: 2rem;
margin: 2rem 0;
flex-wrap: wrap;
}
.control-button {
display: flex;
flex-direction: column;
align-items: center;
padding: 1.5rem;
border-radius: 15px;
background: linear-gradient(145deg, #f0f2f6, #ffffff);
box-shadow: 5px 5px 15px #d1d9e6, -5px -5px 15px #ffffff;
transition: all 0.3s ease;
min-width: 150px;
cursor: pointer;
}
.control-button:hover {
transform: translateY(-2px);
box-shadow: 8px 8px 20px #d1d9e6, -8px -8px 20px #ffffff;
}
.status-indicator {
padding: 0.5rem 1rem;
border-radius: 20px;
font-weight: bold;
text-align: center;
margin: 1rem 0;
}
.status-connected {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.status-disconnected {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.log-container {
background-color: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 5px;
padding: 1rem;
max-height: 300px;
overflow-y: auto;
}
.stButton > button {
height: 60px !important;
font-size: 14px !important;
font-weight: bold !important;
border-radius: 10px !important;
border: 2px solid #e0e0e0 !important;
transition: all 0.3s ease !important;
}
.stButton > button:hover {
transform: translateY(-2px) !important;
box-shadow: 0 4px 8px rgba(0,0,0,0.2) !important;
border-color: #1f77b4 !important;
}