|
| 1 | +# Copilot Instructions for Aquarium AI Home Assistant Integration |
| 2 | + |
| 3 | +## Project Overview |
| 4 | + |
| 5 | +This is a **Home Assistant custom integration** called **Aquarium AI** (domain: `aquarium_ai`). It uses Home Assistant's built-in `ai_task` service to perform AI-powered analysis of aquarium sensor data and optional camera feeds, providing natural-language health assessments, water change recommendations, and persistent notifications. |
| 6 | + |
| 7 | +The integration is distributed via **HACS** (Home Assistant Community Store) and has no Python package dependencies beyond Home Assistant itself. |
| 8 | + |
| 9 | +--- |
| 10 | + |
| 11 | +## Repository Structure |
| 12 | + |
| 13 | +``` |
| 14 | +custom_components/aquarium_ai/ # Main integration code |
| 15 | +├── __init__.py # Integration setup, core AI analysis logic, helper functions |
| 16 | +├── config_flow.py # Multi-step UI configuration flow (ConfigFlow + OptionsFlow) |
| 17 | +├── const.py # All constants, defaults, and default AI prompts |
| 18 | +├── manifest.json # Integration metadata (domain, version, dependencies) |
| 19 | +├── sensor.py # Sensor entities (AI analysis text sensors, per-parameter + overall) |
| 20 | +├── binary_sensor.py # Binary sensors (water change needed, parameter problem) |
| 21 | +├── button.py # Button entity (Run Analysis) |
| 22 | +├── select.py # Select entities (Update Frequency, Notification Format) |
| 23 | +├── switch.py # Switch entities (auto-notifications, per-parameter analysis toggles) |
| 24 | +├── services.yaml # Service definitions (run_analysis, run_analysis_for_aquarium) |
| 25 | +├── strings.json # Default English UI strings |
| 26 | +└── translations/ |
| 27 | + ├── en.json # English translations |
| 28 | + ├── de.json # German translations |
| 29 | + └── template.json # Template for new translations |
| 30 | +
|
| 31 | +.github/ |
| 32 | +├── workflows/ |
| 33 | +│ ├── hassfest.yaml # Validates integration against Home Assistant standards |
| 34 | +│ └── validate.yml # HACS validation + hassfest |
| 35 | +└── ISSUE_TEMPLATE/ |
| 36 | + ├── bug_report.md |
| 37 | + └── feature_request.md |
| 38 | +
|
| 39 | +hacs.json # HACS metadata |
| 40 | +README.md # User-facing documentation |
| 41 | +CONTRIBUTING.md # Contribution guidelines |
| 42 | +TRANSLATION_GUIDE.md # Instructions for adding translations |
| 43 | +``` |
| 44 | + |
| 45 | +--- |
| 46 | + |
| 47 | +## Key Architecture Concepts |
| 48 | + |
| 49 | +### Integration Setup Flow |
| 50 | +1. `async_setup_entry` in `__init__.py` is the main entry point |
| 51 | +2. It forwards setup to all platforms: `sensor`, `binary_sensor`, `switch`, `select`, `button` |
| 52 | +3. Schedules periodic AI analysis using `async_track_time_interval` |
| 53 | +4. Optionally runs analysis on startup (60-second delay via `async_call_later`) |
| 54 | +5. Registers `run_analysis` and `run_analysis_for_aquarium` services |
| 55 | + |
| 56 | +### AI Analysis Pipeline (`__init__.py: send_ai_aquarium_analysis`) |
| 57 | +1. Collects sensor data from configured HA sensor entities using `get_sensor_info()` |
| 58 | +2. Checks per-parameter analysis toggle switches (stored in `entry.data`) |
| 59 | +3. Builds a structured prompt using configurable AI prompt templates from `const.py` |
| 60 | +4. Calls `ai_task.generate_data` service with a structured response schema |
| 61 | +5. Stores the AI response in `hass.data[DOMAIN][entry_id]["sensor_analysis"]` |
| 62 | +6. Sends a `persistent_notification` (if auto-notifications enabled) |
| 63 | +7. Triggers state updates on sensor entities via `async_write_ha_state()` |
| 64 | + |
| 65 | +### Shared Data Pattern |
| 66 | +Entities read AI analysis results from `hass.data[DOMAIN][entry_id]["sensor_analysis"]` — a dict populated after each AI call. Sensor entities poll this dict on `async_update()`. |
| 67 | + |
| 68 | +### Config Entry Data Storage |
| 69 | +All configuration (sensors, toggles, AI prompts, tank info) is stored directly in `config_entry.data`. The options flow updates `entry.data` directly via `hass.config_entries.async_update_entry()` — it does **not** use `entry.options`. |
| 70 | + |
| 71 | +--- |
| 72 | + |
| 73 | +## Entity Naming Conventions |
| 74 | + |
| 75 | +All entities are named with the tank name as a prefix: |
| 76 | +- `{tank_name} Temperature Analysis` (sensor) |
| 77 | +- `{tank_name} Water Change Needed` (binary sensor) |
| 78 | +- `{tank_name} Run Analysis` (button) |
| 79 | +- `{tank_name} Update Frequency` (select) |
| 80 | +- `{tank_name} Analyze Temperature` (switch) |
| 81 | + |
| 82 | +Unique IDs follow the pattern: `{entry_id}_{descriptor}` (e.g., `{entry_id}_temperature_analysis`). |
| 83 | + |
| 84 | +All entities share the same device under: |
| 85 | +```python |
| 86 | +{"identifiers": {(DOMAIN, config_entry.entry_id)}, "name": f"Aquarium AI - {tank_name}"} |
| 87 | +``` |
| 88 | + |
| 89 | +--- |
| 90 | + |
| 91 | +## AI Response Structure |
| 92 | + |
| 93 | +The AI call uses `ai_task.generate_data` with a `structure` dict. Response keys follow these patterns: |
| 94 | +- `{param_name_snake_case}_analysis` — brief (≤200 chars), used for sensor state |
| 95 | +- `{param_name_snake_case}_notification_analysis` — detailed, used in notifications |
| 96 | +- `overall_analysis` / `overall_notification_analysis` — overall health |
| 97 | +- `water_change_recommended` — `"Yes/No + brief reason"` (drives binary sensor state) |
| 98 | +- `water_change_recommendation` — detailed recommendation for notifications |
| 99 | +- `camera_visual_analysis` / `camera_visual_notification_analysis` — visual analysis (only if camera configured and toggle enabled) |
| 100 | + |
| 101 | +Sensor states are capped at 255 characters (truncated with `...`). |
| 102 | + |
| 103 | +--- |
| 104 | + |
| 105 | +## Adding New Parameters or Sensors |
| 106 | + |
| 107 | +When adding a new sensor type (e.g., Ammonia), you must update **all** of the following: |
| 108 | +1. `const.py` — Add `CONF_*_SENSOR`, `CONF_ANALYZE_*`, `DEFAULT_ANALYZE_*` |
| 109 | +2. `__init__.py` — Add to `sensor_mappings` list in `async_setup_entry` |
| 110 | +3. `sensor.py` — Add to `sensor_mappings` in `async_setup_entry` |
| 111 | +4. `binary_sensor.py` — Add to `sensor_mappings` |
| 112 | +5. `switch.py` — Add to `parameter_switches` list |
| 113 | +6. `config_flow.py` — Add `EntitySelector` to sensors step schema in both `AquariumAIConfigFlow` and `AquariumAIOptionsFlow._get_sensors_schema()` |
| 114 | +7. `strings.json` — Add label and description for the new sensor field |
| 115 | +8. `translations/en.json` — Mirror the strings.json changes |
| 116 | +9. `translations/template.json` — Add the translation key |
| 117 | + |
| 118 | +--- |
| 119 | + |
| 120 | +## Validation and CI |
| 121 | + |
| 122 | +There are **no unit tests** in this repository. All testing is done manually in a real Home Assistant environment. |
| 123 | + |
| 124 | +CI runs two checks via GitHub Actions: |
| 125 | +- **`hassfest`** (`hassfest.yaml`): Validates integration metadata, manifest, translations, and strings against HA standards |
| 126 | +- **`HACS validation`** (`validate.yml`): Validates HACS-specific requirements |
| 127 | + |
| 128 | +To validate locally, you can run hassfest in a Home Assistant dev environment: |
| 129 | +```bash |
| 130 | +python -m script.hassfest |
| 131 | +``` |
| 132 | + |
| 133 | +There is no `requirements.txt`, `pyproject.toml`, or linting configuration — the project relies on HA's built-in validation. |
| 134 | + |
| 135 | +--- |
| 136 | + |
| 137 | +## Translation System |
| 138 | + |
| 139 | +- `strings.json` is the source of truth for UI text keys |
| 140 | +- `translations/en.json` must mirror `strings.json` exactly |
| 141 | +- `translations/template.json` is a template for translators |
| 142 | +- New translation files go in `translations/{lang_code}.json` |
| 143 | +- Hassfest validates that translations match the structure defined in `strings.json` |
| 144 | + |
| 145 | +When modifying UI-visible text (config flow steps, labels, descriptions, error messages): |
| 146 | +1. Update `strings.json` |
| 147 | +2. Update `translations/en.json` with the same change |
| 148 | +3. Update `translations/template.json` |
| 149 | +4. Update any other existing translation files if the key structure changes |
| 150 | + |
| 151 | +--- |
| 152 | + |
| 153 | +## Debug Logging |
| 154 | + |
| 155 | +To enable debug logging in Home Assistant, add to `configuration.yaml`: |
| 156 | +```yaml |
| 157 | +logger: |
| 158 | + default: info |
| 159 | + logs: |
| 160 | + custom_components.aquarium_ai: debug |
| 161 | +``` |
| 162 | +
|
| 163 | +All modules use `_LOGGER = logging.getLogger(__name__)`. |
| 164 | + |
| 165 | +--- |
| 166 | + |
| 167 | +## Common Errors and Workarounds |
| 168 | + |
| 169 | +- **`sensor_not_found` error in config flow**: The sensor entity provided does not exist in HA states. Validate that the entity is available before saving. |
| 170 | +- **`ai_task_required` error**: The `ai_task` entity ID was not provided. It is mandatory for the integration to function. |
| 171 | +- **`at_least_one_sensor` error**: At least one sensor entity must be configured. |
| 172 | +- **Sensor state truncation**: AI responses longer than 255 characters are automatically truncated to 252 chars + `...` before storing in sensor state. |
| 173 | +- **Options flow saves to `entry.data` directly**: Unlike typical HA patterns that use `entry.options`, this integration saves all settings directly into `entry.data` via `async_update_entry`. Do not introduce `entry.options` usage without updating all consumers. |
| 174 | +- **No `PLATFORMS` list**: Platform names are hardcoded as a list `["sensor", "binary_sensor", "switch", "select", "button"]` in `async_setup_entry` and `async_unload_entry`. |
| 175 | +- **Hassfest validation failures**: Commonly caused by mismatched keys between `strings.json` and `translations/en.json`, or invalid `manifest.json` fields. Always sync these files after changes. |
| 176 | + |
| 177 | +--- |
| 178 | + |
| 179 | +## Version and Release |
| 180 | + |
| 181 | +The integration version is defined in `manifest.json` (`"version": "1.2.1"`). Update this when releasing new versions. HACS uses GitHub releases — create a release tag matching the version string. |
| 182 | +
|
| 183 | +--- |
| 184 | +
|
| 185 | +## Home Assistant Coding Conventions |
| 186 | +
|
| 187 | +- All setup functions are `async` and prefixed with `async_` |
| 188 | +- `async_setup_entry` / `async_unload_entry` are the standard HA entry points |
| 189 | +- Use `entry.data.get(KEY, DEFAULT)` pattern for safe config access |
| 190 | +- `hass.config_entries.async_update_entry()` is used for runtime config changes |
| 191 | +- Entity state must be written via `self.async_write_ha_state()` after mutations |
| 192 | +- `_LOGGER` is module-level, not class-level |
| 193 | +- Use `vol.Schema` with `voluptuous` for config validation |
| 194 | +- Selectors come from `homeassistant.helpers.selector` |
0 commit comments