Skip to content

New integration for Gaposa blinds#161442

Open
mwatson2 wants to merge 45 commits intohome-assistant:devfrom
mwatson2:gaposa
Open

New integration for Gaposa blinds#161442
mwatson2 wants to merge 45 commits intohome-assistant:devfrom
mwatson2:gaposa

Conversation

@mwatson2
Copy link
Copy Markdown

@mwatson2 mwatson2 commented Jan 22, 2026

New PR for #138754

Proposed change

This change introduces a new component for Gaposa blinds and shades (https://www.gaposa.it/eng). The component uses the cloud pull model, accessing the same server and API as the Gaposa RollApp mobile application (https://www.gaposa.it/eng/news/rollapp/). This cloud service communicates with the shades through their LinkIt hub (https://www.gaposa.it/eng/prod/?residential/electronics/control-units/home-automation/linkit).

The Gaposa cloud API does not expose motor position or battery level. The integration provides open, close, and stop commands. Motion status (opening/closing) is inferred from elapsed time since the last command and an assumed motion duration; no numeric position is reported.

Type of change

  • Dependency upgrade
  • Bugfix (non-breaking change which fixes an issue)
  • New integration (thank you!)
  • New feature (which adds functionality to an existing integration)
  • Deprecation (breaking change to happen in the future)
  • Breaking change (fix/feature causing existing functionality to break)
  • Code quality improvements to existing code or addition of tests

Additional information

Checklist

  • I understand the code I am submitting and can explain how it works.
  • The code change is tested and works locally.
  • Local tests pass. Your PR cannot be merged unless tests pass
  • There is no commented out code in this PR.
  • I have followed the development checklist
  • I have followed the perfect PR recommendations
  • The code has been formatted using Ruff (ruff format homeassistant tests)
  • Tests have been added to verify that the new code works.
  • Any generated code has been carefully reviewed for correctness and compliance with project standards.

If user exposed functionality or configuration variables are added/changed:

If the code communicates with devices, web services, or third-party tools:

  • The manifest file has all fields filled out correctly.
    Updated and included derived files by running: python3 -m script.hassfest.
  • New or updated dependencies have been added to requirements_all.txt.
    Updated by running python3 -m script.gen_requirements_all.
  • For the updated dependencies - a link to the changelog, or at minimum a diff between library versions is added to the PR description.

To help with the load of incoming pull requests:

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new integration for Gaposa blinds and shades, which uses a cloud-based API to control motors via their LinkIt hub. The integration targets Bronze quality scale and includes configuration flow, data coordinator, cover entity implementation, and comprehensive test coverage.

Changes:

  • New Gaposa integration with config flow, coordinator, and cover entity implementation
  • Test suite covering config flow, coordinator, and cover entity functionality
  • Quality scale configuration and strict typing enabled

Reviewed changes

Copilot reviewed 19 out of 21 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
homeassistant/components/gaposa/init.py Integration setup with coordinator initialization and platform forwarding
homeassistant/components/gaposa/config_flow.py User and reauth configuration flows with validation
homeassistant/components/gaposa/coordinator.py Data update coordinator managing API communication and device updates
homeassistant/components/gaposa/cover.py Cover entity implementation with motion tracking and state management
homeassistant/components/gaposa/const.py Constants for commands, states, and intervals
homeassistant/components/gaposa/strings.json Localization strings for config flow
homeassistant/components/gaposa/manifest.json Integration manifest with bronze quality scale
homeassistant/components/gaposa/quality_scale.yaml Quality scale rule compliance status
tests/components/gaposa/*.py Test suite for config flow, coordinator, and cover entity
requirements_all.txt Added pygaposa dependency
requirements_test_all.txt Added pygaposa test dependency
mypy.ini Strict typing configuration for gaposa component
.strict-typing Added gaposa to strict typing list
CODEOWNERS Added maintainer for gaposa integration
tests/testing_config/.storage/core.config_entries Test configuration entry

Comment thread homeassistant/components/gaposa/coordinator.py Outdated
Comment thread homeassistant/components/gaposa/config_flow.py Outdated
Comment thread homeassistant/components/gaposa/__init__.py Outdated
Copy link
Copy Markdown
Member

@erwindouna erwindouna left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for contributing, here's an initial review!

Comment thread homeassistant/components/gaposa/__init__.py Outdated
Comment thread homeassistant/components/gaposa/__init__.py Outdated
Comment thread homeassistant/components/gaposa/__init__.py Outdated
Comment thread homeassistant/components/gaposa/__init__.py Outdated
Comment thread homeassistant/components/gaposa/config_flow.py Outdated
Comment thread homeassistant/components/gaposa/config_flow.py Outdated
Comment thread homeassistant/components/gaposa/config_flow.py Outdated
Comment thread homeassistant/components/gaposa/config_flow.py Outdated
Comment thread homeassistant/components/gaposa/coordinator.py Outdated
Comment thread homeassistant/components/gaposa/coordinator.py Outdated
@joostlek joostlek marked this pull request as draft February 24, 2026 23:03
mwatson2 and others added 19 commits April 10, 2026 07:17
Fixes an issue where the UI state of Gaposa covers wasn't properly updating after actions:
- When stop button is pressed, immediately request a refresh from the API and update UI
- After open/close actions, refresh API state before updating UI when motion completes
- Add tests to verify this behavior

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Fix context parameter in GaposaCover initialization
- Change log levels from INFO to DEBUG for routine operations
- Improve test handling of coroutines to avoid warnings
- Remove loop parameter from Gaposa API initialization
- Move API initialization to coordinator
- Use entry.runtime_data instead of hass.data for storing data
- Update quality_scale.yaml file
- Remove device_actions property as it's not needed
- Remove diagnostics.py for future PR
- Cleanup code by removing excessive comments
The existing test suite constructed GaposaCover and
DataUpdateCoordinatorGaposa directly with mocks, poking at private
attributes and overriding the upstream `hass` fixture. The cover
tests broke when HA tightened EntityPlatform's internals; the
coordinator tests broke when DataUpdateCoordinator.__init__ started
calling frame.report_usage(). Both of those breakages are the result
of the tests reaching around the public interface rather than the
code under test being wrong.

Replace the whole suite with behavior tests that route through a
proper setup:

conftest.py
- Drop the custom `hass` and `verify_cleanup` fixtures that were
  overriding HA core's standard ones.
- Build realistic pygaposa mocks: a Motor MagicMock spec'd from
  pygaposa.Motor (so Motor-typed tests still get isinstance hits)
  with AsyncMock up/down/stop; a device containing two test motors
  (Living Room = UP, Bedroom = DOWN); a Gaposa instance with an
  AsyncMock login/update/close and a clients list matching the
  `list[tuple[Client, User]]` shape the coordinator iterates.
- `mock_gaposa` patches Gaposa in both the coordinator AND config_flow
  modules, so the test never hits the real pygaposa Firebase init.
- `mock_setup_entry` now also mocks async_unload_entry so teardown
  doesn't blow up on runtime_data access when async_setup_entry was
  short-circuited.
- `init_integration` runs real async_setup(entry_id), exercising the
  actual coordinator + cover platform path.

test_cover.py
- Every test now asks hass.states.get("cover.living_room") or calls
  hass.services.async_call(...) rather than constructing GaposaCover.
- test_cover_entities_created / test_cover_initial_state_from_motor
  verify the two motors show up with the right initial states.
- test_cover_supported_features asserts OPEN|CLOSE|STOP and absence
  of position control.
- test_open/close/stop_cover_calls_motor_* verify the mocked Motor
  coroutines get called when services fire.
- test_cover_reports_opening_during_motion_window /
  test_cover_reports_closing_during_motion_window assert the
  entity goes into STATE_OPENING / STATE_CLOSING during the motion
  window. The old tests checked an is_opening attribute that cover
  entities never expose in attributes — it goes into .state.
- test_cover_state_mapping parametrizes motor.state → HA state for
  UP / DOWN / STOP / UNKNOWN.
- test_cover_device_registry_entry verifies each motor is registered
  as its own device.
- test_motion_window_collapses_after_delay asserts the cover returns
  to a steady state after MOTION_DELAY via async_fire_time_changed.

test_coordinator.py
- Tests now access the coordinator via
  `config_entry.runtime_data` after init_integration runs. A small
  `_get_coordinator` helper understands both the current dict shape
  and the direct-coordinator shape that Stage 3 will introduce, so
  these tests survive the refactor.
- Parametrized the auth-failure test on GaposaAuthException /
  FirebaseAuthException.
- Added test_coordinator_populates_data and
  test_on_document_updated_pushes_data for the happy paths.
- Kept the fast-interval and recovery-interval tests, now exercising
  the coordinator that was really set up rather than a hand-
  constructed one.

test_init.py (new)
- test_setup_and_unload: LOADED → unload → NOT_LOADED.
- test_unload_closes_gaposa_client: verifies the mocked Gaposa.close
  is called on unload.
- test_network_failure_during_setup_retries: OSError on first refresh
  → SETUP_RETRY.
- test_auth_failure_during_setup_triggers_reauth (parametrized on
  both auth exception types): confirms the entry lands in
  SETUP_ERROR and a reauth flow is queued.

test_config_flow.py
- Strip the "HERE" docstring typo on line 1.
- Replace the narrow `patch("pygaposa.Gaposa.login", ...)` patches
  with the shared `mock_gaposa` fixture. The old approach left the
  Gaposa class's __init__ to run for real, which leaked an aiohttp
  ClientSession every test and then blew up at teardown.
- Parametrize the validation-error test over the four error modes
  (GaposaAuthException, FirebaseAuthException, ClientConnectionError,
  generic Exception).
- Add test_reauth_flow_invalid_auth_shows_form_error for the
  "still-bad-credentials" path.

Test count: 5 → 33 (all passing). The new tests exercise real
platform setup, so subsequent stages (runtime_data unwrap, cover.py
overhaul, config_flow cleanup) can rely on them as a regression net.
After rebasing onto current upstream/dev the integration started
emitting:

  WARNING: Detected that integration 'gaposa' relies on ContextVar,
  but should pass the config entry explicitly. at
  homeassistant/components/gaposa/coordinator.py, line 33:
  super().__init__(. This will stop working in Home Assistant 2026.8

DataUpdateCoordinator now expects subclasses to pass the ConfigEntry
explicitly via the `config_entry` keyword. Add it to
DataUpdateCoordinatorGaposa.__init__ and plumb the entry through from
async_setup_entry. No behaviour change; just deletes the deprecation
warning and makes the integration forward-compatible with HA 2026.8.
Address the __init__.py feedback from @erwindouna + Copilot:

- Drop the `{"coordinator": coordinator}` dict wrapper on
  runtime_data. The coordinator is the only thing stored; put it
  directly on runtime_data.
- Introduce a typed `GaposaConfigEntry = ConfigEntry[DataUpdateCoordinatorGaposa]`
  alias (modern HA pattern) and use it on both async_setup_entry and
  async_unload_entry. This both removes the manual
  `: DataUpdateCoordinatorGaposa` annotation and lets type checkers
  understand `entry.runtime_data` without casts.
- Read entry.data[...] directly in the coordinator constructor
  instead of materialising intermediate api_key/username/password
  variables.
- Strip the what-not-why comments ("Store runtime data that should
  persist between restarts", "Fetch initial data so we have data
  when entities subscribe", "Call async_setup_entry for each of the
  platforms"). The code is self-explanatory.
- Delete the empty `update_listener` function and its
  `entry.add_update_listener` subscription. There are no options
  configured for this integration, and the no-op function was
  flagged by Copilot.
- Drop the `noqa: F401` re-export of CONF_PASSWORD / CONF_USERNAME
  from `.const`. Callers now import from `homeassistant.const`
  (Stage 7 will delete the `.const` copies entirely).

cover.py:46
- Update the runtime_data access to match: `config_entry.runtime_data`
  is now the coordinator, not a dict.

Tests
- Simplify the helpers in test_coordinator.py and test_cover.py that
  previously handled both the dict and the direct shapes. The dict
  shape no longer exists.

All 33 tests still pass.
Rewrite the config flow to address @erwindouna's PR feedback:

- Drop the deprecated `loop=hass.loop` kwarg on Gaposa(...). Modern
  async libraries use asyncio.get_running_loop() internally and the
  pygaposa constructor accepts websession without it.
- Move validate_input into the flow class as
  `_async_validate_credentials`. Return a (client_id, error) tuple
  instead of raising custom exceptions that the flow then catches —
  this flattens four try/except blocks into a single error string.
- Drop the `{"title": ...}` return-value pattern; the title is a
  constant (DEFAULT_GATEWAY_NAME) so pass it directly to
  async_create_entry.
- Drop `VERSION = 1` (it's the default).
- **Fix unique_id**: the old code used `DOMAIN` as the unique id,
  which meant only one Gaposa account could ever be configured per
  HA instance. Switch to `gaposa.clients[0][0].id` — the stable
  account-scoped Gaposa client id returned after login. Also add a
  test that asserts the created entry's unique_id.
- Use `CONF_API_KEY` / `CONF_USERNAME` / `CONF_PASSWORD` from
  homeassistant.const everywhere, replacing the previous literal
  "api_key" / "username" / "password" string usage in the reauth
  path.
- Replace the manual async_update_entry + async_reload + async_abort
  sequence in reauth_confirm with self.async_update_reload_and_abort.
- Add a `wrong_account` abort so a reauth flow can't silently
  re-bind the entry to a different Gaposa account. Add the matching
  strings.json entry and a test for it.
- Rename the class GaposaConfigFlow (instead of ConfigFlow) so the
  base class doesn't need a qualified `config_entries.ConfigFlow`
  reference.

Drop the now-unused CannotConnect and InvalidAuth custom exceptions.

Tests
- conftest: mock client gets an explicit `client.id`
  (TEST_CLIENT_ID = "gaposa-client-123"), mock_config_entry now uses
  that as its unique_id so init_integration matches the new key.
- test_form_creates_entry now asserts
  `result2["result"].unique_id == TEST_CLIENT_ID`.
- New test_form_aborts_when_already_configured covers the duplicate
  prevention.
- New test_reauth_flow_wrong_account_aborts covers the
  unique-id-mismatch abort.

All 35 tests pass.
- Use a module-level `_LOGGER` constant instead of taking a `logger`
  argument through the constructor (@erwindouna).
- Move the one-time Gaposa login into `_async_setup`, the dedicated
  hook `DataUpdateCoordinator` calls before the first refresh
  (@erwindouna). Removes the conditional "if self.gaposa is None"
  branch that used to gate the login inside `update_gateway`.
- Drop the `update_gateway` helper entirely — its body is now inlined
  into `_async_update_data`. The old method returned a `bool` that
  was always `True` and never used; that dead return is gone.
- Fix the `dictionalry` typo in the _get_data_from_devices docstring
  (Copilot).
- Drop the `new_devices` list that was collected but never used.
- Narrow the update-exception catch from bare `Exception` to
  `(ClientError, TimeoutError, OSError)` — bare catches mask
  programming errors as network flakes.
- Type the coordinator subclass as
  `DataUpdateCoordinator[dict[str, Motor]]` so callers get a real
  data type without annotating.
- Make the listener attribute private (`self._listener`).
- Drop the mypy assert-hack comment; now that `_async_setup`
  unconditionally assigns `self.gaposa`, the type checker can follow
  along without the manual assertion.

__init__.py no longer needs to import logging or pass a logger to
the coordinator — drop both.

All 35 tests still pass.
@mwatson2 mwatson2 marked this pull request as ready for review April 18, 2026 02:19
@home-assistant home-assistant Bot requested review from erwindouna and joostlek April 18, 2026 02:19
HA does not call async_unload_entry on SETUP_RETRY — only on a
transition from LOADED to NOT_LOADED. So if
async_config_entry_first_refresh raises (ConfigEntryNotReady,
ConfigEntryAuthFailed, UpdateFailed, etc.), the coordinator's
Gaposa client and any push-notification listeners it registered
would leak across retries.

Wrap the first_refresh call in try/except that calls
coordinator.async_shutdown() before re-raising. This closes the
aiohttp session, detaches pygaposa device listeners, and stops
the coordinator timer — matching what async_unload_entry does on
the normal unload path.

Move entry.runtime_data assignment back to after the first refresh
(it's no longer needed before, since we handle cleanup inline).

44 tests pass.
@mwatson2 mwatson2 marked this pull request as draft April 20, 2026 18:39
@mwatson2 mwatson2 marked this pull request as ready for review April 20, 2026 19:19
Copilot AI review requested due to automatic review settings April 20, 2026 19:19
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 20 changed files in this pull request and generated 2 comments.

Comment thread homeassistant/components/gaposa/cover.py
Comment thread tests/components/gaposa/test_init.py Outdated
@emontnemery
Copy link
Copy Markdown
Contributor

@mwatson2 Please add links to the pygaposa repo to the PR description and mark the PR "ready for review" when done

@emontnemery emontnemery marked this pull request as draft April 21, 2026 13:13
@mwatson2 mwatson2 marked this pull request as ready for review April 21, 2026 15:45
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 21, 2026 15:46
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 20 changed files in this pull request and generated 1 comment.

Comment thread tests/testing_config/.storage/core.config_entries Outdated
@mwatson2
Copy link
Copy Markdown
Author

@mwatson2 Please add links to the pygaposa repo to the PR description and mark the PR "ready for review" when done

Done!

This file doesn't exist on upstream/dev — it was created by an
earlier commit on this branch as a test-run artifact. Commit
6fd771e blanked the entries but left the file. Remove it entirely
so the PR diff doesn't carry a file that shouldn't exist.
Comment on lines +21 to +29
coordinator = DataUpdateCoordinatorGaposa(
hass,
entry,
api_key=entry.data[CONF_API_KEY],
username=entry.data[CONF_USERNAME],
password=entry.data[CONF_PASSWORD],
name=entry.title,
update_interval=timedelta(seconds=UPDATE_INTERVAL),
)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why don't we just get all the data from the entry inside of the coordinator?

Comment on lines +94 to +104
async def async_step_reauth(
self, _entry_data: Mapping[str, Any]
) -> ConfigFlowResult:
"""Start reauth when the stored credentials stop working."""
return await self.async_step_reauth_confirm()

async def async_step_reauth_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Ask the user for a new password and validate it."""
errors: dict[str, str] = {}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's keep reauth for a follow up PR

def __init__(
self,
hass: HomeAssistant,
config_entry: ConfigEntry,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the extended config entry

Comment on lines +89 to +110
# Attach a listener to every new device so document-level pushes
# from pygaposa trigger async_set_updated_data.
if self._listener is None:
self._listener = self.on_document_updated

current_devices: list[Device] = []
for client, _user in self.gaposa.clients:
for device in client.devices:
current_devices.append(device)
if device not in self.devices:
device.addListener(self._listener)

for device in self.devices:
if device not in current_devices:
device.removeListener(self._listener)

self.devices = current_devices

# Recovered from a transient failure — restore the normal interval.
self.update_interval = timedelta(seconds=UPDATE_INTERVAL)

return self._get_data_from_devices()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So can you please elaborate on what we poll for and what we use listeners for? As in, I am wondering if it makes sense to keep using the coordinator or that we add listeners to every entity, so we don't have to keep a centralized state (as apparently we get updates on device level)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The listeners are callbacks that are called after pygaposa does a document update. Pygaposa polls after a command is sent to ensure we get the updates that occur as a result of the command. I believe the server is waiting until it confirms delivery of the command to the hub before updating the document and this can be a few seconds.

Otherwise, pygaposa doesn't poll, so we need to trigger background updates from here to pick up changes caused by other users (e.g. official Gaposa mobile app).

Comment on lines +52 to +70
@callback
def _async_add_remove_entities() -> None:
"""Add new motors and drop covers for motors that have disappeared."""
latest_ids = set(coordinator.data)
new_entities: list[GaposaCover] = []

for motor_id, motor in coordinator.data.items():
if motor_id not in known_entities:
entity = GaposaCover(coordinator, motor_id, motor)
new_entities.append(entity)
known_entities[motor_id] = entity

if new_entities:
async_add_entities(new_entities)

entity_registry = er.async_get(hass)
device_registry = dr.async_get(hass)
for motor_id in list(known_entities):
if motor_id not in latest_ids:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we keep dynamic devices for a follow up PR?

Comment on lines +124 to +131
def motor(self) -> Motor | None:
"""Return the current Motor object, or ``None`` if it has been removed."""
return self.coordinator.data.get(self._motor_id)

@property
def available(self) -> bool:
"""Entity is available while the motor is still known to the coordinator."""
return super().available and self.motor is not None
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead change self.motor is not None to self._motor_id in self.coordinator.data and change the .get to [], this way you don't have to handle None states

await motor.up(False)
self._begin_motion(COMMAND_UP)
self.async_write_ha_state()
self._schedule_refresh_after_motion()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to make a mental note because things will get clear when I know what role the coordinator has

Comment on lines +45 to +49
discovery:
status: exempt
comment: |
Gaposa is a cloud service; there is no discoverable hub on the
local network to find.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no discoverable hub? The devices are connected somehow right? Can we discover the devices and propose the users to set up their cloud account?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The setup for the hub involves configuring the WiFi (through a hub-hosted temporary AP/web page) and then registering through the mobile app using the serial number / QR code. I didn't see any case where the mobile app attempts local network discovery of the hub. It's just server-side rendez-vous. The hub does have an open port that responds to HTTP but I could only get a single error code from this whatever I sent. I have not looked for mDNS or SSDP, but I could try that when I am next at the install location and make a follow-up PR for discovery if I find anything.

@home-assistant home-assistant Bot marked this pull request as draft April 24, 2026 16:10
Address @joostlek's code review on PR home-assistant#161442:

config_flow.py — drop reauth flow
  Remove async_step_reauth, async_step_reauth_confirm, and the
  STEP_REAUTH_DATA_SCHEMA. Reauth will be added in a follow-up PR
  per reviewer request to keep the initial integration minimal.

strings.json — remove reauth strings
  Drop reauth_confirm step, reauth_successful and wrong_account
  abort strings.

coordinator.py — read credentials from config_entry.data
  Remove api_key/username/password constructor kwargs. The
  coordinator now reads self.config_entry.data[CONF_*] directly
  in _async_setup, as suggested. Type config_entry as
  GaposaConfigEntry (via TYPE_CHECKING to avoid circular import).
  Auth errors on refresh now raise UpdateFailed instead of
  ConfigEntryAuthFailed (no reauth flow to trigger).
  Fix listener comments to accurately describe pygaposa's
  post-command polling mechanism (not Firebase push).
  Rename on_document_updated → _on_device_polled.

__init__.py — simplify coordinator construction
  No more credential kwargs; just pass entry + name + interval.

cover.py — drop dynamic device add/remove
  Replace the _async_add_remove_entities listener pattern with
  a simple one-shot async_add_entities at setup time. Removes
  entity_registry and device_registry cleanup code. Dynamic
  device support will be added in a follow-up PR. Motor property
  uses [] instead of .get(); available checks _motor_id
  membership in coordinator.data.

quality_scale.yaml — flip deferred items to todo
  reauthentication-flow, dynamic-devices, stale-devices → todo.

Tests — align with reduced scope
  Remove reauth flow tests (test_reauth_flow_success,
  test_reauth_flow_wrong_account_aborts,
  test_reauth_flow_invalid_auth_shows_form_error).
  Remove dynamic device tests (test_stale_motor_cleans_up_entity_and_device,
  test_entity_removed_when_motor_gone, test_entity_unavailable_when_motor_gone).
  Update auth error tests to expect UpdateFailed / SETUP_RETRY
  instead of ConfigEntryAuthFailed / SETUP_ERROR.
  Rename test_on_document_updated → test_device_polled.

37 tests pass (was 44; 7 removed for deferred features).
Copilot AI review requested due to automatic review settings April 26, 2026 18:21
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 19 changed files in this pull request and generated 4 comments.

Comment thread tests/components/gaposa/test_cover.py
Comment thread tests/components/gaposa/test_cover.py
Comment thread homeassistant/components/gaposa/cover.py
Comment thread tests/components/gaposa/test_cover.py
Tighten the three motor command assertions to verify the actual
arguments passed to pygaposa:

- motor.up(False) — don't wait for update (we handle refresh ourselves)
- motor.down(False) — same
- motor.stop(True) — wait for backend to confirm stop state
@mwatson2 mwatson2 marked this pull request as ready for review April 26, 2026 20:47
Copilot AI review requested due to automatic review settings April 26, 2026 20:47
@home-assistant home-assistant Bot requested a review from joostlek April 26, 2026 20:47
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 19 changed files in this pull request and generated 3 comments.

Comment thread tests/components/gaposa/test_coordinator.py
Comment thread homeassistant/components/gaposa/coordinator.py
Comment thread tests/components/gaposa/test_init.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants