BLE peripheral emulator that impersonates Bluetooth multimeters for hardware-free decode verification
Project description
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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fakemeter-0.1.1.tar.gz.
File metadata
- Download URL: fakemeter-0.1.1.tar.gz
- Upload date:
- Size: 101.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7d5a932ef20e2676d2dbbb77ad9749e73c4342cfee3a54f077a9509bffb7bf80
|
|
| MD5 |
1f90246e4d0f6708ebd5be3a36abef19
|
|
| BLAKE2b-256 |
f1c72a2e4989396b7d2264dba6108c18a97e22f7580b626b9ec2ed4e4730285d
|
Provenance
The following attestation bundles were made for fakemeter-0.1.1.tar.gz:
Publisher:
release.yml on ble-multimeter/fakemeter
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fakemeter-0.1.1.tar.gz -
Subject digest:
7d5a932ef20e2676d2dbbb77ad9749e73c4342cfee3a54f077a9509bffb7bf80 - Sigstore transparency entry: 1803758133
- Sigstore integration time:
-
Permalink:
ble-multimeter/fakemeter@4cca1d59063c51d13132c3bf23f5d5768a784bd3 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/ble-multimeter
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@4cca1d59063c51d13132c3bf23f5d5768a784bd3 -
Trigger Event:
release
-
Statement type:
File details
Details for the file fakemeter-0.1.1-py3-none-any.whl.
File metadata
- Download URL: fakemeter-0.1.1-py3-none-any.whl
- Upload date:
- Size: 99.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
342c4c30a9e1e4554117aae2bb36b9a42ac6d4b9ede3374669193dd2e6ca67cd
|
|
| MD5 |
812d481421e9018d2b53d42f66cb1552
|
|
| BLAKE2b-256 |
e42c675fee085862e4313ab3337b4b1ccb7364d90ea13aa43715c4258e4f59cb
|
Provenance
The following attestation bundles were made for fakemeter-0.1.1-py3-none-any.whl:
Publisher:
release.yml on ble-multimeter/fakemeter
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fakemeter-0.1.1-py3-none-any.whl -
Subject digest:
342c4c30a9e1e4554117aae2bb36b9a42ac6d4b9ede3374669193dd2e6ca67cd - Sigstore transparency entry: 1803758164
- Sigstore integration time:
-
Permalink:
ble-multimeter/fakemeter@4cca1d59063c51d13132c3bf23f5d5768a784bd3 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/ble-multimeter
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@4cca1d59063c51d13132c3bf23f5d5768a784bd3 -
Trigger Event:
release
-
Statement type: