@homebridge-plugins/homebridge-eufy-security 4.6.0-beta.9 → 4.6.1-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/.claude/CLAUDE.md +175 -0
  2. package/.claude/PRD.md +241 -0
  3. package/.claude/docs/debug-recording-lessons.md +91 -0
  4. package/.claude/docs/hksv-recording-fix.md +160 -0
  5. package/.claude/skills/architect/SKILL.md +76 -0
  6. package/.claude/skills/developer/SKILL.md +59 -0
  7. package/.claude/skills/new-device-support/SKILL.md +101 -51
  8. package/.claude/skills/new-device-support/check-device.mjs +363 -0
  9. package/.claude/skills/new-device-support/map-properties.mjs +99 -10
  10. package/.claude/skills/new-device-support/verify-device.mjs +272 -0
  11. package/.claude/skills/planner/SKILL.md +100 -0
  12. package/.claude/skills/qa/SKILL.md +79 -0
  13. package/.claude/skills/support/SKILL.md +175 -0
  14. package/dist/accessories/CameraAccessory.js +20 -20
  15. package/dist/accessories/CameraAccessory.js.map +1 -1
  16. package/dist/controller/DebugRecordingManager.js +391 -0
  17. package/dist/controller/DebugRecordingManager.js.map +1 -0
  18. package/dist/controller/LocalLivestreamManager.js +230 -11
  19. package/dist/controller/LocalLivestreamManager.js.map +1 -1
  20. package/dist/controller/recordingDelegate.js +22 -8
  21. package/dist/controller/recordingDelegate.js.map +1 -1
  22. package/dist/controller/snapshotDelegate.js +5 -1
  23. package/dist/controller/snapshotDelegate.js.map +1 -1
  24. package/dist/controller/streamingDelegate.js +14 -7
  25. package/dist/controller/streamingDelegate.js.map +1 -1
  26. package/dist/platform.js +7 -23
  27. package/dist/platform.js.map +1 -1
  28. package/dist/settings.js +7 -0
  29. package/dist/settings.js.map +1 -1
  30. package/dist/utils/Talkback.js +127 -55
  31. package/dist/utils/Talkback.js.map +1 -1
  32. package/dist/utils/ffmpeg.js +141 -28
  33. package/dist/utils/ffmpeg.js.map +1 -1
  34. package/dist/utils/utils.js +10 -1
  35. package/dist/utils/utils.js.map +1 -1
  36. package/dist/version.js +1 -1
  37. package/homebridge-ui/public/app.js +5 -0
  38. package/homebridge-ui/public/assets/devices/batterydoorbell2k_large.png +0 -0
  39. package/homebridge-ui/public/assets/devices/eufyCamS4_large.png +0 -0
  40. package/homebridge-ui/public/assets/devices/homebase3_large.png +0 -0
  41. package/homebridge-ui/public/assets/devices/nvr_s4_max_T8N00_large.png +0 -0
  42. package/homebridge-ui/public/assets/devices/poe_bullet_ptz_cam_s4_T8E00_large.png +0 -0
  43. package/homebridge-ui/public/index.html +1 -0
  44. package/homebridge-ui/public/services/api.js +36 -0
  45. package/homebridge-ui/public/utils/device-images.js +2 -0
  46. package/homebridge-ui/public/views/diagnostics.js +12 -0
  47. package/homebridge-ui/public/views/recordings.js +319 -0
  48. package/homebridge-ui/server.js +178 -31
  49. package/package.json +12 -12
  50. package/scripts/decrypt-diagnostics.mjs +6 -7
