Skip to content

Commit 20b5544

Browse files
authored
Dag logic (#15)
Mostly full rewrite of the project, not exhaustive list of features: - switched to PySide6, most of the widgets have been rewritten - the steps are now declared as a DAG instead of an array - cleaner set parameters passed to a step (context, settings and run_info) - easier to overload settings widgets - basic start screen for a run
1 parent b778335 commit 20b5544

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+3926
-6110
lines changed

.github/workflows/setup-action/action.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ runs:
2323
with:
2424
path: .venv
2525
key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
26+
- name: Install PySide dependencies
27+
run: sudo apt install libegl1
28+
shell: bash
2629
- name: Install dependencies
2730
if: inputs.disable_cache == 'true' || steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
2831
run: poetry install

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ dist/
33
sopic.egg-info/
44
__pycache__/
55
.pytest_cache/
6+
dag-*.png

CHANGELOG.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Changelog
22

3-
## Unversioned
3+
## 1.0.0
44

5-
- Minor: switch to poetry
6-
7-
## 0.1.0
5+
- New DAG logic to cycle through steps
6+
- New API for steps and station wrappers
7+
- New settings window

README.md

Lines changed: 68 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
Sopic
22
======
33

4-
Helper library for a test station in a production line.
4+
Test stations GUI library for your hardware on a production line.
55

66
Define a station that will run a number of steps, in sequential order.
7+
Visualize the steps with the GUI
78

89

910
# Screenshots
@@ -15,112 +16,87 @@ Define a station that will run a number of steps, in sequential order.
1516

1617
# Installation
1718

18-
After a clone of the repository
19-
20-
`poetry install`
19+
`pip install sopic`
2120

2221

2322
# How to use
2423

25-
Initialise a `MainWindow` with a station object, child of the `Station` class.
26-
27-
In your station class you can set some parameters and define your list of steps.
24+
Initialise `MainWindow` with a station object, child of the `Station` class.
2825

29-
You can also define a settings dialog when initializing the `MainWindow`.
30-
31-
Check the examples directory (eg: `poetry run python examples/00_basic.py`)
26+
In your station class you can define the steps in a DAG and settings for the
27+
runs.
3228

3329

3430
# Keybinds
3531

36-
### Exit the station
37-
Ctrl-Q
38-
39-
### Settings
40-
**Ctrl-P**
41-
42-
DIsplay the settings dialog. The configuration is saved. The settings can be
43-
reset to the default configuration set in the station class.
44-
45-
### Step selection
46-
**Ctrl-T**
47-
48-
Allow to enable/disable steps. The configuration is not saved.
49-
50-
### Log and settings layout
51-
**Ctrl-B**
52-
53-
Display settings and log window
54-
32+
- `Ctrl-Q`: exit the station
33+
- `Ctrl-P`: display the settings dialog, when available
34+
- `Ctrl-B`: display the logs
5535

5636
# Definitions
5737

5838
A step is the smallest component, a class invoked at the start of the station.
59-
A method from this object will be called to start the step. The step has then
60-
access to data from previous steps, if they provided it, and global settings of
61-
the station.
62-
After being called, the step has to notify the station of the tests passed or
63-
not.
39+
The `start` method from this object will be called to start the step. The step
40+
has then access to data from previous steps, if they provided it, settings and
41+
current run info.
42+
After being called the step can either returns `self.OK()` or `self.KO()` to
43+
notify the station if the step has passed or not. These two methods take a
44+
parameters to select the next step.
6445

65-
A station is a group of steps. It's also defined as a class and will store the
66-
default settings, station configuration and the steps.
46+
A station is a group of steps. The steps are defined in a Direct Acyclic Graph.
6747

68-
A run is the term used to define the "list" of steps from the first to the last,
69-
as defined in the station class.
70-
So a station will perform multiple runs. Each one composed of multiple steps.
48+
A run is the succession of the steps. A run can either Pass or Fail. A station
49+
will perform multiple runs.
7150

7251

7352
# Features
7453

7554
- GUI
7655
- Share data between steps
7756
- Settings for step configuration
78-
- Step selection
7957
- Logs
58+
- Easy to customize with wrappers
8059

8160
# Define a station
8261

8362
```python
8463
from sopic import Station
8564

8665
class MyStation(Station):
87-
# Used as window title
88-
DISPLAY_NAME = 'my station'
89-
# Used for logs, filenames
66+
# Used as window title, log file, settings file
9067
STATION_NAME = 'my-station'
91-
# Same use as STATION_NAME, useful to when saving the previous station
68+
# Same use as STATION_NAME
9269
STATION_ID = 0
93-
94-
# Allow to disable logging to files
95-
disable_file_logging = False
96-
97-
# List of steps in the station
98-
steps = [
99-
Step1,
100-
# Can deactivate step by default
101-
# steps can be enabled with the step selection dialog
102-
(Step2, False),
103-
]
104-
105-
# By default a fail in a step will force the run to go directly to the
106-
# last step, skipping other steps.
107-
# By defining the non blocking steps we allow the run to continue even
108-
# if these steps failed. Useful when testing the station without caring
109-
# about the step output
110-
# Note that the run is still a fail if one of these steps fail.
111-
nonBlockingSteps = [
112-
Step1.STEP_NAME,
113-
]
114-
115-
# Steps that will always be skipped if one previous step has failed.
116-
# Useful when also using nonBlockingSteps
117-
stepsSkippedOnPreviousFail = [
118-
Step2.STEP_NAME,
119-
]
70+
# Optional, displayed in the bottom right corner of the main window
71+
STATION_VERSION = "1.0.0"
72+
73+
# Will generate a png output of the dag
74+
DEBUG = True
75+
76+
# Select the first step, via its key
77+
start_step_key = "start"
78+
79+
# DAG of the steps
80+
dag = {
81+
# first step
82+
# `StartButton` is the step class
83+
# `["select"] is the child, no matter if the StartButton fails, the
84+
# child will be the one with the `"select"` key
85+
"start": (StartButton, ["select"]),
86+
# This step define another step class, `Select`
87+
# This step can select one of two childs
88+
# - "foo", if the step returns the key "ok"
89+
# - "bar", if the step returns the key "ko"
90+
"select": (Select, {"ok": "foo", "ko": "bar"}),
91+
# The next two steps don't have any child, returning for those step
92+
# will end the run
93+
"foo": (AlwaysOK, []),
94+
"bar": (AlwaysKO, []),
95+
}
12096

