dzerik/nivona-ble-emulator

GitHub: dzerik/nivona-ble-emulator

Stars: 1 | Forks: 0

# Nivona BLE Emulator [![build](https://static.pigsec.cn/wp-content/uploads/repos/2026/05/e70da93851054448.svg)](https://github.com/dzerik/nivona-ble-emulator/actions/workflows/build.yml) **Version:** see [`VERSION`](VERSION) · **Changelog:** [`CHANGELOG.md`](CHANGELOG.md) · **Tags:** `emu-v` A BLE peripheral emulator that impersonates a Nivona NICR/NIVO coffee machine on ESP32, for offline development of the Home Assistant `melitta_barista` integration without access to a real machine. Also works with the official Nivona Android app for protocol reverse-engineering. Implements the full Eugster BLE protocol (service `AD00`) — HU handshake, RC4 frame encryption, all documented `H*` commands, per-family recipe layouts, and a finite-state machine that simulates brewing cycles. ## Hardware Primary target: **Seeed XIAO ESP32-C6** (onboard PCB antenna or external U.FL — GPIO14 switches between them, see `main/main.c::xiao_c6_rf_init`). Also builds for **Seeed XIAO ESP32-S3 / S3 Plus** with minor changes to `sdkconfig.defaults` (target + PSRAM config). ## Features | Layer | Implementation status | | -------------- | --------------------- | | Advertising | Random static address (F1:…), mfg data `0x0D` + customer filter | | DIS (`180A`) | Manufacturer / model / serial / hw / fw / sw revisions | | GATT `AD00` | AD01 (control), AD02 (notify), AD03 (write), AD04/5 (stub), AD06 (name) | | Security | Just Works pairing + bonding (NVS-persistent) | | Framing | `S + cmd + [kp] + payload + cs + E`, RC4, MTU chunking, per-cmd size gating | | HU handshake | Full 2-round verifier + session key negotiation | | Commands | HX (status), HV, HL, HI, HS, HR/HW, HA/HB, HE (brew), HZ, HY, HD, HN, Hp, HC/HJ (stub) | | FSM | process / sub_process / info / manipulation / progress | | Brew cycle | READY → PRODUCT, progress 0→100%, async unsolicited HX pushes | | Storage | Numerical + alphanumeric registers persisted in NVS | | Family switch | CLI `family` command selects 600/700/79x/900/900-light/1030/1040/8000 | ## Prerequisites - **ESP-IDF 5.4+** — tested with v5.4.1 RISC-V toolchain - Host serial access to the board (native USB Serial/JTAG) - WiFi network reachable by the board (for OTA + telnet) ## Setup # 1. Set up WiFi credentials (one-time, never committed) cp main/wifi_secrets.h.template main/wifi_secrets.h $EDITOR main/wifi_secrets.h # fill WIFI_SSID / WIFI_PASS # 2. Configure ESP-IDF . $IDF_PATH/export.sh idf.py set-target esp32c6 # or esp32s3 — edit sdkconfig.defaults accordingly # 3. Build and flash idf.py build idf.py -p /dev/ttyACM0 flash monitor After the first USB flash, all subsequent updates go over the air: curl -X POST --data-binary @build/nivona_emulator.bin \ http://nivona-emu.local/ota ## Runtime endpoints | Endpoint | Purpose | | ------------------------------- | ----------------------------------------------------------- | | `GET http:///` | Firmware version, IDF version, compile time | | `GET http:///diag` | JSON of all diagnostic counters (connects, frames, HU, HX, …) | | `POST http:///ota` | Flash a new `nivona_emulator.bin` — body is the raw binary | | `POST http:///reboot` | Reboot the device | | `telnet 23` | Interactive CLI — see commands below | mDNS: the board announces itself as `MDNS_HOSTNAME.local` (default `nivona-emu.local`), so you can use that instead of an IP. ## CLI commands (telnet or USB console) help list registered commands status show FSM state (process, sub_process, manip, progress) diag show diagnostic counters brew start a brew cycle with the given process value cancel cancel active brew trigger set a manipulation (water_empty / beans_empty / tray_full / clean / descale / none) dump dump persisted register store family switch emulated family — 600 / 700 / 79x / 900 / 900-light / 1030 / 1040 / 8000. Takes effect on reboot. pair enter pairing mode (wipes bonds, restarts advertising) forget wipe stored BLE bonds reboot reboot the device ## Testing Python tests in `tests/test_emulator.py` cover protocol helpers, HTTP diagnostics, and full BLE round-trips. Run against a live board: pip install bleak pytest pytest-asyncio requests EMU_IP=192.168.1.29 EMU_MAC=F1:32:04:33:52:DA python3 tests/test_emulator.py # or python3 -m pytest tests/ -v -s ## Architecture main/ ├── main.c application entry: lifecycle, RF switch, SM config ├── nivona_ble.c/h advertising, GAP events, scan response ├── nivona_gatt.c/h GATT service AD00 with 6 characteristics ├── nivona_dis.c/h Device Information Service (180A) ├── nivona_frame.c/h framing (S/E, checksum, RC4, per-cmd size gating) ├── nivona_crypto.c/h RC4 + HU verifier + HU lookup table ├── nivona_dispatch.c/h command router with HU / HX / HV / HL / HI / HR / HW / … ├── nivona_fsm.c/h process state machine (thread-safe) ├── nivona_store.c/h numerical + alphanumeric register store with NVS ├── nivona_brew.c/h async brew cycle task, unsolicited HX pushes ├── nivona_cli.c/h esp_console REPL (USB Serial/JTAG) ├── nivona_wifi.c/h WiFi STA + mDNS ├── nivona_ota.c/h HTTP server (status / diag / ota / reboot) └── nivona_telnet.c/h TCP:23 bridge to esp_console + log mirroring ## Protocol references - Upstream reverse engineering (field notes + reference client): `https://github.com/mpapierski/esp-coffee-bridge` — especially `docs/NIVONA.md` - HA integration — source of truth for protocol constants: `../custom_components/melitta_barista/protocol.py` `../custom_components/melitta_barista/brands/nivona.py` ## Security notes - `main/wifi_secrets.h` is `.gitignore`-d. Never commit it. - The emulator accepts any pairing request (Just Works). Don't expose it on an untrusted network. - OTA has no authentication. Keep port 80 on a trusted LAN. ## License MIT — see [`LICENSE`](LICENSE). Matches the parent `melitta-barista-ha` integration. ## Legal & trademarks This is a research and interoperability tool. It is **not** affiliated with, endorsed by, or sponsored by Nivona Apparate GmbH or Eugster / Frismag AG. "Nivona", "NICR" and "NIVO" are trademarks of their respective owners, used here descriptively only — see [`LEGAL.md`](LEGAL.md) for the full position statement, the EU/US/RU legal basis, and the cooperation channels for any legitimate concern. ## Security Vulnerability reports go through GitHub Security Advisories, not public issues — see [`SECURITY.md`](SECURITY.md).
标签:客户端加密