|
| 1 | +# **Automated ESP32 Testing with GitHub Actions on Raspberry Pi** |
| 2 | +This tutorial demonstrates how to automate **ESP-IDF firmware testing** using **GitHub Actions** and a **Raspberry Pi** configured as a **self-hosted runner**. |
| 3 | +An **ESP32-S3** board is physically connected to the Raspberry Pi via USB. |
| 4 | + |
| 5 | +This tutorial continues from **[Stage 3 — VS Code Pytest Testing](https://github.com/ChandimaJayaneththi/esp32_hw_testing_series/blob/main/stage3_pytest_testing/README.md)**. |
| 6 | +It uses the **same ESP-IDF project and test structure**, but instead of manually running the tests inside VS Code, everything is **automated using GitHub Actions**. |
| 7 | + |
| 8 | +Whenever new code is pushed to the GitHub repository, the workflow automatically: |
| 9 | + |
| 10 | +1. Builds the ESP-IDF project on the Raspberry Pi. |
| 11 | +2. Flashes the ESP32-S3 over USB. |
| 12 | +3. Runs the Unity/Pytest tests on real hardware. |
| 13 | + |
| 14 | +This setup allows **true hardware-in-the-loop continuous integration (CI)** for embedded firmware. |
| 15 | + |
| 16 | +--- |
| 17 | + |
| 18 | +## Overview |
| 19 | + |
| 20 | +| Component | Description | |
| 21 | +|------------|--------------| |
| 22 | +| **Raspberry Pi** | Acts as a self-hosted GitHub Actions runner. | |
| 23 | +| **ESP32-S3** | Device under test, connected via USB to the Pi. | |
| 24 | +| **ESP-IDF v5.5.1** | Installed on Raspberry Pi. | |
| 25 | +| **pytest-embedded** | For running firmware tests automatically. | |
| 26 | +| **GitHub Account & Repository** | To host the workflow and source code. | |
| 27 | +| **Stable Internet Connection** | For Raspberry Pi to communicate with GitHub. | |
| 28 | + |
| 29 | +### GitHub Actions |
| 30 | + |
| 31 | +**GitHub Actions** is GitHub’s built-in automation and CI/CD (Continuous Integration/Continuous Deployment) platform. |
| 32 | +It allows you to define workflows that automatically run tasks whenever specific events occur in your repository — such as pushing new code, creating a pull request, or publishing a release. |
| 33 | + |
| 34 | +#### In this project, GitHub Actions is used to: |
| 35 | +- Automatically **build and test ESP-IDF firmware** each time code changes. |
| 36 | +- Ensure **code quality and stability** before merging. |
| 37 | +- Run **real hardware tests** on an **ESP32-S3 connected to a Raspberry Pi**, without manual flashing or monitoring. |
| 38 | + |
| 39 | +This automation greatly improves productivity and reliability by catching bugs early — just like CI pipelines in large-scale software projects. |
| 40 | + |
| 41 | +--- |
| 42 | + |
| 43 | +## Setup |
| 44 | + |
| 45 | +1. **Clone the repository** |
| 46 | + |
| 47 | + To follow this tutorial, first **clone this repository** which contains the complete project setup, including the ESP-IDF test code and the GitHub Actions workflow file. |
| 48 | + |
| 49 | + ```bash |
| 50 | + git clone https://github.com/ChandimaJayaneththi/esp32_github_actions_ci_rpi.git |
| 51 | + ``` |
| 52 | + Then, create your own GitHub repository (you can name it something like esp32_ci_rpi_runner) and push the cloned project into it |
| 53 | + |
| 54 | +2. **Ensure the Raspberry Pi has internet access and ESP-IDF installed with Pytest:** |
| 55 | + |
| 56 | + ```bash |
| 57 | + cd ~/esp/esp-idf |
| 58 | + ./install.sh --enable-pytest |
| 59 | + ``` |
| 60 | + - Confirm ESP-IDF installation |
| 61 | + ```bash |
| 62 | + cd ~/esp/esp-idf |
| 63 | + . ./export.sh |
| 64 | + idf.py --version |
| 65 | + ``` |
| 66 | +3. **Connect your ESP32-S3 to the Raspberry Pi via USB** |
| 67 | + |
| 68 | + - Check available ports: |
| 69 | + ```bash |
| 70 | + ls /dev/tty* |
| 71 | + ``` |
| 72 | + Example output: /dev/ttyUSB0 |
| 73 | + |
| 74 | +4. **ESP32-S3 Hardware configuration** |
| 75 | + |
| 76 | + | Test | Pin Connections | Notes | |
| 77 | + |------|------------------|-------| |
| 78 | + | **GPIO Test** | `GPIO4 → GPIO5` | Use a jumper wire | |
| 79 | + | **ADC Test** | `GPIO6 → GPIO1` (ADC1_CH0) | Jumper wire required | |
| 80 | + | **UART Test** | `GPIO17 → GPIO16` | UART1 loopback connection | |
| 81 | + |
| 82 | + 🔸 This wiring applies to the **ESP32-S3-DevKitC-1** board. |
| 83 | + 🔸 Adjust pins if using another ESP32 variant. |
| 84 | + |
| 85 | +--- |
| 86 | + |
| 87 | +## Project Structure |
| 88 | + |
| 89 | +```bash |
| 90 | +esp32_github_actions_ci_rpi/ |
| 91 | +├── components/ |
| 92 | +│ ├── test_peripheral/ |
| 93 | +│ │ ├── test/test_peripheral.c |
| 94 | +│ │ ├── CMakeLists.txt |
| 95 | +│ │ └── include/ |
| 96 | +│ │ └── peripheral.h |
| 97 | +│ └── peripheral/ |
| 98 | +│ ├── peripheral.c |
| 99 | +│ └── CMakeLists.txt |
| 100 | +├── test/ |
| 101 | +│ ├── pytest_esp32_test.py |
| 102 | +│ ├── pytest.ini |
| 103 | +│ └── main/ |
| 104 | +│ ├── unit_test_peripheral.c |
| 105 | +│ └── CMakeLists.txt |
| 106 | +├── .github/ |
| 107 | +│ └── workflows/ |
| 108 | +│ └── ci.yml # GitHub Actions Workflow file |
| 109 | +├── CMakeLists.txt |
| 110 | +└── README.md |
| 111 | +``` |
| 112 | + --- |
| 113 | + |
| 114 | +## GitHub Self-Hosted Runner Setup (on Raspberry Pi) |
| 115 | +1. Go to your repository on GitHub → Settings → Actions → Runners |
| 116 | +2. Click `New self-hosted runner` |
| 117 | +3. Select `Runner image` as `Linux` |
| 118 | +4. Select the `Architecture` according to your Raspberry Pi |
| 119 | + - Please confirm the architecture of your Raspberry Pi before configure it using |
| 120 | + ```bash |
| 121 | + uname -a |
| 122 | + ``` |
| 123 | + Output examples: |
| 124 | + - armv7l → 32-bit ARM (e.g. Raspberry Pi 3 running 32-bit OS) |
| 125 | + - aarch64 → 64-bit ARM (e.g. Raspberry Pi 4/5 running 64-bit OS) |
| 126 | +5. Run the commands one by one listed under `Download` and `Configure` sections to install, configure and run the GitHub action runner. |
| 127 | +6. Once it is successfully done, you can see the Raspberry Pi runner and the status of it on GitHub → Settings → Actions → Runners |
| 128 | + |
| 129 | + |
| 130 | +**Reference**: [Managing self-hosted runners](https://docs.github.com/en/actions/how-tos/manage-runners/self-hosted-runners) |
| 131 | + |
| 132 | +--- |
| 133 | + |
| 134 | +## GitHub Actions Workflow (.github/workflows/ci.yml) |
| 135 | +Below is the CI workflow file used to automate build, flash, and test steps: |
| 136 | + |
| 137 | +```yaml |
| 138 | +name: ESP-IDF CI on Raspberry Pi |
| 139 | +
|
| 140 | +on: |
| 141 | + push: |
| 142 | + branches: [ "main" ] |
| 143 | + pull_request: |
| 144 | + branches: [ "main" ] |
| 145 | +
|
| 146 | + workflow_dispatch: |
| 147 | +
|
| 148 | +jobs: |
| 149 | + build: |
| 150 | + runs-on: self-hosted |
| 151 | +
|
| 152 | + steps: |
| 153 | + uses: actions/checkout@v4 |
| 154 | +
|
| 155 | + - name: Set target |
| 156 | + working-directory: test |
| 157 | + run: | |
| 158 | + . ~/esp/esp-idf/export.sh |
| 159 | + idf.py set-target esp32s3 |
| 160 | + |
| 161 | + - name: Build the Project |
| 162 | + working-directory: test |
| 163 | + run: | |
| 164 | + . ~/esp/esp-idf/export.sh |
| 165 | + idf.py build |
| 166 | + |
| 167 | + - name: Run Unit tests |
| 168 | + working-directory: test |
| 169 | + run: | |
| 170 | + . ~/esp/esp-idf/export.sh |
| 171 | + pytest pytest_esp32_test.py --port <PORT> |
| 172 | +``` |
| 173 | +**Note:** Replace "PORT" with the correct COM port the ESP32 is connected to. |
| 174 | + |
| 175 | +### Workflow Breakdown |
| 176 | + 1. `name`:Defines the workflow’s display name in GitHub Actions |
| 177 | +2. `on`: Specifies when the workflow should trigger. |
| 178 | + - Runs automatically on every push or pull request to the main branch. |
| 179 | + - Can also be started manually from the Actions tab using `workflow_dispatch`. |
| 180 | +3. `jobs`: Each job groups a set of steps to be executed on a specific runner. In this case, the job runs on a self-hosted runner |
| 181 | +4. `steps`: Each step defines a task in the CI process. Inside each `steps`, the `run` keyword executes shell commands in the runner’s environment. |
| 182 | + - Checkout repository — uses actions/checkout@v4 to download the code into the runner. |
| 183 | + - Set target — loads the ESP-IDF environment and sets the build target to ESP32-S3. |
| 184 | + - Build the Project — compiles the ESP-IDF project. |
| 185 | + - Run Unit tests — runs automated unit tests on the ESP32-S3 through the connected serial port |
| 186 | + |
| 187 | +--- |
| 188 | + |
| 189 | +## Running the CI Workflow |
| 190 | +1. Trigger the workflow ether commit and push your latest code or manually from the Actions tab. |
| 191 | +2. Go to your repository → Actions tab |
| 192 | + - You’ll see a workflow named "ESP-IDF CI on Raspberry Pi" automatically start. |
| 193 | +3. The workflow will: |
| 194 | + - Build the firmware on the Raspberry Pi |
| 195 | + - Flash the ESP32-S3 |
| 196 | + - Run Pytest tests on hardware |
| 197 | + |
| 198 | +--- |
| 199 | + |
| 200 | +## Expected Output on GitHub Actions |
| 201 | + |
| 202 | + |
| 203 | + |
| 204 | +You will see the following logs under `Run Unit tests` section |
| 205 | +```log |
| 206 | +2025-11-02 04:18:59 #### Running all the registered tests ##### |
| 207 | +2025-11-02 04:18:59 |
| 208 | +2025-11-02 04:18:59 Running GPIO output/input loopback test... |
| 209 | +2025-11-02 04:18:59 /home/chandi/actions-runner/_work/esp32_github_ci_testing/esp32_github_ci_testing/components/test_peripheral/test/test_peripheral.c:19:GPIO output/input loopback test:PASS |
| 210 | +2025-11-02 04:18:59 |
| 211 | +2025-11-02 04:18:59 ----------------------- |
| 212 | +2025-11-02 04:18:59 1 Tests 0 Failures 0 Ignored |
| 213 | +2025-11-02 04:18:59 OK |
| 214 | +2025-11-02 04:19:00 Running ADC reading test... |
| 215 | +2025-11-02 04:19:01 /home/chandi/actions-runner/_work/esp32_github_ci_testing/esp32_github_ci_testing/components/test_peripheral/test/test_peripheral.c:31:ADC reading test:PASS |
| 216 | +2025-11-02 04:19:01 |
| 217 | +2025-11-02 04:19:01 ----------------------- |
| 218 | +2025-11-02 04:19:01 1 Tests 0 Failures 0 Ignored |
| 219 | +2025-11-02 04:19:01 OK |
| 220 | +2025-11-02 04:19:02 Running UART TX/RX test... |
| 221 | +2025-11-02 04:19:02 /home/chandi/actions-runner/_work/esp32_github_ci_testing/esp32_github_ci_testing/components/test_peripheral/test/test_peripheral.c:44:UART TX/RX test:PASS |
| 222 | +2025-11-02 04:19:02 |
| 223 | +2025-11-02 04:19:02 ----------------------- |
| 224 | +2025-11-02 04:19:02 1 Tests 0 Failures 0 Ignored |
| 225 | +2025-11-02 04:19:02 OK |
| 226 | +2025-11-02 04:19:02 |
| 227 | +2025-11-02 04:19:02 #### Starting interactive test menu ##### |
| 228 | +2025-11-02 04:19:02 |
| 229 | +2025-11-02 04:19:02 |
| 230 | +2025-11-02 04:19:02 |
| 231 | +2025-11-02 04:19:02 Press ENTER to see the list of tests. |
| 232 | +
|
| 233 | +============================================================ |
| 234 | +Testing GPIO Output/Input Loopback |
| 235 | +============================================================ |
| 236 | +GPIO test PASSED |
| 237 | + GPIO pins are functioning correctly |
| 238 | + Digital I/O is reliable |
| 239 | +
|
| 240 | +============================================================ |
| 241 | +Testing ADC Reading |
| 242 | +============================================================ |
| 243 | +ADC test PASSED |
| 244 | + ADC readings are within expected range |
| 245 | + Analog measurements are reliable |
| 246 | +
|
| 247 | +============================================================ |
| 248 | +Testing UART TX/RX |
| 249 | +============================================================ |
| 250 | +UART test PASSED |
| 251 | + UART communication is working |
| 252 | + TX/RX loopback successful |
| 253 | +
|
| 254 | +============================================================ |
| 255 | +FINAL TEST SUMMARY |
| 256 | +============================================================ |
| 257 | +Passed: 3/3 - GPIO, ADC, UART |
| 258 | +Failed: 0/3 - None |
| 259 | +
|
| 260 | +ALL TESTS PASSED - System is fully operational |
| 261 | +============================================================ |
| 262 | +. |
| 263 | +
|
| 264 | +============================== 1 passed in 9.28s =============================== |
| 265 | +``` |
| 266 | +--- |
| 267 | + |
| 268 | +## Notes |
| 269 | +This project was tested on: |
| 270 | +- Board: ESP32-S3-DevKitC-1 |
| 271 | +- ESP-IDF: v5.5.1 with Pytest |
| 272 | +- Raspberry Pi HW: Raspberry Pi 3 Model B |
| 273 | +- OS: Debian GNU/Linux 13 (trixie) |
| 274 | +--- |
| 275 | + |
| 276 | +## What You’ll Learn |
| 277 | + |
| 278 | + |
| 279 | + |
| 280 | +--- |
| 281 | + |
| 282 | +## License |
| 283 | +MIT License © 2025 — Chandima Jayaneththi |
0 commit comments