Android: app shell + Custom Settings engine + Home Assistant integration
Generic settings architecture (data-driven, no per-setting UI code):
- SettingDefinition (id/name/description/category/type/default/min/max/
choices/editable/sensitive/requiresRestart/visibleIf) x 8 types:
boolean/string/number/select/multi-select/color/url/secret
- SettingsManager: validation, reset, custom CRUD, export/import
(all-or-nothing with per-key rejection reasons; custom definitions
travel in the export), secrets routed to EncryptedSharedPreferences
(AndroidKeyStore master key) and excluded from export by default
- Settings screen renders controls from the type; search; add/edit/delete
dialogs for custom settings; import/export dialogs
- Built-ins: General / Home Assistant / Appearance / Network / Advanced
Home Assistant integration (official REST + WebSocket APIs only):
- HomeAssistantClient: GET /api/, /api/states, /api/states/{id},
POST /api/services/{domain}/{service}, WS /api/websocket
(auth -> subscribe_events state_changed) with exponential-backoff
reconnect; safe HaError types that never contain the token
- HaRepository: single service layer between UI and client; optimistic
toggles reconciled by WS; poll fallback from refresh-interval setting
- Devices screen: discovery list, search, live states, toggles
(light/switch/fan/input_boolean/humidifier)
- Cleartext permitted for LAN http:// URLs (same stance as the official
HA companion app; TLS verification untouched); consent dialog on first
launch; Record button present but inert until M4 (honest label)
- Tests: 29 unit tests (19 settings incl. secret routing, import
validation, persistence; 10 HA via MockWebServer incl. 401 handling,
unreachable, WS handshake + event + auth_invalid). All green.
Docs: docs/home-assistant.md (install, token creation, storage,
troubleshooting, endpoint table); README + ROADMAP updated honestly
(live e2e against a real HA server still in progress).
This commit is contained in:
parent
b1151c5ec8
commit
4d5402946d
35 changed files with 3389 additions and 4 deletions
|
|
@ -10,6 +10,8 @@ updated in the same commit as the work it describes.
|
|||
| M1 | Auth: register / login / rotating refresh + reuse detection / logout / delete-account, Argon2id, rate limits | done |
|
||||
| M2 | Upload sessions (chunked, resumable), storage abstraction (local + S3), recordings CRUD, ownership checks | done |
|
||||
| M3 | Android: server URL config, login, token persistence + auto-refresh | TODO |
|
||||
| HA-1 | Android: generic Custom Settings engine (8 types, validation, custom CRUD, import/export, secure storage) + Home Assistant integration (REST + WebSocket, discovery, service calls, reconnect) — 29 unit tests green, APK builds | done |
|
||||
| HA-2 | Home Assistant live end-to-end against a real server (token minting flow blocked; unit + MockWebServer coverage only) | in progress |
|
||||
| M4 | Android: foreground-service recording (pause/resume/stop), metadata, Room | TODO |
|
||||
| M5 | Android: WorkManager upload sync (retry, Wi-Fi-only, charging-only, pause) | TODO |
|
||||
| M6 | Android: library (search/filter/sort), playback (seek/speed), waveform, download/delete | TODO |
|
||||
|
|
|
|||
106
docs/home-assistant.md
Normal file
106
docs/home-assistant.md
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
# Home Assistant integration — guide
|
||||
|
||||
SHONAR can talk to a **local** Home Assistant server over Home Assistant's
|
||||
official REST and WebSocket APIs. No cloud, no third-party wrappers, no
|
||||
telemetry — the app talks only to the URL you configure.
|
||||
|
||||
## 1. Install Home Assistant locally
|
||||
|
||||
Any of these works; see <https://www.home-assistant.io/installation/>:
|
||||
|
||||
- **Home Assistant OS** on a Raspberry Pi / VM (recommended)
|
||||
- **Container:** `docker run -d --name homeassistant --restart=unless-stopped \
|
||||
-v ./config:/config -e TZ=UTC --network=host ghcr.io/home-assistant/home-assistant:stable`
|
||||
- **Core:** Python venv install (advanced)
|
||||
|
||||
After first start the UI is at `http://<host>:8123`.
|
||||
|
||||
## 2. Create a long-lived access token
|
||||
|
||||
Home Assistant requires a long-lived access token for third-party apps
|
||||
(SHONAR cannot mint one for you — that's an HA security design):
|
||||
|
||||
1. Open the HA web UI and log in.
|
||||
2. Click your **profile picture** (bottom-left) → **Security**.
|
||||
3. Scroll to **Long-lived access tokens** → **Create Token**.
|
||||
4. Name it (e.g. `SHONAR`) and **copy the token immediately** — it is shown
|
||||
once.
|
||||
|
||||
## 3. Where to enter the URL and token
|
||||
|
||||
SHONAR app → **Settings** → **Home Assistant** category:
|
||||
|
||||
1. Toggle **Enable Home Assistant** on.
|
||||
2. **Home Assistant URL** — e.g. `http://homeassistant.local:8123` or
|
||||
`http://192.168.1.50:8123`.
|
||||
3. **Long-lived access token** — paste the token. Stored encrypted on the
|
||||
device (AndroidKeyStore-backed EncryptedSharedPreferences); never in
|
||||
plaintext, never logged, never included in settings export unless you
|
||||
explicitly request it.
|
||||
|
||||
The **Devices** tab then shows your entities. The connection status appears
|
||||
there: not configured / connecting / connected / error (with a plain-language
|
||||
message you can act on).
|
||||
|
||||
## 4. Test the connection
|
||||
|
||||
- Settings → Home Assistant has the URL/token fields; the Devices screen
|
||||
doubles as the live connection test — hit the refresh icon.
|
||||
- Or from a shell on the same network:
|
||||
`curl -H "Authorization: Bearer <token>" http://<ha-host>:8123/api/`
|
||||
|
||||
## 5. How entity discovery works
|
||||
|
||||
`GET /api/states` returns every entity; the app lists them with friendly
|
||||
names, current state, and a search box. Entities whose domain is switchable
|
||||
(`light`, `switch`, `fan`, `input_boolean`, `humidifier`) get a toggle that
|
||||
calls `POST /api/services/<domain>/turn_on|turn_off` with `entity_id`.
|
||||
Toggles apply optimistically and are reconciled by the live stream.
|
||||
|
||||
Live updates: a WebSocket to `/api/websocket` authenticates and subscribes to
|
||||
`state_changed` events. On disconnect it reconnects with exponential backoff
|
||||
(2s → 60s). A configurable poll fallback (`State refresh interval`, used when
|
||||
the WebSocket is down) keeps the list fresh.
|
||||
|
||||
## 6. How custom settings work
|
||||
|
||||
All settings are data-driven (`SettingDefinition`), not hard-coded UI. Each
|
||||
has an id, name, description, category, type (boolean/string/number/select/
|
||||
multi-select/color/url/secret), default, optional min/max/choices,
|
||||
editability, sensitivity, restart requirement, and conditional visibility.
|
||||
|
||||
Settings → **Add (+)** creates a custom setting with any type; **delete**
|
||||
works on custom settings only (built-ins can be reset but not removed).
|
||||
Search filters across name/id/description. **Export** produces JSON with
|
||||
secrets omitted by default; **Import** validates every entry against known
|
||||
definitions and applies all-or-nothing, reporting per-key rejection reasons.
|
||||
|
||||
## 7. How settings are stored
|
||||
|
||||
| Kind | Location |
|
||||
|---|---|
|
||||
| Ordinary values + custom definitions | Jetpack DataStore (`shonar_settings`) |
|
||||
| Sensitive values (HA token, secret-type settings) | EncryptedSharedPreferences, AES256-GCM values / AES256-SIV keys, master key in AndroidKeyStore (never exportable) |
|
||||
|
||||
## 8. Troubleshooting
|
||||
|
||||
| Symptom | Likely cause / fix |
|
||||
|---|---|
|
||||
| "Authentication failed…" | Token expired/revoked/typo. Create a fresh long-lived access token (step 2). |
|
||||
| "Cannot reach the Home Assistant server" | Wrong URL/IP, phone on different network/VLAN, HA not running, or a firewall blocking 8123. Try the curl test above from the phone's network. |
|
||||
| Cleartext HTTP warning | The app permits cleartext HTTP (like the official Home Assistant Android app) because LAN servers are plain `http://` by default. Android cannot restrict cleartext to IP *ranges* — use `https://` for any non-LAN host; certificate verification is never disabled. |
|
||||
| Entities stale | WebSocket dropped; poll fallback updates after the configured interval; refresh icon forces a reload. |
|
||||
| mDNS name not resolving | Use the IP address instead of `homeassistant.local` (phone's mDNS may be limited by the router). |
|
||||
|
||||
## 9. Endpoints used (official API only)
|
||||
|
||||
| Method | Path | Purpose |
|
||||
|---|---|---|
|
||||
| GET | `/api/` | connection/config test |
|
||||
| GET | `/api/states` | entity discovery |
|
||||
| GET | `/api/states/{entity_id}` | single state |
|
||||
| POST | `/api/services/{domain}/{service}` | service/action calls |
|
||||
| WS | `/api/websocket` | `auth`, `subscribe_events(state_changed)` |
|
||||
|
||||
References: REST <https://developers.home-assistant.io/docs/api/rest/>,
|
||||
WebSocket <https://developers.home-assistant.io/docs/api/websocket/>.
|
||||
Loading…
Add table
Add a link
Reference in a new issue