pyetest: Electronics Testing Tool & Method

The following introduces a python library targeted at filling in spreadsheets to simplify writing pass/fail tests for electronics testing. This method generates easy to maintain test description and acceptance test software with easily defined measurements and test criteria. The method relies on using spreadsheets allowing users who don’t program to define tests and acceptance criteria. The test data reports are generated as intermediate files before the pass/fail tests simplifying reporting and diagnosis.

Example System

This will be easier to describe with an example case. I have a in circuit test stand which is using a LabJack U3 (here after referred to as a data acquisition system AKA a DAQ) connected to a probe board. It is measuring several analog and digital channels as well as controlling the power to the device under test (DUT). The device has a suite of self tests and internal measurements which are readout over RS-485 using a USB-RS-485 dongle.

The test jig runs the following steps:

  1. Programs the devices on the board using test points
  2. Loads configuration data into the devices through RS-485
  3. Read out all data from device and from DAQ system as described in the test description
  4. Processes the resulting data into a test report
  5. Reports a pass/fail result for the board

pyetest aims to improve steps 3, 4, & 5 with a minimal amount of single use code.

Here’s an example of a useful measurement:

Say we are measuring across a 500 mohm current sense resistor with a 0.1% tolerance. We care about the current but we the input voltage can vary by say 5%. In the template spreadsheet we have measurement fields using our DAQ to measure across the sense resistor and a calculated field for the current. The formula for this would look like: =(A1-A2)/0.5. Since our DAQ is measuring in ADC instead of volts we’ll have to add calculation fields to first calculate volts and then the current which is straight forward. The calculated value is filled in and accessible to the pass/fail test which might then use a min/max test with a some error bars. Sure this is easy to put into a python script but it’s even easier to put into a spreadsheet. In this case the control software is all the same and the tests are defined in the input files only. Different board versions can use the same control software with a different set of input files.

Justification & Background

pyetest relies on using what I believe to be the most commonly used interpreted language out there: excel. I’m not advocating for huge unwieldy excel spreadsheets but most people know how to use them and therefore it’s easier to get new people to maintain a test system built this way than one written from custom python. Spreadsheets have a large number of useful function built in and we don’t need to worry about supporting them or sweat the security issues that could come from other possible solutions (using eval statements did come to mind). pyetest uses the libreoffice calc program as a math interpreter to do the intermediate calculations and set the pass/fail criteria. The main bit of hackery here is save the workbooks as temporary files and forcing them to be recalculated. If there’s a better way to do this let me know!

The spreadsheets act as a plugin module to the software without needing the operator to know any python. You express the relationships in the data template and create the pass/fail criteria in the test template.

pyetest Operation

Steps

  • Load spreadsheet template with relationships defined
  • Fill the spreadsheet values, store as a new export. Have all intermediate values stored
  • Reload spreadsheet to check thresholds. This way the requirements only knows about the high/low targets
  • Steps are read, create spreadsheet

Inputs

Data template

This is an xlsx spreadsheet which contains the test names, data source, and field type. The field type specifies if the data is measured, fixed, or calculated. Measured values will be filled in during the test. Fixed values are used in the spreadsheet for calculations of other values such as the number of bits in an ADC. Calculated values have a formula in the value field.

