Clarify presence/occupancy naming; add IndoorUnit.presence_detected#22
Merged
Conversation
The library exposed three occupancy-ish signals with no indication of which are realtime and which are derived, and the TUI labeled the two radar channels "Radar L"/"Radar R" — implying two physical sensors. Evidence (single radar_sensor_distance_from_floor_m, one detection fence, two raw radar channels on QuiltSmartModuleState, and the vendor app's combinedSensorPresence ORing the pair) says: one radar, two detection channels, combined by the app into a single realtime presence value; IndoorUnitOccupancy is the debounced auto-away decision (~3 min set / ~20 min clear). - Add IndoorUnit.presence_detected: realtime OR of both channels, None when offline/unreported (gated like effective_occupancy_state) - Rewrite IndoorUnitPresence/IndoorUnitOccupancy docstrings around the realtime-vs-derived distinction - TUI: "Radar L"/"Radar R" → "Radar ch 0"/"Radar ch 1"; "Occupancy" → "Occupancy (auto-away)" - Docs: drop phantom IndoorUnitState.presence_detected from the model reference, document the three-tier presence model, fix the HA guide mapping the derived state to an entity named "Presence" - Tests for the new property Closes #21
There was a problem hiding this comment.
Pull request overview
This PR clarifies and standardizes the library’s “presence vs occupancy” vocabulary, and adds a new first-class realtime presence API on IndoorUnit that mirrors the vendor app’s combined radar presence signal.
Changes:
- Added
IndoorUnit.presence_detected: bool | None(online-gated, seconds-latency combined presence derived from the two radar channels). - Updated docstrings, docs, and the TUI labels to correctly describe radar channels (not left/right sensors) and to distinguish derived “occupancy (auto-away)” from realtime presence.
- Added unit tests covering the new tri-state semantics and offline/unreported gating.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
src/quilt_hp/models/indoor_unit.py |
Adds presence_detected property and expands presence/occupancy docstrings to document semantics and latency. |
tests/test_models_from_proto.py |
Adds tests validating presence_detected behavior across DETECTED/UNDETECTED/UNSPECIFIED and offline gating. |
src/quilt_hp/cli/tui.py |
Renames TUI labels to “Radar ch 0/1” and “Occupancy (auto-away)” to match corrected semantics. |
docs/reference/models.md |
Removes phantom IndoorUnitState.presence_detected and documents the three-tier model (raw channels / realtime presence / derived occupancy). |
docs/how-to/home-assistant.md |
Updates HA mapping so “Presence” reflects realtime combined presence and adds a separate “Occupancy (auto-away)” entity mapping. |
CHANGELOG.md |
Documents the new API and the terminology/labeling/documentation updates. |
Comment on lines
+6
to
+11
| - `IndoorUnit.presence_detected` — realtime room presence as a single bool: True | ||
| if either radar channel reports DETECTED (mirrors the vendor app's | ||
| `combinedSensorPresence`), False if a channel reports UNDETECTED, None when the | ||
| IDU is offline or the radar hasn't reported. This is the fast (seconds-latency) | ||
| counterpart to the debounced `effective_occupancy_state` (auto-away decision, | ||
| ~3 min to set / ~20 min to clear by default). ([#21]) |
Comment on lines
+148
to
150
| | `binary_sensor` (presence) | Living Room Presence | `IndoorUnit.presence_detected` | OR of both radar channels | No | | ||
| | `binary_sensor` (occupancy) | Living Room Occupancy (auto-away) | `IndoorUnit.effective_occupancy_state` | `occupancy_state == OccupancyState.DETECTED` | No | | ||
| | `light` | Living Room Light | `IndoorUnit.controls` | `led_state`, `led_brightness`, `led_color_code` | Yes — `client.set_indoor_unit()` | |
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.
Fixes #21.
What
IndoorUnit.presence_detected— realtime room presence (Trueif either radar channel is DETECTED,Falseif a channel reports UNDETECTED,Nonewhen offline/unreported). Mirrors the vendor app'scombinedSensorPresenceand is the seconds-latency counterpart to the minutes-latencyeffective_occupancy_state.IndoorUnitPresencenow says what sensor0/sensor1 actually are (two detection channels of the single radar, lockstep in practice, channel semantics unconfirmed);IndoorUnitOccupancydocuments the auto-away debounce (~3 min set / ~20 min clear).IndoorUnitState.presence_detectedfield fromreference/models.md, documented the three-tier presence model there, and fixedhow-to/home-assistant.mdwhich mapped the derived occupancy state to an entity named "Presence".Evidence
See #21 — KMP sources (
SpaceViewNode.combinedSensorPresence, singleradar_sensor_distance_from_floor_m), plus 2026-07-20 live-stream observation: both channels flip together in the same diff; a 43 s walk-through never movedoccupancy_state.ruff+mypyclean; full suite passes at 88.7% coverage.