Skip to content

Add new Hotspring Integration - #177992

Draft
Moustachauve wants to merge 32 commits into
home-assistant:devfrom
Moustachauve:hotspring-init
Draft

Add new Hotspring Integration#177992
Moustachauve wants to merge 32 commits into
home-assistant:devfrom
Moustachauve:hotspring-init

Conversation

@Moustachauve

@Moustachauve Moustachauve commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Proposed change

This PR introduces a new Hotspring integration to control hot tubs that are equipped with the official Connected Spa Kit from Hotspring.

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 diff between library versions and ideally a link to the changelog/release notes is added to the PR description.

To help with the load of incoming pull requests:

@home-assistant home-assistant Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

When adding new integrations, limit included platforms to a single platform. While we appreciate the effort, reviewing larger than necessary PRs slows down the review process. Please reduce this PR to a single platform. See the review process for more details.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds a local-polling Hot Spring spa integration with configuration, diagnostics, and water-heater control.

Changes:

  • Adds config-entry setup, polling, and reconfiguration.
  • Exposes spa temperature through a water-heater entity.
  • Adds diagnostics, tests, dependency metadata, and generated registration.

Reviewed changes

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

Show a summary per file
File Description
.strict-typing Enables strict typing.
homeassistant/components/hotspring/__init__.py Sets up and unloads the integration.
homeassistant/components/hotspring/config_flow.py Implements setup and reconfiguration flows.
homeassistant/components/hotspring/const.py Defines integration constants.
homeassistant/components/hotspring/coordinator.py Polls the spa API.
homeassistant/components/hotspring/diagnostics.py Exposes diagnostic data.
homeassistant/components/hotspring/entity.py Defines shared entity metadata.
homeassistant/components/hotspring/icons.json Adds icon metadata.
homeassistant/components/hotspring/manifest.json Declares integration metadata and dependency.
homeassistant/components/hotspring/strings.json Adds configuration-flow strings.
homeassistant/components/hotspring/water_heater.py Implements temperature control.
homeassistant/generated/config_flows.py Registers the config flow.
homeassistant/generated/integrations.json Registers generated integration metadata.
requirements_all.txt Adds python-hotspring.
tests/components/hotspring/__init__.py Adds platform setup helper.
tests/components/hotspring/conftest.py Adds fixtures and API mocks.
tests/components/hotspring/test_config_flow.py Tests setup and reconfiguration.
tests/components/hotspring/test_diagnostics.py Tests diagnostics.
tests/components/hotspring/test_init.py Tests entry lifecycle and setup failure.
tests/components/hotspring/test_water_heater.py Tests state and temperature control.

Comment thread homeassistant/components/hotspring/config_flow.py Outdated
Comment thread homeassistant/components/hotspring/diagnostics.py Outdated
Comment thread tests/components/hotspring/test_diagnostics.py Outdated
Comment thread tests/components/hotspring/test_water_heater.py
Comment thread homeassistant/components/hotspring/manifest.json Outdated
Copilot AI review requested due to automatic review settings August 2, 2026 02:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 21 out of 23 changed files in this pull request and generated no new comments.

Suppressed comments (4)

