fakemeter
A Linux BLE peripheral emulator that impersonates Bluetooth multimeters, so a
meter's official phone app connects to it and decodes frames you craft. Set a
known reading (e.g. 4.200 V DC); if the app displays it, the decode is correct —
if it differs, you've found a bug. A hardware-free way to verify BLE multimeter
decode logic (built to validate the uni-t-mmu-ble drivers).
┌─────────────────────────┐ BLE ┌──────────────────────────┐
│ fakemeter (this tool) │ advertise 0xFFF0 │ phone │
│ ─────────────────── │ ──────────────────▶ │ ┌────────────────────┐ │
│ craft bytes for a │ │ │ vendor app / │ │
│ KNOWN reading, e.g. │ notify (0xFFF4) │ │ Web-Bluetooth app │ │
│ "4.200 V DC" │ ──── frame bytes ──▶│ │ DECODES + DISPLAYS │ │
│ │ │ └────────────────────┘ │
│ (you know the input) │ ◀── writes (0xFFF3) │ (human reads output) │
└─────────────────────────┘ logged └──────────────────────────┘
Eleven profiles across the OWON, Voltcraft, UNI-T, AiCare and BDM families — five live-validated against the real vendor apps (table below).
Requirements
- Linux with BlueZ (tested on 5.72) and a working BLE adapter —
hciconfigshould show itUP RUNNING. bluezero, which sits on the distro'spython3-gi+python3-dbus(PyGObject). Those don't pip-build cleanly, so install them from apt and let the install see them (below).
Install
sudo apt install python3-gi python3-dbus # system BLE stack
pipx install --system-site-packages fakemeter # isolated CLI, recommended
# — or into a venv —
python3 -m venv --system-site-packages .venv && . .venv/bin/activate
pip install fakemeter
Both give you the fakemeter command. (From source: clone, then pip install -e .
in the same kind of --system-site-packages venv.)
Run
fakemeter --profile voltcraft # advertises + opens a REPL
--profile takes any id from the table. Connect the vendor app to the advertised
device — it shows the initial 4.200 V DC. Drive it from the REPL:
p play a preset v 230.5 V set value / function / prefix
f hold toggle a flag r re-send the current frame
raw <hex> inject an arbitrary frame s voltcraft bit-sweep
series <id> / auth <mode> / walk on|off ? help q quit
Useful flags:
--name NAME— advertised name. Several apps only list an exact model name (see Gotchas) — pass the real one, not the*-FAKEdefault.--adapter hciN— which adapter (defaulthci0; also accepts a BD address).--self-check— publish, verify advert + GATT, run encoder round-trips, exit. No phone needed; confirms the host is sane.--no-walk— fixed reading (for precise byte-mapping).--no-unsolicited— disable the raw-HCI no-CCCD delivery path.-v— log every notify / write.
Profiles
"Live-validated" = a reading was read off the real vendor app's screen. "Byte-verified" = the encoder round-trips bit-exact against a port of the app's own decoder, but hasn't been put on-screen yet.
| Profile | Family / format | Vendor app (Android pkg) | Status | Notes / quirks |
|---|---|---|---|---|
voltcraft |
OWON R10W, 15-byte LE | Voltcraft VC800/900 (com.voltcraft.series800, OWON iMeter rebadge) |
✅ live-validated | Flag order settled (LSB-first). Interactive buttons + value-walk + HOLD all on-screen. ⚠️ device-card shows a red "disconnected" badge even while live data flows — app quirk, does NOT block the reading. |
owon-plus |
OWON 6-byte binary (R2W) | OWON iMeter (com.owon.imeter) |
✅ live-validated + real-HW corroborated | The OWON workhorse. Confirmed against a physical B35T+. The "+" meters (B35T+/B41T+) are this binary format. Use iMeter (writes the CCCD), not BLE4.0. |
owon-old |
OWON 14-byte ASCII (B35) | OWON BLE4.0 (com.owon.MultimeterBLE) |
⚠️ byte-verified, LEGACY | 31/31 round-trip green, but no live oracle and no real hardware exists in the wild — every real meter is binary. Flagged for likely removal (see its module docstring). |
bdm |
YSCoCo XOR-scrambled | Bluetooth DMM (com.yscoco.wyboem), AN9002 |
✅ live-validated | Needed a device-type-byte fix (descrambled byte[2]=0x03, AB_300) to render the right unit. Advert name must be exactly Bluetooth DMM or ZY. |
ai-care |
AiCare self-addressing (FFB0) | INTELLIGENT MULTIMETER (aicare.net.cn.iMultimeter) |
✅ live-validated | Scan gate is manufacturer-data, not name — the emulator advertises AC FF <mac-reversed> so the app lists it (see Gotchas). The readout only updates after you tap the green "Start" button. |
uni-t |
UNI-T AB-CD, 19-byte (polled) | UNI-T Smart Measure (com.uni_t.multimeter), as UT60BT |
✅ live-validated | Handshake-then-stream. Needed a range-index unit fix (range 0 = mV, not V). Advert name must be exactly a supported model (e.g. UT60BT). |
ut202bt |
UNI-T (shares uni_t.encode) |
Smart Measure | 🟡 inherits uni-t fix | Same encoder as uni-t, so it inherits the range fix; not separately put on-screen. |
ut117c |
UNI-T 16-bit-len encoder | Smart Measure | 🟡 byte-verified | Own encoder; per-model unit/range sweep against its app still owed. |
ut171 |
UNI-T 16-bit-len encoder | Smart Measure | 🟡 byte-verified | Own encoder; not live-swept. |
ut181a |
UNI-T 16-bit-len encoder | Smart Measure | 🟡 byte-verified, partial | MAIN value block only; secondary block + datalog deferred (need a HW capture). |
ut219p |
UNI-T 16-bit-len encoder | Smart Measure | 🟡 byte-verified, partial | Standard live-data frame only; daoPos→param dispatch + battery-gate handshake deferred. |
Verifying a decode
fakemeter --profile <id> --name <exact-model>.- Open the vendor app (or nRF Connect / a Web-Bluetooth client) and connect.
- It shows the initial reading. Set values (
v 230.5 V), toggle flags (f hold), play presets (p) — if the display matches what you sent, the decode is right. Changing the value live proves it's not a static template;raw <hex>maps any byte/bit to the screen.
Gotchas
The practical ones — full detail in docs/PROGRESS.md.
- Use the real model name. App scan lists are filtered: Bluetooth DMM accepts
only
Bluetooth DMM/ZY; UNI-T Smart Measure only exact models (UT60BT,UT219P, …). ai-care instead gates on advertised manufacturer-data, which the profile emits automatically — so any--nameworks there. - OWON apps need an LE-only adapter. They
connectGatt(AUTO); on a dual-mode adapter Android picks Classic and bonds, blocking LE GATT. Runsudo btmgmt --index 0 bredr offand clear the phone's cached device record. - The OWON BLE4.0 (Java) app never subscribes via CCCD. fakemeter still reaches
it by injecting ATT notifications over a raw HCI socket (on by default for stream
profiles). That path needs
CAP_NET_RAW:sudo setcap cap_net_raw+ep "$(readlink -f "$(which python3)")". Apps that subscribe normally use the standard BlueZ path and need no extra privilege. - Restarting drops a phone's bond (stale LTK → "incorrect PIN"). Clear it on
both sides:
bluetoothctl remove <addr>+ toggle the phone's Bluetooth, then re-add from the app's scan screen.
Tests
pip install pytest && pytest -q # 262 passed
Pure-Python (no BLE hardware needed): each profile round-trips readings through its
encoder and a port of the matching decoder (tests/decode_*.py), checking
value / unit / decimal point / sign, plus the FFF1 auth math, the interactive
reactions, the meter-core engine, and the raw-HCI PDU layout. CI runs them on
Python 3.10–3.13.
Docs
docs/PROGRESS.md— per-profile validation log, protocol details, and gotchas in full.docs/adding-a-profile.md— how to add a profile + the layering map (base→meter_core→owon_base/uni_t_base→ per-model).docs/voltcraft-measurement-protocol.md,docs/owon-voltcraft-handshake.md— the R10W measurement frame and the OWON FFF1/FFF2 connect handshake.
License
MIT — see LICENSE.
Metadata
Release files for fakemeter 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| fakemeter-0.1.1.tar.gz | 101.1 kB | With provenance | Details for fakemeter-0.1.1.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| fakemeter-0.1.1-py3-none-any.whl | Python 3 | none | any | Details for fakemeter-0.1.1-py3-none-any.whl |
Total release size: 200.5 kB
Release files / fakemeter-0.1.1.tar.gz
| Download URL | fakemeter-0.1.1.tar.gz |
|---|---|
| Size | 101.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7d5a932ef20e2676d2dbbb77ad9749e73c4342cfee3a54f077a9509bffb7bf80
|
|
BLAKE2b-256 checksum How to use checksums |
f1c72a2e4989396b7d2264dba6108c18a97e22f7580b626b9ec2ed4e4730285d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 12, 2026.
Transparency logRelease files / fakemeter-0.1.1-py3-none-any.whl
| Download URL | fakemeter-0.1.1-py3-none-any.whl |
|---|---|
| Size | 99.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
342c4c30a9e1e4554117aae2bb36b9a42ac6d4b9ede3374669193dd2e6ca67cd
|
|
BLAKE2b-256 checksum How to use checksums |
e42c675fee085862e4313ab3337b4b1ccb7364d90ea13aa43715c4258e4f59cb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 12, 2026.
Transparency logSecurity for fakemeter 0.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for fakemeter-0.1.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 12, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.