feat: Litter-Robot 5 Pro camera recording with visit tracking#1
Open
Legendberg wants to merge 1227 commits intodevfrom
Open
feat: Litter-Robot 5 Pro camera recording with visit tracking#1Legendberg wants to merge 1227 commits intodevfrom
Legendberg wants to merge 1227 commits intodevfrom
Conversation
Owner
Author
Progress Update (2026-02-25)New in this push
Verified working
Still needs testing
|
Legendberg
added a commit
to Legendberg/pylitterbot
that referenced
this pull request
Mar 2, 2026
This library change is one half of a two-part update. The companion Home Assistant integration PR (Legendberg/core#1) builds on top of these changes to deliver: - Local MP4 recording of every litter box visit and cleaning cycle - HTTP serving of recordings with Range request support for mobile playback - Media source browser in HA ("Browse Snapshots") for reviewing recordings - Per-pet visit sensors with daily aggregates (weight, waste, duration) - Reassign/unassign visit button entities in the HA UI - Example Lovelace dashboard (Whisker Dashboard) --- ## Camera streaming (pylitterbot/camera.py) Adds full WebRTC camera support for the Litter-Robot 5 Pro: - `CameraClient`: REST client for the Watford camera API. Handles session generation (`generate_session()`), which returns TURN credentials, a session token, and a signaling WebSocket URL. Supports `auto_start=False` for pre-caching TURN credentials without waking the camera. - `CameraSignalingRelay`: Manages the browser↔camera WebRTC signaling flow. Connects to the Watford signaling WebSocket, forwards the SDP offer, receives the SDP answer and ICE candidates, and relays browser ICE candidates back to the camera. - ICE fix: was sending `offer.sdp` (no ICE candidates) instead of `localDescription.sdp` (all gathered candidates). aiortc gathers ICE candidates synchronously in `setLocalDescription()`. - TURN credential fix: Whisker uses non-standard keys (`turnUrl`, `stunUrl`, `password`) instead of the standard WebRTC format (`urls`, `credential`). Both formats are now handled. - Late ICE candidate reconnect: the Watford signaling WebSocket closes ~17 s after the offer is sent (intentional server behaviour, after delivering answer + ICE candidates). Browser ICE candidates arriving after closure are buffered in `_pending_candidates` and forwarded via a lightweight reconnect on the same `sessionToken`. A faster ping interval (5 s, down from 15 s) keeps the connection alive until the server closes it. - `LitterRobot5.get_camera_client()` returns a `CameraClient` for the robot's camera, raising `CameraNotAvailableException` when the robot has no camera or the metadata is missing. ## Pet visit reassignment (robot/litterrobot5.py) - `reassign_pet_visit(event_id, from_pet_id, to_pet_id)`: PATCHes the Whisker activities endpoint to reassign or unassign a litter box visit. Omit `to_pet_id` to unassign; omit `from_pet_id` to assign without removing from another pet. Returns the updated activity dict. ## Camera mic control (robot/litterrobot5.py) - `set_audio_enabled(value)`: enables/disables the camera microphone via the camera settings API. Updates local state and emits EVENT_UPDATE. ## Bug fix: sleep_mode_enabled (robot/litterrobot5.py) - `sleep_mode_enabled` now handles both list-shaped and dict-shaped `sleepSchedules` responses from the API. ## Dependencies (pyproject.toml) - Adds `aiortc>=1.9.0`, `aiohttp>=3.9.0`, `av>=11.0.0` for WebRTC/media. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
235af56 to
8bb609b
Compare
This was referenced Mar 2, 2026
Legendberg
added a commit
to Legendberg/pylitterbot
that referenced
this pull request
Mar 6, 2026
This library change is one half of a two-part update. The companion Home Assistant integration PR (Legendberg/core#1) builds on top of these changes to deliver: - Local MP4 recording of every litter box visit and cleaning cycle - HTTP serving of recordings with Range request support for mobile playback - Media source browser in HA ("Browse Snapshots") for reviewing recordings - Per-pet visit sensors with daily aggregates (weight, waste, duration) - Reassign/unassign visit button entities in the HA UI - Example Lovelace dashboard (Whisker Dashboard) --- Adds full WebRTC camera support for the Litter-Robot 5 Pro: - `CameraClient`: REST client for the Watford camera API. Handles session generation (`generate_session()`), which returns TURN credentials, a session token, and a signaling WebSocket URL. Supports `auto_start=False` for pre-caching TURN credentials without waking the camera. - `CameraSignalingRelay`: Manages the browser↔camera WebRTC signaling flow. Connects to the Watford signaling WebSocket, forwards the SDP offer, receives the SDP answer and ICE candidates, and relays browser ICE candidates back to the camera. - ICE fix: was sending `offer.sdp` (no ICE candidates) instead of `localDescription.sdp` (all gathered candidates). aiortc gathers ICE candidates synchronously in `setLocalDescription()`. - TURN credential fix: Whisker uses non-standard keys (`turnUrl`, `stunUrl`, `password`) instead of the standard WebRTC format (`urls`, `credential`). Both formats are now handled. - Late ICE candidate reconnect: the Watford signaling WebSocket closes ~17 s after the offer is sent (intentional server behaviour, after delivering answer + ICE candidates). Browser ICE candidates arriving after closure are buffered in `_pending_candidates` and forwarded via a lightweight reconnect on the same `sessionToken`. A faster ping interval (5 s, down from 15 s) keeps the connection alive until the server closes it. - `LitterRobot5.get_camera_client()` returns a `CameraClient` for the robot's camera, raising `CameraNotAvailableException` when the robot has no camera or the metadata is missing. - `reassign_pet_visit(event_id, from_pet_id, to_pet_id)`: PATCHes the Whisker activities endpoint to reassign or unassign a litter box visit. Omit `to_pet_id` to unassign; omit `from_pet_id` to assign without removing from another pet. Returns the updated activity dict. - `set_audio_enabled(value)`: enables/disables the camera microphone via the camera settings API. Updates local state and emits EVENT_UPDATE. - `sleep_mode_enabled` now handles both list-shaped and dict-shaped `sleepSchedules` responses from the API. - Adds `aiortc>=1.9.0`, `aiohttp>=3.9.0`, `av>=11.0.0` for WebRTC/media. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Robert Resch <robert@resch.dev>
…tant#164264) Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
…nt#165288) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…ssistant#165503) Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds Home Assistant entity support for the Litter-Robot 5 Pro camera and expands LR5 platform coverage with new entity types. - `LitterRobotCameraEntity` supports WebRTC live streaming via `async_handle_async_webrtc_offer()` / `async_on_webrtc_candidate()`. - Delegates signaling to `CameraSignalingRelay` from pylitterbot, which relays SDP offer/answer and ICE candidates to the Watford signaling server. - Pre-caches a camera session on startup (`auto_start=False`) to have TURN credentials ready before the first stream request, without waking the camera unnecessarily. - Checks session expiration before each stream; refreshes in background if stale. - `async_camera_image()` returns the latest cloud video thumbnail. - **Light** (`light.py`): globe night light with brightness and mode (on/off/auto/random). - **Event** (`event.py`): camera motion event entity that fires on pet_visit, cat_detect, cycle_completed, and related activity types. - **Time** (`time.py`): sleep mode start/end time configuration. - **Binary sensor** (`binary_sensor.py`): LR5-specific sensors — sleeping, hopper connected, laser dirty, bonnet/drawer removed. - `entity.py`: adds `LitterRobot5` to the typed entity generic and exposes `robot.has_camera` guard for camera-specific entities. - `select.py`: adds camera view select (globe/front) for LR5 Pro. - `switch.py`: adds camera enable and microphone enable switches. - `manifest.json`: adds `aiortc>=1.9.0` dependency for WebRTC support. - `icons.json`: adds icons for new entity types. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Records every litter box visit and cleaning cycle locally as MP4 files,
served via a custom HA HTTP view and browseable in the media browser.
Enabled and configured through a new options flow entry.
`RecordingManager` drives three recording modes:
- **Visit recording** (`trigger_visit_recording()`): starts on `cat_detect`,
switches camera view from front → globe, records until `pet_visit`
signals completion or a max-duration timeout. File is renamed with the
pet name on completion (e.g. `YYYYMMDD_HHMMSS_VISIT_Willow.mp4`).
- **Cycle recording** (`trigger_cycle_recording()`): starts when robot
enters `CLEAN_CYCLE` status, records continuously until
`signal_cycle_complete()` or a max-duration timeout.
- **Fixed-duration recording** (`trigger_recording()`): used for
standalone events (e.g. a `pet_visit` with no prior `cat_detect`).
Files are written as `YYYYMMDD_HHMMSS_{EVENT_TYPE}[_{PetName}].mp4`
under `<config>/media/litterrobot/<serial>/`. PyAV encodes H.264/yuv420p
and ffmpeg post-processes with `-movflags faststart` for immediate
playback before download completes.
`LitterRobotRecordingView` serves recordings at
`/api/litterrobot/recordings/{serial}/{filename}`. Uses
`aiohttp.web.FileResponse` which handles HTTP Range requests natively,
enabling seek and mobile playback. Filename is validated to prevent path
traversal.
`LitterRobotMediaSource` exposes recordings in the HA media browser
("Browse Snapshots"). Lists MP4 files newest-first per robot, with
human-readable titles parsed from the filename stem:
- `PET_VISIT_Willow` → "Pet Visit (Willow) - 2026-03-01 17:05"
- `CYCLE_COMPLETED` → "Cycle Completed - 2026-03-01 17:05"
- `VISIT` → "Visit (Unassigned) - 2026-03-01 17:05"
Adds a recording options step with:
- Enable/disable recording toggle
- Recording duration (seconds)
- Retention period (days) — old recordings pruned automatically
- Event type multi-select (pet_visit, cat_detect, cycle_completed, etc.)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…hboard `LitterRobotPetLastVisitSensor` — one entity per pet, under the pet's own HA device: - State: most recent event label (e.g. "Pet Visit") - Attributes: `timestamp`, `duration`, `pet_weight`, `waste_type`, `waste_weight_oz`, `pet_id`, `event_id`, `is_reassigned` - Daily aggregates: `visits_today`, `urine_today`, `feces_today`, `urine_weight_today_oz`, `feces_weight_today_oz`, `total_duration_today` `LitterRobotLastEventSensor` — last activity across all event types on the robot device, with the same attribute set. - Activity cache: warm-up fetch on first poll (up to 100 pet_visit activities) for accurate daily aggregates; subsequent polls merge new activities incrementally using `messageId` deduplication. - Fast camera poll (10 s): polls activities and camera videos for new events, triggers recordings via `RecordingManager`. - Fast state poll (3 s): polls robot status for `CLEAN_CYCLE` detection to start cycle recordings with minimal latency. - `EVENT_UPDATE` subscription: fires immediately on any robot state change, providing a real-time path for cycle/visit detection alongside the polling fallback. - Camera thumbnail download on each 5-minute coordinator refresh. `litterrobot.reassign_visit` service: - `event_id`: the activity event to reassign - `from_pet_id` / `to_pet_id`: omit either to assign/unassign - `config_entry_id`: optional — auto-detected when only one entry exists - `ReassignVisitButton`: one per pet pair (e.g. Willow→Loki, Loki→Willow). Pressing reassigns the pet's most recent visit to the target pet. - `UnassignVisitButton`: one per pet. Pressing removes the pet assignment from their most recent visit. A ready-to-use Lovelace dashboard for the full integration. Requires `lovelace_gen` and `fold-entity-row` from HACS. Covers: - Robot status, actions, controls, night light, camera, sleep schedule, alerts, and diagnostics sections - Feeder-Robot status, actions, and controls - Per-pet activity and last-visit detail with reassign buttons - Last litter box visit summary across all robots Adds translation keys for all new entities: per-pet sensors, event sensor, reassign/unassign buttons, and the reassign service. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix cooldown reset key format in manual recording service — was using plain serial string but _last_trigger_times uses (serial, event_type) tuples, so the pop never matched - Fix _apply_faststart deleting temp file before checking ffmpeg return code — recording was silently lost on ffmpeg failure - Fix path traversal in HTTP recording view — use Path.resolve() with is_relative_to() instead of string-matching for ".." - Fix blocking I/O in _finalize_recording — move all file operations into executor job to avoid blocking the event loop Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix duplicate unique IDs: remove LR5 litter_level/pet_weight duplicated between shared (LR4,LR5) and LR5-only sensor entries - Fix duplicate panel_brightness select: split LR4 into own key - Add noqa: BLE001 for intentional broad exception catches - Replace try/except/pass with contextlib.suppress - Fix import sorting and formatting (ruff) - Guard hass.http.register_view for test environments - Remove unused test imports, fix PET_DATA redefinition - Add check=False to subprocess.run call Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Revert button map to upstream's single-description-per-key pattern with tuple type keys, add (LitterRobot5,) for change_filter - Use translation-based HomeAssistantError in ReassignVisitButton and UnassignVisitButton instead of raw English strings - Wrap reassign_pet_visit calls in LitterRobotException handler - Add translation_key to ReassignVisitButton with pet_name placeholder - Add exception translations: no_recent_visit, visit_no_event_id, robot_not_found, reassign_failed - Move LR5/LR5Pro test data from inline common.py to JSON fixture files matching upstream's fixture pattern - Fix test_light ROBOT_5_DATA import after fixture migration Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix command_exception tests: match translation key (command_failed, firmware_update_failed) instead of English error text - Fix LR5 select duplicates: move globe_brightness, globe_light, and panel_brightness to LR4-only since LR5 uses its own night light entity and LR5-specific brightness/mode enums - All 79 tests pass (77 teardown errors are upstream vacuum clean_area.name translation issue) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add @whisker_command to camera switch, mic switch, night light, and camera view select methods for consistent exception handling - Convert all raw English HomeAssistantError strings to translation- based errors in camera.py and services.py - Add explicit _attr_translation_key to LitterRobotNightLight - Add translations: camera_not_available, camera_stream_failed, recording_not_enabled, no_cameras_found, activity_not_found Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add PARALLEL_UPDATES to camera.py and light.py - Add start_recording service to services.yaml and strings.json - Add service icons for start_recording and reassign_visit - Remove invalid options.selector section from strings.json - Fix placeholder quoting in activity_not_found translation - Sync translations/en.json with strings.json (all new exceptions, entities, and services) - Revert test match patterns to translated messages now that translations load correctly - hassfest validates clean, all 79 tests pass with 0 errors Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3c4fc81 to
ce0e513
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
LR5 Pro camera integration for Home Assistant with local video recording — no Whisker cloud subscription required.
Camera & Recording
Sensors & Controls
Services
litterrobot.start_recording— manually trigger a recording sessionlitterrobot.reassign_visit— reassign or unassign a pet visit (config_entry_id auto-detected)Technical Notes
turnUrl/stunUrl/passwordformatTesting
Dependencies