12197
# Default settings of the station. Can be updated with the SettingsDialog.
122-
defaultSettings = {
123-
'random-setting': '42',
98+
default_settings = {
99+
"random-settings": {"value": 42, "label": "A random settings"},
124100
}
125101
```
126102

@@ -130,53 +106,41 @@ class MyStation(Station):
130106
from sopic import Step
131107

132108
class MyStep(Step):
133-
# name of the step, used for display and logs
109+
# Name of the step, used for display and logs
134110
STEP_NAME = 'my-step'
135-
# number of retries allowed on fail
111+
# Number of retries allowed on fail
136112
MAX_RETRIES = 1
137113

138-
# called when the step is started
139-
# stepsData contains data of the previous steps, if available.
140-
# Data of steps is available via `stepsData[Step1.STEP_NAME]`.
141-
# Settings is available at `stepsData['__settings']`.
142-
# Check the End step in the example folder for more use of the stepsData.
143-
def start(self, stepsData):
114+
# Called when the step is started
115+
# `context` contains value stored by previous steps
116+
# `settings` contains the station settings
117+
# `run_info` contains current run information
118+
def start(self, context, settings, run_info):
144119
super().start()
145120

146121
# Add your tests here
147-
# You have access to `self.logger` to log some info.
148-
# You have access to the object `self.stepData` to store data, it will
149-
# be available for future the next steps. This object is reset on each run.
150-
# The step will have to return either `OK` or `KO`
151-
152-
# A string can be passed to describe the success of the step.
153-
return self.OK('all good')
154-
155-
# On error, the step should return `self.KO`
156-
# terminate, flag will force the run to end.
157-
# errorStr and errorCode is used to describe the error
158-
# return self.KO(
159-
terminate = False,
160-
errorStr = "something happened",
161-
errorCode = 42
162-
)
163-
164-
# Used if the step define use a StepUI
165-
# Check the steps in the examples directory
166-
def getWidget(self):
167-
if (self.widet === None):
168-
self.widget = MyStepUI()
169-
return self.widget
122+
123+
return self.OK()
170124
```
171125

172126

173127
# Examples
174128

175129
[Check the examples directory](./examples)
176130

131+
To run the examples:
132+
```bash
133+
git clone https://github.com/Taldrain/sopic
134+
cd sopic
135+
poetry install
136+
poetry run examples/00_basic.py
137+
```
138+
177139

178140
# See also
179141

180142
[exclave](https://github.com/exclave/exclave)
181143

182144
[Exclave: Hardware Testing in Mass Production, Made Easier](https://www.bunniestudios.com/blog/?p=5450)
145+
146+
[awesome-hardware-test](https://github.com/sschaetz/awesome-hardware-test)

assets/basic-run.png

42.7 KB
Loading

assets/basic.png

13.8 KB
Loading

assets/settings.png

66.1 KB
Loading

examples/00_basic.py

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,31 @@
11
#!/usr/bin/env python3
22

33
import sys
4-
from PyQt5.QtWidgets import QApplication
4+
from PySide6.QtWidgets import QApplication
55

6-
from sopic.station import Station
7-
from sopic.gui import MainWindow
6+
from sopic import MainWindow, Station
87

98
from examples.steps import Select, AlwaysOK, AlwaysKO
109

1110

1211
class BasicStation(Station):
13-
DISPLAY_NAME = "basic station"
14-
STATION_NAME = "basic-station"
12+
STATION_NAME = "basic station"
1513
STATION_ID = 0
16-
STATION_VERSION = "0.0.1"
14+
STATION_VERSION = "1.0.0"
1715

18-
disableFileLogging = True
16+
DEBUG = True
1917

20-
steps = [
21-
Select,
22-
AlwaysOK,
23-
# This step will always return KO,
24-
# the next step will always be skipped
25-
AlwaysKO,
26-
# Never called
27-
Select,
28-
Select,
29-
]
18+
dag = {
19+
# will allow to either go to step `foo` or `bar`
20+
"select": (Select, {"ok": "foo", "ko": "bar"}),
21+
"foo": (AlwaysOK, []),
22+
"bar": (AlwaysKO, []),
23+
}
24+
25+
start_step_key = "select"
3026

3127

3228
if __name__ == "__main__":
33-
Q_APP = QApplication(sys.argv)
29+
app = QApplication([])
3430
MainWindow(BasicStation).show()
35-
sys.exit(Q_APP.exec_())
31+
sys.exit(app.exec())

examples/01_blocking_step.py

Lines changed: 0 additions & 35 deletions
This file was deleted.

examples/01_settings.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#!/usr/bin/env python3
2+
3+
import sys
4+
from PySide6.QtWidgets import QApplication
5+
6+
from sopic import MainWindow, Station
7+
8+
from examples.steps import PrintSettings, GetSettings
9+
10+
11+
class SettingsStation(Station):
12+
STATION_NAME = "settings-station"
13+
STATION_ID = 1
14+
STATION_VERSION = "1.0.0"
15+
16+
DEBUG = True
17+
18+
dag = {
19+
"print": (PrintSettings, ["get"]),
20+
"get": (GetSettings, []),
21+
}
22+
23+
default_settings = {
24+
"random-settings": {"value": 42, "label": "A random settings"},
25+
"read-only": {"value": 12, "label": "Read-only settings", "edit": False},
26+
"no-label": {"value": "hello"},
27+
}
28+
29+
30+
if __name__ == "__main__":
31+
app = QApplication([])
32+
MainWindow(SettingsStation).show()
33+
sys.exit(app.exec())

0 commit comments

Comments
 (0)