Skip to content

Commit d6ee685

Browse files
authored
Release 2025.5.1
Wasp in a Box Feature
2 parents a461bea + 67bd52f commit d6ee685

34 files changed

+2014
-700
lines changed
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
---
2+
description:
3+
globs:
4+
alwaysApply: true
5+
---
6+
# Area Occupancy Detection Integration
7+
8+
## Project Structure
9+
10+
The integration is organized in the `custom_components/area_occupancy` directory with the following key files:
11+
12+
- [custom_components/area_occupancy/__init__.py](mdc:custom_components/area_occupancy/__init__.py): Integration setup and platform registration
13+
- [custom_components/area_occupancy/binary_sensor.py](mdc:custom_components/area_occupancy/binary_sensor.py): Occupancy Status binary sensor
14+
- [custom_components/area_occupancy/calculate_prob.py](mdc:custom_components/area_occupancy/calculate_prob.py): Bayesian probability calculations
15+
- [custom_components/area_occupancy/calculate_prior.py](mdc:custom_components/area_occupancy/calculate_prior.py): Prior probability calculations
16+
- [custom_components/area_occupancy/config_flow.py](mdc:custom_components/area_occupancy/config_flow.py): Configuration UI flow
17+
- [custom_components/area_occupancy/const.py](mdc:custom_components/area_occupancy/const.py): Constants and defaults
18+
- [custom_components/area_occupancy/coordinator.py](mdc:custom_components/area_occupancy/coordinator.py): Data update coordination
19+
- [custom_components/area_occupancy/manifest.json](mdc:custom_components/area_occupancy/manifest.json): Integration metadata
20+
- [custom_components/area_occupancy/probabilities.py](mdc:custom_components/area_occupancy/probabilities.py): Default probability values
21+
- [custom_components/area_occupancy/sensor.py](mdc:custom_components/area_occupancy/sensor.py): Probability and Prior sensors
22+
- [custom_components/area_occupancy/service.py](mdc:custom_components/area_occupancy/service.py): Integration services
23+
- [custom_components/area_occupancy/types.py](mdc:custom_components/area_occupancy/types.py): Type definitions
24+
25+
## Development Guidelines
26+
27+
### Code Organization
28+
- All constants must be defined in `const.py`
29+
- All custom types must be defined in `types.py`
30+
- Configuration flow logic must be in `config_flow.py`
31+
- Service definitions must be in `services.yaml` with implementation in `service.py`
32+
- Sensor entities must be defined in `sensor.py`
33+
- Binary sensor entities must be defined in `binary_sensor.py`
34+
- Core Bayesian calculation logic must be in `calculate_prob.py`
35+
- Prior probability calculation logic must be in `calculate_prior.py`
36+
- Default probability values must be defined in `probabilities.py`
37+
- Coordinator logic must be in `coordinator.py`
38+
39+
### Coding Standards
40+
- Use snake_case for all file names, variables, and functions
41+
- Follow PEP8 standards strictly
42+
- Use specific exceptions (not general Exception)
43+
- Include stack traces in debug logs with exc_info=True
44+
- Use f-strings for log messages
45+
- Run ruff check and format before commits
46+
47+
### Testing Requirements
48+
- Write unit tests in `tests/` directory using pytest
49+
- Cover core logic: coordinator updates, calculations, config flow, entity states
50+
- Achieve minimum 90% test coverage
51+
- Test edge cases: sensor unavailability, invalid configs, zero history
52+
- Use Home Assistant test harness (hass, mock_config_entry, etc.)
53+
54+
### Integration Features
55+
- Occupancy Probability Sensor: Shows Bayesian probability as percentage
56+
- Occupancy Status Sensor: Binary sensor based on probability threshold
57+
- Occupancy Prior Sensor: Shows overall prior probability
58+
- Individual Prior Sensors: Show priors for each input entity
59+
- Dynamic prior calculation based on historical data
60+
- Fallback to default probabilities when history insufficient
61+
62+
### Data Handling
63+
- Use DataUpdateCoordinator for state management
64+
- Calculate priors using historical data from recorder
65+
- Handle missing data gracefully with defaults
66+
- Efficient state listeners to minimize system load
67+
- Store calculated priors in coordinator data
68+
69+
### Configuration
70+
- Allow selection of input entities (motion, devices, etc.)
71+
- Configure probability threshold for binary sensor
72+
- Set history period for prior calculations
73+
- Select primary occupancy indicator
74+
- Provide options for reconfiguration
75+
76+
### Documentation
77+
- Include docstrings for all public interfaces
78+
- Document Bayesian calculation methodology
79+
- Provide clear error messages and logging
80+
- Maintain README with setup and usage instructions
81+
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
---
2+
description:
3+
globs:
4+
alwaysApply: true
5+
---
6+
# Bayesian Probability Calculations
7+
8+
## Prior Probability Calculation
9+
10+
The prior probabilities for each input entity are calculated in [custom_components/area_occupancy/calculate_prior.py](mdc:custom_components/area_occupancy/calculate_prior.py) using historical data:
11+
12+
1. For each input entity:
13+
- Query historical states using recorder component
14+
- Compare with primary occupancy indicator states
15+
- Calculate:
16+
- prob_given_true: P(Entity State | Area Occupied)
17+
- prob_given_false: P(Entity State | Area Not Occupied)
18+
- prior_probability: P(Entity State)
19+
20+
2. If insufficient history:
21+
- Fall back to defaults in [custom_components/area_occupancy/probabilities.py](mdc:custom_components/area_occupancy/probabilities.py)
22+
23+
## Composite Bayesian Calculation
24+
25+
The real-time occupancy probability is calculated in [custom_components/area_occupancy/calculate_prob.py](mdc:custom_components/area_occupancy/calculate_prob.py):
26+
27+
1. For each input entity:
28+
- Get current state
29+
- Use corresponding priors (calculated or default)
30+
- Apply Bayes' theorem:
31+
P(Occupied | Evidence) = P(Evidence | Occupied) * P(Occupied) / P(Evidence)
32+
33+
2. Combine probabilities using:
34+
- For independent evidence: P = P1 * P2 * ... * Pn
35+
- For dependent evidence: Use appropriate weighting/correlation factors
36+
37+
3. Normalize final probability to 0-100% range
38+
39+
## Implementation Guidelines
40+
41+
### Prior Calculation
42+
- Use significant state changes from recorder
43+
- Consider time windows for correlation
44+
- Handle missing or invalid data
45+
- Cache results to avoid recalculation
46+
- Update periodically (configurable interval)
47+
48+
### Real-time Calculation
49+
- Update on any input entity state change
50+
- Handle unavailable entities gracefully
51+
- Apply confidence weighting
52+
- Consider temporal factors
53+
- Optimize for performance
54+
55+
### Data Flow
56+
1. [coordinator.py](mdc:custom_components/area_occupancy/coordinator.py) triggers updates
57+
2. [calculate_prior.py](mdc:custom_components/area_occupancy/calculate_prior.py) computes priors
58+
3. [calculate_prob.py](mdc:custom_components/area_occupancy/calculate_prob.py) computes final probability
59+
4. Results update sensors in [sensor.py](mdc:custom_components/area_occupancy/sensor.py)
60+
61+
### Error Handling
62+
- Validate probability ranges (0-1)
63+
- Handle division by zero
64+
- Log calculation steps at debug level
65+
- Provide fallback values
66+
- Report calculation errors
67+
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
---
2+
description:
3+
globs: tests/*
4+
alwaysApply: false
5+
---
6+
# Testing Requirements
7+
8+
## Test Structure
9+
10+
Tests are organized in the `tests` directory following Home Assistant conventions:
11+
12+
- [tests/test_init.py](mdc:tests/test_init.py): Integration setup tests
13+
- [tests/test_config_flow.py](mdc:tests/test_config_flow.py): Configuration flow tests
14+
- [tests/test_coordinator.py](mdc:tests/test_coordinator.py): Data coordinator tests
15+
- [tests/test_calculate_prior.py](mdc:tests/test_calculate_prior.py): Prior calculation tests
16+
- [tests/test_calculate_prob.py](mdc:tests/test_calculate_prob.py): Probability calculation tests
17+
- [tests/test_sensor.py](mdc:tests/test_sensor.py): Sensor entity tests
18+
- [tests/test_binary_sensor.py](mdc:tests/test_binary_sensor.py): Binary sensor tests
19+
- [tests/conftest.py](mdc:tests/conftest.py): Pytest fixtures and configuration
20+
21+
## Test Coverage Requirements
22+
23+
### Core Components (100% coverage)
24+
- Prior probability calculations
25+
- Bayesian probability calculations
26+
- Configuration validation
27+
- Entity state management
28+
- Service handlers
29+
30+
### Integration Components (90% coverage)
31+
- Setup and initialization
32+
- State updates and listeners
33+
- Historical data fetching
34+
- Error handling paths
35+
- Coordinator updates
36+
37+
## Test Categories
38+
39+
### Unit Tests
40+
- Test individual functions in isolation
41+
- Mock external dependencies
42+
- Verify calculation accuracy
43+
- Test edge cases and error conditions
44+
- Validate type hints and interfaces
45+
46+
### Integration Tests
47+
- Test component interactions
48+
- Verify state propagation
49+
- Test configuration flows
50+
- Validate service calls
51+
- Test update coordination
52+
53+
### Mocking Guidelines
54+
- Use `pytest-mock` for mocking
55+
- Mock Home Assistant core services
56+
- Mock recorder/history data
57+
- Mock entity states and updates
58+
- Provide realistic test data
59+
60+
## Test Cases
61+
62+
### Prior Calculation Tests
63+
- Calculate priors with sufficient history
64+
- Handle insufficient history data
65+
- Test correlation calculations
66+
- Verify default fallbacks
67+
- Test time window handling
68+
69+
### Probability Calculation Tests
70+
- Test Bayesian formula implementation
71+
- Verify probability combinations
72+
- Test normalization
73+
- Handle missing/invalid inputs
74+
- Test threshold calculations
75+
76+
### Configuration Tests
77+
- Validate entity selections
78+
- Test option validation
79+
- Verify schema updates
80+
- Test error handling
81+
- Validate migrations
82+
83+
### Entity Tests
84+
- Test sensor creation
85+
- Verify state updates
86+
- Test attribute handling
87+
- Validate availability
88+
- Test cleanup
89+
90+
## Test Execution
91+
92+
### Setup
93+
- Use pytest
94+
- Enable coverage reporting
95+
- Configure test fixtures
96+
- Set up mocking utilities
97+
- Prepare test data
98+
99+
### Running Tests
100+
- Run with `pytest tests/`
101+
- Generate coverage report
102+
- Verify minimum coverage
103+
- Check test performance
104+
- Validate clean teardown
105+
106+
### CI Integration
107+
- Run tests in GitHub Actions
108+
- Enforce coverage thresholds
109+
- Report test results
110+
- Block merges on failures
111+
- Archive test artifacts
112+

custom_components/area_occupancy/__init__.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
_LOGGER = logging.getLogger(__name__)
1818

19+
# # Define platforms
20+
# PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR, Platform.NUMBER]
21+
1922

2023
async def async_setup(hass: HomeAssistant, config: dict) -> bool:
2124
"""Set up the Area Occupancy Detection integration."""
@@ -91,22 +94,21 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
9194
# Load stored data and initialize states
9295
try:
9396
await coordinator.async_setup()
94-
9597
except Exception as err:
9698
_LOGGER.error("Failed to load stored data: %s", err)
9799
raise ConfigEntryNotReady("Failed to load stored data") from err
98100

99-
# Trigger an initial refresh
100-
await coordinator.async_refresh()
101-
102101
# Store the coordinator for future use
103102
hass.data[DOMAIN][entry.entry_id] = {"coordinator": coordinator}
104103

104+
# Set up services
105+
await async_setup_services(hass)
106+
105107
# Setup platforms
106108
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
107109

108110
# Add an update listener to handle configuration updates
109-
entry.async_on_unload(entry.add_update_listener(async_update_options))
111+
entry.async_on_unload(entry.add_update_listener(_async_entry_updated))
110112

111113
except Exception as err:
112114
_LOGGER.exception(
@@ -126,15 +128,23 @@ async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
126128

127129
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
128130
"""Unload a config entry."""
129-
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
130-
if unload_ok:
131+
_LOGGER.debug("Unloading Area Occupancy config entry")
132+
133+
# Unload all platforms
134+
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
135+
# Clean up
136+
coordinator = hass.data[DOMAIN][entry.entry_id]["coordinator"]
137+
await coordinator.async_shutdown()
131138
hass.data[DOMAIN].pop(entry.entry_id)
139+
if not hass.data[DOMAIN]:
140+
hass.data.pop(DOMAIN)
141+
132142
return unload_ok
133143

134144

135-
async def async_update_options(hass: HomeAssistant, entry: ConfigEntry) -> None:
136-
"""Update options when config entry is updated."""
145+
async def _async_entry_updated(hass: HomeAssistant, entry: ConfigEntry) -> None:
146+
"""Handle config entry update."""
147+
_LOGGER.debug("Config entry updated, updating coordinator")
137148
coordinator = hass.data[DOMAIN][entry.entry_id]["coordinator"]
138149
await coordinator.async_update_options()
139-
# Trigger a refresh *after* options have been processed by the coordinator
140150
await coordinator.async_refresh()

custom_components/area_occupancy/binary_sensor.py

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from __future__ import annotations
44

5+
import logging
6+
57
from homeassistant.components.binary_sensor import (
68
BinarySensorDeviceClass,
79
BinarySensorEntity,
@@ -14,6 +16,9 @@
1416

1517
from .const import DOMAIN, NAME_BINARY_SENSOR
1618
from .coordinator import AreaOccupancyCoordinator
19+
from .virtual_sensor import async_setup_virtual_sensors
20+
21+
_LOGGER = logging.getLogger(__name__)
1722

1823

1924
class AreaOccupancyBinarySensor(
@@ -62,12 +67,33 @@ async def async_setup_entry(
6267
coordinator: AreaOccupancyCoordinator = hass.data[DOMAIN][config_entry.entry_id][
6368
"coordinator"
6469
]
65-
async_add_entities(
66-
[
67-
AreaOccupancyBinarySensor(
68-
coordinator=coordinator,
69-
entry_id=config_entry.entry_id,
70-
),
71-
],
72-
update_before_add=True,
70+
71+
# 1. Create the main sensor instance
72+
main_sensor = AreaOccupancyBinarySensor(
73+
coordinator=coordinator,
74+
entry_id=config_entry.entry_id,
7375
)
76+
77+
# 2. Call virtual sensor setup to get virtual sensor instances
78+
virtual_sensors_to_add = []
79+
try:
80+
virtual_sensors_to_add = await async_setup_virtual_sensors(
81+
hass,
82+
config_entry,
83+
async_add_entities, # Pass it along, though it shouldn't be used inside now
84+
coordinator,
85+
)
86+
except (ImportError, ModuleNotFoundError):
87+
_LOGGER.warning("Virtual sensor module not available")
88+
except Exception:
89+
_LOGGER.exception("Error setting up virtual sensors")
90+
91+
# 3. Combine main and virtual sensors
92+
all_sensors_to_add = [main_sensor, *virtual_sensors_to_add]
93+
94+
# 4. Add all entities in a single call
95+
if all_sensors_to_add:
96+
_LOGGER.debug("Adding %d binary sensor entities", len(all_sensors_to_add))
97+
async_add_entities(all_sensors_to_add, update_before_add=True)
98+
else:
99+
_LOGGER.warning("No binary sensor entities to add.")

0 commit comments

Comments
 (0)