{"id":50332968,"url":"https://github.com/dzerik/nivona-ble-emulator","last_synced_at":"2026-05-29T11:01:12.349Z","repository":{"id":359135259,"uuid":"1244694809","full_name":"dzerik/nivona-ble-emulator","owner":"dzerik","description":"ESP32 BLE peripheral emulator for Nivona/Eugster coffee machines — protocol-accurate impersonation for offline development","archived":false,"fork":false,"pushed_at":"2026-05-20T14:07:17.000Z","size":92,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-20T19:11:00.988Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dzerik.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"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-05-20T14:02:43.000Z","updated_at":"2026-05-20T14:11:58.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/dzerik/nivona-ble-emulator","commit_stats":null,"previous_names":["dzerik/nivona-ble-emulator"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/dzerik/nivona-ble-emulator","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dzerik%2Fnivona-ble-emulator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dzerik%2Fnivona-ble-emulator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dzerik%2Fnivona-ble-emulator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dzerik%2Fnivona-ble-emulator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dzerik","download_url":"https://codeload.github.com/dzerik/nivona-ble-emulator/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dzerik%2Fnivona-ble-emulator/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33648534,"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-05-29T02:00:06.066Z","response_time":107,"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":[],"created_at":"2026-05-29T11:01:11.309Z","updated_at":"2026-05-29T11:01:12.218Z","avatar_url":"https://github.com/dzerik.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Nivona BLE Emulator\n\n[![build](https://github.com/dzerik/nivona-ble-emulator/actions/workflows/build.yml/badge.svg)](https://github.com/dzerik/nivona-ble-emulator/actions/workflows/build.yml)\n\n**Version:** see [`VERSION`](VERSION) · **Changelog:**\n[`CHANGELOG.md`](CHANGELOG.md) · **Tags:** `emu-v\u003cX.Y.Z\u003e`\n\n\u003e Companion project to the\n\u003e [`dzerik/melitta-barista-ha`](https://github.com/dzerik/melitta-barista-ha)\n\u003e Home Assistant integration. The emulator lived inside that repo under\n\u003e `esp_emulator/` from v0.44.0 through v0.51.0; it was split out for\n\u003e independent release cadence, cleaner HACS indexing, and so its\n\u003e ESP-IDF build pipeline doesn't compete with the integration's pytest\n\u003e CI. Git history (11 commits, 6 tags `emu-v0.2.0` → `emu-v0.7.0`) was\n\u003e preserved via `git subtree split`.\n\nA BLE peripheral emulator that impersonates a Nivona NICR/NIVO coffee machine\non ESP32, for offline development of the Home Assistant `melitta_barista`\nintegration without access to a real machine. Also works with the official\nNivona Android app for protocol reverse-engineering.\n\nImplements the full Eugster BLE protocol (service `AD00`) — HU handshake,\nRC4 frame encryption, all documented `H*` commands, per-family recipe\nlayouts, and a finite-state machine that simulates brewing cycles.\n\n## Hardware\n\nPrimary target: **Seeed XIAO ESP32-C6** (onboard PCB antenna or external\nU.FL — GPIO14 switches between them, see `main/main.c::xiao_c6_rf_init`).\n\nAlso builds for **Seeed XIAO ESP32-S3 / S3 Plus** with minor changes to\n`sdkconfig.defaults` (target + PSRAM config).\n\n## Features\n\n| Layer          | Implementation status |\n| -------------- | --------------------- |\n| Advertising    | Random static address (F1:…), mfg data `0x0D` + customer filter |\n| DIS (`180A`)   | Manufacturer / model / serial / hw / fw / sw revisions          |\n| GATT `AD00`    | AD01 (control), AD02 (notify), AD03 (write), AD04/5 (stub), AD06 (name) |\n| Security       | Just Works pairing + bonding (NVS-persistent)                   |\n| Framing        | `S + cmd + [kp] + payload + cs + E`, RC4, MTU chunking, per-cmd size gating |\n| HU handshake   | Full 2-round verifier + session key negotiation                 |\n| Commands       | HX (status), HV, HL, HI, HS, HR/HW, HA/HB, HE (brew), HZ, HY, HD, HN, Hp, HC/HJ (stub) |\n| FSM            | process / sub_process / info / manipulation / progress          |\n| Brew cycle     | READY → PRODUCT, progress 0→100%, async unsolicited HX pushes   |\n| Storage        | Numerical + alphanumeric registers persisted in NVS             |\n| Family switch  | CLI `family` command selects 600/700/79x/900/900-light/1030/1040/8000 |\n\n## Prerequisites\n\n- **ESP-IDF 5.4+** — tested with v5.4.1 RISC-V toolchain\n- Host serial access to the board (native USB Serial/JTAG)\n- WiFi network reachable by the board (for OTA + telnet)\n\n## Setup\n\n```bash\n# 1. Set up WiFi credentials (one-time, never committed)\ncp main/wifi_secrets.h.template main/wifi_secrets.h\n$EDITOR main/wifi_secrets.h    # fill WIFI_SSID / WIFI_PASS\n\n# 2. Configure ESP-IDF\n. $IDF_PATH/export.sh\nidf.py set-target esp32c6      # or esp32s3 — edit sdkconfig.defaults accordingly\n\n# 3. Build and flash\nidf.py build\nidf.py -p /dev/ttyACM0 flash monitor\n```\n\nAfter the first USB flash, all subsequent updates go over the air:\n```bash\ncurl -X POST --data-binary @build/nivona_emulator.bin \\\n     http://nivona-emu.local/ota\n```\n\n## Runtime endpoints\n\n| Endpoint                        | Purpose                                                     |\n| ------------------------------- | ----------------------------------------------------------- |\n| `GET  http://\u003chost\u003e/`           | Firmware version, IDF version, compile time                 |\n| `GET  http://\u003chost\u003e/diag`       | JSON of all diagnostic counters (connects, frames, HU, HX, …) |\n| `POST http://\u003chost\u003e/ota`        | Flash a new `nivona_emulator.bin` — body is the raw binary  |\n| `POST http://\u003chost\u003e/reboot`     | Reboot the device                                           |\n| `telnet \u003chost\u003e 23`              | Interactive CLI — see commands below                        |\n\nmDNS: the board announces itself as `MDNS_HOSTNAME.local` (default\n`nivona-emu.local`), so you can use that instead of an IP.\n\n## CLI commands (telnet or USB console)\n\n```\nhelp                     list registered commands\nstatus                   show FSM state (process, sub_process, manip, progress)\ndiag                     show diagnostic counters\nbrew \u003cpv\u003e                start a brew cycle with the given process value\ncancel                   cancel active brew\ntrigger \u003cm\u003e              set a manipulation (water_empty / beans_empty /\n                         tray_full / clean / descale / none)\ndump                     dump persisted register store\nfamily \u003ckey\u003e             switch emulated family — 600 / 700 / 79x / 900 /\n                         900-light / 1030 / 1040 / 8000. Takes effect on reboot.\npair                     enter pairing mode (wipes bonds, restarts advertising)\nforget                   wipe stored BLE bonds\nreboot                   reboot the device\n```\n\n## Testing\n\nPython tests in `tests/test_emulator.py` cover protocol helpers, HTTP\ndiagnostics, and full BLE round-trips. Run against a live board:\n\n```bash\npip install bleak pytest pytest-asyncio requests\nEMU_IP=192.168.1.29 EMU_MAC=F1:32:04:33:52:DA python3 tests/test_emulator.py\n# or\npython3 -m pytest tests/ -v -s\n```\n\n## Architecture\n\n```\nmain/\n├── main.c              application entry: lifecycle, RF switch, SM config\n├── nivona_ble.c/h      advertising, GAP events, scan response\n├── nivona_gatt.c/h     GATT service AD00 with 6 characteristics\n├── nivona_dis.c/h      Device Information Service (180A)\n├── nivona_frame.c/h    framing (S/E, checksum, RC4, per-cmd size gating)\n├── nivona_crypto.c/h   RC4 + HU verifier + HU lookup table\n├── nivona_dispatch.c/h command router with HU / HX / HV / HL / HI / HR / HW / …\n├── nivona_fsm.c/h      process state machine (thread-safe)\n├── nivona_store.c/h    numerical + alphanumeric register store with NVS\n├── nivona_brew.c/h     async brew cycle task, unsolicited HX pushes\n├── nivona_cli.c/h      esp_console REPL (USB Serial/JTAG)\n├── nivona_wifi.c/h     WiFi STA + mDNS\n├── nivona_ota.c/h      HTTP server (status / diag / ota / reboot)\n└── nivona_telnet.c/h   TCP:23 bridge to esp_console + log mirroring\n```\n\n## Protocol references\n\n- Upstream reverse engineering (field notes + reference client):\n  `https://github.com/mpapierski/esp-coffee-bridge` — especially `docs/NIVONA.md`\n- HA integration — source of truth for protocol constants:\n  `../custom_components/melitta_barista/protocol.py`\n  `../custom_components/melitta_barista/brands/nivona.py`\n\n## Security notes\n\n- `main/wifi_secrets.h` is `.gitignore`-d. Never commit it.\n- The emulator accepts any pairing request (Just Works). Don't expose it\n  on an untrusted network.\n- OTA has no authentication. Keep port 80 on a trusted LAN.\n\n## License\n\nMIT — see [`LICENSE`](LICENSE). Matches the parent\n`melitta-barista-ha` integration.\n\n## Legal \u0026 trademarks\n\nThis is a research and interoperability tool. It is **not**\naffiliated with, endorsed by, or sponsored by Nivona Apparate GmbH\nor Eugster / Frismag AG. \"Nivona\", \"NICR\" and \"NIVO\" are\ntrademarks of their respective owners, used here descriptively\nonly — see [`LEGAL.md`](LEGAL.md) for the full position statement,\nthe EU/US/RU legal basis, and the cooperation channels for any\nlegitimate concern.\n\n## Security\n\nVulnerability reports go through GitHub Security Advisories, not\npublic issues — see [`SECURITY.md`](SECURITY.md).\n\n## Contributing\n\nSee [`CONTRIBUTING.md`](CONTRIBUTING.md) for the build quickstart,\nbranching/commit conventions, and PR checklist.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdzerik%2Fnivona-ble-emulator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdzerik%2Fnivona-ble-emulator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdzerik%2Fnivona-ble-emulator/lists"}