@@ -0,0 +1,175 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with the homebridge-eufy-security plugin.
4
+
5
+ ## Project Overview
6
+
7
+ Homebridge plugin that exposes Eufy Security devices to Apple HomeKit. Published as `@homebridge-plugins/homebridge-eufy-security` under the `homebridge-plugins` GitHub organization.
8
+
9
+ Depends on `eufy-security-client` (upstream: bropat/eufy-security-client) for cloud API, P2P, push notifications, and MQTT communication.
10
+
11
+ For detailed functional requirements, device coverage, configuration options, and architecture boundaries, see `.claude/PRD.md`.
12
+
13
+ ## Build & Dev Commands
14
+
15
+ ```bash
16
+ npm run build # rimraf dist -> tsc -> copy media/ to dist/
17
+ npm run build-plugin # rimraf dist -> tsc (no media copy)
18
+ npm run lint # eslint 'src/**/*.ts' --max-warnings=0
19
+ npm run lint-fix # eslint with --fix
20
+ ```
21
+
22
+ - No test suite -- there are no unit or integration tests
23
+ - Output: `dist/`
24
+ - `--max-warnings=0` is enforced -- all warnings must be fixed before committing
25
+ - ESLint uses flat config (`eslint.config.mjs`); `@typescript-eslint/no-explicit-any` is globally disabled -- do not add eslint-disable comments for it
26
+ - Run `npm run lint` and `npm run build` before pushing
27
+
28
+ ## Architecture
29
+
30
+ Entry point `src/index.ts` registers `EufySecurityPlatform` with Homebridge. The platform class (`src/platform.ts`) is the core -- it initializes the `EufySecurity` client, discovers devices, and creates HomeKit accessories.
31
+
32
+ **Accessory classes** in `src/accessories/` map Eufy device types to HomeKit services:
33
+ - `BaseAccessory.ts` -- root base class (characteristic registration, service pruning)
34
+ - `Device.ts` (`DeviceAccessory`) -- extends `BaseAccessory`; adds sensor/battery services, property helpers
35
+ - `CameraAccessory` -- cameras, doorbells, floodlights (handles streaming delegates); extends `DeviceAccessory`
36
+ - `StationAccessory` -- base stations (security system service for arm/disarm); extends `BaseAccessory` directly
37
+ - `LockAccessory`, `EntrySensorAccessory`, `MotionSensorAccessory`, `SmartDropAccessory` -- extend `DeviceAccessory`
38
+ - `AutoSyncStationAccessory` -- virtual accessory that syncs station guard mode with HomeKit
39
+
40
+ **Streaming pipeline** in `src/controller/`:
41
+ - `streamingDelegate.ts` -- HomeKit camera streaming (FFmpeg-based)
42
+ - `recordingDelegate.ts` -- HomeKit Secure Video recording
43
+ - `snapshotDelegate.ts` -- snapshot handling
44
+ - `LocalLivestreamManager.ts` -- manages P2P livestream sessions
45
+
46
+ **Utilities** in `src/utils/`: logging (`utils.ts`), FFmpeg wrapper (`ffmpeg.ts`), two-way audio (`Talkback.ts`), config schema (`configTypes.ts`), internal interfaces (`interfaces.ts`).
47
+
48
+ **Constants** in `src/settings.ts`: HKSV segment lengths, snapshot cache ages, streaming bitrate headroom, IDR intervals -- reference this when tuning streaming or recording behaviour.
49
+
50
+ **Plugin UI** in `homebridge-ui/`: `server.js` (plain JS, eslint-ignored) handles UI server logic and diagnostics generation. The UI and the plugin runtime (`src/`) are **separate processes** that share state through `accessories.json` on disk (written by `src/utils/accessoriesStore.ts`, read by `homebridge-ui/server.js`). Both independently import `eufy-security-client` types (`DeviceType`, `PropertyName`, `Device`, etc.) -- changes to the device/station record shape must be kept in sync between `accessoriesStore.ts` and `server.js`.
51
+
52
+ ### Key source files for device registration and triage
53
+
54
+ - `src/platform.ts` (`register_device`) -- device registration logic; devices can stack multiple capabilities (independent `if` blocks, not `else if`)
55
+
56
+ ## Key Technical Details
57
+
58
+ - ESM project (`"type": "module"`, `"module": "NodeNext"`) -- imports must use `.js` extensions (NodeNext resolution requires explicit extensions)
59
+ - TypeScript strict mode, ES2022 target (`noImplicitAny: false` relaxes implicit-any checks)
60
+ - Node.js 20, 22, or 24 required
61
+ - Homebridge >=1.9.0 or ^2.0.0-beta
62
+ - Uses `ffmpeg-for-homebridge` for video transcoding
63
+ - `src/version.ts` is auto-generated at prebuild time -- do not edit manually
64
+ - `prepublishOnly` runs lint + build automatically before `npm publish`
65
+ - For local development, `eufy-security-client` can be pointed to a local path (e.g. `"../eufy-security-client"`)
66
+
67
+ ## Git Workflow
68
+
69
+ **IMPORTANT: Create the branch BEFORE editing any files.**
70
+
71
+ ```bash
72
+ # Create dedicated branch from beta before making any changes
73
+ git checkout beta-*.*.* && git pull origin beta-*.*.*
74
+ git checkout -b [fix/feat/chore]/<short-description>
75
+
76
+ # Stage and commit each change individually
77
+ git add <file>
78
+ git commit -m "fix: <concise description of what changed and why>"
79
+
80
+ # Push and create PR
81
+ git push -u origin fix/<short-description>
82
+ gh pr create --base beta-*.*.* --title "<concise title>" --body-file /tmp/pr-body.md
83
+ ```
84
+
85
+ - Branch from `beta-*.*.*`, not `master`
86
+ - PR target: `beta-*.*.*`
87
+ - Branch naming: `fix/`, `feat/`, `chore/` prefix
88
+
89
+ ### Commit message rules
90
+
91
+ - Single line, no line breaks mid-sentence
92
+ - Describe the **spirit** of the change, not the code diff
93
+ - No Co-Authored-By
94
+
95
+ ### PR body
96
+
97
+ - Write to `/tmp/pr-body-<branch>.md` using file creation -- **never** use heredoc (`cat << EOF`) in the terminal (quotes and special characters break it)
98
+ - Describe the **spirit** of the change, not the code diff
99
+ - Concise description of the problem and fix
100
+ - PR body is **not** the release note -- keep it focused on the code change for reviewers
101
+ - No Co-Authored-By
102
+
103
+ ### Release notes
104
+
105
+ - Write to `/tmp/release-notes-<version>.md` using file creation
106
+ - **Audience is end users** -- focus on what matters to them: new devices, behaviour changes, removed settings, required actions
107
+ - **Concise, bullet-driven** -- no markdown tables, no verbose paragraphs. Short section intros (1-2 sentences max) followed by bullet lists
108
+ - **No internal milestones** -- don't mention "first GA since X" or beta iteration counts
109
+ - **Structure for a branch** (e.g., `4.4.x`), not a single version:
110
+ - Individual `## v4.4.x` entries at the top with version-specific changes
111
+ - A shared `## What's in 4.4.x` section below covering the full branch
112
+ - **Required actions front and center** -- if users need to change config or upgrade Node.js, say so early and clearly
113
+ - **Tone**: direct, no filler, no emojis
114
+
115
+ ### Issue comments
116
+
117
+ - Use `gh issue comment <number> --repo homebridge-plugins/homebridge-eufy-security --body "<message>"`
118
+ - Use first person ("I")
119
+ - Thank the user by @mention
120
+ - Be formal and concise
121
+ - **Audience is end users** -- keep language simple, no internal jargon
122
+ - Don't scope a fix to a specific device when it applies broadly
123
+ - When a fix is merged, check the published beta version (`npm view @homebridge-plugins/homebridge-eufy-security dist-tags`) and mention it in the comment
124
+
125
+ ## Dependency Policy -- `eufy-security-client`
126
+
127
+ **CRITICAL:**
128
+ - **`beta-*.*.*` branches** -- use the `dev` dist-tag (`"eufy-security-client": "dev"`)
129
+ - **`master` branch** -- **NEVER** use the `dev` dist-tag. Always use a pinned stable version (e.g. `"^3.7.2"`). Before merging to master, replace `"dev"` with the corresponding stable release version.
130
+
131
+ When updating the `eufy-security-client` version in `package.json`:
132
+ 1. Check the upstream changelog and README at https://github.com/bropat/eufy-security-client/blob/master/README.md
133
+ 2. Identify breaking changes, new features, or device support changes
134
+ 3. Summarize the impact in the PR body
135
+ 4. If there are breaking changes, note any required code adjustments
136
+
137
+ ## Skills
138
+
139
+ Skills in `.claude/skills/` provide structured workflows for common tasks:
140
+
141
+ | Skill | Purpose |
142
+ |---|---|
143
+ | `planner` | Build a step-by-step action plan with guardrails before coding |
144
+ | `developer` | Execute code changes following an approved plan or direct instructions |
145
+ | `qa` | Verify build, lint, imports, architecture, and git hygiene before pushing |
146
+ | `architect` | Quick codebase questions, mini-bug debugging, workflow improvement |
147
+ | `support` | Triage GitHub issues using diagnostics archives and logs |
148
+ | `new-device-support` | Full workflow to add a new Eufy device type across both repos |
149
+
150
+ Typical flow: **planner** -> **developer** -> **qa**. Use **architect** for exploration and **support** for issue triage.
151
+
152
+ ## Diagnostic Triage
153
+
154
+ For issue triage, use the `support` skill with an issue number (e.g. `/support 423`). For the full triage reference, see `.github/prompts/diag-triage.prompt.md`. For adding new device types, use the `new-device-support` skill. Key points:
155
+
156
+ - Diagnostics archives are encrypted (RSA-4096 + AES-256-GCM); decrypt with `node scripts/decrypt-diagnostics.mjs <file>.tar.gz`
157
+ - Always check archive completeness before analysis -- missing runtime logs means the plugin wasn't running
158
+ - When diagnostics are incomplete or debug is disabled, ask the user to upgrade to the latest [beta](https://github.com/homebridge-plugins/homebridge-eufy-security/wiki/Special-Version-(BETA---RC---HKSV)) and follow the [Basic Troubleshooting](https://github.com/homebridge-plugins/homebridge-eufy-security/wiki/Basic-Troubleshooting) steps to enable Detailed Logging, reproduce the issue, and re-export diagnostics
159
+ - Narrow down whether the issue is in `homebridge-eufy-security` (accessory registration, HomeKit mapping, config handling) or in `eufy-security-client` (device discovery, property events, P2P, push notifications)
160
+ - If the environment indicates HOOBS: label `hoobs` + `wontfix` and close -- HOOBS is not supported
161
+
162
+ ### Label recommendations
163
+
164
+ | Label | When to use |
165
+ |---|---|
166
+ | `depends on eufy-security-client` | Issue originates in bropat's eufy-security-client library |
167
+ | `device-support` | New device type support request |
168
+ | `debug log missing` | Diagnostics lack logs or debug mode was disabled |
169
+ | `configuration issue` | Problem caused by user config |
170
+ | `livestream` | Camera livestream, video feed, P2P, RTSP, streaming failures |
171
+ | `question` | Further information is requested |
172
+ | `resolved in beta` | Fix already in current beta |
173
+ | `needs triage` | New issue awaiting initial analysis |
174
+ | `duplicate` | Issue or PR already exists |
175
+ | `hoobs` | HOOBS-specific -- label as `hoobs` + `wontfix` and close |
package/.claude/PRD.md ADDED
@@ -0,0 +1,241 @@
1
+ # PRD -- homebridge-eufy-security
2
+
3
+ Product Requirements Document for `@homebridge-plugins/homebridge-eufy-security`.
4
+
5
+ ## Purpose
6
+
7
+ Expose Eufy Security devices to Apple HomeKit via Homebridge. Users control cameras, doorbells, locks, sensors, base stations, and delivery boxes from the Home app -- including live video, HomeKit Secure Video recording, arm/disarm, and two-way audio.
8
+
9
+ ## Target Users
10
+
11
+ Homebridge users with Eufy Security hardware who want native HomeKit integration without Eufy's official HomeKit firmware (limited or unavailable for most devices).
12
+
13
+ ## System Requirements
14
+
15
+ | Requirement | Value |
16
+ |---|---|
17
+ | Node.js | 20, 22, or 24 |
18
+ | Homebridge | >=1.9.0 or ^2.0.0-beta |
19
+ | Eufy Account | Dedicated guest account (admin accounts blocked) |
20
+ | FFmpeg | Bundled via `ffmpeg-for-homebridge` |
21
+
22
+ ---
23
+
24
+ ## Functional Requirements
25
+
26
+ ### FR-1: Device Discovery & Registration
27
+
28
+ - Connect to Eufy cloud via `eufy-security-client` (cloud API, P2P, push notifications, MQTT)
29
+ - Discover stations and devices automatically on plugin startup
30
+ - Batch device processing with 10-second debounce to avoid thrashing during sync
31
+ - Hot-add/remove devices after initial discovery completes
32
+ - Restore cached accessories across Homebridge restarts
33
+ - Prune stale cached accessories when `cleanCache: true`
34
+ - Devices can stack multiple capabilities (camera + motion sensor + doorbell) via independent registration blocks
35
+
36
+ ### FR-2: Camera Streaming
37
+
38
+ #### FR-2.1: Live Streaming
39
+ - P2P direct stream (default, low latency)
40
+ - RTSP stream (opt-in per camera)
41
+ - FFmpeg transcoding to H.264 + AAC-ELD with SRTP encryption
42
+ - Hardware encoder detection: VideoToolbox (macOS), V4L2 (Raspberry Pi), VAAPI/QSV (Linux)
43
+ - Fallback to libx264 software encoding
44
+ - Configurable max resolution, bitrate, frame rate
45
+ - Max concurrent streams per camera (default 2)
46
+ - Configurable livestream duration (default 30s, max 86400s)
47
+ - RTCP keep-alive monitoring with inactivity timeout
48
+
49
+ #### FR-2.2: HomeKit Secure Video (HKSV)
50
+ - Record motion/doorbell events as fMP4 fragments
51
+ - 4-second segment length (HomeKit requirement)
52
+ - 8-second timeshift buffer for pre-motion capture
53
+ - Audio recording (AAC-ELD or AAC-LC per HomeKit config)
54
+ - Snapshot capture during active recording
55
+ - Max 3 segment errors before connection reset
56
+
57
+ #### FR-2.3: Snapshots
58
+ - Four handling modes: AlwaysFresh, Balanced, CloudOnly (default), Auto
59
+ - Intelligent caching with configurable thresholds (fresh: 15s, balanced: 30s)
60
+ - Placeholder images for offline/disabled/unavailable states
61
+ - Cloud snapshot skip within 30s of livestream capture (prevents quality degradation)
62
+
63
+ #### FR-2.4: Two-Way Audio (Talkback)
64
+ - Bidirectional audio via duplex stream
65
+ - Caches audio data until device is ready
66
+ - 2-second silence auto-stop
67
+ - Configurable channel count (mono default)
68
+
69
+ ### FR-3: Security System (Stations)
70
+
71
+ - Map Eufy guard modes to HomeKit security states (Stay, Away, Night, Off)
72
+ - Per-station guard mode mapping (configurable numeric mode values)
73
+ - Manual alarm trigger with configurable duration and HomeKit mode filter
74
+ - Alarm arm delay and alarm event handling via push notifications
75
+ - Multi-station sync via virtual AutoSyncStationAccessory (opt-in)
76
+
77
+ ### FR-4: Locks
78
+
79
+ - Lock/unlock control via HomeKit LockMechanism service
80
+ - Lock jammed state detection via push notifications
81
+ - Lock management service (auto-security timeout, admin-only access)
82
+
83
+ ### FR-5: Sensors
84
+
85
+ #### FR-5.1: Motion Sensors
86
+ - Standalone motion detection service
87
+ - Camera-integrated motion with configurable timeout
88
+ - Multi-event support: person, pet, vehicle, sound, crying, dog, stranger
89
+
90
+ #### FR-5.2: Entry Sensors (Contact Sensors)
91
+ - Door/window open/closed state
92
+
93
+ ### FR-6: Doorbells
94
+
95
+ - ProgrammableSwitchEvent on ring
96
+ - Optional motion-triggers-doorbell mode
97
+ - Optional indoor chime control switch
98
+ - Snapshot delay on doorbell ring (configurable)
99
+
100
+ ### FR-7: Floodlights
101
+
102
+ - Lightbulb service for floodlight control
103
+ - Combined camera + light capabilities
104
+
105
+ ### FR-8: Smart Drop (Package Boxes)
106
+
107
+ - One-way open via LockMechanism
108
+ - Package delivery detection via ContactSensor
109
+
110
+ ### FR-9: Per-Device Switches
111
+
112
+ - Enable/disable device switch
113
+ - Motion detection toggle switch
114
+ - Light control switch (floodlights)
115
+ - Indoor chime toggle switch
116
+ - All switches individually configurable per camera
117
+
118
+ ---
119
+
120
+ ## Configuration Requirements
121
+
122
+ ### CR-1: Global Configuration
123
+ - Eufy credentials (username, password, device name)
124
+ - Country code (ISO 3166-1 alpha-2)
125
+ - Polling interval (default 10 minutes)
126
+ - Global guard mode mapping (hkHome, hkAway, hkNight, hkOff)
127
+ - Device/station ignore lists by serial number
128
+ - Debug logging toggle (`enableDetailedLogging`)
129
+ - Log file opt-out (`omitLogFiles`)
130
+ - Cache cleanup toggle (`cleanCache`, default true)
131
+ - Auto-sync station mode toggle
132
+ - Embedded PKCS1 support toggle (Node.js <24.5 workaround)
133
+
134
+ ### CR-2: Per-Camera Configuration
135
+ - Stream source: P2P or RTSP
136
+ - Enable/disable camera streaming entirely
137
+ - Snapshot handling method (AlwaysFresh, Balanced, CloudOnly, Auto)
138
+ - Audio enable/disable
139
+ - Talkback enable/disable with channel count
140
+ - HKSV recording duration
141
+ - Motion timeout
142
+ - Per-switch visibility (enable, motion, light, indoor chime)
143
+ - Full FFmpeg video config override (codec, resolution, bitrate, filters, encoder options, custom binary path)
144
+
145
+ ### CR-3: Per-Station Configuration
146
+ - Guard mode mapping overrides (hkHome, hkAway, hkNight, hkOff)
147
+ - Manual alarm trigger modes and duration
148
+
149
+ ---
150
+
151
+ ## Plugin UI Requirements
152
+
153
+ ### UI-1: Device Discovery
154
+ - Login flow with TFA and captcha support
155
+ - Discovery progress reporting (authenticating, queuing, processing, done)
156
+ - Skip button for unsupported device intel wait
157
+ - Admin account detection and blocking
158
+
159
+ ### UI-2: Device Management
160
+ - Display discovered stations and devices with type, capabilities, power status
161
+ - Show unsupported devices separately
162
+ - Detect running plugin via heartbeat (accessories.json `storedAt` < 90s old)
163
+ - Load stored accessories from cache when plugin is not running
164
+
165
+ ### UI-3: Diagnostics
166
+ - Generate encrypted diagnostics archive (RSA-4096 + AES-256-GCM)
167
+ - Rate-limited to 60-second minimum between downloads
168
+ - Archive includes: config (sanitized), all log files, accessories.json, system info
169
+ - Decryptable via `node scripts/decrypt-diagnostics.mjs`
170
+
171
+ ### UI-4: Storage Management
172
+ - Reset plugin storage (persistent.json, accessories.json)
173
+ - Clean all storage option
174
+ - System info endpoint (plugin version, Node.js version, host OS)
175
+
176
+ ---
177
+
178
+ ## Non-Functional Requirements
179
+
180
+ ### NFR-1: Logging
181
+ - Rotating file streams (daily rotation, max 3 archives, 200MB limit)
182
+ - Styled console output (color-coded by level)
183
+ - Debug mode adds file:line references and library-level logging
184
+ - Separate log streams: plugin, library, UI server, UI library, FFmpeg (global + per-camera + snapshots)
185
+
186
+ ### NFR-2: Reliability
187
+ - Connection retry (3 attempts, 30s backoff) on Eufy cloud failure
188
+ - P2P stream retry with exponential backoff (2-10s, max 3 attempts, 15s timeout per attempt)
189
+ - Multi-consumer stream sharing via reference counting (streaming, recording, snapshots share one P2P connection)
190
+ - 5-second grace period before stopping shared P2P stream
191
+ - Node.js MaxListeners raised to 30 for multi-camera setups
192
+ - fMP4 box validation (max 50MB per box) during HKSV recording
193
+
194
+ ### NFR-3: Compatibility
195
+ - ESM module (`"type": "module"`, `"module": "NodeNext"`)
196
+ - TypeScript strict mode, ES2022 target
197
+ - PKCS1 padding workaround for Node.js 20/22 (restored natively in Node.js 24.5+)
198
+ - Hardware codec probing at startup with graceful software fallback
199
+
200
+ ### NFR-4: Security
201
+ - Encrypted credential persistence (never logged)
202
+ - Guest account enforcement (admin account usage blocked)
203
+ - TFA/captcha graceful handling with shutdown and re-auth prompt
204
+ - Diagnostics archives encrypted before export
205
+
206
+ ---
207
+
208
+ ## Architecture Boundaries
209
+
210
+ ### homebridge-eufy-security (this plugin)
211
+ - Accessory registration and HomeKit service mapping
212
+ - FFmpeg transcoding pipeline
213
+ - Configuration handling and validation
214
+ - Plugin UI (device discovery, diagnostics, storage management)
215
+ - accessories.json persistence for UI communication
216
+
217
+ ### eufy-security-client (upstream dependency)
218
+ - Eufy cloud API authentication and communication
219
+ - P2P connection establishment and stream management
220
+ - Push notification and MQTT event handling
221
+ - Device type classification and property management
222
+ - Command execution (arm/disarm, lock/unlock, etc.)
223
+
224
+ Issues must be triaged to the correct layer. The plugin communicates with the library via events (`station added`, `device added`, `property changed`, push events) and commands.
225
+
226
+ ---
227
+
228
+ ## Device Type Coverage
229
+
230
+ | Category | HomeKit Services |
231
+ |---|---|
232
+ | Cameras | CameraRTPStreamManagement, MotionSensor, optional Switches |
233
+ | Doorbells | Doorbell + Camera services, ProgrammableSwitchEvent |
234
+ | Floodlights | Camera + Lightbulb services |
235
+ | Base Stations | SecuritySystem (arm/disarm), optional manual alarm Switch |
236
+ | Smart Locks | LockMechanism, LockManagement |
237
+ | Entry Sensors | ContactSensor |
238
+ | Motion Sensors | MotionSensor |
239
+ | Smart Drop | LockMechanism (open-only), ContactSensor (package detect) |
240
+ | Video Locks | Camera + Lock services (LockWifiVideo) |
241
+ | Auto-Sync Station | Virtual SecuritySystem syncing multiple stations |
@@ -0,0 +1,91 @@
1
+ # Debug Recording Pipeline — Lessons Learned (PRs #888–#894, 2026-03-26)
2
+
3
+ Six PRs in one day to fix a feature that should have landed in one or two. Root causes were not understanding the FFmpeg process architecture, fixing symptoms instead of root causes, and not testing against real hardware before merging.
4
+
5
+ ## 1. Understand the P2P FFmpeg process architecture BEFORE touching it
6
+
7
+ P2P cameras use SEPARATE FFmpeg processes for video and audio. The video process has only one input (raw H264 via TCP). There is no `input 1` for audio. Any `-map 1:a` on the video process will fail silently or crash.
8
+
9
+ **Rule**: Before adding FFmpeg arguments, trace the FULL command that gets spawned. Check `ffmpeg-<serial>.log` in diagnostics. Verify what each input index maps to.
10
+
11
+ **Why**: PRs #888, #890, #891 all tried to add audio to the video process file tee — impossible by architecture. Three PRs wasted.
12
+
13
+ **How to apply**: When modifying `getCombinedArguments()` or `buildRawMuxArgs()`, always log or mentally construct the full FFmpeg command and verify each `-i` and `-map` index.
14
+
15
+ ## 2. Homebridge plugin UI runs in an iframe with strict CSP
16
+
17
+ Homebridge enforces `frame-src 'self' data: https://developers.homebridge.io`. This means:
18
+ - `data:` URIs work for downloads
19
+ - `blob:` URLs are BLOCKED
20
+
21
+ **Rule**: Never use `URL.createObjectURL()` / `blob:` for downloads in the plugin UI. Use the same `btoa()` + `data:` URI pattern as diagnostics download.
22
+
23
+ **Why**: PR #893 switched to Blob URLs for "memory efficiency", immediately broke downloads. Had to revert.
24
+
25
+ **How to apply**: When writing download logic in `homebridge-ui/`, copy the pattern from `diagnostics.js` `_downloadDiagnostics()`. Check CSP before changing.
26
+
27
+ ## 3. Destroy PassThrough forks when their consumer dies
28
+
29
+ When piping a source stream to multiple PassThrough forks via `.pipe()`, if ANY fork's consumer dies (e.g., FFmpeg crashes) and the fork is not destroyed, it fills its internal buffer (16KB default) and applies permanent backpressure on the source — starving ALL other consumers.
30
+
31
+ **Rule**: Always destroy PassThrough forks when their downstream consumer exits. Store references and destroy in cleanup handlers.
32
+
33
+ **Why**: PR #888 created forks without cleanup. The bug was masked by 4MB highWaterMark in #890, exposed when removed in #891, finally fixed in #893.
34
+
35
+ **How to apply**: Any time you create a `new PassThrough()` and pipe a shared source into it, ensure the fork is destroyed when its consumer (FFmpeg, socket, etc.) exits. Use both socket `close` handler AND session cleanup as belt-and-suspenders.
36
+
37
+ ## 4. FFmpeg ignores stdin when reading from TCP
38
+
39
+ When FFmpeg reads input from a TCP socket (`-i tcp://...`), it is blocked in the socket read loop and does NOT monitor stdin. Writing `'q'` or `'q\n'` to stdin has no effect. The process will never exit gracefully via stdin.
40
+
41
+ **Rule**: To stop FFmpeg reading from TCP, destroy the TCP socket. FFmpeg detects EOF and exits cleanly, finalizing the container.
42
+
43
+ **Why**: PR #888 used `stdin.write('q')`, #893 added newline, neither worked. FFmpeg was always SIGKILL'd after timeout. PR #894 fixed it by destroying the socket.
44
+
45
+ **How to apply**: In `stopSessionBySerial()`, destroy `session.videoSocket` and `session.audioSocket` instead of writing to stdin.
46
+
47
+ ## 5. P2P data is inherently bursty — never use wallclock timestamps
48
+
49
+ `-use_wallclock_as_timestamps 1` assigns PTS based on when FFmpeg receives each frame. P2P delivers data in bursts at every level: initial buffer dump, ongoing video chunks (~1.3s gaps between bursts), and audio (entire ADTS batch arrives in one TCP write). Wallclock faithfully records these arrival times, producing choppy video and crushed audio (25s of audio crammed into 2ms of timestamps).
50
+
51
+ Deferred piping (PR #891) only prevents the initial buffer dump. It does NOT make ongoing P2P delivery real-time.
52
+
53
+ **Rule**: Never use `-use_wallclock_as_timestamps` for P2P recordings. Use `-fflags +genpts -r <fps>` for video (generates smooth PTS from declared framerate) and `-fflags +genpts` for audio (generates PTS from sample rate). The FPS comes from `metadata.videoFPS`.
54
+
55
+ **Why**: PR #888 used wallclock -> burst -> 0.07s. PR #894 used wallclock + deferred piping -> choppy video (1.3s frame gaps) and silent audio (all timestamps within 2ms). PR #897 uses genpts + fps from metadata.
56
+
57
+ **How to apply**: In `buildRawMuxArgs()`, use `-fflags +genpts -r <fps>` before each input. Deferred piping in `LocalLivestreamManager.onStationLivestreamStart` is still needed to prevent the initial buffer burst from skewing the generated PTS.
58
+
59
+ ## 6. The processed file tee copies raw INPUT, not encoded OUTPUT
60
+
61
+ In FFmpeg multi-output, `-map 0:v -c:v copy` on a second output copies from INPUT 0 (raw P2P), not the encoded output of the first output. To capture the encoded stream, you'd need the `tee` muxer (blocked by SRTP params) or double encoding (wasteful).
62
+
63
+ **Rule**: Don't use the file tee to capture "what HomeKit sees". It captures the raw P2P feed. For P2P cameras, use DebugRecordingManager instead (captures both video+audio). Keep the file tee only for RTSP cameras where DebugRecordingManager doesn't apply.
64
+
65
+ **Why**: PRs #888-#891 assumed the file tee captured the encoded stream. It never did.
66
+
67
+ ## 7. HomeKit probes streams with a stop/restart cycle
68
+
69
+ HomeKit sends startStream -> runs for 2-3s -> sends stopStream -> waits -> sends startStream again. This is normal probe behavior. If the file recording uses the P2P sessionId (which persists across HomeKit sessions), the second start overwrites the first file.
70
+
71
+ **Rule**: For any per-HomeKit-session file recording, use `request.sessionID` (HomeKit's session ID), not the P2P livestream sessionId.
72
+
73
+ **How to apply**: Relevant if the RTSP file tee is ever extended. Currently moot for P2P since the file tee was removed.
74
+
75
+ ## 8. Test against real hardware before merging
76
+
77
+ Every PR #888-#893 was merged and found broken on the next beta. A single 15-second test (enable debug livestream -> stream camera -> check recording) would have caught most issues immediately.
78
+
79
+ **Rule**: Before merging debug recording changes, the test plan MUST actually be executed. At minimum: enable feature, stream for 15s, ffprobe the output, check eufy-security.log for errors.
80
+
81
+ **Why**: The ADTS crash (FFmpeg exit 255) was visible in the very first test. The 0.07s duration was visible in the first ffprobe. Six PRs could have been two.
82
+
83
+ ## 9. Fragmented MP4 breaks VLC and QuickTime
84
+
85
+ `-movflags frag_keyframe+empty_moov` writes fragmented MP4 (fMP4) — designed for live streaming, not file playback. VLC shows ~1s of content then stops. QuickTime can't play it at all.
86
+
87
+ **Rule**: Use `-movflags +faststart` for debug recordings written to disk. Standard MP4 with moov relocated to the start is universally playable.
88
+
89
+ **Why**: PR #897 used fMP4 for crash resilience (already-written fragments survive SIGKILL). But graceful shutdown via socket destroy (lesson #4) makes this unnecessary. PR #898 switched to faststart.
90
+
91
+ **How to apply**: Only use `frag_keyframe+empty_moov` when streaming to a non-seekable output (e.g., HTTP). For file output, always use `+faststart`.
@@ -0,0 +1,160 @@
1
+ # HKSV Recording Reliability Fix
2
+
3
+ **PR:** [#878](https://github.com/homebridge-plugins/homebridge-eufy-security/pull/878)
4
+ **Branch:** `fix/hksv-recording-reliability`
5
+ **Affects:** All cameras with HomeKit Secure Video enabled
6
+
7
+ ---
8
+
9
+ ## Problem
10
+
11
+ HKSV recordings never appeared in the Apple Home timeline. Users could enable HKSV in Apple Home settings, but no recordings were captured on motion events. Two independent issues combined to make HKSV completely non-functional.
12
+
13
+ ## Root Cause 1: HKSV state lost on every restart
14
+
15
+ ### How HKSV state persistence works
16
+
17
+ When a user enables HKSV for a camera in Apple Home:
18
+
19
+ 1. HomeKit sets the `Active` characteristic to `true` on the `CameraRecordingManagement` service
20
+ 2. HAP-NodeJS stores this in two places:
21
+ - The `cachedAccessories` JSON (service characteristic values)
22
+ - The `ControllerStorage` file (controller-specific state)
23
+ 3. On next boot, `CameraController._initWithServices()` restores services from cache
24
+ 4. `ControllerStorage.restoreController()` calls `deserialize()` to restore `recordingActive`
25
+
26
+ ### What was broken
27
+
28
+ `CameraAccessory.configureVideoStream()` removed two controller-managed services (`CameraOperatingMode` and `DataStreamTransportManagement`) from the cached accessory before calling `configureController()`. This was added to prevent a "duplicate UUID error".
29
+
30
+ However, this broke the restoration flow:
31
+
32
+ ```
33
+ HAP-NodeJS CameraController._initWithServices():
34
+
35
+ IF all 3 services found in cache:
36
+ → Reuse cached services (preserves Active state) ✓
37
+
38
+ ELSE (any service missing):
39
+ → Create NEW services with Active=false ✗
40
+ ```
41
+
42
+ By removing 2 of the 3 services, every restart forced the `ELSE` path, resetting `recordingActive` to `false`. HomeKit set `Active=true` once, but it was lost on every boot.
43
+
44
+ ### The fix
45
+
46
+ Removed the stale service removal code entirely. HAP-NodeJS's `_initWithServices()` already handles service reuse through its `serializedControllers` path — the "duplicate UUID error" it was designed to prevent doesn't occur when services are properly managed by the controller.
47
+
48
+ ## Root Cause 2: P2P cold-start delay
49
+
50
+ ### The timing problem
51
+
52
+ When HomeKit requests an HKSV recording after motion detection, the recording delegate must:
53
+
54
+ 1. Establish a P2P connection to the camera (`getLocalLiveStream()`)
55
+ 2. Start FFmpeg to transcode the stream
56
+ 3. Begin yielding fMP4 fragments to HomeKit
57
+
58
+ Step 1 takes **~4.8 seconds** (observed in diagnostics). HomeKit has an approximate **5-second timeout** for the first data. This means recordings frequently failed to start in time.
59
+
60
+ ### How reference implementations solve this
61
+
62
+ | Plugin | Approach | Prebuffer Duration |
63
+ |--------|----------|--------------------|
64
+ | homebridge-unifi-protect | Always-on RTSP timeshift buffer | Configurable |
65
+ | Scrypted | Ring buffer, 2.5x HomeKit's prebufferLength | 10s default |
66
+ | homebridge-camera-ui | Always-on prebuffer | 4s |
67
+
68
+ All use prebuffering. For battery-powered Eufy cameras, always-on prebuffering is not viable.
69
+
70
+ ### The fix: motion-triggered P2P pre-warming
71
+
72
+ When the plugin detects motion (before HomeKit even requests a recording), it proactively starts the P2P connection:
73
+
74
+ ```
75
+ Motion Detected → Plugin sets MotionDetected=true on characteristic
76
+ → Plugin calls preWarmStream() (P2P starts in background)
77
+ → HomeKit receives motion notification
78
+ → HomeKit sends DATA_SEND OPEN → handleRecordingStreamRequest()
79
+ → getLocalLiveStream() returns instantly (P2P already warm)
80
+ ```
81
+
82
+ A new `preWarmStream()` method on `LocalLivestreamManager` starts the P2P connection without creating a consumer fork. The stream auto-cleans up after the `STOP_GRACE_MS` (5 seconds) grace period if HomeKit doesn't request a recording.
83
+
84
+ RTSP cameras are excluded from pre-warming since their stream URL is immediately available.
85
+
86
+ ## Additional fix: updateRecordingActive log bug
87
+
88
+ `recordingDelegate.ts` line 282 had swapped log arguments:
89
+
90
+ ```typescript
91
+ // Before (wrong — message as camera name, camera name as message):
92
+ log.debug(`Recording: ${active}`, this.accessory.displayName);
93
+
94
+ // After (correct):
95
+ log.info(this.camera.getName(), `HKSV recording enabled/disabled by HomeKit.`);
96
+ ```
97
+
98
+ This made HKSV state changes invisible in logs, making the root cause much harder to diagnose. Promoted to INFO level since this is a critical diagnostic signal.
99
+
100
+ ## HKSV Recording Flow (Architecture Reference)
101
+
102
+ ```
103
+ ┌─────────────────────────────────────────────────────────────┐
104
+ │ eufy-security-client │
105
+ │ Device emits 'motion detected' / 'person detected' / etc. │
106
+ └──────────────────────────┬──────────────────────────────────┘
107
+
108
+
109
+ ┌──────────────────────────────────────────────────────────────┐
110
+ │ CameraAccessory.ts │
111
+ │ 1. characteristic.updateValue(true) → HomeKit notified │
112
+ │ 2. preWarmStream() → P2P starts warming │
113
+ └──────────────────────────┬───────────────────────────────────┘
114
+
115
+ ┌────────────┴────────────┐
116
+ │ │
117
+ ▼ ▼
118
+ ┌──────────────────────┐ ┌─────────────────────────────────┐
119
+ │ LocalLivestream │ │ HAP-NodeJS RecordingManagement │
120
+ │ Manager.ts │ │ │
121
+ │ P2P connecting... │ │ HomeKit receives MotionDetected │
122
+ │ Stream warming... │ │ HomeKit decides to record │
123
+ │ ✓ Stream ready │ │ DATA_SEND OPEN → │
124
+ └──────────┬───────────┘ └───────────────┬───────────────────┘
125
+ │ │
126
+ │ ▼
127
+ │ ┌───────────────────────────────────┐
128
+ │ │ recordingDelegate.ts │
129
+ │ │ handleRecordingStreamRequest() │
130
+ └──────────────►│ configureInputSource() │
131
+ P2P already │ → getLocalLiveStream() instant │
132
+ warm! │ FFmpeg starts │
133
+ │ yield fMP4 fragments → HomeKit │
134
+ └───────────────────────────────────┘
135
+ ```
136
+
137
+ ## Files Changed
138
+
139
+ | File | Change |
140
+ |------|--------|
141
+ | `src/accessories/CameraAccessory.ts` | Removed stale service removal; added motion-triggered pre-warming |
142
+ | `src/controller/LocalLivestreamManager.ts` | Added `preWarmStream()` method; updated `isStreamActive()` |
143
+ | `src/controller/recordingDelegate.ts` | Fixed `updateRecordingActive` log arguments |
144
+
145
+ ## User Impact
146
+
147
+ ### After upgrading
148
+
149
+ Users who had HKSV enabled but never saw recordings should see them start working. Some users may need to:
150
+
151
+ 1. Restart Homebridge after the upgrade
152
+ 2. If recordings still don't appear, toggle HKSV off and on in Apple Home:
153
+ - Open Home app → Camera settings → Recording Options → Disable → Re-enable
154
+
155
+ ### Battery considerations
156
+
157
+ The P2P pre-warming starts a livestream connection on every motion event. For battery-powered cameras:
158
+ - The stream only runs for 5 seconds if HomeKit doesn't request a recording
159
+ - This is similar to what already happens for snapshot fetching on motion
160
+ - Impact on battery life should be minimal but is worth monitoring