This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
zigpy-ota is a repository and toolset for managing OTA (Over-The-Air) firmware images for Zigbee devices supported by zigpy. It contains:
- A collection of OTA firmware images organized by manufacturer in the
images/directory - Metadata files (
.yaml) describing each firmware image - CLI tools for managing and processing OTA submissions
- Automated GitHub workflows for handling community OTA submissions
zigpy-ota is a critical component in the Zigbee firmware update pipeline:
zigpy-ota → zigpy → Home Assistant ZHA integration → End users
- Manufacturers submit firmware updates via GitHub issue forms
- Automated PRs are created for each submission
- Once approved and merged, a GitHub release must be created to publish the updated
zigpy_v2_ota.jsonindex - zigpy consumes this index to provide firmware updates
- Home Assistant's ZHA integration (using zigpy) makes updates available to end users with Zigbee devices
This means changes merged to this repository directly impact Home Assistant users worldwide. Quality, accuracy, and legal compliance are critical.
Note for End Users: End users don't interact with this repository directly. OTA updates are automatically available through Home Assistant's ZHA integration and other zigpy-based applications. The firmware index is published at https://github.com/zigpy/zigpy-ota/releases for production use, and the dev channel is available at https://raw.githubusercontent.com/zigpy/zigpy-ota/release/files/dev/zigpy_v2_ota.json for testing.
There are two main ways to contribute:
1. Submitting OTA Firmware Images (Recommended)
Use the GitHub issue form - this is the easiest method:
- Go to: https://github.com/zigpy/zigpy-ota/issues/new/choose
- Select one of two options:
- "Submit OTA (URL)" (
.github/ISSUE_TEMPLATE/01-submit-ota-url.yml) - Provide a download URL to the OTA file - "Submit OTA (File Upload)" (
.github/ISSUE_TEMPLATE/02-submit-ota-file.yml) - Upload the OTA file directly (must be zipped first)
- "Submit OTA (URL)" (
- Fill out the form with:
- Title: Use format
Add Manufacturer Device vX.X.X - OTA image URL or upload zipped OTA file (depending on which form)
- How to handle existing images (required dropdown):
- Keep all existing images (without automatic version constraints)
- Keep existing images and set
min_current_file_versionto highest existing version
- Manufacturer name (optional, for display purposes)
- Release notes (optional but recommended)
- Optional metadata (model names, version constraints, hardware versions, etc.)
- Third-party download checkbox (for external hosting - URL form only - disabled at the moment)
- Checklist items (at least confirm the file format)
- Title: Use format
Important: Submit one issue per OTA image. Multiple versions should be separate issues. An automated PR will be created from your submission for review by maintainers.
2. Manual YAML Metadata Edits
For updates to existing images (e.g., adding min_current_file_version to an existing image):
- Fork the repository
- Edit the
.yamlfile in theimages/directory - Submit a pull request with your changes
General Contributing Guidelines:
- Use the GitHub issue form for OTA submissions (preferred)
- Follow the existing code style
- Add tests for new functionality
- Ensure all tests pass before submitting (
pytest) - Follow pre-commit hooks for code quality
- Verify legal distribution rights for firmware files (see Legal Requirements section)
The repository uses a multi-channel release system:
Release Channels:
- stable - Production releases for end users
- beta - Pre-release testing versions
- dev - Latest development builds from the
devbranch
Images are included in channels based on their channel YAML field:
- No
channelfield (default): included in stable, beta, and dev channel: beta: included in beta and dev onlychannel: dev: included in dev only
Index file naming (GitHub release assets only):
- Stable releases:
zigpy_v2_ota.json,z2m_v1_ota.json, andmarkdown_v1.md - Beta releases:
zigpy_v2_ota_beta.json,z2m_v1_ota_beta.json, andmarkdown_v1_beta.md
Note: Files on the release/files branch use the same names (zigpy_v2_ota.json, z2m_v1_ota.json, markdown_v1.md) in all channel folders.
How Releases Work:
-
When a GitHub release is created, the index files are attached as release assets and mirrored to the
release/filesbranch -
The
release/versionbranch maintains channel tracking files (stable.json,beta.json,dev.json) that point to the corresponding index URLs:{ "schemas": { "zigpy_v2": { "version": "0.0.15", "url": "https://github.com/zigpy/zigpy-ota/releases/download/0.0.15/zigpy_v2_ota.json" }, "z2m_v1": { "version": "0.0.15", "url": "https://github.com/zigpy/zigpy-ota/releases/download/0.0.15/z2m_v1_ota.json" }, "markdown_v1": { "version": "0.0.15", "url": "https://github.com/zigpy/zigpy-ota/releases/download/0.0.15/markdown_v1.md" } } }version: The release version identifier (or"dev"for development builds)url: Direct URL to the OTA metadata for that release (GitHub release asset for stable/beta,release/filesbranch for dev)- Schema keys (
zigpy_v2,z2m_v1,markdown_v1) allow for future schema evolution while maintaining backward compatibility
Client Access:
- Recommended: Clients fetch version files via stable URLs to discover the index URL dynamically:
https://raw.githubusercontent.com/zigpy/zigpy-ota/release/version/stable.jsonhttps://raw.githubusercontent.com/zigpy/zigpy-ota/release/version/beta.jsonhttps://raw.githubusercontent.com/zigpy/zigpy-ota/release/version/dev.json
- The version file provides the URL to the actual
zigpy_v2_ota.jsonindex - This allows programmatic discovery of the latest release for each channel
- Libraries should use this approach rather than hardcoding direct URLs
The release/files Branch:
- Contains index files organized by channel:
dev/,beta/,stable/ - Each channel folder contains
zigpy_v2_ota.json,z2m_v1_ota.json, andmarkdown_v1.md - The
dev/folder is automatically updated whenever a new OTA image is merged to thedevbranch - The
beta/andstable/folders are updated when corresponding releases are made - Dev channel accessible at:
https://raw.githubusercontent.com/zigpy/zigpy-ota/release/files/dev/zigpy_v2_ota.json
JSON Index Structure:
The zigpy_v2_ota.json is an object containing a firmwares array:
{
"firmwares": [
{
"binary_url": "https://raw.githubusercontent.com/zigpy/zigpy-ota/dev/images/manufacturer/firmware.ota",
"manufacturer_id": 1234,
"image_type": 789,
"file_version": 123456,
"file_size": 12345678,
"checksum": "sha3-256:dd36540218dc33264acfa82e3433bd2efeeb0318c56970c7a38a3cfc312d0124",
"release_notes": "- Fixed issues\n- Added new features",
"min_hardware_version": 5,
"max_hardware_version": 10
}
]
}Key fields:
binary_url- Download URL (GitHub raw for local files, external URL for third-party)manufacturer_id,image_type,file_version- Device matching identifierschecksum- SHA3-256 hash for verificationsource_url- Original source URL provided in the YAML metadatathird_party_download- (Optional) Set totruewhen file is hosted externally
There are two types of YAML metadata files in the images/ directory:
1. Regular (repo-hosted) OTA files:
-
Contains both
.otabinary file AND.ota.yamlmetadata file -
The OTA binary is stored directly in the repository
-
OTA files are renamed to a standardized format:
<manufacturer_id>-<image_type>-<file_version>_<hash>.ota(hash is first 6 chars of SHA3-256). This renaming is done automatically when submitting via the issue form. -
YAML contains basic metadata (checksum/file_size extracted from binary automatically):
# OTA metadata file_name: 100B-010C-01001A02_abc123.ota source_file_name: original_firmware.zigbee source_url: https://manufacturer.com/original_firmware.zigbee release_notes: |- - Bug fixes - New features # Optional fields (all types of YAML): manufacturer_names: [Manufacturer A, Manufacturer B] model_names: [Model X, Model Y] min_current_file_version: 100 max_current_file_version: 200 min_hardware_version: 5 max_hardware_version: 10 specificity: 3
file_name: The standardized filename in the repository (auto-generated from OTA metadata)source_file_name: The original filename from the source (preserved for reference)
-
Example:
images/signify/100B-010C-01002602_dd3654.ota+.yaml
2. Third-party (externally-hosted) OTA files:
-
Contains ONLY
.ota.yamlmetadata file (NO binary in repo) -
The OTA binary is hosted externally and referenced by URL
-
YAML must include OTA metadata under
third_party_downloadkey (since no binary in repo):# OTA metadata file_name: 100B-010C-01001A02_abc123.ota source_file_name: original_firmware.zigbee source_url: https://manufacturer.com/original_firmware.zigbee # OTA image metadata required for third-party hosted images: third_party_download: manufacturer_id: 1234 image_type: 789 file_version: 123456 file_size: 12345678 checksum_sha3_256: dd36540218dc33264acfa82e3433bd2efeeb0318c56970c7a38a3cfc312d0124 checksum_sha512: abc123... release_notes: |- - Bug fixes - New features # Optional fields (all types of YAML): manufacturer_names: [Manufacturer A, Manufacturer B] model_names: [Model X, Model Y] min_current_file_version: 100 max_current_file_version: 200 min_hardware_version: 5 max_hardware_version: 10 specificity: 3
-
Example:
images/signify/100B-010C-01002802_dd3654.ota.yaml(no binary)
Key differences:
- Regular YAML: Checksum/file_size/manufacturer_id/image_type/file_version extracted from binary automatically
- Third-party YAML: Must provide all fields under the
third_party_downloadkey since there's no binary to extract from - The presence of the
third_party_downloadkey (as an object with required fields) tells the system to use the external URL instead of looking for a local binary file
How to create third-party YAML:
There is no dedicated CLI command to generate third-party YAML metadata from an image. Use one of these approaches:
- GitHub Issue Form (Recommended): Submit via issue form (URL option) and check the "Third-party download" checkbox - this internally uses the
zigpy-ota prepare-prcommand (called by GitHub Actions) which automatically downloads the file, extracts all required metadata, and generates the third-party YAML - Manual Creation: Download the OTA file temporarily, extract metadata using zigpy, then create the YAML file manually with all required fields under
third_party_download - Temporary Download: Download the file, use
zigpy-ota generate-stub-metadata, then manually restructure the metadata under thethird_party_downloadkey
Optional fields (available for both types):
manufacturer_names- List of manufacturer name strings for device matchingmodel_names- List of model name strings for device matchingmin_current_file_version- Minimum current firmware version for update eligibilitymax_current_file_version- Maximum current firmware version for update eligibilitymin_hardware_version- Minimum hardware version compatibilitymax_hardware_version- Maximum hardware version compatibilityspecificity- Priority level when multiple images match (higher = more specific)channel- Release channel (omit for stable which appears in all channels;betaappears in beta and dev;devappears only in dev)pull_request- PR number that added this image (auto-added by GitHub Actions workflow)disabled- Set totrueto exclude from JSON indexes while keeping the file in the repo
Real examples of the complete submission workflow can be found in tests/__snapshots__/test_prepare_pr.ambr:
1. Issue Markdown (generated by GitHub from issue form submission):
-
Example:
tests/data/gh_issues/issue_hue_old_replace.md -
Contains user-submitted data: OTA URL, manufacturer name, release notes, optional metadata
-
Format matches the GitHub issue template in
.github/ISSUE_TEMPLATE/01-submit-ota-url.yml -
Example content:
### OTA image URL https://otau.meethue.com/storage/ZGB_100B_010C/.../firmware.zigbee ### Manufacturer name Hue ### How to handle existing images of the same type Keep existing images and set `min_current_file_version` to highest existing version ### Release notes - Bug fixes - Performance improvements ### Optional metadata model_names: [test test, test2] manufacturer_names: SignifyX min_current_file_version: 0x00010002
2. PR Markdown (generated by zigpy-ota prepare-pr):
## OTA File Submission
### File Information
- **Filename**: `100B-010C-01001A02_879c1b.ota`
- **Manufacturer Directory**: `signify`
- **Manufacturer ID**: `0x100B` (4107)
- **Image Type**: `0x010C` (268)
- **File Version**: `0x01001A02` (16783874)
- **Hosting**: Repository file (hosted in zigpy-ota)
### Metadata
- **Manufacturer Names**: SignifyX
- **Model Names**: test test, test2
- **Source URL**: https://otau.meethue.com/...
### Release Notes
- Bug fixes
- Performance improvements3. Generated YAML Metadata (created automatically):
# Regular (repo-hosted)
file_name: 100B-010C-01001A02_879c1b.ota
source_file_name: firmware.zigbee
source_url: https://manufacturer.com/firmware.zigbee
release_notes: |-
- Bug fixes
- Performance improvements
manufacturer_names:
- SignifyX
model_names:
- test test
- test2
min_current_file_version: 65538# Third-party (externally-hosted)
file_name: 100B-010C-01001A02_879c1b.ota
source_file_name: firmware.zigbee
source_url: https://manufacturer.com/firmware.zigbee
third_party_download:
manufacturer_id: 4107
image_type: 268
file_version: 16783874
file_size: 126
checksum_sha3_256: 879c1b0d3f7d89795ca649622c4b4628651559b088dfdf306bc1c5cd8c573e3a
checksum_sha512: abc123...
release_notes: |-
- Bug fixes
- Performance improvements
manufacturer_names:
- Signify
model_names:
- Hue Lamp
- Hue Bulb4. Final OTA Index Entry (in zigpy_v2_ota.json):
{
"binary_url": "https://raw.githubusercontent.com/zigpy/zigpy-ota/dev/images/signify/firmware.ota",
"manufacturer_id": 4107,
"image_type": 268,
"file_version": 16783874,
"file_size": 126,
"checksum": "sha3-256:879c1b0d3f...",
"manufacturer_names": ["SignifyX"],
"model_names": ["test test", "test2"],
"release_notes": "- Bug fixes\n- Performance improvements",
"min_current_file_version": 65538
}See tests/__snapshots__/test_prepare_pr.ambr for complete workflow examples including file replacement warnings, third-party hosting, and version management scenarios.
# Clone the repository
git clone https://github.com/zigpy/zigpy-ota.git
cd zigpy-ota
# Run setup script (creates venv, installs dependencies, sets up pre-commit)
script/setup
# Activate the virtual environment
source .venv/bin/activate
# Or manually:
# uv sync
# pre-commit install# Run all tests
pytest
# Update snapshots after intentional changes
pytest --snapshot-update
# Run specific test file
pytest tests/test_prepare_pr.py
# Run with coverage
pytest --cov=zigpy_ota --cov-report=htmlCritical Test: tests/test_prepare_pr.py
This test is critical because it validates the entire prepare-pr workflow - the automated process that converts GitHub issue submissions into PRs. It tests end-to-end:
- Parsing issue markdown
- Downloading OTA files
- Generating YAML metadata
- Creating PR markdown
- Building the OTA index
- Schema validation
The test is easily expandable using parametrized fixtures. Currently tests 14 scenarios:
@pytest.mark.parametrize(
("issue_paths", "ota_paths"),
[
# Test 1: Single image - old version only
pytest.param(["issue_hue_old"], ["ota_hue_old"], id="single_old"),
# Test 2: Old + new version with keep existing
pytest.param(
["issue_hue_old", "issue_hue_new_keep"],
["ota_hue_old", "ota_hue_new"],
id="old_plus_new_keep",
),
# ... 12 more scenarios
],
indirect=["issue_paths", "ota_paths"],
)
def test_prepare_pr_command_with_snapshot(...):
# Test runs prepare-pr for each scenario and validates outputCurrent test coverage includes:
- Single image submissions
- Multiple images (keep existing)
- Multiple images (replace existing)
- File re-uploads (same filename)
- No optional metadata
- Third-party hosting
- Regular + third-party mixed
- Third-party + regular mixed
- Downgrades (old replacing new)
- Middle version replacing both ends
- Auto-set min_current_file_version
- SET_MIN_VERSION behavior
- Manual override warnings
- Hardware version overrides
To add new test coverage:
- Create new issue markdown file(s) in
tests/data/gh_issues/ - Add fixture(s) for the new issue file(s) (follow existing pattern)
- Add new
pytest.param(...)entry to the parametrize list with descriptive ID - Run
pytest --snapshot-update tests/test_prepare_pr.pyto create snapshots - Review snapshots in
tests/__snapshots__/test_prepare_pr.ambrto ensure correctness - Commit issue files and snapshots
Example expansion for a new scenario:
# Add fixture
@pytest.fixture
def issue_hue_new_scenario() -> Path:
"""Issue file for new scenario description."""
return Path("tests/data/gh_issues/issue_hue_new_scenario.md")
# Add to parametrize list
pytest.param(
["issue_hue_new_scenario"],
["ota_hue_old"],
id="descriptive_test_name",
),This makes it easy to expand coverage for:
- New edge cases discovered in production
- Changes to the issue form template
- New metadata field combinations
- Different manufacturer/device scenarios
- Complex version management cases
Other Important Tests:
tests/test_ota_index.py - Validates the real images/ directory:
- Tests
generate-indexcommand against actual repo images - Ensures all images can be parsed and indexed
- Validates output against zigpy's
REMOTE_PROVIDER_SCHEMA - Tests
disabled: trueflag functionality - Tests
--allow-missing-yamlflag behavior - Tests both zigpy and z2m output formats
- Critical: This test fails if there are problems with images or YAML in the repo
tests/test_parse_issue.py - Validates GitHub issue parsing:
- Tests parsing of issue markdown from GitHub issue form submissions
- Uses parametrized fixtures for easy expansion (see
issue_markdown_filefixture) - Tests both full and empty submissions
- Validates all issue fields: OTA file, URL, manufacturer, release notes, metadata, checklist
- Easily expandable: Add new issue markdown files to
paramslist to test new scenarios
tests/test_cli.py - Tests basic CLI commands:
generate-stub-metadata- Creates YAML from OTA filegenerate-stub-metadata-all- Batch YAML generation- Tests error handling (nonexistent files, etc.)
- Uses snapshot testing for output validation
The generate-index command has several validation flags and output format options:
zigpy-ota generate-index [OPTIONS]Output Format Options:
--format- Output format:zigpy(default),z2m(Zigbee2MQTT), ormarkdown(human-readable)zigpy: Generates JSON index for zigpy/Home Assistant ZHA (zigpy_v2_ota.json)z2m: Generates JSON index for Zigbee2MQTT (z2m_v1_ota.json)markdown: Generates human-readable index (markdown_v1.md) - see below- To generate multiple formats, run the command multiple times with different
--formatoptions
Markdown Index Format:
The markdown format generates a human-readable index file (markdown_v1.md) useful for:
- Browsing the repository contents without parsing JSON
- Documentation and reference
- Reviewing what firmware images are available
The markdown index includes:
- Channel and total image count (e.g., "42 + 3 disabled")
- Images grouped by manufacturer directory
- For each image: Original File Name, Manufacturer ID, Image Type, File Version, File Size, Binary URL, Checksum, Source URL, Header String, Hardware/File version constraints, Manufacturer/Model Names, Specificity, Release Notes, Pull Request link
[disabled]marker for images withdisabled: truein YAML[stale]marker for images that are "dominated" by newer versions
Stale Image Detection:
An image is considered "stale" (dominated) when a newer image exists that would match all the same devices. Specifically, image A is dominated by image B when:
- Same manufacturer ID and image type
- B has a higher file version than A
- B's constraints are a superset of A's (or both have no constraints):
- If A has
model_names, B must have the same or a superset - If A has
manufacturer_names, B must have the same or a superset - If A has version constraints (
min/max_current_file_version), B must not have constraints that would exclude devices A would match
- If A has
This detection helps identify firmware images that are effectively obsolete because a newer version would be preferred for all matching devices.
Note: Disabled images are excluded from zigpy/z2m JSON indexes but included in markdown for visibility. Stale images are included in zigpy JSON (for reference) but excluded from z2m JSON.
Validation Options:
The generate-index command has validation flags (all default to strict/fail):
# Strict validation (recommended for CI/CD)
zigpy-ota generate-index
# Development mode flags:
--allow-missing-yaml # Allow OTA images without corresponding YAML metadata
--allow-missing-ota # Allow YAML files without corresponding OTA binary
--allow-inconsistent-yaml # Allow third-party YAML with local OTA files present
--allow-filename-mismatch # Allow YAML file_name field not matching filename
--allow-invalid-yaml # Allow YAML files that fail to parse
--allow-collisions # Allow duplicate images (same ID/type/version)
# Download and validate third-party/remote hosted images (not for CI/CD)
--validate-third-partyDetailed flag descriptions:
-
--allow-missing-yaml/--no-allow-missing-yaml(default: fail)- Controls whether OTA images without corresponding YAML metadata are allowed
- Default behavior: Fails if any
.zigbeeor.otafile lacks a.yamlfile - Use
--allow-missing-yamlto skip files without YAML (useful during development) - Use in CI/CD to ensure all images have metadata before release
-
--allow-missing-ota/--no-allow-missing-ota(default: fail)- Controls whether YAML files without corresponding OTA binary are allowed
- Default behavior: Fails if a
.yamlfile exists but no matching.otaor.zigbeefile - Exception: Third-party YAML files (with
third_party_downloadkey) don't need local binaries - Use
--allow-missing-otaduring development when adding YAML before the binary
-
--allow-inconsistent-yaml/--no-allow-inconsistent-yaml(default: fail)- Controls whether third-party YAML files can coexist with local OTA files
- Default behavior: Fails if a
.yamlhasthird_party_downloadkey but a local.zigbeefile exists - This catches mistakes where someone marks a file as third-party but also committed the binary
- Use
--allow-inconsistent-yamlto allow this (generally not recommended)
-
--allow-filename-mismatch/--no-allow-filename-mismatch(default: fail)- Controls whether the YAML
file_namefield must match the actual filename - Default behavior: Fails if
file_namein YAML doesn't match the.ota/.zigbeefilename - Ensures consistency between metadata and actual files
- Use
--allow-filename-mismatchduring migrations or debugging
- Controls whether the YAML
-
--allow-invalid-yaml/--no-allow-invalid-yaml(default: fail)- Controls whether YAML files that fail to parse are allowed
- Default behavior: Fails if any
.yamlfile has syntax errors or invalid structure - Use
--allow-invalid-yamlto skip problematic files during debugging
-
--allow-collisions/--no-allow-collisions(default: fail)- Controls whether duplicate images (same manufacturer ID, image type, and file version) are allowed
- Default behavior: Fails if two images would have identical matching criteria
- Prevents ambiguous firmware matching where multiple files could apply
- Use
--allow-collisionsonly for debugging duplicate detection
-
--validate-third-party(flag, default: off)- Downloads and validates third-party OTA images from their URLs
- Default behavior: Trusts the metadata in YAML without downloading
- When enabled: Actually downloads each third-party file and validates:
- File is accessible at the URL
- Checksum matches
- File size matches
- Manufacturer ID, image type, file version match
- Warning: Much slower as it downloads every external file
- Useful before releases to ensure all third-party URLs are still valid
Other Options:
--images-path- Path to images directory (default:images/)--output-file- Output JSON file path (default depends on format)--tag- Git tag/branch for GitHub raw URLs (default: fromconst.py)
# Run all pre-commit hooks
pre-commit run --all-files
# Type checking (mypy)
pre-commit run mypy --all-files
# or
mypy zigpy_ota
# Linting and formatting (ruff)
ruff check zigpy_ota
ruff format zigpy_ota# Generate metadata for a single OTA image
zigpy-ota generate-stub-metadata path/to/image.ota
# Generate metadata for all images in the images folder
zigpy-ota generate-stub-metadata-all
# Generate index from images
zigpy-ota generate-index --format zigpy # zigpy JSON (default)
zigpy-ota generate-index --format z2m # Zigbee2MQTT JSON
zigpy-ota generate-index --format markdown # Human-readable markdown
# Parse a GitHub issue submission (for testing)
zigpy-ota parse-issue path/to/issue.md
# Prepare PR changes from a GitHub issue
zigpy-ota prepare-pr path/to/issue.md --output-pr-markdown /tmp/pr_body.md
# Generate a fake OTA image for testing
zigpy-ota generate-fake-ota output.ota --manufacturer-id 0x100B --image-type 0x010C --file-version 0x01001A02
# Replace OTA files with fake versions (for reducing test file sizes)
zigpy-ota replace-with-fake-ota tests/data/ota_files/*.zigbeeThe repository handles two main workflows:
-
OTA Submission Processing (GitHub Issues → PR)
- User submits OTA via GitHub issue form (
.github/ISSUE_TEMPLATE/01-submit-ota-url.ymlor02-submit-ota-file.yml) - GitHub Actions workflow (
process-ota-submission.yml) triggers - Issue data is parsed (
actions/issue/issue_parsing.py) - OTA file is downloaded (URL form) or extracted from uploaded ZIP (file upload form), validated, and saved to appropriate manufacturer directory
- Metadata YAML is generated
- PR is automatically created with the new files
- User submits OTA via GitHub issue form (
-
Metadata Index Generation (Images + YAML → JSON)
- Scans
images/directory for.otaand.yamlfile pairs - Parses OTA files using zigpy library to extract metadata
- Merges OTA metadata with YAML metadata
- Generates final
zigpy_v2_ota.jsonindex file for zigpy consumption
- Scans
zigpy_ota/actions/ - Core business logic organized by action type:
pr/- PR preparation: downloading, extracting, validating OTA files, saving to diskprepare_files.py- Main PR preparation logic (called by CLI and GitHub workflow)download_utils.py- HTTP downloads, ZIP extractionpr_utils.py- Manufacturer detection, file management, duplicate handling
issue/- GitHub issue parsing from markdown formatmetadata/- OTA parsing, YAML generation/parsing, metadata mergingota_parsing.py- Uses zigpy to parse and validate OTA binary filesyaml_metadata_generation.py- Creates YAML metadata filesyaml_parsing.py- Reads and validates YAML metadatamerging.py- Combines OTA + YAML metadata into final JSON indexstale_validation.py- Detects stale (dominated) images that are obsoleted by newer versions
markdown/- Markdown generation utilitiesmarkdown_gen.py- Generates human-readable markdown index of all firmware imagesutils.py- Shared formatting utilities (hex/decimal formatting, list formatting)
testing/- Fake OTA generation for tests
zigpy_ota/models/ - Data models (using dataclasses):
issue_model.py- Represents parsed GitHub issue data (IssueData,OTAFile,IssueChecklist)yaml_metadata.py- YAML metadata structure (YamlMetadataFile,YamlMetadataThirdParty)ota_metadata.py- OTA file metadata extracted from binaryindex_metadata.py- Final JSON index format for zigpypr_result.py- Result of PR preparation with paths and metadata
zigpy_ota/utils/ - Helper utilities:
manufacturers.py- Manufacturer ID to directory name mappingyaml_utils.py- YAML serialization helperscli_formatting.py- Logging and CLI output formatting
zigpy_ota/cli.py - Click-based CLI interface exposing all commands
Third-Party Downloads: OTA files can be hosted externally instead of stored in the repo. The YAML contains checksums and metadata under the third_party_download key, and the actual OTA file is downloaded from source_url when needed. This is controlled by the "Third-party download" checkbox in issue submissions.
Existing Images Handling: When a new OTA is submitted via the GitHub issue form, users must choose how to handle existing images with the same manufacturer ID and image type. This is a required dropdown in the issue form with two strategies:
- Keep with version constraint (
SET_MIN_VERSION) - Keep existing images but automatically setmin_current_file_versionon the new image to the highest existing version (that's still older than the new image)- Recommended for most cases
- Useful for multi-step upgrades where devices must upgrade through intermediate versions
- Prevents downgrades while maintaining upgrade path
- Example: Device at v1 must upgrade to v2 before upgrading to v3
- Important: Due to how zigpy's firmware matching algorithm works, it's typically sufficient to set
min_current_file_versionon the newer image to require a previous upgrade first. Zigpy automatically prioritizes and prefers more specific images based on the device's current firmware version, somax_current_file_versionconstraints are usually not needed. For the generated Z2M index,maxFileVersionis automatically computed frommin_current_file_versionconstraints of newer images.
- Keep all (
KEEP_ALL) - Keep all existing images without setting automatic version constraints- For manual metadata management
- Use when you need fine-grained control over version constraints
- Requires manual editing of YAML metadata files to set constraints
Note: The "Replace existing images" option has been disabled. The repository now retains all firmware images for historic purposes and to potentially support combined changelogs across multiple OTA versions in ZHA. Since zigpy always prefers newer firmware versions, older images are only used when they have constraints (like min_current_file_version or model_names) that make them more specific to certain devices.
Manufacturer Directories: OTA files are organized by manufacturer in images/manufacturer_name/. Known manufacturers have specific directories (e.g., aeotec/, frient/), while unknown ones go to images/other/.
Fake OTA Generation: For testing purposes, minimal valid OTA files can be generated that have correct headers but minimal payload data. This is used to reduce test file sizes while maintaining valid OTA structure.
- Test data is located in
tests/data/ - Snapshot testing is used with
syrupy(snapshots intests/__snapshots__/) - OTA test files should be minimal fakes (use
zigpy-ota replace-with-fake-otato shrink real files) - Mock external HTTP requests in tests (don't hit real URLs)
- Test both local file and third-party download workflows
The zigpy-ota software code is licensed under GPL-3.0 (GNU General Public License v3.0). See the LICENSE file.
Important: Binary firmware image files in this repository are third-party content from their respective manufacturers and are NOT covered by the GPL license. These firmware files remain under their original copyright and licensing terms as set by their manufacturers. Users must comply with applicable manufacturer terms when using these firmware files.
Firmware files are copyrighted works. Only contribute files if you have verified legal distribution rights:
- Official manufacturer source with permitted redistribution
- Explicit written permission from copyright holder
- Open source licensed firmware
- You are the copyright holder
Always provide the original source URL in the source_url field if known.
zigpy- Zigbee protocol library providing OTA parsing and validationclick- CLI frameworkruamel.yaml- YAML serialization (preserves comments and formatting)requests- HTTP downloadspytest- Testing frameworkruff- Linting and formattingmypy- Type checkinguv- Fast Python package installer (recommended)
- Main branch:
dev - OTA submission PRs use branch naming:
bot/ota-submission-issue/{issue_number} - GitHub Actions bot account (
zigpy-bot) creates automated PRs - Pre-commit hooks enforce code quality before commits