{"id":51324038,"url":"https://github.com/myselfshravan/miband-lit","last_synced_at":"2026-07-01T16:30:40.469Z","repository":{"id":364338822,"uuid":"1267338568","full_name":"myselfshravan/miband-lit","owner":"myselfshravan","description":"Read your Mi Band 5's heart rate live on your Mac over BLE — auth handshake cracked, real-time Streamlit dashboard, two-way notifications, and a heartbeat-driven smart bulb. Python + bleak.","archived":false,"fork":false,"pushed_at":"2026-06-12T16:31:44.000Z","size":45,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-12T17:27:06.211Z","etag":null,"topics":["ble","bleak","bluetooth-low-energy","heart-rate","iot","macos","mi-band","miband5","python","reverse-engineering","smart-home","streamlit"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/myselfshravan.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-12T12:57:20.000Z","updated_at":"2026-06-12T16:31:55.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/myselfshravan/miband-lit","commit_stats":null,"previous_names":["myselfshravan/miband-lit"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/myselfshravan/miband-lit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/myselfshravan%2Fmiband-lit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/myselfshravan%2Fmiband-lit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/myselfshravan%2Fmiband-lit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/myselfshravan%2Fmiband-lit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/myselfshravan","download_url":"https://codeload.github.com/myselfshravan/miband-lit/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/myselfshravan%2Fmiband-lit/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35015052,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-01T02:00:05.325Z","response_time":130,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ble","bleak","bluetooth-low-energy","heart-rate","iot","macos","mi-band","miband5","python","reverse-engineering","smart-home","streamlit"],"created_at":"2026-07-01T16:30:39.590Z","updated_at":"2026-07-01T16:30:40.464Z","avatar_url":"https://github.com/myselfshravan.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# miband-lit ⌚️❤️\n\nRead your **Mi Band 5's heart rate live on your Mac** over Bluetooth LE — with a\nStreamlit dashboard, the ability to push text notifications and vibrations back\nto the band, and (for fun) a heart-rate-driven smart bulb. No Zepp app needed\nonce it's working.\n\n\u003e 📖 **The full story / writeup:** [`BLOG.md`](BLOG.md) — how I cracked the auth\n\u003e handshake, fought the sensor, and survived the Bluetooth gremlins.\n\n**Stack:** Python · [`bleak`](https://github.com/hbldh/bleak) (BLE) · Streamlit ·\nSQLite · macOS. Tested on a Mi Band 5; the protocol is shared with Mi Band 4.\n\n## Setup\n\n```sh\ngit clone https://github.com/myselfshravan/miband-lit.git\ncd miband-lit\npython3 -m venv venv\n./venv/bin/python -m pip install -r requirements.txt\n\n# Get the auth-key extractor (used once, see below)\ngit clone https://github.com/argrento/huami-token.git\n./venv/bin/python -m pip install loguru\n\n# Your secrets go here (gitignored)\ncp config.example.sh config.sh   # then edit with your UUID + auth key\n```\n\nEverything runs in the local `venv/`. Run scripts with `./venv/bin/python ...`.\n\n## One-time: get your band's auth key\n\nThe band won't stream sensor data until you authenticate with the 16-byte key\nthat was created when Zepp first paired it. `huami-token` pulls it from your\nZepp account.\n\n```sh\n# You log in to YOUR Zepp account; it talks to Zepp's servers, not me.\n./venv/bin/python huami-token/main.py --method amazfit -e you@example.com -b\n```\n\nIt prompts for your Zepp password, then prints each paired device:\n\n```\nDevice 0:\n  MAC: C8:0F:xx:xx:xx:xx, Active: Yes\n  Key: 0xagh4d8e1...   \u003c-- this 32-hex-char value is your auth key\n```\n\nCopy the key (the part after `0x`). It does not change unless you re-pair the\nband, so you only do this once.\n\n## Each time you want to read HR — the easy way\n\n1. **Free the band from your phone** — it only talks to one device at a time.\n   Turn Bluetooth OFF on the phone (airplane mode is surest). Just closing\n   Zepp is usually not enough.\n\n2. **Run the launcher** (it cycles the Mac's Bluetooth to free the band, then\n   connects and streams):\n\n   ```sh\n   ./run.sh                 # stream until Ctrl-C\n   DURATION=60 ./run.sh     # stop after ~60 seconds\n   ```\n\n   Wear the band snugly. You'll see `Auth: SUCCESS`, the green sensor LEDs\n   light up, then `HR: NN bpm` lines (~one every several seconds).\n\nYour band's UUID and auth key are already baked into `run.sh`.\n\n## Dashboard (live chart + send notifications)\n\nA Streamlit dashboard shows a live heart-rate chart and lets you push text\nnotifications and vibrations to the band.\n\n```sh\n./start_dashboard.sh\n```\n\nThis cycles Bluetooth and starts everything: the **band service** (BLE), the\n**WiZ bulb sync**, and **Streamlit** all run in the background, while a live\n**hacker-style console feed** (`console.py`) takes the foreground terminal —\nstreaming a colour-coded line per heartbeat with trend arrows and the live bulb\ncolour. The dashboard is at http://localhost:8501. Ctrl-C stops everything.\n\nGreat for demos: hold your breath, watch the BPM dip in the terminal feed, then\npoint at the dashboard chart, the band, and the light all moving together.\n\nArchitecture (why two processes):\n- `band_service.py` holds the single BLE connection, streams HR into a shared\n  SQLite DB (`miband.db`), and drains a commands table.\n- `dashboard.py` (Streamlit) only reads the DB and writes commands. It never\n  touches Bluetooth — Streamlit reruns top-to-bottom on every click and can't\n  hold a live connection, and the band only allows one connection anyway.\n\nWhat the dashboard can do:\n- Live BPM chart (adjustable window), current/min/max/average, connection status.\n- **Send notification** — text shows on the band's screen. The \"type\" picks the\n  ANS category (sms/call/email/…), which changes the icon. Keep it short\n  (~80 chars max — it's a single BLE write, no chunking).\n- **Vibrate band** — buzzes it (handy as a \"find my band\" ping).\n\nBand service log: `band_service.log`. It auto-reconnects if the link drops.\n\n## ⚡ Heart rate → WiZ bulb colour\n\nIf you have a WiZ smart bulb on the same Wi-Fi, your heart rate drives its\ncolour in real time: **green** at rest → yellow → **red** when it climbs. The\nbulb sync starts automatically with `./start_dashboard.sh` (set\n`WIZ_BULB=0` to skip it), or run it standalone alongside the band service:\n\n```sh\n./venv/bin/python wiz_pulse.py            # auto-discovers bulb + auto-scales range\nWIZ_IP=192.168.1.4 ./venv/bin/python wiz_pulse.py\nHR_LOW=80 HR_HIGH=115 ./venv/bin/python wiz_pulse.py   # fixed range overrides auto\n```\n\nBy default the colour range **auto-scales** to your recent HR (last 3 min), so\nthe full green→red sweep always maps onto your actual fluctuation. A fixed wide\nrange like 50–150 would squash a resting HR (say 83–109) into one yellow-green\ncolour — making the bulb look stuck. Set `HR_LOW`/`HR_HIGH` to pin a fixed range.\n\nIt just reads the latest BPM from `miband.db` and sends UDP colour commands to\nthe bulb (WiZ listens on port 38899). The `WizLight` class is reused from my\n[wiz-hack](https://github.com/myselfshravan/wiz-hack) project.\n\n### Hook anything else into the band\n\nThe same store is a clean integration point in both directions:\n\n```python\nimport store\n# Outbound: buzz your wrist from any script / webhook / cron\nstore.add_command(\"notify\", {\"text\": \"Deploy finished ✅\", \"category\": \"sms\"})\n# Inbound: react to live heart rate\nts, bpm = store.latest_reading()\n```\n\nSo AI-token-usage alerts, CI notifications, or anything with an API can push to\nthe band, and anything can react to your heart rate.\n\n## What actually works (notes from setup)\n\n- **Two HR modes, both flaky.** `MODE=manual` fires the sensor in short\n  bursts; `MODE=continuous` (the dashboard default) streams real values about\n  every 3s. Either way the band runs a ~15–20s measurement *session* then\n  stops. Re-issuing `START` re-kicks it; issuing `STOP` mid-session reliably\n  kills the stream, so the service never sends `STOP` while running.\n- **Cadence ceiling ≈ one reading every few seconds — NOT per-beat.** The Mi\n  Band 5 reports an *averaged BPM*, not individual beat timing (no RR-interval\n  data over BLE), and even that arrives in unreliable bursts. True per-beat is\n  not possible with this hardware.\n- **Smoothing fills the gaps.** `wiz_pulse.py` eases the bulb colour toward the\n  latest reading at ~12 fps (`WIZ_FPS` / `WIZ_EASE`), so the light *glides*\n  between sparse readings and looks continuous even though the data is coarse.\n\n## Manual invocation (without the launcher)\n\n```sh\n./venv/bin/python scan.py                          # find the band's UUID\n./venv/bin/python heartrate.py \u003cUUID\u003e \u003cAUTH_KEY\u003e   # MODE=manual by default\n```\n\n## Troubleshooting\n\n- **`Auth: FAILED - wrong key`** — key is wrong or band was re-paired. Re-run\n  huami-token. Make sure you copied the value *after* `0x` and dropped the `0x`.\n- **`Auth: timed out`** — usually the band is still bonded to the phone, or it\n  reconnected to Zepp mid-handshake. Kill Zepp / phone Bluetooth and retry.\n- **scan.py shows nothing / \"Band not found\"** — the band isn't advertising.\n  Either the phone is holding it (turn phone Bluetooth off), or the Mac has a\n  wedged half-open connection from a previous run. Fix the Mac side with:\n  `blueutil -p 0 \u0026\u0026 sleep 3 \u0026\u0026 blueutil -p 1` (the launcher does this for you).\n  Also confirm macOS has Bluetooth permission for your terminal app\n  (System Settings → Privacy \u0026 Security → Bluetooth).\n- **Connect hangs with no output** — almost always the phone re-grabbed the\n  band, or a wedged Mac connection. Cycle the Mac's Bluetooth and retry.\n- **Connects but no HR values** — make sure the band is actually on your wrist;\n  the optical sensor won't fire in open air.\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmyselfshravan%2Fmiband-lit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmyselfshravan%2Fmiband-lit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmyselfshravan%2Fmiband-lit/lists"}