Added basic GUI

This commit is contained in:
David Rice
2026-01-07 11:47:54 +00:00
parent 0b11e8541f
commit 8e92a4b47e
19 changed files with 633 additions and 0 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
appbackground.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
arriveico.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
darkblue-led.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

BIN
fail.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
gray-led.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

BIN
green-led.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

BIN
ligthblue-led.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

245
main.py Normal file
View File

@@ -0,0 +1,245 @@
#!/user/bin/env python3
"""
AUTOMOTIVE POWER SIMULATOR APP - MAIN.PY
- ENTRY POINT OF APPLICATION
VERSION: 1.0
AUTHOR: D. RICE 14/11/2025
© 2025 ARRIVE
"""
# Imports
import sys
from PySide6.QtWidgets import (QApplication, QDialog, QMainWindow)
from PySide6.QtCore import (QThread, QObject, Signal, Slot)
import time
from ui_mainwindow import Ui_MainWindow
from ui_scanning import Ui_scanningDialog
from ui_connerror import Ui_connerrorDialog
# Scanning Window Class
class ScanningWindow(QDialog):
def __init__(self, parent=None):
# Initialise print window
super(ScanningWindow, self).__init__(parent)
# Finish print window initial setup
self.ui = Ui_scanningDialog()
self.ui.setupUi(self)
# No device warning Window Class
class NoDeviceWindow(QDialog):
def __init__(self, parent=None):
# Initialise print window
super(NoDeviceWindow, self).__init__(parent)
# Finish print window initial setup
self.ui = Ui_connerrorDialog()
self.ui.setupUi(self)
# Static method to create the dialog and return response
@staticmethod
def get_response(parent=None):
dialog = NoDeviceWindow(parent)
results = dialog.exec()
return results
# Main window class
class MainWindow(QMainWindow):
def __init__(self):
# Initialise main window
super(MainWindow, self).__init__()
# Finish main window initial setup
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
# Set up timer prototype
self.counter = 0
# Hold process reference.
self.p = None
# Thread and Worker initialisation
self.worker_thread = None
self.worker = None
# Add start button action
self.ui.connButton.setCheckable(True)
self.ui.connButton.clicked.connect(self.conn_button_press)
# Set initial size
initial_width = 1024
initial_height = 768
# Lock the window size by setting min/max to the same values
self.setMinimumSize(initial_width, initial_height)
self.setMaximumSize(initial_width, initial_height)
self.setGeometry(0, 0, 1024, 768)
self.setFixedSize(1024, 768)
self.show()
def conn_button_press (self):
global IDN_Response_flag
global Error_flag
text_conn = self.ui.connButton.text()
if text_conn == "CONNECT":
self.ui.connButton.setText("DISCONNECT")
w = ScanningWindow(self)
w.show()
QApplication.processEvents()
# Run Connection actions here
#self.start_worker()
while ((IDN_Response_flag == False) and (Error_flag == False)):
QApplication.processEvents()
if IDN_Response_flag == True:
w.close()
IDN_Response_flag = False
Error_flag = False
self.run_scope_setup()
if Error_flag == True:
response = NoDeviceWindow.get_response(self)
self.ui.connButton.setText("CONNECT")
self.closeEvent()
self.ui.manu.setText("---")
self.ui.model.setText("---")
self.ui.sn.setText("---")
self.ui.fw.setText("---")
IDN_Response_flag = False
Error_flag = False
QApplication.processEvents()
else:
self.ui.connButton.setText("CONNECT")
self.closeEvent()
self.ui.manu.setText("---")
self.ui.model.setText("---")
self.ui.sn.setText("---")
self.ui.fw.setText("---")
QApplication.processEvents()
def run_scope_setup (self):
self.worker.run_setup()
@Slot()
def start_worker(self):
# Create a QThread
self.worker_thread = QThread()
#nCreate the worker QObject (Now created directly, no import needed)
self.worker = ScopeWorker()
# Move the worker object to the thread
self.worker.moveToThread(self.worker_thread)
# Connect Signals
self.worker_thread.started.connect(self.worker.run)
self.worker.idn_signal.connect(self.handle_idn)
self.worker.data_ready_signal.connect(self.handle_data_ready)
self.worker.error_signal.connect(self.handle_error)
# Clean-up connections
self.worker.finished_signal.connect(self.worker_thread.quit)
self.worker.finished_signal.connect(self.worker.deleteLater)
self.worker_thread.finished.connect(self.worker_thread.deleteLater)
# Start the QThread, which triggers worker.run
self.worker_thread.start()
@Slot()
def signal_acquisition(self):
"""Sends the signal to the worker to start pulling data."""
if self.worker:
self.log_text.append("Main: Sending START_ACQUISITION signal.")
self.status_label.setText("Status: Acquiring data...")
# This is safe because start_acquisition only sets a flag
self.worker.start_acquisition()
# --- SLOTS: Receive data safely from the worker thread ---
@Slot(str)
def handle_idn(self, idn_string):
global IDN_Response_flag
global Error_flag
# Received the initial IDN from the worker
IDN_Response_flag = True
# Split the string by the comma delimiter
components = idn_string.strip().split(',')
# Split the string by the comma delimiter
components = idn_string.strip().split(',')
if len(components) == 4:
# Assign the components to meaningful keys in a dictionary
parsed_data = {
"manufacturer": components[0],
"model": components[1],
"serial_number": components[2],
"firmware_version": components[3]
}
self.ui.manu.setText(parsed_data.get("manufacturer"))
self.ui.model.setText(parsed_data.get("model"))
self.ui.sn.setText(parsed_data.get("serial_number"))
self.ui.fw.setText(parsed_data.get("firmware_version"))
else:
Error_flag = True
self.ui.manu.setText("---")
self.ui.model.setText("---")
self.ui.sn.setText("---")
self.ui.fw.setText("---")
@Slot(str)
def handle_data_ready(self, data_snippet):
"""Received the acquired data from the worker."""
self.status_label.setText("Status: Data Ready. Waiting for next command.")
self.log_text.append(f"Main: Received Waveform Data Snippet:\n{data_snippet}")
@Slot(str)
def handle_error(self, message):
global Error_flag
Error_flag = True
def closeEvent(self):
"""Handle application close to stop the thread gracefully."""
if self.worker_thread and self.worker_thread.isRunning():
self.worker.stop()
# Wait a moment for the thread to clean up
self.worker_thread.wait(500)
# Run main
if __name__ == '__main__':
# Launch main window
app = QApplication(sys.argv)
app.setStyle('Fusion')
window = MainWindow()
window.show()
app.exec()
sys.exit()

