interface-test-kit 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- interface_test_kit-0.1.0/LICENSE +21 -0
- interface_test_kit-0.1.0/PKG-INFO +257 -0
- interface_test_kit-0.1.0/README.md +223 -0
- interface_test_kit-0.1.0/pyproject.toml +55 -0
- interface_test_kit-0.1.0/setup.cfg +4 -0
- interface_test_kit-0.1.0/src/interface_test_kit/__init__.py +3 -0
- interface_test_kit-0.1.0/src/interface_test_kit/checks/__init__.py +47 -0
- interface_test_kit-0.1.0/src/interface_test_kit/checks/can.py +186 -0
- interface_test_kit-0.1.0/src/interface_test_kit/checks/confirm.py +30 -0
- interface_test_kit-0.1.0/src/interface_test_kit/checks/ethernet.py +97 -0
- interface_test_kit-0.1.0/src/interface_test_kit/checks/serial.py +172 -0
- interface_test_kit-0.1.0/src/interface_test_kit/checks/usb.py +243 -0
- interface_test_kit-0.1.0/src/interface_test_kit/cli.py +130 -0
- interface_test_kit-0.1.0/src/interface_test_kit/context.py +43 -0
- interface_test_kit-0.1.0/src/interface_test_kit/detect.py +22 -0
- interface_test_kit-0.1.0/src/interface_test_kit/model.py +108 -0
- interface_test_kit-0.1.0/src/interface_test_kit/profile.py +178 -0
- interface_test_kit-0.1.0/src/interface_test_kit/profiles/demo.yaml +22 -0
- interface_test_kit-0.1.0/src/interface_test_kit/report.py +142 -0
- interface_test_kit-0.1.0/src/interface_test_kit/runner.py +71 -0
- interface_test_kit-0.1.0/src/interface_test_kit/session.py +110 -0
- interface_test_kit-0.1.0/src/interface_test_kit/sim/__init__.py +1 -0
- interface_test_kit-0.1.0/src/interface_test_kit/sim/can.py +40 -0
- interface_test_kit-0.1.0/src/interface_test_kit/sim/fakes.py +64 -0
- interface_test_kit-0.1.0/src/interface_test_kit/sim/serial.py +44 -0
- interface_test_kit-0.1.0/src/interface_test_kit/sim/world.py +96 -0
- interface_test_kit-0.1.0/src/interface_test_kit/station.py +54 -0
- interface_test_kit-0.1.0/src/interface_test_kit/ui.py +127 -0
- interface_test_kit-0.1.0/src/interface_test_kit.egg-info/PKG-INFO +257 -0
- interface_test_kit-0.1.0/src/interface_test_kit.egg-info/SOURCES.txt +45 -0
- interface_test_kit-0.1.0/src/interface_test_kit.egg-info/dependency_links.txt +1 -0
- interface_test_kit-0.1.0/src/interface_test_kit.egg-info/entry_points.txt +2 -0
- interface_test_kit-0.1.0/src/interface_test_kit.egg-info/requires.txt +6 -0
- interface_test_kit-0.1.0/src/interface_test_kit.egg-info/top_level.txt +1 -0
- interface_test_kit-0.1.0/tests/test_can.py +157 -0
- interface_test_kit-0.1.0/tests/test_cli.py +9 -0
- interface_test_kit-0.1.0/tests/test_demo.py +77 -0
- interface_test_kit-0.1.0/tests/test_ethernet.py +110 -0
- interface_test_kit-0.1.0/tests/test_model.py +40 -0
- interface_test_kit-0.1.0/tests/test_profile.py +94 -0
- interface_test_kit-0.1.0/tests/test_report.py +156 -0
- interface_test_kit-0.1.0/tests/test_runner.py +89 -0
- interface_test_kit-0.1.0/tests/test_serial.py +154 -0
- interface_test_kit-0.1.0/tests/test_session.py +268 -0
- interface_test_kit-0.1.0/tests/test_station.py +36 -0
- interface_test_kit-0.1.0/tests/test_ui.py +98 -0
- interface_test_kit-0.1.0/tests/test_usb.py +162 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ralf Steinert
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: interface-test-kit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Operator-guided interface test for Linux embedded and panel PCs: serial with handshake, USB, Ethernet and CAN, with per-variant profiles and reports
|
|
5
|
+
Author: Ralf Steinert
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/steitec/interface-test-kit
|
|
8
|
+
Project-URL: Repository, https://github.com/steitec/interface-test-kit
|
|
9
|
+
Project-URL: Changelog, https://github.com/steitec/interface-test-kit/blob/main/CHANGELOG.md
|
|
10
|
+
Project-URL: Issues, https://github.com/steitec/interface-test-kit/issues
|
|
11
|
+
Keywords: embedded,production test,end-of-line test,rma,serial,rs232,usb,ethernet,can,socketcan,loopback
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Manufacturing
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Software Development :: Embedded Systems
|
|
23
|
+
Classifier: Topic :: Software Development :: Testing
|
|
24
|
+
Classifier: Topic :: System :: Hardware
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: pyserial>=3.5
|
|
29
|
+
Requires-Dist: pyyaml>=6.0
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
32
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
# interface-test-kit
|
|
36
|
+
|
|
37
|
+
Interface test software that runs **on** a Linux embedded or panel PC and
|
|
38
|
+
checks its serial ports (including the handshake lines), USB ports, Ethernet
|
|
39
|
+
and CAN. The interfaces are fitted with test equipment: loopback plugs,
|
|
40
|
+
labelled USB test sticks, a network cable and a CAN cable. An operator is
|
|
41
|
+
guided through the test step by step and only has to answer Y/N.
|
|
42
|
+
|
|
43
|
+
It is written for the two situations where this test matters:
|
|
44
|
+
|
|
45
|
+
- **End-of-line test:** every unit gets a test report before it is shipped.
|
|
46
|
+
- **RMA:** reproduce the fault the customer reported, and prove the repair
|
|
47
|
+
afterwards (both runs are stored under the RMA number).
|
|
48
|
+
|
|
49
|
+
Faulty test equipment is the classic time sink in both cases: a worn
|
|
50
|
+
loopback plug makes a good unit fail. That is why there is an **equipment
|
|
51
|
+
self-test** (`itk selftest`), which checks the plugs, sticks and cables on a
|
|
52
|
+
known-good reference unit.
|
|
53
|
+
|
|
54
|
+
Everything can be tried without hardware: `itk demo` runs a full session
|
|
55
|
+
against a simulated unit, with faults you can switch on.
|
|
56
|
+
|
|
57
|
+
## Quick start
|
|
58
|
+
|
|
59
|
+
Needs Linux and Python 3.10+.
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
python -m venv .venv && . .venv/bin/activate
|
|
63
|
+
pip install -e ".[dev]"
|
|
64
|
+
|
|
65
|
+
itk demo # healthy unit -> PASS
|
|
66
|
+
itk demo --fault serial_cts # broken handshake line -> FAIL in "Serial 1"
|
|
67
|
+
itk demo --list-faults
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
Scan or enter the serial number: EPC-24-00137
|
|
72
|
+
Profile: EPC-24 variant B (demo) (v1). Correct? [Y/N] Y
|
|
73
|
+
Plug the loopback plugs into COM1 and COM2, the two test sticks into the blue USB ports, ...
|
|
74
|
+
Press Enter when ready.
|
|
75
|
+
Serial 1: handshake line faulty (RTS->CTS). Check the loopback plug; if it is fine, send the unit to repair.
|
|
76
|
+
Retry this step? [Y/N] N
|
|
77
|
+
[FAIL] Serial 1 Serial 1: handshake line faulty (RTS->CTS). Check the loopback plug; ...
|
|
78
|
+
[PASS] Serial 2
|
|
79
|
+
[PASS] USB
|
|
80
|
+
[PASS] LAN1
|
|
81
|
+
[PASS] CAN
|
|
82
|
+
Is the power LED green? [Y/N] Y
|
|
83
|
+
[PASS] Power LED
|
|
84
|
+
|
|
85
|
+
========================================
|
|
86
|
+
RESULT: FAIL
|
|
87
|
+
========================================
|
|
88
|
+
Same failure on several units? Run the equipment self-test.
|
|
89
|
+
Report saved: reports/EPC-24-00137_20261028-134954
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Injectable faults (demo)
|
|
93
|
+
|
|
94
|
+
| `--fault` | Defect | Caught by |
|
|
95
|
+
|---|---|---|
|
|
96
|
+
| `serial_cts` | RTS→CTS line broken | Serial 1 |
|
|
97
|
+
| `serial_data` | no data comes back | Serial 1 |
|
|
98
|
+
| `usb_missing` | only one test stick plugged in | USB |
|
|
99
|
+
| `usb_slow` | test sticks far too slow | USB |
|
|
100
|
+
| `usb_wrong_speed` | USB 3 port only runs at 480 Mbit/s | USB |
|
|
101
|
+
| `usb_no_label` | a stick without the `ITKTEST` label | USB |
|
|
102
|
+
| `eth_no_link` | no Ethernet link | LAN1 |
|
|
103
|
+
| `eth_speed` | link at 100 instead of 1000 Mbit/s | LAN1 |
|
|
104
|
+
| `can_no_ack` | frames sent from can0 never arrive | CAN |
|
|
105
|
+
| `led_off` | the operator sees the power LED off | Power LED |
|
|
106
|
+
|
|
107
|
+
## What is tested
|
|
108
|
+
|
|
109
|
+
| Step type | What it checks |
|
|
110
|
+
|---|---|
|
|
111
|
+
| `serial` | Test pattern (all 256 byte values plus random data) at every baud rate in the profile, then each handshake line of the loopback plug in both states, with the other outputs held opposite so shorts show up. Reports the exact line, e.g. `DTR->DSR`. |
|
|
112
|
+
| `usb` | Number of mounted test sticks, optionally which port each one is in, the negotiated USB speed (catches a USB 3 port running at USB 2), and a write/read/compare of a test file with throughput limits. |
|
|
113
|
+
| `ethernet` | Link, negotiated speed and duplex, ping to a counterpart with a packet-loss limit. |
|
|
114
|
+
| `can` | Two CAN ports wired together exchange standard and extended frames in both directions; on real CAN hardware the bitrate is set first and the controller must end in `ERROR-ACTIVE` or `ERROR-WARNING`. |
|
|
115
|
+
| `confirm` | A manual check: the operator looks at something and answers Y/N. |
|
|
116
|
+
|
|
117
|
+
**Retries:** loose plugs happen. When a step fails, the operator can retry it.
|
|
118
|
+
A step counts as PASS when its first attempt passes, or when its last **two**
|
|
119
|
+
attempts pass in a row. After a fail followed by a pass, the tool runs the
|
|
120
|
+
step once more on its own. The limit is `max_attempts` (default 4). The
|
|
121
|
+
report lists every attempt and marks the step "passed after retry".
|
|
122
|
+
|
|
123
|
+
### USB safety rules
|
|
124
|
+
|
|
125
|
+
The USB test never formats anything and never writes to a block device. It
|
|
126
|
+
only writes one file, `itk-test.bin`, into the mount point of a **mounted**
|
|
127
|
+
filesystem on a **USB** disk whose **label starts with** the profile's label
|
|
128
|
+
(default `ITKTEST`, so `ITKTEST1`, `ITKTEST2` work for several sticks). The
|
|
129
|
+
file is deleted afterwards. A stick with another label is ignored and
|
|
130
|
+
listed in the report.
|
|
131
|
+
|
|
132
|
+
## Running it on a test station
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
itk run # end-of-line test
|
|
136
|
+
itk run --rma RMA-2026-0042 # RMA: the number goes into the report and folder name
|
|
137
|
+
itk run --only serial-1,usb # RMA: just these steps
|
|
138
|
+
itk selftest # check the test equipment on the reference unit
|
|
139
|
+
itk sync # upload reports saved while the network drive was down
|
|
140
|
+
itk check-profile profiles/*.yaml
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The flow for the operator:
|
|
144
|
+
|
|
145
|
+
1. Scan or type the serial number. If it does not match the profile's pattern, the tool asks again.
|
|
146
|
+
2. The profile is chosen automatically (single profile, or DMI / device-tree match). Otherwise the operator picks it from a numbered list. Then the operator confirms it.
|
|
147
|
+
3. Before anything runs, the tool checks that every port and interface exists and is accessible.
|
|
148
|
+
4. The setup instruction is shown: what to plug in where.
|
|
149
|
+
5. The steps run, one line each. Manual checks and retries ask Y/N.
|
|
150
|
+
6. A large `RESULT: PASS` / `FAIL` / `ABORTED` banner appears, then the report is written.
|
|
151
|
+
|
|
152
|
+
Ctrl+C aborts cleanly: the steps finished so far are written with the result `ABORTED`.
|
|
153
|
+
|
|
154
|
+
Exit codes: `0` PASS, `1` FAIL, `2` aborted or configuration error.
|
|
155
|
+
|
|
156
|
+
### Station file
|
|
157
|
+
|
|
158
|
+
`/etc/itk/station.yaml` (or `--station`, or `$ITK_STATION`). See
|
|
159
|
+
[`examples/station.yaml`](examples/station.yaml):
|
|
160
|
+
|
|
161
|
+
```yaml
|
|
162
|
+
report_dir: /mnt/test-reports/itk # network drive, mounted by the OS
|
|
163
|
+
mount_point: /mnt/test-reports # must be mounted, else the share counts as unreachable
|
|
164
|
+
spool_dir: /var/lib/itk/spool # reports wait here until `itk sync`
|
|
165
|
+
profile_dir: /etc/itk/profiles
|
|
166
|
+
name: EOL-STATION-1
|
|
167
|
+
ask_operator: false
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
If the network drive is not reachable, the report is saved in `spool_dir`
|
|
171
|
+
and the operator is told so. The next run, or `itk sync`, uploads it. With
|
|
172
|
+
`mount_point` set, a share that is not mounted is noticed instead of
|
|
173
|
+
quietly filling the empty mount-point folder on the local disk.
|
|
174
|
+
|
|
175
|
+
### Profiles
|
|
176
|
+
|
|
177
|
+
One YAML file per product variant. The product owner decides what is
|
|
178
|
+
tested, and the profile writes it down. A complete, commented example is in
|
|
179
|
+
[`examples/profiles/epc24-variant-b.yaml`](examples/profiles/epc24-variant-b.yaml):
|
|
180
|
+
|
|
181
|
+
```yaml
|
|
182
|
+
name: EPC-24 variant B
|
|
183
|
+
version: 3 # stored in every report
|
|
184
|
+
detect: {dmi_product_name: EPC-24B}
|
|
185
|
+
serial_number: {pattern: "^EPC-24-\\d{5}$"}
|
|
186
|
+
setup: Plug the loopback plugs into COM1 and COM2, ...
|
|
187
|
+
steps:
|
|
188
|
+
- {type: serial, name: Serial 1, port: /dev/ttyS0, loopback: 9pin-full, bauds: [9600, 115200]}
|
|
189
|
+
- {type: usb, name: USB, count: 2, ports: [1-1, 2-1], min_write_mbs: 10, min_speed_mbps: 5000}
|
|
190
|
+
- {type: ethernet, name: LAN1, iface: eth0, speed: 1000, duplex: full, ping: 192.168.100.1}
|
|
191
|
+
- {type: can, name: CAN, pair: [can0, can1], bitrate: 500000}
|
|
192
|
+
- {type: confirm, name: Power LED, prompt: "Is the power LED green?"}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Step ids for `--only` come from the name (`Serial 1` → `serial-1`) unless a
|
|
196
|
+
step sets `id:`. `itk check-profile` reports mistakes with their line
|
|
197
|
+
number, including misspelled fields.
|
|
198
|
+
|
|
199
|
+
**Reference profiles** (`reference: true`) are only offered by `itk selftest`.
|
|
200
|
+
A step can ask for the equipment's number (`equipment: Loopback plug number`),
|
|
201
|
+
which is stored in the self-test report. In self-test mode the messages talk
|
|
202
|
+
about the equipment instead of the unit, for example "Loopback plug on Serial 1
|
|
203
|
+
faulty (DTR->DSR not connected)". See
|
|
204
|
+
[`examples/profiles/reference-epc24.yaml`](examples/profiles/reference-epc24.yaml).
|
|
205
|
+
|
|
206
|
+
### Reports
|
|
207
|
+
|
|
208
|
+
Each run gets its own folder: `<serial>[_<rma>]_<YYYYmmdd-HHMMSS>/`
|
|
209
|
+
(self-tests go to `selftest/`). It contains:
|
|
210
|
+
|
|
211
|
+
- `result.json`: serial number, RMA, profile name and version, station,
|
|
212
|
+
operator, start and end time, tool version, result, and per step every
|
|
213
|
+
attempt with its message, details and measurements (value, unit, limits,
|
|
214
|
+
passed). This is the interface for further tools.
|
|
215
|
+
- `report.txt`: the same as plain, printable text.
|
|
216
|
+
|
|
217
|
+
## Hardware validation status
|
|
218
|
+
|
|
219
|
+
This first version is developed and tested **in simulation only**. The
|
|
220
|
+
simulation covers the logic, but the following still has to be verified on
|
|
221
|
+
real hardware:
|
|
222
|
+
|
|
223
|
+
- Modem control lines through real UARTs and real loopback plugs. The data
|
|
224
|
+
path is tested through a Linux pseudo-terminal with pyserial; the lines are
|
|
225
|
+
tested against a simulated plug.
|
|
226
|
+
- USB throughput and negotiated speed on real sticks and ports. The sysfs
|
|
227
|
+
layout and `lsblk` output are faked in the tests.
|
|
228
|
+
- CAN on real controllers and transceivers, including setting the bitrate
|
|
229
|
+
and reading the error state. The SocketCAN code itself is tested against
|
|
230
|
+
a virtual `vcan` interface.
|
|
231
|
+
- `ip` and `ping` behaviour on the target distributions.
|
|
232
|
+
|
|
233
|
+
## Development
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
pip install -e ".[dev]"
|
|
237
|
+
pytest -v
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
The CAN round-trip test needs a `vcan0` interface and is skipped otherwise.
|
|
241
|
+
Without root you can run it in a user network namespace:
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
unshare -rn sh -c 'ip link add vcan0 type vcan && ip link set up vcan0 && pytest tests/test_can.py'
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Roadmap
|
|
248
|
+
|
|
249
|
+
- RS485 (half-duplex loopback with RTS direction control)
|
|
250
|
+
- GPIO loopback pairs, audio loopback
|
|
251
|
+
- Guided checks for display, keyboard and touch
|
|
252
|
+
- Tracking of test equipment across runs
|
|
253
|
+
- CAN partner mode, Ethernet throughput (iperf3), RMA before/after comparison
|
|
254
|
+
|
|
255
|
+
## License
|
|
256
|
+
|
|
257
|
+
MIT
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# interface-test-kit
|
|
2
|
+
|
|
3
|
+
Interface test software that runs **on** a Linux embedded or panel PC and
|
|
4
|
+
checks its serial ports (including the handshake lines), USB ports, Ethernet
|
|
5
|
+
and CAN. The interfaces are fitted with test equipment: loopback plugs,
|
|
6
|
+
labelled USB test sticks, a network cable and a CAN cable. An operator is
|
|
7
|
+
guided through the test step by step and only has to answer Y/N.
|
|
8
|
+
|
|
9
|
+
It is written for the two situations where this test matters:
|
|
10
|
+
|
|
11
|
+
- **End-of-line test:** every unit gets a test report before it is shipped.
|
|
12
|
+
- **RMA:** reproduce the fault the customer reported, and prove the repair
|
|
13
|
+
afterwards (both runs are stored under the RMA number).
|
|
14
|
+
|
|
15
|
+
Faulty test equipment is the classic time sink in both cases: a worn
|
|
16
|
+
loopback plug makes a good unit fail. That is why there is an **equipment
|
|
17
|
+
self-test** (`itk selftest`), which checks the plugs, sticks and cables on a
|
|
18
|
+
known-good reference unit.
|
|
19
|
+
|
|
20
|
+
Everything can be tried without hardware: `itk demo` runs a full session
|
|
21
|
+
against a simulated unit, with faults you can switch on.
|
|
22
|
+
|
|
23
|
+
## Quick start
|
|
24
|
+
|
|
25
|
+
Needs Linux and Python 3.10+.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
python -m venv .venv && . .venv/bin/activate
|
|
29
|
+
pip install -e ".[dev]"
|
|
30
|
+
|
|
31
|
+
itk demo # healthy unit -> PASS
|
|
32
|
+
itk demo --fault serial_cts # broken handshake line -> FAIL in "Serial 1"
|
|
33
|
+
itk demo --list-faults
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
Scan or enter the serial number: EPC-24-00137
|
|
38
|
+
Profile: EPC-24 variant B (demo) (v1). Correct? [Y/N] Y
|
|
39
|
+
Plug the loopback plugs into COM1 and COM2, the two test sticks into the blue USB ports, ...
|
|
40
|
+
Press Enter when ready.
|
|
41
|
+
Serial 1: handshake line faulty (RTS->CTS). Check the loopback plug; if it is fine, send the unit to repair.
|
|
42
|
+
Retry this step? [Y/N] N
|
|
43
|
+
[FAIL] Serial 1 Serial 1: handshake line faulty (RTS->CTS). Check the loopback plug; ...
|
|
44
|
+
[PASS] Serial 2
|
|
45
|
+
[PASS] USB
|
|
46
|
+
[PASS] LAN1
|
|
47
|
+
[PASS] CAN
|
|
48
|
+
Is the power LED green? [Y/N] Y
|
|
49
|
+
[PASS] Power LED
|
|
50
|
+
|
|
51
|
+
========================================
|
|
52
|
+
RESULT: FAIL
|
|
53
|
+
========================================
|
|
54
|
+
Same failure on several units? Run the equipment self-test.
|
|
55
|
+
Report saved: reports/EPC-24-00137_20261028-134954
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Injectable faults (demo)
|
|
59
|
+
|
|
60
|
+
| `--fault` | Defect | Caught by |
|
|
61
|
+
|---|---|---|
|
|
62
|
+
| `serial_cts` | RTS→CTS line broken | Serial 1 |
|
|
63
|
+
| `serial_data` | no data comes back | Serial 1 |
|
|
64
|
+
| `usb_missing` | only one test stick plugged in | USB |
|
|
65
|
+
| `usb_slow` | test sticks far too slow | USB |
|
|
66
|
+
| `usb_wrong_speed` | USB 3 port only runs at 480 Mbit/s | USB |
|
|
67
|
+
| `usb_no_label` | a stick without the `ITKTEST` label | USB |
|
|
68
|
+
| `eth_no_link` | no Ethernet link | LAN1 |
|
|
69
|
+
| `eth_speed` | link at 100 instead of 1000 Mbit/s | LAN1 |
|
|
70
|
+
| `can_no_ack` | frames sent from can0 never arrive | CAN |
|
|
71
|
+
| `led_off` | the operator sees the power LED off | Power LED |
|
|
72
|
+
|
|
73
|
+
## What is tested
|
|
74
|
+
|
|
75
|
+
| Step type | What it checks |
|
|
76
|
+
|---|---|
|
|
77
|
+
| `serial` | Test pattern (all 256 byte values plus random data) at every baud rate in the profile, then each handshake line of the loopback plug in both states, with the other outputs held opposite so shorts show up. Reports the exact line, e.g. `DTR->DSR`. |
|
|
78
|
+
| `usb` | Number of mounted test sticks, optionally which port each one is in, the negotiated USB speed (catches a USB 3 port running at USB 2), and a write/read/compare of a test file with throughput limits. |
|
|
79
|
+
| `ethernet` | Link, negotiated speed and duplex, ping to a counterpart with a packet-loss limit. |
|
|
80
|
+
| `can` | Two CAN ports wired together exchange standard and extended frames in both directions; on real CAN hardware the bitrate is set first and the controller must end in `ERROR-ACTIVE` or `ERROR-WARNING`. |
|
|
81
|
+
| `confirm` | A manual check: the operator looks at something and answers Y/N. |
|
|
82
|
+
|
|
83
|
+
**Retries:** loose plugs happen. When a step fails, the operator can retry it.
|
|
84
|
+
A step counts as PASS when its first attempt passes, or when its last **two**
|
|
85
|
+
attempts pass in a row. After a fail followed by a pass, the tool runs the
|
|
86
|
+
step once more on its own. The limit is `max_attempts` (default 4). The
|
|
87
|
+
report lists every attempt and marks the step "passed after retry".
|
|
88
|
+
|
|
89
|
+
### USB safety rules
|
|
90
|
+
|
|
91
|
+
The USB test never formats anything and never writes to a block device. It
|
|
92
|
+
only writes one file, `itk-test.bin`, into the mount point of a **mounted**
|
|
93
|
+
filesystem on a **USB** disk whose **label starts with** the profile's label
|
|
94
|
+
(default `ITKTEST`, so `ITKTEST1`, `ITKTEST2` work for several sticks). The
|
|
95
|
+
file is deleted afterwards. A stick with another label is ignored and
|
|
96
|
+
listed in the report.
|
|
97
|
+
|
|
98
|
+
## Running it on a test station
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
itk run # end-of-line test
|
|
102
|
+
itk run --rma RMA-2026-0042 # RMA: the number goes into the report and folder name
|
|
103
|
+
itk run --only serial-1,usb # RMA: just these steps
|
|
104
|
+
itk selftest # check the test equipment on the reference unit
|
|
105
|
+
itk sync # upload reports saved while the network drive was down
|
|
106
|
+
itk check-profile profiles/*.yaml
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The flow for the operator:
|
|
110
|
+
|
|
111
|
+
1. Scan or type the serial number. If it does not match the profile's pattern, the tool asks again.
|
|
112
|
+
2. The profile is chosen automatically (single profile, or DMI / device-tree match). Otherwise the operator picks it from a numbered list. Then the operator confirms it.
|
|
113
|
+
3. Before anything runs, the tool checks that every port and interface exists and is accessible.
|
|
114
|
+
4. The setup instruction is shown: what to plug in where.
|
|
115
|
+
5. The steps run, one line each. Manual checks and retries ask Y/N.
|
|
116
|
+
6. A large `RESULT: PASS` / `FAIL` / `ABORTED` banner appears, then the report is written.
|
|
117
|
+
|
|
118
|
+
Ctrl+C aborts cleanly: the steps finished so far are written with the result `ABORTED`.
|
|
119
|
+
|
|
120
|
+
Exit codes: `0` PASS, `1` FAIL, `2` aborted or configuration error.
|
|
121
|
+
|
|
122
|
+
### Station file
|
|
123
|
+
|
|
124
|
+
`/etc/itk/station.yaml` (or `--station`, or `$ITK_STATION`). See
|
|
125
|
+
[`examples/station.yaml`](examples/station.yaml):
|
|
126
|
+
|
|
127
|
+
```yaml
|
|
128
|
+
report_dir: /mnt/test-reports/itk # network drive, mounted by the OS
|
|
129
|
+
mount_point: /mnt/test-reports # must be mounted, else the share counts as unreachable
|
|
130
|
+
spool_dir: /var/lib/itk/spool # reports wait here until `itk sync`
|
|
131
|
+
profile_dir: /etc/itk/profiles
|
|
132
|
+
name: EOL-STATION-1
|
|
133
|
+
ask_operator: false
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
If the network drive is not reachable, the report is saved in `spool_dir`
|
|
137
|
+
and the operator is told so. The next run, or `itk sync`, uploads it. With
|
|
138
|
+
`mount_point` set, a share that is not mounted is noticed instead of
|
|
139
|
+
quietly filling the empty mount-point folder on the local disk.
|
|
140
|
+
|
|
141
|
+
### Profiles
|
|
142
|
+
|
|
143
|
+
One YAML file per product variant. The product owner decides what is
|
|
144
|
+
tested, and the profile writes it down. A complete, commented example is in
|
|
145
|
+
[`examples/profiles/epc24-variant-b.yaml`](examples/profiles/epc24-variant-b.yaml):
|
|
146
|
+
|
|
147
|
+
```yaml
|
|
148
|
+
name: EPC-24 variant B
|
|
149
|
+
version: 3 # stored in every report
|
|
150
|
+
detect: {dmi_product_name: EPC-24B}
|
|
151
|
+
serial_number: {pattern: "^EPC-24-\\d{5}$"}
|
|
152
|
+
setup: Plug the loopback plugs into COM1 and COM2, ...
|
|
153
|
+
steps:
|
|
154
|
+
- {type: serial, name: Serial 1, port: /dev/ttyS0, loopback: 9pin-full, bauds: [9600, 115200]}
|
|
155
|
+
- {type: usb, name: USB, count: 2, ports: [1-1, 2-1], min_write_mbs: 10, min_speed_mbps: 5000}
|
|
156
|
+
- {type: ethernet, name: LAN1, iface: eth0, speed: 1000, duplex: full, ping: 192.168.100.1}
|
|
157
|
+
- {type: can, name: CAN, pair: [can0, can1], bitrate: 500000}
|
|
158
|
+
- {type: confirm, name: Power LED, prompt: "Is the power LED green?"}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Step ids for `--only` come from the name (`Serial 1` → `serial-1`) unless a
|
|
162
|
+
step sets `id:`. `itk check-profile` reports mistakes with their line
|
|
163
|
+
number, including misspelled fields.
|
|
164
|
+
|
|
165
|
+
**Reference profiles** (`reference: true`) are only offered by `itk selftest`.
|
|
166
|
+
A step can ask for the equipment's number (`equipment: Loopback plug number`),
|
|
167
|
+
which is stored in the self-test report. In self-test mode the messages talk
|
|
168
|
+
about the equipment instead of the unit, for example "Loopback plug on Serial 1
|
|
169
|
+
faulty (DTR->DSR not connected)". See
|
|
170
|
+
[`examples/profiles/reference-epc24.yaml`](examples/profiles/reference-epc24.yaml).
|
|
171
|
+
|
|
172
|
+
### Reports
|
|
173
|
+
|
|
174
|
+
Each run gets its own folder: `<serial>[_<rma>]_<YYYYmmdd-HHMMSS>/`
|
|
175
|
+
(self-tests go to `selftest/`). It contains:
|
|
176
|
+
|
|
177
|
+
- `result.json`: serial number, RMA, profile name and version, station,
|
|
178
|
+
operator, start and end time, tool version, result, and per step every
|
|
179
|
+
attempt with its message, details and measurements (value, unit, limits,
|
|
180
|
+
passed). This is the interface for further tools.
|
|
181
|
+
- `report.txt`: the same as plain, printable text.
|
|
182
|
+
|
|
183
|
+
## Hardware validation status
|
|
184
|
+
|
|
185
|
+
This first version is developed and tested **in simulation only**. The
|
|
186
|
+
simulation covers the logic, but the following still has to be verified on
|
|
187
|
+
real hardware:
|
|
188
|
+
|
|
189
|
+
- Modem control lines through real UARTs and real loopback plugs. The data
|
|
190
|
+
path is tested through a Linux pseudo-terminal with pyserial; the lines are
|
|
191
|
+
tested against a simulated plug.
|
|
192
|
+
- USB throughput and negotiated speed on real sticks and ports. The sysfs
|
|
193
|
+
layout and `lsblk` output are faked in the tests.
|
|
194
|
+
- CAN on real controllers and transceivers, including setting the bitrate
|
|
195
|
+
and reading the error state. The SocketCAN code itself is tested against
|
|
196
|
+
a virtual `vcan` interface.
|
|
197
|
+
- `ip` and `ping` behaviour on the target distributions.
|
|
198
|
+
|
|
199
|
+
## Development
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
pip install -e ".[dev]"
|
|
203
|
+
pytest -v
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The CAN round-trip test needs a `vcan0` interface and is skipped otherwise.
|
|
207
|
+
Without root you can run it in a user network namespace:
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
unshare -rn sh -c 'ip link add vcan0 type vcan && ip link set up vcan0 && pytest tests/test_can.py'
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Roadmap
|
|
214
|
+
|
|
215
|
+
- RS485 (half-duplex loopback with RTS direction control)
|
|
216
|
+
- GPIO loopback pairs, audio loopback
|
|
217
|
+
- Guided checks for display, keyboard and touch
|
|
218
|
+
- Tracking of test equipment across runs
|
|
219
|
+
- CAN partner mode, Ethernet throughput (iperf3), RMA before/after comparison
|
|
220
|
+
|
|
221
|
+
## License
|
|
222
|
+
|
|
223
|
+
MIT
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "interface-test-kit"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Operator-guided interface test for Linux embedded and panel PCs: serial with handshake, USB, Ethernet and CAN, with per-variant profiles and reports"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
authors = [{ name = "Ralf Steinert" }]
|
|
13
|
+
requires-python = ">=3.10"
|
|
14
|
+
dependencies = [
|
|
15
|
+
"pyserial>=3.5",
|
|
16
|
+
"pyyaml>=6.0",
|
|
17
|
+
]
|
|
18
|
+
keywords = ["embedded", "production test", "end-of-line test", "rma", "serial", "rs232", "usb", "ethernet", "can", "socketcan", "loopback"]
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Development Status :: 3 - Alpha",
|
|
21
|
+
"Environment :: Console",
|
|
22
|
+
"Intended Audience :: Manufacturing",
|
|
23
|
+
"Intended Audience :: Developers",
|
|
24
|
+
"Operating System :: POSIX :: Linux",
|
|
25
|
+
"Programming Language :: Python :: 3",
|
|
26
|
+
"Programming Language :: Python :: 3.10",
|
|
27
|
+
"Programming Language :: Python :: 3.11",
|
|
28
|
+
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Programming Language :: Python :: 3.13",
|
|
30
|
+
"Topic :: Software Development :: Embedded Systems",
|
|
31
|
+
"Topic :: Software Development :: Testing",
|
|
32
|
+
"Topic :: System :: Hardware",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
Homepage = "https://github.com/steitec/interface-test-kit"
|
|
37
|
+
Repository = "https://github.com/steitec/interface-test-kit"
|
|
38
|
+
Changelog = "https://github.com/steitec/interface-test-kit/blob/main/CHANGELOG.md"
|
|
39
|
+
Issues = "https://github.com/steitec/interface-test-kit/issues"
|
|
40
|
+
|
|
41
|
+
[project.optional-dependencies]
|
|
42
|
+
dev = ["pytest>=8", "build>=1.2"]
|
|
43
|
+
|
|
44
|
+
[project.scripts]
|
|
45
|
+
itk = "interface_test_kit.cli:main"
|
|
46
|
+
|
|
47
|
+
[tool.setuptools.packages.find]
|
|
48
|
+
where = ["src"]
|
|
49
|
+
|
|
50
|
+
[tool.setuptools.package-data]
|
|
51
|
+
interface_test_kit = ["profiles/*.yaml"]
|
|
52
|
+
|
|
53
|
+
[tool.pytest.ini_options]
|
|
54
|
+
testpaths = ["tests"]
|
|
55
|
+
markers = ["vcan: needs a vcan0 interface"]
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Registry of step types. Each check module registers itself on import."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from ..context import Context
|
|
10
|
+
from ..model import Attempt
|
|
11
|
+
from ..ui import UI
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _no_preflight(config: Any, ctx: "Context") -> list[str]:
|
|
15
|
+
return []
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(frozen=True)
|
|
19
|
+
class CheckType:
|
|
20
|
+
parse: Callable[[dict], Any]
|
|
21
|
+
run: Callable[[Any, "Context", "UI"], "Attempt"]
|
|
22
|
+
preflight: Callable[[Any, "Context"], list[str]] = _no_preflight
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
STEP_TYPES: dict[str, CheckType] = {}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def register(name: str, check: CheckType) -> None:
|
|
29
|
+
STEP_TYPES[name] = check
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def fields(raw: dict, required: set[str], optional: dict[str, Any]) -> dict:
|
|
33
|
+
"""Check the keys of a step's type-specific fields and fill in defaults.
|
|
34
|
+
|
|
35
|
+
The step's ``name`` is always passed along, so checks can use it in messages.
|
|
36
|
+
"""
|
|
37
|
+
optional = {"name": "", **optional}
|
|
38
|
+
for key in raw:
|
|
39
|
+
if key not in required and key not in optional:
|
|
40
|
+
raise ValueError(f"unknown field '{key}'")
|
|
41
|
+
for key in sorted(required):
|
|
42
|
+
if key not in raw:
|
|
43
|
+
raise ValueError(f"{key}: missing")
|
|
44
|
+
return {**optional, **raw}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
from . import can, confirm, ethernet, serial, usb # noqa: E402,F401 (register themselves)
|