|
|
[<< Previous Chapter](CLI-Wrapper) | [Home](Home) | [Next Chapter >>](Using-the-CyDAQ-User-Interface)
|
|
|
|
|
|
[[_TOC_]]
|
|
|
|
|
|
# Overview
|
|
|
The GUI tool is an interface written in PyQt5. It utilizes an instance of the CLI wrapper class to communicate with the CyDAQ. The main code can be found under `gui/app.py` (start at the bottom of the file). The class MainWindow is the app's starting point; all other widgets/elements are added. It also contains a thread pool for running commands asynchronously and the CLI Wrapper. Each widget is also its class, which can all be found in the `gui/widgets/` directory.
|
|
|
|
|
|
# Running the App
|
|
|
If you haven't downloaded the required packages, do so with this command:
|
|
|
```bash
|
|
|
python3 -m pip install -r requirements.txt
|
|
|
```
|
|
|
|
|
|
The app itself can be run with the following command:
|
|
|
```bash
|
|
|
python gui/app.py
|
|
|
```
|
|
|
If you add new changes, they can also be seen with the above app without needing to build anything new. Make sure to save your changes in the other file.
|
|
|
|
|
|
# Compiling and Building an EXE file
|
|
|
|
|
|
Simply run the `compile.sh` file found in the root directory of the project. It will output an .exe file in the `dist/` folder.
|
|
|
|
|
|
```bash
|
|
|
./compile.sh
|
|
|
```
|
|
|
> Note: Make sure you can run the app using the above commands first before building.
|
|
|
|
|
|
If you would like to change the current configuration, the documentation to do so can be found here [(PyInstaller Docs)](https://pyinstaller.org/en/stable/usage.html).
|
|
|
|
|
|
Currently, the `compile.sh` file is configured to generate a single .exe file with no external dependency files, with the CyDAQ logo and named `CyDAQ.exe`. These are the only things we recommend changing, as changing any of the other arguments may result in the application breaking.
|
|
|
|
|
|
# Changing GUI elements in QT Designer
|
|
|
You can open ```.ui``` files (located in [gui/qtdesigner](https://git.ece.iastate.edu/sd/sdmay23-47/-/tree/master/gui/qtdesigner)) with QT Designer and make any necessary changes to the various pages and widgets.
|
|
|
|
|
|
Once the changes are done, you must re-build the python files (located in [gui/generated](https://git.ece.iastate.edu/sd/sdmay23-47/-/tree/master/gui/generated). We provide a script do do this automatically, and can be ran with the following:
|
|
|
|
|
|
```bash
|
|
|
cd gui
|
|
|
```
|
|
|
|
|
|
```bash
|
|
|
bash build.sh
|
|
|
```
|
|
|
> Note: You MUST run this script from within the GUI directory.
|
|
|
|
|
|
There are more instructions later in the document that outline the steps required to add new pages and widgets to the app, and how the build script must be changed to support the new .UI files.
|
|
|
|
|
|
# Add a Module
|
|
|
If you want to add a module to the CyDAQ Interface, you must design the page in QtDesigner, build it, and import it into a class file to make it recognizable and accessible from the front page. This also allows for backend logic that can be handled on the module page.
|
|
|
|
|
|
### Design the Page
|
|
|
|
|
|
First, you'll need to create the page in QtDesigner from which to import the actual buttons, switches, inputs, outputs, etc., with which you will work with in your code.
|
|
|
|
|
|
1. Open QtDesigner
|
|
|
2. Create a new file with the type 'Widget'
|
|
|
|
|
|

|
|
|
|
|
|
3. Add whichever components, layouts, forms, etc. you want on your page design. Make sure you name each item that you will be using in the backend such as buttons, labels that change, forms, etc.
|
|
|
|
|
|

|
|
|
|
|
|
4. Make sure to rename the window to something like new_module in the top right "Object Inspector."
|
|
|
|
|
|

|
|
|
|
|
|
5. Save the file in the `gui/qtdesigner/` folder with the name of the new widget.
|
|
|
6. Add an entry to the `build.sh` and `build-linux.sh` similar to the following:
|
|
|
|
|
|
__build.sh__:
|
|
|
```bash
|
|
|
python -m PyQt5.uic.pyuic --from-imports qtdesigner/NewModuleName.ui -o generated/NewModuleUI.py
|
|
|
```
|
|
|
|
|
|
__build-linux.sh__:
|
|
|
```bash
|
|
|
pyuic5 --from-imports qtdesigner/NewModuleName.ui -o generated/NewModuleUI.py
|
|
|
```
|
|
|
7. The following commands must be run to generate the new .py files. The generated files can be found in `generated/`.
|
|
|
```bash
|
|
|
cd gui
|
|
|
```
|
|
|
|
|
|
```bash
|
|
|
bash build.sh
|
|
|
```
|
|
|
|
|
|
or if on linux:
|
|
|
```bash
|
|
|
bash build-linux.sh
|
|
|
```
|
|
|
|
|
|
### Creating the Widget Backend
|
|
|
|
|
|
1. Create the file in `gui/widgets/` and name it with a lowercase and underscore format similar to what is already in the folder (this is so the file name and class name are not the same)
|
|
|
2. In the file, name it in the form of a class, and import the MainWindow object and the generated Ui page.
|
|
|
3. The code snippet below is how to properly get the module set up and imported from the MainWindow:
|
|
|
```python
|
|
|
from PyQt5 import QtWidgets
|
|
|
from generated.NewModuleUI import Ui_new_module
|
|
|
|
|
|
class NewModuleName(QtWidgets.QWidget, Ui_new_module, CyDAQModeWidget):
|
|
|
def __init__(self, mainWindow):
|
|
|
super(NewModuleName, self).__init__() # Initiates the superclass (parent)
|
|
|
self.setupUi(self) # Used in the generated .py file
|
|
|
|
|
|
# Import parent window and worker object from parent
|
|
|
self.mainWindow = mainWindow
|
|
|
|
|
|
# Share resources from main window
|
|
|
self.threadpool = self.mainWindow.threadpool
|
|
|
self.wrapper = mainWindow.wrapper
|
|
|
self.logger = self.mainWindow.logger
|
|
|
|
|
|
|
|
|
# define other methods below
|
|
|
```
|
|
|
> Note: Check out basic_operation.py in `gui/widgets` for a more in-depth example.
|
|
|
|
|
|
### Adding the page to the MainWindow
|
|
|
1. Open `gui/app.py`.
|
|
|
2. At the top, add a new import statement for your new module that you created in the above section with the other imports like this:
|
|
|
```python
|
|
|
from widgets import NewModuleWidget
|
|
|
```
|
|
|
3. Scroll down to the `__init__()` method and find the '# Widgets` comment.
|
|
|
4. Add a new line with the following format right under that comment:
|
|
|
```python
|
|
|
self.new_module = NewModuleWidget(self)
|
|
|
```
|
|
|
5. A few lines down, you'll see this line:
|
|
|
```python
|
|
|
self.widgets = []
|
|
|
```
|
|
|
Find the line with the last widget added, and add a new line after that (it's important that you keep the specific order!):
|
|
|
```python
|
|
|
self.widgets.append(self.new_module)
|
|
|
```
|
|
|
6. Lastly, scroll down to the ` The following are methods for switching...` comment, and, underneath all of the other `switchTo...` methods, add a new one with this format:
|
|
|
```python
|
|
|
def switchToNewModule(self):
|
|
|
self.stack.setCurrentIndex(x)
|
|
|
```
|
|
|
|
|
|
Where 'x' is the next number in the index (check the method above it and add 1 to that number). This tells the application that there is a new page at that index, and to switch to that index specifically when you want to get to that page.
|
|
|
|
|
|
### Adding it to the Mode Selector Page
|
|
|
|
|
|
Once you've created the widgets class, we need to assign the page to a button on QtDesigner and in the mode selector widget code. If you already have a button created and ready to be assigned, you can skip to the next section.
|
|
|
|
|
|
#### Add a Button
|
|
|
1. First, open QtDesigner and open the `gui/qtdesigner/ModeSelectorWidget.ui` file, drag a button onto the layout however you choose, and set the text to "New Module," whatever your module name is.
|
|
|
2. Then, in the top right Object Inspector, set the name of your new QButton to "newModuleBtn" or something along that format.
|
|
|
3. Save the file and exit.
|
|
|
4. Regenerate the file using the `build.sh` (Windows) or `build-linux.sh` (Linux) file.
|
|
|
|
|
|
#### Adding to the `mode_selector.py` File
|
|
|
1. Open the `gui/widgets/mode_selector.py` file.
|
|
|
2. Scroll to the `__init__()` method in the `ModeSelectorWidget` class.
|
|
|
3. Add code with the following format (replacing your button name) under the `Mode Selector Buttons` comment:
|
|
|
```python
|
|
|
newWidgetButton = self.new_widget_btn
|
|
|
newWidgetButton.setCheckable(True)
|
|
|
newWidgetButton.clicked.connect(lambda: self.mainWindow.switchToNewModule())
|
|
|
```
|
|
|
### Conclusion
|
|
|
|
|
|
You should now have a new module added. You have the .ui design file, the generated .py file from it, the backend module file where the inner logic is run, and now it is hooked into the main window to get it's resources and threadpools as well as accessible from the Mode Selector page.
|
|
|
|
|
|
# CyDAQ Communication
|
|
|
The GUI uses the CLI Wrapper class to communicate with the CyDAQ. This class was purposefully built for this project and exists in the same repository. An instance of the CLI Wrapper class holds a running process of the CLI Tool, which also exists in this repository.
|
|
|
|
|
|
Each method of the CLI Wrapper is blocking, which means it needs to be run asynchronously with the main thread of the UI. This is achieved using the existing PyQT5 QRunnable and QThreadPool classes to push the function calls into another thread. This means that long-running functions, like sampling/retrieving data, won't cause the GUI to freeze when run.
|
|
|
|
|
|
The CLI Wrapper class raises custom exceptions when certain errors occur, for example, a sudden disconnect of the CyDAQ. These errors can be caught by the GUI code and handled appropriately.
|
|
|
|
|
|
# Logging
|
|
|
We are using the built in logging library for Python to handle terminal, file, and GUI logging. The logger object is first created in the __init__ function of MainWindow in `app.py`, then passed to each subsequent widget/window that needs it. The logging object also gets passed to the Wrapper, so all wrapper commands and response are included in logging outputs.
|
|
|
|
|
|
The Debug page uses the logging object to write logs to the GUI. Check out the `LogHandler` class in `debug.py` for it's implementation.
|
|
|
|
|
|
|
|
|
[<< Previous Chapter](CLI-Wrapper) | [Home](Home) | [Next Chapter >>](Using-the-CyDAQ-User-Interface) |