OpenMiwear
A Python toolkit for extracting, merging, auditing and analyzing MiWear device dumps — from the command line.
OpenMiwear is a set of small, focused command-line tools for the everyday chores of working with MiWear device artifacts — unpacking .tar.gz / .zip / .gz bundles, merging rotated logs, extracting assertions, auditing resource directories, analyzing runtime log patterns, and driving serial consoles.
Each tool does one thing, takes sensible defaults, and produces human- or machine-readable output (plain text, CSV, Markdown, or interactive HTML).
Features
- Zero runtime dependencies for the core tools — pure Python standard library.
- Batch extraction of
.tar.gz,.zipand.gzarchives in a single pass. - Log workflow: pull logs over
adb, merge rotated shards, extract assertions, filter by pattern. - Resource audit: duplicate detection, unused-asset scan, two-directory diff — with Markdown + interactive HTML reports.
- Log analyzer for AppID / screen-transition patterns, exportable to CSV or interactive HTML.
- Optional serial console (miniterm, one-shot, periodic, batch-file) via
pyserial. - Works on Python 3.10+, Linux / macOS / Windows.
Installation
Published on PyPI as the miwear package:
# Core tools
pip install miwear
# Core + serial console helper
pip install 'miwear[serial]'
[!TIP] Every command accepts
--helpfor the full option list and--versionto print the installed version.
Quick start
# Extract and merge a device log bundle
miwear_log ~/Downloads/log.tar.gz
# Or pull straight from a connected Android device via adb
miwear_log --phone -f 123456_abc
# Audit a resources directory for duplicates and open an HTML report
miwear_check -d ./resources -e bin
Commands
| Command | Purpose |
|---|---|
miwear_log |
Extract a MiWear log archive and merge its shards into a single log file |
miwear_assert |
Extract assertion blocks from a log |
miwear_gz |
Decompress and merge .gz log shards |
miwear_tz |
Batch-extract every .tar.gz archive in a directory |
miwear_uz |
Batch-extract every .zip archive in a directory |
miwear_ez |
Pull files out of a ZIP archive by precise filename-stem suffix |
miwear_check |
Resource audit — duplicates, unused assets, directory diff |
miwear_loganalyzer |
Parse AppID / screen log patterns into CSV or interactive HTML |
miwear_ota |
Diff two OTA packages with ddelta and explain the delta size |
miwear_serial |
Serial console helper (requires pyserial) |
Usage
miwear_log
Extract a log bundle and merge its shards into one file.
# Positional path (recommended)
miwear_log ~/Downloads/log.tar.gz
# Or with -f / --filename
miwear_log -f ~/Downloads/log.tar.gz
# Pull straight from an Android phone via adb
miwear_log --phone -f 123456_abc
miwear_assert
Extract assertion blocks from a merged log.
miwear_assert -i mi.log -o assert_log.txt
miwear_gz
Decompress and concatenate all .gz shards in a directory.
miwear_gz --path ./logs --log_file my.log --output_file merged.log
miwear_tz / miwear_uz
Batch-extract every archive in a directory.
miwear_tz --path ./logs # *.tar.gz
miwear_uz --path ./logs # *.zip
miwear_ez
Pull files out of a ZIP archive into an output directory, without performing a full extraction. Files are selected by one or more precise filename-stem suffixes: ap matches *ap.elf but not *app.elf. Pass several suffixes like ap cp ota to extract multiple at once, or all to extract everything. If no ZIP is given, the current directory is scanned and you pick one interactively.
# Auto-detect a ZIP, extract *ap.elf (default target is 'ap')
miwear_ez
# Extract every *.elf (legacy behavior), plus ota.zip
miwear_ez all
# A different stem suffix, e.g. *cp.elf
miwear_ez cp
# Multiple suffixes in one pass: *ap.elf, *cp.elf and *ota.elf
miwear_ez ap cp ota
# Explicit archive, multiple extensions, custom output directory
miwear_ez ap -z firmware.zip -e bin hex -o ./out
miwear_check
Four modes: dup (default), unused, both, and diff. All modes emit Markdown + interactive HTML reports with search / filter / sort.
Duplicate detection
miwear_check -d ./resources -e bin
miwear_check -d ./resources -e bin png
miwear_check -d ./resources -e bin -p theme_ config_
miwear_check -d ./resources -e bin --action delete
Unused resource scan
-d sets where resources live; -c sets where to look for references (defaults to -d).
miwear_check -m unused -d ./project -e bin
miwear_check -m unused -d ./project -c ./project/ota -e bin
miwear_check -m unused -d ./project -e bin --code-ext ".c,.h,.cpp,.java"
# Run duplicate + unused together
miwear_check -m both -d ./project -e bin
Two-directory diff
Matches files by base name (everything before the first dot) under the same relative path — so confirm.indexed_8.png and confirm.bin pair up automatically.
miwear_check -m diff --path1 ./design --path2 ./converted
miwear_check -m diff --path1 ./design --path2 ./converted -i .git,node_modules
miwear_check -m diff --path1 ./design --path2 ./converted --sort count
Report output
miwear_check -d ./resources -e bin -o my_report.md
miwear_check -d ./resources -e bin --no-output
[!NOTE] When the scan directory contains both
view/andview_XX/res*variants,miwear_checkprompts you to pick one (default: the variant with the most files). Pass-ito skip the prompt.
miwear_loganalyzer
Parse MiWear runtime log patterns and export to CSV or HTML.
# Default: all analyzers → miwear.csv
miwear_loganalyzer -f 1.log
# Interactive HTML report (opens in browser)
miwear_loganalyzer -f 1.log --html --open-browser
# Run a single analyzer
miwear_loganalyzer -f 1.log -t appid
miwear_loganalyzer -f 1.log -t screen -o screens.csv
miwear_ota
Diff two OTA packages file by file, generate a real ddelta patch for every payload image, and explain where the delta size comes from. Emits an HTML report (plus a Markdown mirror), the .patch files, and a deflated bundle.
[!NOTE] The report body is in Chinese, matching the layout this tool inherited from its in-house predecessor.
[!IMPORTANT]
ddelta_generateis not bundled — it is an architecture-specific binary built from the Vela source tree. Build it once:cd <vela-root>/external/ddelta/ddelta && make clean && makeIt is then picked up automatically when you run inside a Vela checkout. Otherwise pass
--ddelta <path>or exportMIWEAR_DDELTA_GENERATE=<path>. Without it the tool still runs, reporting byte-level comparison only.
[!IMPORTANT]
--blocksizeis required (unless you pass--no-ddelta). It is the same block size your OTA packaging step passes toddelta_generate, it differs per project, and a non-zero value switches ddelta to in-place patching — which changes the resulting patch. Pass0to reproduce a build that does not set a block size. The value is printed in the report header and as a summary card so a report is never ambiguous.
# Reproduce a build that uses a 32M block size
miwear_ota ota_old.zip ota_new.zip --blocksize 32M
# A build that does not set a block size at all
miwear_ota ota_old.zip ota_new.zip --blocksize 0
# Explicit tool path, verify every patch by applying it back onto the old image
miwear_ota ota_old.zip ota_new.zip --blocksize 8M \
--ddelta ~/vela/external/ddelta/ddelta/ddelta_generate --verify
# Any payload extension, or all files; compare two extracted build trees
miwear_ota old.zip new.zip --blocksize 32M -e bin img
miwear_ota ./out_old ./out_new --blocksize 32M -e all
# Byte-level comparison only, no patches, no block size needed
miwear_ota ota_old.zip ota_new.zip --no-ddelta
# Terminal summary only, no report files
miwear_ota ota_old.zip ota_new.zip --blocksize 32M --no-output
What the report explains
- Per-file raw delta, deflated delta and delta/new ratios.
ddeltawrites uncompressed patches by design, so the deflated bundle is the number that maps to download cost. - Delta structure per
DDELTA60patch: header, control stream, diff data (derived from the old image) vs extra data (literal new bytes). Extra-dominated deltas mean little was reusable from the old image. - Change classification per 4 KB chunk: sparse fill (zeros → data), data cleared (data → zeros), in-place rewrite, and whether changes are localized or scattered.
- Images that are byte-identical between packages, and files that exist in only one package.
miwear_serial
[!IMPORTANT] Requires
pyserial. Install withpip install 'miwear[serial]'.
Interactive session
miwear_serial -p /dev/ttyACM0 -b 921600
# Press Ctrl+] to exit
One-shot command
miwear_serial -p /dev/ttyUSB1 -b 115200 -c "ps"
miwear_serial -p /dev/ttyUSB1 -b 115200 -c "ps" -r # parse response
Periodic / batch
# Repeat on a timer
miwear_serial -p /dev/ttyACM1 -i 1.0 -c "ps"
miwear_serial -p /dev/ttyACM1 -i 2.0 -c "ps" --count 5
# Batch commands from a file
miwear_serial -f commands.txt
miwear_serial -f commands.txt -i 2.0 --count 5
Record to a log file
miwear_serial -p /dev/ttyACM0 -b 921600 -s # miwear.log (default)
miwear_serial -p /dev/ttyACM0 -b 921600 -s log.txt # custom path
Requirements
- Python 3.10+
pyserial— only formiwear_serial
Development
Clone the repo and run the full lint + test + integration suite that mirrors CI:
git clone https://github.com/Junbo-Zheng/OpenMiwear.git
cd OpenMiwear
pip install flake8 black mypy pytest
./build.sh
build.sh runs flake8, black --check, mypy, pytest, and then exercises each CLI against the fixtures in tests/data/. GitHub Actions mirrors this on every push and pull request via .github/workflows/ci.yml, with the test job running against a Python 3.10 / 3.11 / 3.12 / 3.13 matrix.
Releases are automated through .github/workflows/publish.yml: publishing a GitHub Release triggers a build and uploads the distribution to PyPI via OIDC trusted publishing — no API tokens involved.
[!NOTE] The repository on GitHub is
Junbo-Zheng/OpenMiwear, but the PyPI distribution and Python import/command names use the shortermiwearidentifier (e.g.import miwear,miwear_log …). This is intentional — keep the project name OpenMiwear when referring to the repo, and the package namemiwearwhen invoking the tools.
Release files for miwear 0.0.26
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| miwear-0.0.26.tar.gz | 78.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| miwear-0.0.26-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 163.1 kB
Release files / miwear-0.0.26.tar.gz
| Download URL | miwear-0.0.26.tar.gz |
|---|---|
| Size | 78.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
38c8689bbba115472686ec44d77024f9e172b948f122e82e9018cdc04e3c387a
|
|
BLAKE2b-256 checksum How to use checksums |
d8c21a6c2b429be6af99adb1357083386d784282bdcfafd00524fcdc7cf52f43
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.10.12
|
Release files / miwear-0.0.26-py3-none-any.whl
| Download URL | miwear-0.0.26-py3-none-any.whl |
|---|---|
| Size | 84.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
281d6682877f35f34e65c3e431fe83b29686f4c8670bbdad9f5abb5c96bcaf96
|
|
BLAKE2b-256 checksum How to use checksums |
b1ef63a2eeed8384b030262c99ac210e93ee8b20ddf9166ca5789c2cad1ce974
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.10.12
|