|
|
#!/usr/bin/env python3
|
|
|
"""
|
|
|
Configure Session Widget - vzug-e-hinge
|
|
|
========================================
|
|
|
Visual command sequence builder.
|
|
|
"""
|
|
|
|
|
|
from PyQt6.QtWidgets import (
|
|
|
QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, QLabel,
|
|
|
QLineEdit, QPushButton, QListWidget, QListWidgetItem,
|
|
|
QMessageBox, QSplitter, QFormLayout, QSpinBox, QComboBox, QCheckBox
|
|
|
)
|
|
|
from PyQt6.QtCore import Qt, pyqtSignal
|
|
|
import json
|
|
|
|
|
|
from database.init_database import DatabaseManager
|
|
|
|
|
|
|
|
|
class ConfigureSessionWidget(QWidget):
|
|
|
"""Visual command sequence builder."""
|
|
|
|
|
|
profile_saved = pyqtSignal(int)
|
|
|
profile_deleted = pyqtSignal(int)
|
|
|
|
|
|
def __init__(self, db_manager: DatabaseManager):
|
|
|
super().__init__()
|
|
|
self.db_manager = db_manager
|
|
|
self.db_conn = db_manager.get_connection()
|
|
|
self.current_profile_id = None
|
|
|
self.command_sequence = [] # List of {command_id, command_name, delay_ms}
|
|
|
self._init_ui()
|
|
|
self._load_profiles()
|
|
|
self._load_uart_commands()
|
|
|
|
|
|
def _init_ui(self):
|
|
|
"""Initialize UI."""
|
|
|
layout = QVBoxLayout()
|
|
|
|
|
|
title = QLabel("Configure Session Profiles")
|
|
|
title.setStyleSheet("font-size: 16px; font-weight: bold;")
|
|
|
layout.addWidget(title)
|
|
|
|
|
|
splitter = QSplitter(Qt.Orientation.Horizontal)
|
|
|
splitter.addWidget(self._create_list_panel())
|
|
|
splitter.addWidget(self._create_editor_panel())
|
|
|
splitter.setSizes([300, 700])
|
|
|
|
|
|
layout.addWidget(splitter)
|
|
|
self.setLayout(layout)
|
|
|
|
|
|
def _create_list_panel(self):
|
|
|
"""Create profile list panel."""
|
|
|
widget = QWidget()
|
|
|
layout = QVBoxLayout()
|
|
|
|
|
|
layout.addWidget(QLabel("Session Profiles:"))
|
|
|
|
|
|
self.profile_list = QListWidget()
|
|
|
self.profile_list.currentItemChanged.connect(self._on_profile_selected)
|
|
|
layout.addWidget(self.profile_list)
|
|
|
|
|
|
btn_layout = QHBoxLayout()
|
|
|
self.btn_add = QPushButton("Add New")
|
|
|
self.btn_add.clicked.connect(self._on_add_clicked)
|
|
|
btn_layout.addWidget(self.btn_add)
|
|
|
|
|
|
self.btn_delete = QPushButton("Delete")
|
|
|
self.btn_delete.clicked.connect(self._on_delete_clicked)
|
|
|
self.btn_delete.setEnabled(False)
|
|
|
btn_layout.addWidget(self.btn_delete)
|
|
|
|
|
|
layout.addLayout(btn_layout)
|
|
|
widget.setLayout(layout)
|
|
|
return widget
|
|
|
|
|
|
def _create_editor_panel(self):
|
|
|
"""Create visual command builder."""
|
|
|
widget = QWidget()
|
|
|
layout = QVBoxLayout()
|
|
|
|
|
|
# Profile name and description
|
|
|
form = QFormLayout()
|
|
|
self.name_input = QLineEdit()
|
|
|
form.addRow("Profile Name:", self.name_input)
|
|
|
self.desc_input = QLineEdit()
|
|
|
form.addRow("Description:", self.desc_input)
|
|
|
|
|
|
# Print RX checkbox
|
|
|
self.print_rx_checkbox = QCheckBox("Print Command RX (disable packet detection & DB saving)")
|
|
|
self.print_rx_checkbox.setToolTip(
|
|
|
"Enable: Print raw RX buffer to data monitor, skip database saving\n"
|
|
|
"Disable: Normal packet detection and database logging"
|
|
|
)
|
|
|
form.addRow("", self.print_rx_checkbox)
|
|
|
|
|
|
layout.addLayout(form)
|
|
|
|
|
|
# Add command section
|
|
|
add_group = QGroupBox("Add Command")
|
|
|
add_layout = QVBoxLayout()
|
|
|
|
|
|
add_form = QFormLayout()
|
|
|
self.cmd_combo = QComboBox()
|
|
|
add_form.addRow("UART Command:", self.cmd_combo)
|
|
|
|
|
|
self.delay_spin = QSpinBox()
|
|
|
self.delay_spin.setRange(0, 60000)
|
|
|
self.delay_spin.setValue(3000)
|
|
|
self.delay_spin.setSuffix(" ms")
|
|
|
add_form.addRow("Delay After:", self.delay_spin)
|
|
|
|
|
|
add_layout.addLayout(add_form)
|
|
|
|
|
|
btn_add_cmd = QPushButton("➕ Add to Sequence")
|
|
|
btn_add_cmd.clicked.connect(self._on_add_command)
|
|
|
add_layout.addWidget(btn_add_cmd)
|
|
|
|
|
|
add_group.setLayout(add_layout)
|
|
|
layout.addWidget(add_group)
|
|
|
|
|
|
# Command sequence list
|
|
|
seq_group = QGroupBox("Command Sequence")
|
|
|
seq_layout = QVBoxLayout()
|
|
|
|
|
|
self.sequence_list = QListWidget()
|
|
|
seq_layout.addWidget(self.sequence_list)
|
|
|
|
|
|
# Sequence controls
|
|
|
seq_btn_layout = QHBoxLayout()
|
|
|
|
|
|
self.btn_up = QPushButton("↑ Move Up")
|
|
|
self.btn_up.clicked.connect(self._on_move_up)
|
|
|
seq_btn_layout.addWidget(self.btn_up)
|
|
|
|
|
|
self.btn_down = QPushButton("↓ Move Down")
|
|
|
self.btn_down.clicked.connect(self._on_move_down)
|
|
|
seq_btn_layout.addWidget(self.btn_down)
|
|
|
|
|
|
self.btn_remove = QPushButton("✖ Remove")
|
|
|
self.btn_remove.clicked.connect(self._on_remove_command)
|
|
|
seq_btn_layout.addWidget(self.btn_remove)
|
|
|
|
|
|
seq_layout.addLayout(seq_btn_layout)
|
|
|
seq_group.setLayout(seq_layout)
|
|
|
layout.addWidget(seq_group)
|
|
|
|
|
|
# Save button
|
|
|
btn_layout = QHBoxLayout()
|
|
|
self.btn_save = QPushButton("Save Profile")
|
|
|
self.btn_save.clicked.connect(self._on_save_clicked)
|
|
|
self.btn_save.setEnabled(False)
|
|
|
btn_layout.addWidget(self.btn_save)
|
|
|
btn_layout.addStretch()
|
|
|
|
|
|
layout.addLayout(btn_layout)
|
|
|
widget.setLayout(layout)
|
|
|
return widget
|
|
|
|
|
|
def _load_uart_commands(self):
|
|
|
"""Load UART commands into dropdown."""
|
|
|
self.cmd_combo.clear()
|
|
|
cursor = self.db_conn.execute(
|
|
|
"SELECT command_id, command_name FROM uart_commands WHERE is_active=1 ORDER BY command_name"
|
|
|
)
|
|
|
for row in cursor.fetchall():
|
|
|
self.cmd_combo.addItem(row[1], row[0])
|
|
|
|
|
|
def _load_profiles(self):
|
|
|
"""Load profiles from database."""
|
|
|
self.profile_list.clear()
|
|
|
cursor = self.db_conn.execute(
|
|
|
"SELECT profile_id, profile_name FROM session_profiles ORDER BY profile_name"
|
|
|
)
|
|
|
for row in cursor.fetchall():
|
|
|
item = QListWidgetItem(row[1])
|
|
|
item.setData(Qt.ItemDataRole.UserRole, row[0])
|
|
|
self.profile_list.addItem(item)
|
|
|
|
|
|
def _load_profile_details(self, profile_id: int):
|
|
|
"""Load profile into editor."""
|
|
|
cursor = self.db_conn.execute(
|
|
|
"SELECT profile_name, description, command_sequence, print_command_rx FROM session_profiles WHERE profile_id = ?",
|
|
|
(profile_id,)
|
|
|
)
|
|
|
row = cursor.fetchone()
|
|
|
if row:
|
|
|
self.name_input.setText(row[0] or "")
|
|
|
self.desc_input.setText(row[1] or "")
|
|
|
|
|
|
# Load print_command_rx checkbox
|
|
|
print_rx = bool(row[3]) if len(row) > 3 and row[3] is not None else False
|
|
|
self.print_rx_checkbox.setChecked(print_rx)
|
|
|
|
|
|
# Parse JSON and populate sequence
|
|
|
try:
|
|
|
json_obj = json.loads(row[2] or "{}")
|
|
|
self.command_sequence = []
|
|
|
for cmd in json_obj.get("commands", []):
|
|
|
cmd_id = cmd["command_id"]
|
|
|
# Get command name from database
|
|
|
c = self.db_conn.execute(
|
|
|
"SELECT command_name FROM uart_commands WHERE command_id = ?", (cmd_id,)
|
|
|
)
|
|
|
cmd_row = c.fetchone()
|
|
|
if cmd_row:
|
|
|
self.command_sequence.append({
|
|
|
"command_id": cmd_id,
|
|
|
"command_name": cmd_row[0],
|
|
|
"delay_ms": cmd["delay_ms"]
|
|
|
})
|
|
|
self._update_sequence_list()
|
|
|
except:
|
|
|
self.command_sequence = []
|
|
|
|
|
|
self.current_profile_id = profile_id
|
|
|
self.btn_delete.setEnabled(True)
|
|
|
self.btn_save.setEnabled(True) # Always allow saving
|
|
|
|
|
|
def _update_sequence_list(self):
|
|
|
"""Update sequence list display."""
|
|
|
self.sequence_list.clear()
|
|
|
for i, cmd in enumerate(self.command_sequence, 1):
|
|
|
text = f"{i}. {cmd['command_name']} ({cmd['delay_ms']}ms)"
|
|
|
self.sequence_list.addItem(text)
|
|
|
|
|
|
def _on_add_command(self):
|
|
|
"""Add command to sequence."""
|
|
|
if self.cmd_combo.count() == 0:
|
|
|
QMessageBox.warning(self, "Error", "No UART commands available")
|
|
|
return
|
|
|
|
|
|
cmd_id = self.cmd_combo.currentData()
|
|
|
cmd_name = self.cmd_combo.currentText()
|
|
|
delay = self.delay_spin.value()
|
|
|
|
|
|
self.command_sequence.append({
|
|
|
"command_id": cmd_id,
|
|
|
"command_name": cmd_name,
|
|
|
"delay_ms": delay
|
|
|
})
|
|
|
|
|
|
self._update_sequence_list()
|
|
|
self.btn_save.setEnabled(True)
|
|
|
|
|
|
def _on_move_up(self):
|
|
|
"""Move selected command up."""
|
|
|
idx = self.sequence_list.currentRow()
|
|
|
if idx > 0:
|
|
|
self.command_sequence[idx], self.command_sequence[idx-1] = \
|
|
|
self.command_sequence[idx-1], self.command_sequence[idx]
|
|
|
self._update_sequence_list()
|
|
|
self.sequence_list.setCurrentRow(idx-1)
|
|
|
self.btn_save.setEnabled(True)
|
|
|
|
|
|
def _on_move_down(self):
|
|
|
"""Move selected command down."""
|
|
|
idx = self.sequence_list.currentRow()
|
|
|
if 0 <= idx < len(self.command_sequence) - 1:
|
|
|
self.command_sequence[idx], self.command_sequence[idx+1] = \
|
|
|
self.command_sequence[idx+1], self.command_sequence[idx]
|
|
|
self._update_sequence_list()
|
|
|
self.sequence_list.setCurrentRow(idx+1)
|
|
|
self.btn_save.setEnabled(True)
|
|
|
|
|
|
def _on_remove_command(self):
|
|
|
"""Remove selected command."""
|
|
|
idx = self.sequence_list.currentRow()
|
|
|
if idx >= 0:
|
|
|
del self.command_sequence[idx]
|
|
|
self._update_sequence_list()
|
|
|
self.btn_save.setEnabled(True)
|
|
|
|
|
|
def _on_profile_selected(self, current, previous):
|
|
|
"""Handle profile selection."""
|
|
|
if current:
|
|
|
profile_id = current.data(Qt.ItemDataRole.UserRole)
|
|
|
self._load_profile_details(profile_id)
|
|
|
|
|
|
def _on_add_clicked(self):
|
|
|
"""Handle add button."""
|
|
|
self.name_input.clear()
|
|
|
self.desc_input.clear()
|
|
|
self.print_rx_checkbox.setChecked(False) # Default to disabled
|
|
|
self.command_sequence = []
|
|
|
self._update_sequence_list()
|
|
|
|
|
|
self.profile_list.clearSelection()
|
|
|
self.current_profile_id = None
|
|
|
self.btn_delete.setEnabled(False)
|
|
|
self.btn_save.setEnabled(True)
|
|
|
self.name_input.setFocus()
|
|
|
|
|
|
def _on_delete_clicked(self):
|
|
|
"""Handle delete button."""
|
|
|
if not self.current_profile_id:
|
|
|
return
|
|
|
|
|
|
reply = QMessageBox.question(
|
|
|
self, "Confirm Delete",
|
|
|
f"Delete profile '{self.name_input.text()}'?",
|
|
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
|
|
)
|
|
|
if reply != QMessageBox.StandardButton.Yes:
|
|
|
return
|
|
|
|
|
|
try:
|
|
|
self.db_conn.execute(
|
|
|
"DELETE FROM session_profiles WHERE profile_id = ?",
|
|
|
(self.current_profile_id,)
|
|
|
)
|
|
|
self.db_conn.commit()
|
|
|
self.profile_deleted.emit(self.current_profile_id)
|
|
|
self._load_profiles()
|
|
|
self.name_input.clear()
|
|
|
self.desc_input.clear()
|
|
|
self.command_sequence = []
|
|
|
self._update_sequence_list()
|
|
|
self.current_profile_id = None
|
|
|
self.btn_save.setEnabled(False)
|
|
|
self.btn_delete.setEnabled(False)
|
|
|
QMessageBox.information(self, "Success", "Profile deleted")
|
|
|
except Exception as e:
|
|
|
QMessageBox.critical(self, "Error", f"Delete failed: {str(e)}")
|
|
|
|
|
|
def _on_save_clicked(self):
|
|
|
"""Handle save button."""
|
|
|
name = self.name_input.text().strip()
|
|
|
if not name:
|
|
|
QMessageBox.warning(self, "Error", "Profile name required")
|
|
|
return
|
|
|
|
|
|
if not self.command_sequence:
|
|
|
QMessageBox.warning(self, "Error", "Add at least one command")
|
|
|
return
|
|
|
|
|
|
desc = self.desc_input.text().strip()
|
|
|
print_rx = 1 if self.print_rx_checkbox.isChecked() else 0
|
|
|
|
|
|
# Build JSON
|
|
|
json_obj = {
|
|
|
"commands": [
|
|
|
{"command_id": cmd["command_id"], "delay_ms": cmd["delay_ms"]}
|
|
|
for cmd in self.command_sequence
|
|
|
]
|
|
|
}
|
|
|
json_text = json.dumps(json_obj)
|
|
|
|
|
|
try:
|
|
|
if self.current_profile_id:
|
|
|
self.db_conn.execute(
|
|
|
"UPDATE session_profiles SET profile_name=?, description=?, "
|
|
|
"command_sequence=?, print_command_rx=?, last_modified=datetime('now') WHERE profile_id=?",
|
|
|
(name, desc, json_text, print_rx, self.current_profile_id)
|
|
|
)
|
|
|
msg = "Profile updated"
|
|
|
else:
|
|
|
cursor = self.db_conn.execute(
|
|
|
"INSERT INTO session_profiles (profile_name, description, command_sequence, print_command_rx) "
|
|
|
"VALUES (?, ?, ?, ?)",
|
|
|
(name, desc, json_text, print_rx)
|
|
|
)
|
|
|
self.current_profile_id = cursor.lastrowid
|
|
|
msg = "Profile created"
|
|
|
|
|
|
self.db_conn.commit()
|
|
|
self.profile_saved.emit(self.current_profile_id)
|
|
|
self._load_profiles()
|
|
|
QMessageBox.information(self, "Success", msg)
|
|
|
self.btn_save.setEnabled(False)
|
|
|
except Exception as e:
|
|
|
QMessageBox.critical(self, "Error", f"Save failed: {str(e)}")
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
import sys
|
|
|
from PyQt6.QtWidgets import QApplication
|
|
|
|
|
|
app = QApplication(sys.argv)
|
|
|
db = DatabaseManager("database/ehinge.db")
|
|
|
db.initialize()
|
|
|
|
|
|
widget = ConfigureSessionWidget(db)
|
|
|
widget.setWindowTitle("Configure Session Profiles")
|
|
|
widget.resize(1000, 600)
|
|
|
widget.show()
|
|
|
|
|
|
sys.exit(app.exec())
|