Recreate all functionality from the Nimly Connect app in Home Assistant:
- PIN codes (set/change/delete) without Zigbee sleepy device issues
- Event history with user identification
- Lock/unlock via cloud as backup
- APK
com.easyaccess.connectv1.27.84 - React Native with Hermes bytecode → 3.1M lines of decompiled JS
- Found all API endpoints, auth flow, CAS protocol
- Result: Complete API spec in
docs/nimly-connect-app/iotiliti-api-spec.yaml
- Keyfree, Salus, Forebygg, Homely, Copiax, Tekam, iotiliti
- All use identical codebase, only config varies
- Found new prod URL:
api.customer.prod-neutralclone.onesti.aws.neurosys.pro - Found Developer Options menu, LF's separate Keycloak realm
- Result: Complete overview in
docs/nimly-connect-app/reversing-notes.md
- Boot sequence: DNS →
boot-v2.onesti.io→ NTP → MQTT (port 8883) - MQTT broker:
3.75.35.23(AWS eu-central-1), self-signed cert - Software stack: Embedded Linux, Dropbear SSH 2020.81, OpenSSL 1.1.1+
- Result: Complete in
docs/connect-bridge/hardware-gateway.md
- OAuth2 auth works (
POST /oauth/v2/token) /locations— returns locations/locations/{id}/users— returns users/users/me— returns profile/devices/{id}— exists, requires GUID- Result: Auth and user data works
- The lock was removed from ZHA and paired with the Connect Bridge
- The app can lock/unlock and shows gateway + Touch Pro
- PIN codes survive re-pairing (stored locally on the lock)
This is the main blocker. GET /locations/{id}/group-devices returns an empty array even though the app shows devices (gateway + Touch Pro) under the same location.
Tested with:
- Fresh OAuth2 token
- Both location IDs (HusA and Hus)
- Old URL (
api-neutralclone.iotiliti.cloud) - New URL (
api.customer.prod-neutralclone.onesti.aws.neurosys.pro) - With and without
X-Company-Idheader
All return []. The app uses the exact same endpoint (verified in decompiled code).
Possible causes:
- Server-side access control we don't understand
- Token is missing a claim/scope that the app's token has
- The app sets up something during onboarding that grants device access
- Devices are tied to the gateway ID, not the location ID
- There is a race condition — devices appear after a polling cycle
We tried to see the actual HTTP traffic from the app:
| Method | Result |
|---|---|
| mitmproxy + proxy on phone | App refuses (does not trust user CA, targetSdk=35) |
| apk-mitm (patch APK) | Crashes — Ezviz SDK NullPointerException + NinePatch drawable corruption |
| apk-mitm --skip-patches | Still crashes (NinePatch) |
| PCAPdroid | Captures only hostnames/IPs, not URL paths (TLS) |
| React Native DevTools | Release build, no debug port |
| adb backup | App blocks backup (allowBackup=false) |
| run-as | Package not debuggable |
You need to see what the app actually sends — HTTP method, path, headers, body. Choose one of these approaches:
- Set up an Android emulator (x86_64, Google APIs, not a Play Store image)
- Emulator images with Google APIs have root via
adb root - Install mitmproxy CA as system cert:
adb push cert.pem /system/etc/security/cacerts/ - Install Nimly Connect APK
- Set proxy, capture all traffic
- Advantage: Easiest, no patching needed
- Download
frida-gadgetfor arm64 from GitHub releases - Use
objection patchapk(requires x64 machine for apktool, or Docker) - Use
--skip-resources --ignore-nativelibsto avoid NinePatch crash - Hook
OkHttp3orfetchto log all requests - Advantage: Works on a real device, sees request+response
apktool donly the base APK (not split APKs)- Add
networkSecurityConfigthat trusts user CAs - DO NOT patch OkHttp or other classes
apktool b, sign, install together with unmodified split APKs- Advantage: Avoids apk-mitm's destructive changes
- Write an email to Onesti (contact info at onestiproducts.io)
- Ask about API documentation for integration partners
- Mention that we are building an open-source HA integration
- Advantage: Official support, no reversing needed
Once you have the device ID (GUID):
# PIN setting via cloud (bypasses Zigbee sleepy device)
POST /devices/{deviceId}/access
Authorization: Bearer <token>
{"type": "pin", "code": "1234", "userId": "..."}
# Event history
GET /devices/{deviceId}/event-history
Authorization: Bearer <token>
# Lock/unlock
POST /devices/{deviceId}/lock
Authorization: Bearer <token>
{"action": "lock"} # or "unlock"The integration should be a hybrid — ZHA for local control, cloud API for PIN setting and event history.
| File | Contents |
|---|---|
docs/nimly-connect-app/reversing-notes.md |
Complete APK reversing |
docs/nimly-connect-app/iotiliti-api-spec.yaml |
OpenAPI spec (unverified) |
docs/nimly-connect-app/app-architecture.md |
System architecture and white-label |
docs/connect-bridge/hardware-gateway.md |
Hub hardware and network analysis |
docs/slot-numbering.md |
Slot numbering uncertainty |
docs/debugging.md |
Debugging guide |
secrets.md (gitignored) |
All client secrets, company IDs, test credentials |
reversing/ |
APK files and decompiled code |
- Decompiled Nimly Connect, found API endpoints
- Decompiled BLE app, documented ekey protocol
- Wireshark boot capture, paired lock with hub, tested cloud API, decompiled all white-label apps, MITM attempts failed, options flow UX improved