54
main.spec Normal file
View File

@@ -0,0 +1,54 @@
# -*- mode: python ; coding: utf-8 -*-
from PyInstaller.utils.hooks import copy_metadata
datas = []
datas += copy_metadata('nidaqmx')
block_cipher = None
a = Analysis(
['main.py'],
pathex=[],
binaries=[],
datas=datas,
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name='main',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='main',
)

BIN
pass.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
red-led.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

39
test.py Normal file
View File

@@ -0,0 +1,39 @@
def parse_idn_response(idn_string):
"""
Parses the SCPI *IDN? response string into individual components.
Args:
idn_string: The raw string received from the instrument.
Example: "Agilent Technologies,DSO80204B,MY46002160,05.30.0005"
Returns:
A dictionary containing the manufacturer, model, serial number,
and firmware version.
"""
# Split the string by the comma delimiter
components = idn_string.strip().split(',')
if len(components) == 4:
# Assign the components to meaningful keys in a dictionary
parsed_data = {
"manufacturer": components[0],
"model": components[1],
"serial_number": components[2],
"firmware_version": components[3]
}
return parsed_data
else:
# Handle cases where the string format is unexpected
print(f"Warning: Unexpected IDN string format. Expected 4 parts, got {len(components)}.")
return {"raw_response": idn_string, "components": components}
# Example Usage:
response_string = "Agilent Technologies,DSO80204B,MY46002160,05.30.0005"
data = parse_idn_response(response_string)
# Print the resulting dictionary
print(data)
# You can access individual elements like this:
print(f"Manufacturer: {data.get('manufacturer')}")
print(f"Model: {data.get('model')}")

18
test_eth.py Normal file
View File

@@ -0,0 +1,18 @@
import vxi11
class DSO80204B(vxi11.Instrument):
def __init__(self, host, *args, **kwargs):
super(DSO80204B, self).__init__(host, *args, **kwargs)
def get_identification(self):
return self.ask("*IDN?")
def main():
# Get scope
instrument = DSO80204B('192.168.45.2')
print (instrument.get_identification())
if __name__ == "__main__":
main()

66
ui_connerror.py Normal file
View File