namevalueunittype
0version0.8.Xnanmeasured
1firmware_version0.1.8nanmeasured
2compile_time09:09:00nanmeasured
3compile_dateSeptember 9th, 2023nanmeasured
4slave_address246nanmeasured
5p3_3_micro_volts3260000uVmeasured
6p3_3_reading2024ADCmeasured
7+3.3GAIN=1/2nanfixed
8+3.3OFFSET0Vfixed
9+3.3V=B8/4096*3.3/B9Vcalculated
10+3.3ERROR=ABS(B11/24-1)*100%calculated
11+3.3COMPARISONERROR=ABS(B11/(B7/1000000)-1)*100%calculated
12p23_micro_volts23000000uVmeasured
13p23_reading1200ADCmeasured
14+23GAIN=1/25nanfixed
15+23OFFSET0Vfixed
16+23V=B15/4096*3.3/B16Vcalculated
17+23ERROR=ABS(B18/23.3-1)*100%calculated
18+23COMPARISONERROR=ABS(B18/(B14/1000000)-1)*100%calculated
19p5_micro_volts5000000uVmeasured
20p5_reading2500ADCmeasured
21+5OFFSET0Vfixed
22+5GAIN=5/2nanfixed
23+5=B22/4096B243.3Vcalculated
24+5ERROR=ABS((B25/5-1)*100)%calculated
25+5COMPARISONERROR=ABS(B25/(B21/1000000)-1)*100%calculated
26fault_status9nanmeasured
27+5 Fault=MOD(_xlfn.FLOOR.MATH(B$28/POWER(2,0)),2)nancalculated
28+24 Fault=MOD(_xlfn.FLOOR.MATH(B$28/POWER(2,1)),2)nancalculated
29Error Code 2=MOD(_xlfn.FLOOR.MATH(B$28/POWER(2,2)),2)nancalculated
30DAQ Fault=MOD(_xlfn.FLOOR.MATH(B$28/POWER(2,3)),2)nancalculated
31Error Code 4=MOD(_xlfn.FLOOR.MATH(B$28/POWER(2,4)),2)nancalculated
32MW Fault=MOD(_xlfn.FLOOR.MATH(B$28/POWER(2,5)),2)nancalculated
33Error Code 6=MOD(_xlfn.FLOOR.MATH(B$28/POWER(2,6)),2)nancalculated
34Error Code 7=MOD(_xlfn.FLOOR.MATH(B$28/POWER(2,7)),2)nancalculated
35Visible Fault=MOD(_xlfn.FLOOR.MATH(B$28/POWER(2,8)),2)nancalculated
36Temperature Fault=MOD(_xlfn.FLOOR.MATH(B$28/POWER(2,9)),2)nancalculated
37Moisture Fault=MOD(_xlfn.FLOOR.MATH(B$28/POWER(2,10)),2)nancalculated
38Watchdog Fault=MOD(_xlfn.FLOOR.MATH(B$28/POWER(2,11)),2)nancalculated
39Hardware Fault=MOD(_xlfn.FLOOR.MATH(B$28/POWER(2,12)),2)nancalculated
40I2C Fault=MOD(_xlfn.FLOOR.MATH(B$28/POWER(2,13)),2)nancalculated
41Error Code 14=MOD(_xlfn.FLOOR.MATH(B$28/POWER(2,14)),2)nancalculated
42Modbus Fault=MOD(_xlfn.FLOOR.MATH(B$28/POWER(2,15)),2)nancalculated

Test template

A spreadsheet which defines the pass/fail criteria. The structure is open for extension but includes at a minimum the field name, a value field that will be filled in, and a passes column that contains an expression (usually something like =E1=B1 or =E1>=C1 & E1<=D1).

nameexpected valueunitminmaxvaluepasses
0version0.8.Xnannannan0.8.X=B2=F2
1firmware_version0.1.8nannannan0.1.8=B3=F3
2compile_time09:09:00nannannan09:09:00=B4=F4
3compile_dateSeptember 9th, 2023nannannanSeptember 9th, 2023=B5=F5
4slave_address246nannannan246=B6=F6
5p3_3_micro_volts3300000uV3.2e+063.4e+063260000=F7>=D7 & F7<=E7
6p3_3_reading2100ADCnannan2024nan
7+3.3ERROR0%0386.4111328125=F9>=D9 & F9<=E9
8p23_micro_volts23000000uV1.8e+073e+0723000000=F10>=D10 & F10<=E10
9p23_reading1200ADCnannan1200nan
10+23ERROR0%033.73357027896994=F12>=D12 & F12<=E12
11p5_micro_volts5000000uV4.9e+065.1e+065000000=F13>=D13 & F13<=E13
12p5_reading2500ADCnannan2500nan
13+5ERROR0%030.7080078125=F15>=D15 & F15<=E15
14fault_status0nannannan9nan
15+5 Fault=MOD(_xlfn.FLOOR.MATH(B$16/POWER(2,0)),2)nannannan1=B17=F17
16+24 Fault=MOD(_xlfn.FLOOR.MATH(B$16/POWER(2,1)),2)nannannan0=B18=F18
17Error Code 2=MOD(_xlfn.FLOOR.MATH(B$16/POWER(2,2)),2)nannannan0=B19=F19
18DAQ Fault=MOD(_xlfn.FLOOR.MATH(B$16/POWER(2,3)),2)nannannan1=B20=F20
19Error Code 4=MOD(_xlfn.FLOOR.MATH(B$16/POWER(2,4)),2)nannannan0=B21=F21
20MW Fault=MOD(_xlfn.FLOOR.MATH(B$16/POWER(2,5)),2)nannannan0=B22=F22
21Error Code 6=MOD(_xlfn.FLOOR.MATH(B$16/POWER(2,6)),2)nannannan0=B23=F23
22Error Code 7=MOD(_xlfn.FLOOR.MATH(B$16/POWER(2,7)),2)nannannan0=B24=F24
23Visible Fault=MOD(_xlfn.FLOOR.MATH(B$16/POWER(2,8)),2)nannannan0=B25=F25
24Temperature Fault=MOD(_xlfn.FLOOR.MATH(B$16/POWER(2,9)),2)nannannan0=B27=F27
25Moisture Fault=MOD(_xlfn.FLOOR.MATH(B$16/POWER(2,10)),2)nannannan0nan
26Watchdog Fault=MOD(_xlfn.FLOOR.MATH(B$16/POWER(2,11)),2)nannannan0=B28=F28
27Hardware Fault=MOD(_xlfn.FLOOR.MATH(B$16/POWER(2,12)),2)nannannan0=B29=F29
28I2C Fault=MOD(_xlfn.FLOOR.MATH(B$16/POWER(2,13)),2)nannannan0=B30=F30
29Error Code 14=MOD(_xlfn.FLOOR.MATH(B$16/POWER(2,14)),2)nannannan0=B31=F31
30Modbus Fault=MOD(_xlfn.FLOOR.MATH(B$16/POWER(2,15)),2)nannannan0=B32=F32

Measurements Data Frame

Data taken from all our sources.

namevalueunittype
0version0.8.Xnanmeasured
1firmware_version0.1.8nanmeasured
2compile_time09:09:00nanmeasured
3compile_dateSeptember 9th, 2023nanmeasured
4slave_address246nanmeasured
5p3_3_micro_volts3260000uVmeasured
6p3_3_reading2024ADCmeasured
7p23_micro_volts23000000uVmeasured
8p23_reading1200ADCmeasured
9p5_micro_volts5000000uVmeasured
10p5_reading2500ADCmeasured
11fault_status9nanmeasured

Outputs

Measurement Results

This is the data template file with the measured values filled in. All the calculation expressions are retained in the file.

namevalueunittype
0version0.8.Xnanmeasured
1firmware_version0.1.8nanmeasured
2compile_time09:09:00nanmeasured
3compile_dateSeptember 9th, 2023nanmeasured
4slave_address246nanmeasured
5p3_3_micro_volts3260000uVmeasured
6p3_3_reading2024ADCmeasured
7+3.3GAIN0.5nanfixed
8+3.3OFFSET0Vfixed
9+3.3V3.261328125Vcalculated
10+3.3ERROR86.4111328125%calculated
11+3.3COMPARISONERROR0.0407400306748462%calculated
12p23_micro_volts23000000uVmeasured
13p23_reading1200ADCmeasured
14+23GAIN0.04nanfixed
15+23OFFSET0Vfixed
16+23V24.169921875Vcalculated
17+23ERROR3.73357027896994%calculated
18+23COMPARISONERROR5.0866168478261%calculated
19p5_micro_volts5000000uVmeasured
20p5_reading2500ADCmeasured
21+5OFFSET0Vfixed
22+5GAIN2.5nanfixed
23+55.035400390625Vcalculated
24+5ERROR0.7080078125%calculated
25+5COMPARISONERROR0.7080078125%calculated
26fault_status9nanmeasured
27+5 Fault1nancalculated
28+24 Fault0nancalculated
29Error Code 20nancalculated
30DAQ Fault1nancalculated
31Error Code 40nancalculated
32MW Fault0nancalculated
33Error Code 60nancalculated
34Error Code 70nancalculated
35Visible Fault0nancalculated
36Temperature Fault0nancalculated
37Moisture Fault0nancalculated
38Watchdog Fault0nancalculated
39Hardware Fault0nancalculated
40I2C Fault0nancalculated
41Error Code 140nancalculated
42Modbus Fault0nancalculated

