.github/workflows | ||
docs | ||
examples/esp_ghota_example | ||
include | ||
src | ||
.gitignore | ||
API.md | ||
CMakeLists.txt | ||
idf_component.yml | ||
Kconfig | ||
LICENSE | ||
README.md |
GITHUB OTA for ESP32 devices
Automate your OTA and CI/CD pipeline with Github Actions to update your ESP32 devices in the field direct from github releases
Features
- Uses the esp_htps_ota library under the hood to update firmware images
- Can also update spiffs/littlefs/fatfs partitions
- Uses SemVer to compare versions and only update if a newer version is available
- Plays nicely with App rollback and anti-rollback features of the esp-idf bootloader
- Download firmware and partitiion images from the github release page directly
- Supports multiple devices with different firmware images
- Includes a sample Github Actions that builds and releases images when a new tag is pushed
- Updates can be triggered manually, or via a interval timer
- Uses a streaming JSON parser for to reduce memory usage (Github API responses can be huge)
- Supports Private Repositories (Github API token required*)
- Supports Github Enterprise
- Supports Github Personal Access Tokens to overcome Github API Ratelimits
- Sends progress of Updates via the esp_event_loop
Note: You should be careful with your GitHub PAT and putting it in the source code. I would suggest that you store the PAT in NVS, and the user enters it when running, as otherwise the PAT would be easily extractable from your firmware images.
Usage
via the Espressif Component Registry:
idf.py add-dependency Fishwaldo/ghota^0.0.1
Example
After Initilizing Network Access, Start a timer to periodically check for new releases:
ghota_config_t ghconfig = {
.filenamematch = "GithubOTA-esp32.bin", // Glob Pattern to match against the Firmware file
.storagenamematch = "storage-esp32.bin", // Glob Pattern to match against the storage firmware file
.storagepartitionname = "storage", // Update the storage partition
.updateInterval = 60, // Check for updates every 60 minuites
};
ghota_client_handle_t *ghota_client = ghota_init(&ghconfig);
if (ghota_client == NULL) {
ESP_LOGE(TAG, "ghota_client_init failed");
return;
}
esp_event_handler_register(GHOTA_EVENTS, ESP_EVENT_ANY_ID, &ghota_event_callback, ghota_client); // Register a handler to get updates on progress
ESP_ERROR_CHECK(ghota_start_update_timer(ghota_client)); // Start the timer to check for updates
Manually Checking for updates:
ghota_config_t ghconfig = {
.filenamematch = "GithubOTA-esp32.bin",
.storagenamematch = "storage-esp32.bin",
.storagepartitionname = "storage",
.updateInterval = 60,
};
ghota_client_handle_t *ghota_client = ghota_init(&ghconfig);
if (ghota_client == NULL) {
ESP_LOGE(TAG, "ghota_client_init failed");
return;
}
esp_event_handler_register(GHOTA_EVENTS, ESP_EVENT_ANY_ID, &ghota_event_callback, ghota_client);
ESP_ERROR_CHECK(ghota_check(ghota_client));
semver_t *cur = ghota_get_current_version(ghota_client);
if (cur) {
ESP_LOGI(TAG, "Current version: %d.%d.%d", cur->major, cur->minor, cur->patch);
semver_free(cur);
}
semver_t *new = ghota_get_latest_version(ghota_client);
if (new) {
ESP_LOGI(TAG, "New version: %d.%d.%d", new->major, new->minor, new->patch);
semver_free(new);
}
ESP_ERROR_CHECK(ghota_update(ghota_client));
ESP_ERROR_CHECK(ghota_free(ghota_client));
Configuration
The following configuration options are available:
* config.filenamematch <- Glob pattern to match against the firmware file from the Github Releases page.
* config.storagenamematch <- Glob pattern to match against the storage file from the Github Releases page.
* config.storagepartitionname <- Name of the storage partition to update (as defined in partitions.csv)
* config.hostname <- Hostname of the Github API (default: api.github.com)
* config.orgname <- Name of the Github User or Organization
* config.reponame <- Name of the Github Repository
* config.updateInterval <- Interval in minutes to check for updates
Github Actions
The Github Actions included in this repository can be used to build and release firmware images to Github Releases. This is a good way to automate your CI/CD pipeline, and update your devices in the field. In this example, we build two variants of the Firmware - on for a ESP32 and one for a ESP32-S3 device Using the filenamematch and storagenamematch config options, we can match against the correct firmware image for the device.
on:
push:
pull_request:
branches: [master]
permissions:
contents: write
name: Build
jobs:
build:
strategy:
fail-fast: true
matrix:
targets: [esp32, esp32s3]
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v3
with:
submodules: 'recursive'
- name: esp-idf build
uses: Fishwaldo/esp-idf-ci-action@v1.1
with:
esp_idf_version: v4.4.3
target: ${{ matrix.targets }}
path: 'examples/esp_ghota_example'
- name: Rename artifact
run: |
ls -lah
cp examples/esp_ghota_example/build/esp_ghota_example.bin esp_ghota_example-${{ matrix.targets }}.bin
cp examples/esp_ghota_example/build/storage.bin storage-${{ matrix.targets }}.bin
- name: Archive Firmware Files
uses: actions/upload-artifact@v3
with:
name: ${{ matrix.targets }}-firmware
path: "*-${{ matrix.targets }}.bin"
release:
needs: build
runs-on: ubuntu-latest
steps:
- name: Download Firmware Files
uses: actions/download-artifact@v2
with:
path: release
- name: Release Firmware
uses: ncipollo/release-action@v1
if: startsWith(github.ref, 'refs/tags/')
with:
artifacts: release/*/*.bin
generateReleaseNotes: true
allowUpdates: true
token: ${{ secrets.GITHUB_TOKEN }}