feat: expose_recommended_entities service + voice/LLM docs (#65)#66
Conversation
Adds a one-shot service that exposes an opinionated headline set of 17
entities (battery SOC, PV/grid/load power, today's and lifetime energy
totals, inverter status) to one or more voice/LLM assistants. Mirrors the
dashboard generator's UX: user runs it once per inverter, gets a notification
listing what was exposed, and can re-run any time without losing manual
customisations (service only ever exposes, never un-exposes).
Default assistant is `conversation`, which covers Assist, the LLM tools API,
and MCP-via-conversation. Optional `assistants` parameter lets users target
`cloud.alexa` / `cloud.google_assistant` / custom assistants.
Topology variation is handled implicitly: PV-only installs skip battery
entries, three-phase inverters use the same entity keys, etc. — the service
matches by `_{key}` suffix on `unique_id` and silently skips keys with no
corresponding entity.
Also adds a "Voice assistants & LLM access" README section covering:
- Why exposure matters (HA's device-class allowlist excludes power/energy/
battery, so headline sensors are invisible to voice/LLM by default)
- How to use the new service
- The recommended manual-expose set for users who prefer point-and-click
- Suggested aliases (documented, not shipped — entity-registry aliases have
no provenance marker so we can't distinguish ours from user edits)
- Why HA filters these device classes out by default
While in the README services table, also documents the redetect_plant
service from #62 which wasn't added to README at the time.
Tests: two happy-path tests under HA's harness — one for the default
assistant target, one for a custom `assistants` list. Mirrors the lightweight
test pattern the project favours; no error-branch tests since those
exercise HA's device-registry lookup rather than our code.
Closes #65
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request introduces a new service, expose_recommended_entities, which allows users to easily expose a curated, opinionated set of headline entities (such as battery SOC, PV power, and grid import/export) to voice and LLM assistants. It also includes comprehensive documentation in the README and corresponding unit tests. The review feedback focuses on improving robustness and usability: validating that the assistants list is not empty, defensively skipping disabled entities during exposure (and updating the tests accordingly), and formatting the list of exposed entities as a markdown bulleted list in the persistent notification for better readability.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughThis PR implements a Home Assistant service to expose a recommended set of energy entities (battery state, power flows, energy totals, status) to voice assistants. Users call the service with a device ID and optional assistant list; the handler resolves the device, matches entities by configured key suffixes, exposes them via Home Assistant's exposure API, and notifies the user. Includes documentation, configuration metadata, and integration tests. ChangesVoice Assistant Entity Exposure
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
tests/test_init.py (1)
300-323: ⚡ Quick winStrengthen the custom-assistants test to assert full fan-out coverage.
This test currently checks only the assistant set; it can miss regressions where only a subset of entities is exposed.
Proposed enhancement
async def test_expose_recommended_entities_honours_custom_assistants_list( hass, mock_client, setup_integration ): @@ + entity_reg = er.async_get(hass) + expected_entity_ids = { + entry.entity_id + for entry in er.async_entries_for_config_entry(entity_reg, setup_integration.entry_id) + for key in EXPOSE_RECOMMENDED_ENTITY_KEYS + if entry.unique_id.endswith(f"_{key}") + } + with patch("custom_components.givenergy_local.async_expose_entity") as expose_mock: @@ - # Two assistants × N entities → 2N total exposure calls. + # Two assistants x N entities -> 2N total exposure calls. assistants_called = {call.args[1] for call in expose_mock.call_args_list} assert assistants_called == {"conversation", "cloud.alexa"} + assert len(expose_mock.call_args_list) == 2 * len(expected_entity_ids) + exposed_pairs = {(call.args[1], call.args[2]) for call in expose_mock.call_args_list} + assert exposed_pairs == { + (assistant, entity_id) + for assistant in {"conversation", "cloud.alexa"} + for entity_id in expected_entity_ids + }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/test_init.py` around lines 300 - 323, Test test_expose_recommended_entities_honours_custom_assistants_list currently only asserts assistants were used and can miss missing entity exposures; update the assertion to verify full fan-out by checking that async_expose_entity (patched as expose_mock) was called for every (entity_id, assistant) pair invoked by SERVICE_EXPOSE_RECOMMENDED_ENTITIES — e.g., compute the set of entity IDs discovered from the device (or count N entities) and assert len(expose_mock.call_args_list) == len(entities) * len(assistants) and/or assert the set of (call.args[0], call.args[1]) pairs equals the Cartesian product of entity IDs and the assistants list to ensure each entity was exposed for each assistant.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@custom_components/givenergy_local/__init__.py`:
- Around line 110-114: The SERVICE_EXPOSE_RECOMMENDED_ENTITIES_SCHEMA currently
allows an empty "assistants" list; update that schema so "assistants" must be a
non-empty list. Modify SERVICE_EXPOSE_RECOMMENDED_ENTITIES_SCHEMA to validate
assistants with cv.ensure_list and a length check (e.g., vol.Length(min=1)) in
the vol.All chain and then validate items as strings so calls with assistants:
[] are rejected.
In `@tests/test_init.py`:
- Line 320: Replace the ambiguous Unicode multiplication sign in the comment
"Two assistants × N entities → 2N total exposure calls." by an ASCII character
(e.g., "x" or "*") so Ruff RUF003 is not triggered; update the comment to "Two
assistants x N entities → 2N total exposure calls." (or use "*") and then run
the project's formatter/linter fix (uv run ruff check --fix && uv run ruff
format) to apply and verify the change.
---
Nitpick comments:
In `@tests/test_init.py`:
- Around line 300-323: Test
test_expose_recommended_entities_honours_custom_assistants_list currently only
asserts assistants were used and can miss missing entity exposures; update the
assertion to verify full fan-out by checking that async_expose_entity (patched
as expose_mock) was called for every (entity_id, assistant) pair invoked by
SERVICE_EXPOSE_RECOMMENDED_ENTITIES — e.g., compute the set of entity IDs
discovered from the device (or count N entities) and assert
len(expose_mock.call_args_list) == len(entities) * len(assistants) and/or assert
the set of (call.args[0], call.args[1]) pairs equals the Cartesian product of
entity IDs and the assistants list to ensure each entity was exposed for each
assistant.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: f897a590-b440-42c8-9395-cb9cb096ff36
📒 Files selected for processing (6)
README.mdcustom_components/givenergy_local/__init__.pycustom_components/givenergy_local/const.pycustom_components/givenergy_local/services.yamlcustom_components/givenergy_local/translations/en.jsontests/test_init.py
| blocking=True, | ||
| ) | ||
|
|
||
| # Two assistants × N entities → 2N total exposure calls. |
There was a problem hiding this comment.
Replace ambiguous multiplication sign in comment.
Line 320 uses ×, which Ruff flags as RUF003.
Proposed fix
- # Two assistants × N entities → 2N total exposure calls.
+ # Two assistants x N entities -> 2N total exposure calls.As per coding guidelines **/*.py: Run uv run ruff check --fix && uv run ruff format before committing code.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # Two assistants × N entities → 2N total exposure calls. | |
| # Two assistants x N entities -> 2N total exposure calls. |
🧰 Tools
🪛 Ruff (0.15.14)
[warning] 320-320: Comment contains ambiguous × (MULTIPLICATION SIGN). Did you mean x (LATIN SMALL LETTER X)?
(RUF003)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/test_init.py` at line 320, Replace the ambiguous Unicode multiplication
sign in the comment "Two assistants × N entities → 2N total exposure calls." by
an ASCII character (e.g., "x" or "*") so Ruff RUF003 is not triggered; update
the comment to "Two assistants x N entities → 2N total exposure calls." (or use
"*") and then run the project's formatter/linter fix (uv run ruff check --fix &&
uv run ruff format) to apply and verify the change.
The inverter status sensor is defined with `key="status"` and
`translation_key="inverter_status"`. The curated set had `inverter_status`,
which never matched `unique_id.endswith("_inverter_status")` because the
unique_id suffix is `_status` — so the documented "is everything ok?" entity
was silently skipped on every service call.
The existing test missed this because both its expected-set computation
and the production matching logic used the same broken comparison —
classic tautology. Rework the test to look entities up by their literal
unique_id (`{serial}_{key}`), which fails loudly when a curated key isn't
the actual entity description key.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four small improvements from the PR #66 review pass: - Skip disabled entities in the handler — disabled entities have no registry state for assistants to consume, so exposing them is a no-op at best and a confusion at worst. - Enforce assistants list non-empty via vol.Length(min=1) so an explicit empty list raises at validation time rather than silently producing an empty notification. - Format the exposed-entity notification as a bulleted markdown list including entity_ids — 17 comma-separated names was hard to scan. - Mirror the disabled-entity filter in the test so the all-or-nothing assertion accounts for it correctly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes #65.
Summary
Adds a one-shot service that exposes an opinionated headline set of 17 entities (battery SOC, PV/grid/load power, today's and lifetime energy totals, inverter status) to one or more voice/LLM assistants. Mirrors the `generate_dashboard` UX: run it once per inverter, get a persistent notification listing what was exposed, re-run any time without losing manual customisations.
Plus a new README section explaining how exposure works for this integration's headline entities.
Why a service (not an options-flow toggle)
The original issue body proposed an options-flow toggle for one-time auto-expose. After discussion we landed on a service instead, mirroring `generate_dashboard`:
The curated set (17 entities)
Topology variation is handled implicitly: PV-only installs skip battery entries, three-phase inverters use the same keys, etc. Matching is by `_{key}` suffix on `unique_id` — keys with no corresponding entity are silently skipped.
What's not included (and why)
Aliases — documented, not shipped
After a semble investigation of how HA Core integrations handle entity aliases, the verdict is don't seed aliases programmatically:
The README documents a suggested starting set users can copy in 30 seconds.
Files
Tests
Two new tests, both happy-path under HA's harness:
No error-branch tests (unknown device, no matching entry) — those exercise HA's device-registry lookup, not our code, matching the project's lightweight-test preference.
96 tests pass total (was 94 baseline).
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Tests