@@ -0,0 +1,66 @@
from PySide6.QtCore import (QCoreApplication, QMetaObject, QRect, Qt)
from PySide6.QtGui import (QFont)
from PySide6.QtWidgets import (QDialogButtonBox, QLabel)
class Ui_connerrorDialog(object):
def setupUi(self, Dialog):
if not Dialog.objectName():
Dialog.setObjectName(u"Dialog")
Dialog.resize(320, 160)
# Set the window flags to remove the title bar
Dialog.setWindowFlags(Qt.WindowType.FramelessWindowHint)
font = QFont()
font.setFamilies([u"Optimism Sans"])
font.setPointSize(8)
font.setBold(True)
Dialog.setFont(font)
dialog_style_sheet = f"""
QDialog {{
background-image: url("");
background-color: #FF80D4;
border: 1px solid #FF33BB;
border-radius: 10px;
}}
"""
Dialog.setStyleSheet(dialog_style_sheet)
self.buttonBox = QDialogButtonBox(Dialog)
self.buttonBox.setObjectName(u"buttonBox")
self.buttonBox.setFont(font)
self.buttonBox.setGeometry(QRect(10, 120, 300, 30))
self.buttonBox.setOrientation(Qt.Horizontal)
self.buttonBox.setStandardButtons(QDialogButtonBox.Ok)
self.buttonBox.setCenterButtons(True)
button_style = """
QPushButton {
background-color: #FF80D4;
border: 1px solid #FF33BB;
border-radius: 1px;
padding: 10px 20px 10px 20px;
}
"""
self.buttonBox.setStyleSheet(button_style)
self.label = QLabel(Dialog)
self.label.setObjectName(u"label")
self.label.setFont(font)
self.label.setGeometry(QRect(10, 40, 300, 50))
self.label.setAlignment(Qt.AlignCenter)
self.label.setWordWrap(True)
self.retranslateUi(Dialog)
self.buttonBox.accepted.connect(Dialog.accept)
QMetaObject.connectSlotsByName(Dialog)
# setupUi
def retranslateUi(self, Dialog):
self.label.setText(QCoreApplication.translate("Dialog", u"COULDN'T CONNECT TO DSO80204B", None))
# retranslateUi

170
ui_mainwindow.py Normal file
View File

