diff --git a/__pycache__/ui_connerror.cpython-312.pyc b/__pycache__/ui_connerror.cpython-312.pyc new file mode 100644 index 0000000..ffa66cb Binary files /dev/null and b/__pycache__/ui_connerror.cpython-312.pyc differ diff --git a/__pycache__/ui_mainwindow.cpython-312.pyc b/__pycache__/ui_mainwindow.cpython-312.pyc new file mode 100644 index 0000000..1a12301 Binary files /dev/null and b/__pycache__/ui_mainwindow.cpython-312.pyc differ diff --git a/__pycache__/ui_scanning.cpython-312.pyc b/__pycache__/ui_scanning.cpython-312.pyc new file mode 100644 index 0000000..b482186 Binary files /dev/null and b/__pycache__/ui_scanning.cpython-312.pyc differ diff --git a/appbackground.jpg b/appbackground.jpg new file mode 100644 index 0000000..1c32dc9 Binary files /dev/null and b/appbackground.jpg differ diff --git a/arriveico.png b/arriveico.png new file mode 100644 index 0000000..ed9e56e Binary files /dev/null and b/arriveico.png differ diff --git a/darkblue-led.png b/darkblue-led.png new file mode 100644 index 0000000..52e120b Binary files /dev/null and b/darkblue-led.png differ diff --git a/fail.png b/fail.png new file mode 100644 index 0000000..84c8c04 Binary files /dev/null and b/fail.png differ diff --git a/gray-led.png b/gray-led.png new file mode 100644 index 0000000..5c56f9e Binary files /dev/null and b/gray-led.png differ diff --git a/green-led.png b/green-led.png new file mode 100644 index 0000000..24f2000 Binary files /dev/null and b/green-led.png differ diff --git a/ligthblue-led.png b/ligthblue-led.png new file mode 100644 index 0000000..4e63bab Binary files /dev/null and b/ligthblue-led.png differ diff --git a/main.py b/main.py new file mode 100644 index 0000000..3a24e52 --- /dev/null +++ b/main.py @@ -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() diff --git a/main.spec b/main.spec new file mode 100644 index 0000000..53beb69 --- /dev/null +++ b/main.spec @@ -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', +) diff --git a/pass.png b/pass.png new file mode 100644 index 0000000..926ae4d Binary files /dev/null and b/pass.png differ diff --git a/red-led.png b/red-led.png new file mode 100644 index 0000000..3d8bd41 Binary files /dev/null and b/red-led.png differ diff --git a/test.py b/test.py new file mode 100644 index 0000000..2a10f0a --- /dev/null +++ b/test.py @@ -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')}") \ No newline at end of file diff --git a/test_eth.py b/test_eth.py new file mode 100644 index 0000000..399f45e --- /dev/null +++ b/test_eth.py @@ -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() \ No newline at end of file diff --git a/ui_connerror.py b/ui_connerror.py new file mode 100644 index 0000000..4f24d3a --- /dev/null +++ b/ui_connerror.py @@ -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 \ No newline at end of file diff --git a/ui_mainwindow.py b/ui_mainwindow.py new file mode 100644 index 0000000..b46fe29 --- /dev/null +++ b/ui_mainwindow.py @@ -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 + + diff --git a/ui_scanning.py b/ui_scanning.py new file mode 100644 index 0000000..fa34879 --- /dev/null +++ b/ui_scanning.py @@ -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 \ No newline at end of file