This repository contains an automated Hardware-In-Loop (HIL) testing framework for validating dishwasher control electronics. Tests run on a Raspberry Pi 5 connected to the Device Under Test (DUT) via GPIO and I2C, simulating real-world inputs and verifying correct outputs.
The test harness:
- Simulates inputs to the control board (door switch, water level, temperature sensor, manual switches)
- Monitors outputs from the control board (motor contactor, heater, valves, pumps, indicator lights)
- Validates behavior matches expected state machine transitions
Tests execute in a GitHub Actions CI/CD pipeline using the systemd runner on the Pi 5.
-
Output relay board: https://czh-labs.com/products/din-rail-mount-8-spdt-iot-power-relay-module-for-raspberry-pi (or similar 8-ch relay board)
-
I2C DAC: https://www.amazon.com/dp/B0BWTX88JL
-
Custom input board: https://github.com/jimpudar/hil-input-board-kicad
-
Dishwasher control board: https://github.com/jimpudar/dishwasher-controller-kicad
-
Dishwasher control software: https://github.com/jimpudar/dishwasher-controller-qpn
┌─────────────────────┐ ┌─────────────────────┐
│ Raspberry Pi 5 │ │ DUT (Dishwasher │
│ (Test Harness) │ │ Controller) │
│ │ │ │
│ Output Relays──────┼────────>│ Inputs │
│ (Simulated inputs) │ │ │
│ │ │ │
│ Input Board <──────┼─────────│ Outputs │
│ (Monitored outputs)│ │ │
│ │ │ │
│ I2C (MCP4725) ─────┼────────>│ RTD Temperature │
│ (Temp simulation) │ │ Sensor Input │
└─────────────────────┘ └─────────────────────┘
These pins simulate inputs to the dishwasher control board.
These are connected to the DUT through the relay board.
| GPIO | Signal | Harness Connection | DUT Connection |
|---|---|---|---|
| 5 | E-Stop | Relay 1 NO / Common | ESTOP+ / ESTOP- QC Terminals |
| 6 | Manual Rinse | Relay 2 NO / Common | MAN SW 3 / MAN SW 2 QC Terminals |
| 13 | Manual Wash | Relay 3 NO / Common | MAN SW 1 / MAN SW 2 QC Terminals |
| 16 | Door Switch | Relay 4 NO / Common | DOOR+ / DOOR- QC Terminals |
| 19 | Timed Fill | Relay 5 NO / Common | FILL SW 3 / FILL SW 2 QC Terminals |
| 20 | Stop | Relay 6 NO / Common | FILL SW 1 / FILL SW 2 QC Terminals |
| 21 | Float | Relay 7 NO / Common | FLOAT+ / FLOAT- QC Terminals |
| 26 | TCO | Relay 8 NO / Common | TCO+ / TCO- QC Terminals |
These pins monitor outputs from the dishwasher control board.
The custom input board is used. The input board needs to be configured as follows:
CH1, CH2: Active HIGH, isolated low sides
CH3-CH8: Active LOW, all low sides connected to DUT chassis
| GPIO | Signal | DUT Connection | Description |
|---|---|---|---|
| 4 | Motor Contactor | + to MOTOR+, - to MOTOR- | Sense the voltage drop across the contactor coil. |
| 12 | Heater Contactor | + to HEATER+, - to HEATER- | Sense the voltage drop across the contactor coil. |
| 17 | Rinse Valve Relay | + to TP28 | Sense the arduino output |
| 22 | Detergent Pump Relay | + to TP26 | Sense the arduino output |
| 23 | Rinse Aid Pump Relay | + to TP24 | Sense the arduino output |
| 24 | Timed Fill Indicator | + to TP20 | Sense the arduino output |
| 25 | Rinse Indicator | + to TP13 | Sense the arduino output |
| 27 | Ready Indicator | + to TP9 | Sense the arduino output |
The MCP4725 DAC simulates the RTD temperature sensor input to the control board.
The GND pin connects to the DUT chassis, and the OUT pin connects to TP25.
240VAC is supplied using a homemade dim-bulb / isolation transformer. This powers the entire DUT.
There is NO chassis ground connection while connected to the isolation transformer.
Care must be taken to not touch any of the 120VAC terminals on the DUT board, as they can be live during testing.
The Raspberry Pi 5 is powered using the PoE+ Hat, which supplies the relays through the 5V rail.
The DUT is not galvanically isolated from the Pi because of the DAC connection. If an external 4-20mA current source is used instead of the DAC, fewer tests can be run but the Pi is galvanically isolated.
This project uses a Raspberry Pi 5, configured to run headless.
-
Use Raspberry Pi Imager to write Raspberry Pi OS Lite (64-bit) to the SD card.
-
Enable SSH access and ensure your public key is present or password is configured properly.
-
Merge the cloud-init configuration from this repository:
dotnet run cloud-init/merge.cs <user-data on SD card> cloud-init/user-data-partial.yml
-
Eject the SD card, insert it into the Pi, and boot. It will become available via SSH after cloud-init completes.
-
SSH into the Pi and run the script in the home directory to register the worker with GitHub Actions.
TODO: Currently I have been having failures with the cloud-init on first boot because NTP sync is happening out of order.
If this happens, you can SSH to the Pi, run cloud-init clean, and reboot. It should succeed on the second try since
the clock will have been set properly.
TODO: I believe you currently need to manually enable I2C using the raspi-config TUI. This should be added to cloud-init.
Tests can be run manually via GitHub Actions on a self-hosted runner tagged [self-hosted, hil].