@@ -0,0 +1,170 @@
# -*- coding: utf-8 -*-
from PySide6.QtCore import (QCoreApplication, QMetaObject, QRect, Qt)
from PySide6.QtGui import (QBrush, QColor, QFont, QIcon, QPalette, QPixmap, QFontDatabase)
from PySide6.QtWidgets import (QCheckBox, QFrame, QLabel, QProgressBar, QSlider, QPushButton, QWidget, QVBoxLayout)
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
if not MainWindow.objectName():
MainWindow.setObjectName(u"MainWindow")
MainWindow.setToolButtonStyle(Qt.ToolButtonIconOnly)
MainWindow.setAnimated(True)
MainWindow.setDocumentMode(False)
MainWindow.setWindowIcon(QIcon('/home/davidrice/Python/Automotive-Power-Simulator-App/arriveico.png'))
fontmain = QFont()
fontmain.setFamilies([u"Optimism Sans"])
fontmain.setPointSize(8)
fontmain.setBold(True)
MainWindow.setFont(fontmain)
# Ensure the path is correct for your system!
image_path = "/home/davidrice/Python/Automotive-Power-Simulator-App/appbackground.jpg" # Example Path
# --- Define and Apply the Style Sheet ---
bg_style_sheet = f"""
QWidget {{
background-image: url("{image_path}");
background-repeat: no-repeat;
background-position: center;
}}
"""
self.centralwidget = QWidget(MainWindow)
self.centralwidget.setObjectName(u"centralwidget")
self.centralwidget.setStyleSheet(bg_style_sheet)
self.header = QLabel(self.centralwidget)
self.header.setObjectName(u"header")
self.header.setGeometry(QRect(50, 35, 650, 50))
font = QFont()
font.setFamilies([u"Optimism Sans"])
font.setPointSize(24)
font.setBold(True)
self.header.setFont(font)
self.header.setStyleSheet("color: #5F016F;")
self.header.setAlignment(Qt.AlignCenter)
self.test_area = QFrame(self.centralwidget)
self.test_area.setObjectName(u"test_area")
self.test_area.setGeometry(QRect(50, 115, 924, 618))
self.test_area.setFrameShape(QFrame.StyledPanel)
self.test_area.setFrameShadow(QFrame.Raised)
button_style = """
QPushButton {
background-color: #FF80D4;
border: 1px solid #FF33BB;
border-radius: 1px;
}
"""
self.connButton = QPushButton(self.test_area)
self.connButton.setObjectName(u"connButton")
self.connButton.setGeometry(QRect(25, 25, 125, 25))
self.connButton.setStyleSheet(button_style)
text_label_style = """
QLabel {
background-image: url("");
background-color: #FF80D4;
border: 0px solid #FF33BB;
border-radius: 0px;
}
"""
self.manuLabel = QLabel(self.test_area)
self.manuLabel.setObjectName(u"manuLabel")
self.manuLabel.setGeometry(QRect(200, 5, 250, 15))
self.manuLabel.setAlignment(Qt.AlignCenter)
self.manuLabel.setStyleSheet(text_label_style)
label_style = """
QLabel {
background-image: url("");
background-color: #FF80D4;
border: 1px solid #FF33BB;
border-radius: 1px;
}
"""
self.manu = QLabel(self.test_area)
self.manu.setObjectName(u"manu")
self.manu.setGeometry(QRect(200, 25, 250, 25))
self.manu.setAlignment(Qt.AlignCenter)
self.manu.setStyleSheet(label_style)
self.modelLabel = QLabel(self.test_area)
self.modelLabel.setObjectName(u"modelLabel")
self.modelLabel.setGeometry(QRect(475, 5, 125, 15))
self.modelLabel.setAlignment(Qt.AlignCenter)
self.modelLabel.setStyleSheet(text_label_style)
self.model = QLabel(self.test_area)
self.model.setObjectName(u"model")
self.model.setGeometry(QRect(475, 25, 125, 25))
self.model.setAlignment(Qt.AlignCenter)
self.model.setStyleSheet(label_style)
self.snLabel = QLabel(self.test_area)
self.snLabel.setObjectName(u"snlabel")
self.snLabel.setGeometry(QRect(625, 5, 125, 15))
self.snLabel.setAlignment(Qt.AlignCenter)
self.snLabel.setStyleSheet(text_label_style)
self.sn = QLabel(self.test_area)
self.sn.setObjectName(u"sn")
self.sn.setGeometry(QRect(625, 25, 125, 25))
self.sn.setAlignment(Qt.AlignCenter)
self.sn.setStyleSheet(label_style)
self.fwLabel = QLabel(self.test_area)
self.fwLabel.setObjectName(u"fwLabel")
self.fwLabel.setGeometry(QRect(775, 5, 125, 15))
self.fwLabel.setAlignment(Qt.AlignCenter)
self.fwLabel.setStyleSheet(text_label_style)
self.fw = QLabel(self.test_area)
self.fw.setObjectName(u"fw")
self.fw.setGeometry(QRect(775, 25, 125, 25))
self.fw.setAlignment(Qt.AlignCenter)
self.fw.setStyleSheet(label_style)
frame_style = """
QFrame {
background-image: url("");
background-color: #FF80D4;
border: 1px solid #FF33BB;
border-radius: 10px;
}
"""
self.test_area.setStyleSheet(frame_style)
MainWindow.setCentralWidget(self.centralwidget)
self.header.raise_()
self.test_area.raise_()
self.retranslateUi(MainWindow)
QMetaObject.connectSlotsByName(MainWindow)
# setupUi
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"AUTOMOTIVE POWER SIMULATOR V1.0",
None))
self.header.setText(QCoreApplication.translate("MainWindow", u"AUTOMOTIVE POWER SIMULATOR", None))
self.connButton.setText(QCoreApplication.translate("MainWindow", u"CONNECT", None))
self.manuLabel.setText(QCoreApplication.translate("MainWindow", u"MANUFACTURER", None))
self.manu.setText(QCoreApplication.translate("MainWindow", u"---", None))
self.modelLabel.setText(QCoreApplication.translate("MainWindow", u"MODEL", None))
self.model.setText(QCoreApplication.translate("MainWindow", u"---", None))
self.snLabel.setText(QCoreApplication.translate("MainWindow", u"SERIAL NO", None))
self.sn.setText(QCoreApplication.translate("MainWindow", u"---", None))
self.fwLabel.setText(QCoreApplication.translate("MainWindow", u"F/W REV", None))
self.fw.setText(QCoreApplication.translate("MainWindow", u"---", None))
# retranslateUi

41
ui_scanning.py Normal file
View File

@@ -0,0 +1,41 @@
from PySide6.QtCore import (QCoreApplication, QRect, Qt)
from PySide6.QtGui import (QFont)
from PySide6.QtWidgets import (QLabel, QFrame)
class Ui_scanningDialog(object):
def setupUi(self, Dialog):
if not Dialog.objectName():
Dialog.setObjectName(u"Dialog")
Dialog.resize(320, 160)
# Set the window flags to remove the title bar
Dialog.setWindowFlags(Qt.WindowType.FramelessWindowHint)
font = QFont()
font.setFamilies([u"Optimism Sans"])
font.setPointSize(8)
font.setBold(True)
Dialog.setFont(font)
self.label = QLabel(Dialog)
self.label.setObjectName(u"label")
self.label.setFont(font)
self.label.setGeometry(QRect(10, 10, 300, 140))
self.label.setAlignment(Qt.AlignCenter)
self.label.setWordWrap(True)
label_style = """
QLabel {
background-image: url("");
background-color: #FF80D4;
border: 1px solid #FF33BB;
border-radius: 10px;
}
"""
self.label.setStyleSheet(label_style)
self.retranslateUi(Dialog)
# setupUi
def retranslateUi(self, Dialog):
self.label.setText(QCoreApplication.translate("Dialog", u"SCANNING FOR DSO80204B", None))
# retranslateUi