tests/components/hotspring/test_diagnostics.py:21

  • Commit the generated Syrupy snapshot for this assertion. There is no tests/components/hotspring/snapshots/test_diagnostics.ambr, so this test reports a missing snapshot and fails in normal CI runs.
    assert (
        await get_diagnostics_for_config_entry(hass, hass_client, init_integration)
        == snapshot

tests/components/hotspring/test_water_heater.py:34

  • Commit the generated Syrupy snapshots for this test. No Hot Spring snapshot files are present, so the state and entity-registry snapshot assertions fail as missing snapshots in normal CI runs.
    assert state == snapshot

homeassistant/components/hotspring/config_flow.py:34

  • Handle HotSpringError in this validation path as well. The same update() call is treated as raising both HotSpringConnectionError and HotSpringError by the coordinator, so a known API/response failure currently escapes the config flow instead of returning a form error.
            except HotSpringConnectionError:
                errors["base"] = "cannot_connect"

homeassistant/components/hotspring/diagnostics.py:11

  • Redact the spa's ssid field from diagnostics. The current SpaInfo payload includes the user's Wi-Fi network name, so diagnostics exports expose network-identifying data; other integrations redact this field (for example, homeassistant/components/aosmith/diagnostics.py:24).
TO_REDACT = {"unique_id", "mac_address"}

- Handle HotSpringError in config flow
- Redact hostname and ssid from diagnostics payload
- Add exception translations for UpdateFailed and HomeAssistantError
- Add approved Syrupy test snapshots
Copilot AI review requested due to automatic review settings August 2, 2026 02:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 23 out of 25 changed files in this pull request and generated no new comments.

Copilot AI review requested due to automatic review settings August 2, 2026 02:46
@Moustachauve
Moustachauve marked this pull request as ready for review August 2, 2026 02:50
@Moustachauve
Moustachauve requested a review from a team as a code owner August 2, 2026 02:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 20 out of 23 changed files in this pull request and generated no new comments.

Suppressed comments (3)

homeassistant/components/hotspring/quality_scale.yaml:56

  • Mark diagnostics as todo rather than exempt. Planning diagnostics for a follow-up means the rule applies but is not implemented; established quality-scale metadata represents this as todo (for example, homeassistant/components/energyid/quality_scale.yaml:64-66), while exemptions are reserved for integrations with no diagnosable data.
  diagnostics:
    status: exempt
    comment: Diagnostics will be added in a follow-up PR.

tests/components/hotspring/test_water_heater.py:60

  • Use an in-range temperature in this successful service test. The default test unit system is Celsius and this entity advertises a 40 °C maximum, so 103 is converted to 217.4 °F—well beyond the device's 104 °F maximum—and the permissive mock masks that this is not a valid successful device call.
            ATTR_TEMPERATURE: 103,

homeassistant/components/hotspring/coordinator.py:48

  • Add a setup/update test for this HotSpringError branch. The setup test only injects HotSpringConnectionError, so the invalid_response translation path is currently unverified even though it has distinct behavior and dedicated handling.
        except HotSpringError as error:

mac_address is optional since it's derived from a value of the API. However this value is guaranteed to be present currently.
Copilot AI review requested due to automatic review settings August 3, 2026 06:22
@Moustachauve
Moustachauve marked this pull request as ready for review August 3, 2026 06:26
@home-assistant
home-assistant Bot requested a review from erwindouna August 3, 2026 06:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 20 out of 22 changed files in this pull request and generated no new comments.

Copilot AI review requested due to automatic review settings August 3, 2026 06:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 20 out of 22 changed files in this pull request and generated no new comments.

@MartinHjelmare
MartinHjelmare removed the request for review from a team August 3, 2026 07:24

@erwindouna erwindouna left a comment

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.

Making solid progress, @Moustachauve! Some follow up and new points. :)

errors["base"] = "cannot_connect"
else:
await self.async_set_unique_id(
spa.info.mac_address or spa.info.root_topic

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.

What is and how unique is the value of root_topic?

Comment on lines +35 to +37
self._abort_if_unique_id_configured(
updates={CONF_HOST: user_input[CONF_HOST]}
)

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 need for a value, if we've setup the Mac address above. Plus it's a bit odd that we now let it check on the CONF_HOST. Best to omit it and just leave it without a parameter.

errors=errors,
)

async def _async_get_spa(self, host: str) -> Spa:

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.

Is this only use case? If you're re-adding the reconfigure flow I'd suggest to rename it to validate_input to standardize like other integrations.

info = self.coordinator.data.info
identifier = info.mac_address or info.root_topic
self._attr_unique_id = f"{identifier}_{key}"
connections = set()

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.

What is this used for? Dynamic devices?

raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="cannot_connect",
translation_placeholders={"error": str(error)},

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.

Idem feedback on the translations of errors.

raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="cannot_connect",
translation_placeholders={"error": str(error)},

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.

Idem feedback on the translations of errors.


@property
@override
def current_operation(self) -> str | 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.

How can the type be alternative None, if you always return a valid state constant?


@pytest.fixture
def mock_onboarding() -> Generator[MagicMock]:
"""Mock that Home Assistant is currently onboarding."""

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.

Well, you're putting it on False. Why is this Mock needed to begin with? :)

@home-assistant
home-assistant Bot marked this pull request as draft August 3, 2026 08:26
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.

3 participants