Test Results

This is the test template file with the values filled in. The boolean passes values can then be read out to determine which tests passed.

nameexpected valueunitminmaxvaluepasses
0version0.8.Xnannannan0.8.X1
1firmware_version0.1.8nannannan0.1.81
2compile_time09:09:00nannannan09:09:001
3compile_dateSeptember 9th, 2023nannannanSeptember 9th, 20231
4slave_address246nannannan2461
5p3_3_micro_volts3300000uV3.2e+063.4e+0632600001
6p3_3_reading2100ADCnannan2024nan
7+3.3ERROR0%0386.41113281251
8p23_micro_volts23000000uV1.8e+073e+07230000001
9p23_reading1200ADCnannan1200nan
10+23ERROR0%033.733570278969941
11p5_micro_volts5000000uV4.9e+065.1e+0650000001
12p5_reading2500ADCnannan2500nan
13+5ERROR0%030.70800781251
14fault_status0nannannan9nan
15+5 Fault0nannannan10
16+24 Fault0nannannan01
17Error Code 20nannannan01
18DAQ Fault0nannannan10
19Error Code 40nannannan01
20MW Fault0nannannan01
21Error Code 60nannannan01
22Error Code 70nannannan01
23Visible Fault0nannannan01
24Temperature Fault0nannannan01
25Moisture Fault0nannannan0nan
26Watchdog Fault0nannannan01
27Hardware Fault0nannannan01
28I2C Fault0nannannan01
29Error Code 140nannannan01
30Modbus Fault0nannannan01

Procedure

  1. Take all the measurements needed and generate a dataframe of data. This can be made all sorts of ways. I typically add a source column to the data template to parse the tag names to define the tests. How to set up these measurements is a separate section of a test suite and is essentially tied to the system you’re using.
  2. Load the template files and call pyetest_run
  3. Save the data workbook and test results for future retrieval and analysis
  4. Turn the tests into a data frame and return a pass/fail analysis
import pandas
import openpyxl
import pyetest

data_template_file = "data_template.xlsx"
measurement_result_file = "measurement_result.xlsx"
measured_data_file = "dummy-values.xlsx"
test_template_file = "test_template.xlsx"
test_result_file = "test-results.xlsx"

data_template_workbook = openpyxl.open(data_template_file)
test_template_workbook = openpyxl.open(test_template_file)
dummy_values_df = pandas.read_excel(measured_data_file)

data_workbook, test_workbook = pyetest.pyetest_run(data_template_workbook, test_template_workbook, dummy_values_df)
test_results_df = pyetest.worksheet_to_df(test_workbook.active)
print(len(test_results_df[test_results_df["passes"] == False]) == 0)
print(test_results_df[test_results_df["passes"] == False])

test_workbook.save(test_result_file)
data_workbook.save(measurement_result_file)
False
         name expected value  unit  min  max value passes
         15   +5 Fault              0  None  NaN  NaN     1  False
         18  DAQ Fault              0  None  NaN  NaN     1  False

Further Work

There are some functions that excel doesn’t do easily that would be useful. When needing to add a new measurement type the first goal is to figure out an excel way to do it. It’s a well understood interpreter after all. Those that can’t be done using excel functions I am adding as plugins. The danger there is taking the plugin route and increasing complexity when a simpler solution is available.

I would like to add curve fitting but as this is a specialized function I would rather add this and other special functions to the data collection step. I expect pyetest to stay as it is with other libraries being used to add additional functionality to the system.

Other Options

For completeness some examples of existing ICT software are:

  • Marvin Test Solutions: ATEasy
  • Teradyne: Teststation
  • Agilent: i3070
  • National Instruments: LabVIEW NXG, Teststand, LabVIEW
  • Blue Clover: PLT

I could not find any open source projects that fit the use case, if I missed any please let me know, I’d love to hear about them!