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.
Files changed (47) hide show
  1. interface_test_kit-0.1.0/LICENSE +21 -0
  2. interface_test_kit-0.1.0/PKG-INFO +257 -0
  3. interface_test_kit-0.1.0/README.md +223 -0
  4. interface_test_kit-0.1.0/pyproject.toml +55 -0
  5. interface_test_kit-0.1.0/setup.cfg +4 -0
  6. interface_test_kit-0.1.0/src/interface_test_kit/__init__.py +3 -0
  7. interface_test_kit-0.1.0/src/interface_test_kit/checks/__init__.py +47 -0
  8. interface_test_kit-0.1.0/src/interface_test_kit/checks/can.py +186 -0
  9. interface_test_kit-0.1.0/src/interface_test_kit/checks/confirm.py +30 -0
  10. interface_test_kit-0.1.0/src/interface_test_kit/checks/ethernet.py +97 -0
  11. interface_test_kit-0.1.0/src/interface_test_kit/checks/serial.py +172 -0
  12. interface_test_kit-0.1.0/src/interface_test_kit/checks/usb.py +243 -0
  13. interface_test_kit-0.1.0/src/interface_test_kit/cli.py +130 -0
  14. interface_test_kit-0.1.0/src/interface_test_kit/context.py +43 -0
  15. interface_test_kit-0.1.0/src/interface_test_kit/detect.py +22 -0
  16. interface_test_kit-0.1.0/src/interface_test_kit/model.py +108 -0
  17. interface_test_kit-0.1.0/src/interface_test_kit/profile.py +178 -0
  18. interface_test_kit-0.1.0/src/interface_test_kit/profiles/demo.yaml +22 -0
  19. interface_test_kit-0.1.0/src/interface_test_kit/report.py +142 -0
  20. interface_test_kit-0.1.0/src/interface_test_kit/runner.py +71 -0
  21. interface_test_kit-0.1.0/src/interface_test_kit/session.py +110 -0
  22. interface_test_kit-0.1.0/src/interface_test_kit/sim/__init__.py +1 -0
  23. interface_test_kit-0.1.0/src/interface_test_kit/sim/can.py +40 -0
  24. interface_test_kit-0.1.0/src/interface_test_kit/sim/fakes.py +64 -0
  25. interface_test_kit-0.1.0/src/interface_test_kit/sim/serial.py +44 -0
  26. interface_test_kit-0.1.0/src/interface_test_kit/sim/world.py +96 -0
  27. interface_test_kit-0.1.0/src/interface_test_kit/station.py +54 -0
  28. interface_test_kit-0.1.0/src/interface_test_kit/ui.py +127 -0
  29. interface_test_kit-0.1.0/src/interface_test_kit.egg-info/PKG-INFO +257 -0
  30. interface_test_kit-0.1.0/src/interface_test_kit.egg-info/SOURCES.txt +45 -0
  31. interface_test_kit-0.1.0/src/interface_test_kit.egg-info/dependency_links.txt +1 -0
  32. interface_test_kit-0.1.0/src/interface_test_kit.egg-info/entry_points.txt +2 -0
  33. interface_test_kit-0.1.0/src/interface_test_kit.egg-info/requires.txt +6 -0
  34. interface_test_kit-0.1.0/src/interface_test_kit.egg-info/top_level.txt +1 -0
  35. interface_test_kit-0.1.0/tests/test_can.py +157 -0
  36. interface_test_kit-0.1.0/tests/test_cli.py +9 -0
  37. interface_test_kit-0.1.0/tests/test_demo.py +77 -0
  38. interface_test_kit-0.1.0/tests/test_ethernet.py +110 -0
  39. interface_test_kit-0.1.0/tests/test_model.py +40 -0
  40. interface_test_kit-0.1.0/tests/test_profile.py +94 -0
  41. interface_test_kit-0.1.0/tests/test_report.py +156 -0
  42. interface_test_kit-0.1.0/tests/test_runner.py +89 -0
  43. interface_test_kit-0.1.0/tests/test_serial.py +154 -0
  44. interface_test_kit-0.1.0/tests/test_session.py +268 -0
  45. interface_test_kit-0.1.0/tests/test_station.py +36 -0
  46. interface_test_kit-0.1.0/tests/test_ui.py +98 -0
  47. 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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """interface-test-kit: operator-guided interface test for Linux embedded PCs."""
2
+
3
+ __version__ = "0.1.0"
@@ -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)