Skip to content

Add smart #5 support, fix login flow#414

Open
slider wants to merge 3 commits into
DasBasti:mainfrom
slider:slider/hashtag5_support
Open

Add smart #5 support, fix login flow#414
slider wants to merge 3 commits into
DasBasti:mainfrom
slider:slider/hashtag5_support

Conversation

@slider

@slider slider commented Apr 7, 2026

Copy link
Copy Markdown

Summary

This PR improves Smart #5 support and makes login/setup more resilient when the Smart auth backend is unstable.

Problem

Config flow login could fail with generic auth errors even when credentials were correct, due to intermittent Smart backend/API failures (for example temporary 5xx responses). This made initial setup unreliable.

Changes

  • Added retry + backoff logic for credential validation in config_flow.
  • Added explicit transient error handling:
    • maps temporary connection/server issues to cannot_connect
    • keeps real credential issues as auth
  • Added new cannot_connect translation strings in:
    • custom_components/smarthashtag/translations/en.json
    • custom_components/smarthashtag/translations/de.json
  • Updated integration naming/docs to include Smart #1/#3/#5:
    • README
    • HACS metadata
    • flow title / docstrings
  • Updated dependency and integration version:
    • pysmarthashtag==0.8.2
    • integration version 0.8.3
  • Extended config flow tests to cover transient connection/server failure behavior.

Validation

  • pytest -q tests/test_config_flow.py passed
  • pytest -q passed
  • ruff check custom_components tests passed

Notes

This does not remove all possible login failures (backend can still be down), but it improves UX by retrying and surfacing the correct error class (cannot_connect vs auth).

Summary by CodeRabbit

@coderabbitai

coderabbitai Bot commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3a58207a-3834-477c-85f0-07ed9561afab

📥 Commits

Reviewing files that changed from the base of the PR and between 40a4ea7 and cb7db72.

📒 Files selected for processing (1)
  • custom_components/smarthashtag/__init__.py
✅ Files skipped from review due to trivial changes (1)
  • custom_components/smarthashtag/init.py

📝 Walkthrough

Walkthrough

The PR adds retry logic with transient connection error handling to the SmartHashtag integration configuration flow, bumps the integration version to 0.8.3 and the pysmarthashtag dependency to 0.8.2, and updates branding across documentation and docstrings to include "Smart #5" alongside "Smart #1/#3".

Changes

Connection Retry Logic and Smart #5 Support

Layer / File(s) Summary
Version & Dependency Updates
custom_components/smarthashtag/const.py, custom_components/smarthashtag/manifest.json
Integration version bumped from 0.8.2 to 0.8.3; pysmarthashtag requirement updated from 0.8.1 to 0.8.2.
Connection Error Handling
custom_components/smarthashtag/config_flow.py
Added CannotConnect exception class, _LOGIN_RETRIES = 5 constant, and helper methods _is_connection_error(...) and _is_retryable_error(...) to classify transport/HTTP errors and timeouts.
Credential Validation with Retry
custom_components/smarthashtag/config_flow.py
_test_credentials(...) now loops up to 5 times with exponential backoff (capped at 4s), catching SmartAPIError, httpx exceptions, asyncio.TimeoutError, and OSError, raising CannotConnect for transport issues and SmartAPIError otherwise.
Flow Integration
custom_components/smarthashtag/config_flow.py
async_step_user and async_step_custom_endpoints now catch CannotConnect explicitly and map it to error key "cannot_connect", while SmartAPIError continues to map to "auth".
Branding & Documentation
README.md, hacs.json, custom_components/smarthashtag/__init__.py, custom_components/smarthashtag/climate.py, custom_components/smarthashtag/coordinator.py, custom_components/smarthashtag/switch.py
Updated module docstrings, README heading, and HACS name to include "Smart #5" alongside "Smart #1/#3"; EVCC example vehicle title changed from "Smart #1" to "Smart".
Translations
custom_components/smarthashtag/translations/en.json, custom_components/smarthashtag/translations/de.json
Updated config.flow_title from "Smart #1/#3" to "Smart #1/#3/#5"; added new error key config.error.cannot_connect with user-facing messages in English and German.
Tests
tests/test_config_flow.py
Added test_form_with_transient_connection_error to verify 500 backend errors map to "cannot_connect" error; patched asyncio.sleep to suppress retry delays in test_form_with_eu_region.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor User
    participant ConfigFlow
    participant SmartAccount
    participant Retry Logic
    participant Backend

    User->>ConfigFlow: Submit credentials
    ConfigFlow->>Retry Logic: Start retry loop (max 5 attempts)
    loop Retry up to 5 times
        Retry Logic->>SmartAccount: login()
        SmartAccount->>Backend: HTTP request
        alt Connection success
            Backend-->>SmartAccount: Success response
            SmartAccount-->>Retry Logic: ✓ Complete
        else Connection failure (500, 502, 503, 504, timeout)
            Backend-->>SmartAccount: Error / Timeout
            SmartAccount-->>Retry Logic: Raise httpx.HTTPStatusError
            Retry Logic->>Retry Logic: Classify as retryable
            Retry Logic->>Retry Logic: Sleep with backoff (if not last attempt)
        end
    end
    Retry Logic-->>ConfigFlow: CannotConnect or SmartAPIError
    ConfigFlow-->>User: Show "cannot_connect" or "auth" error
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • DasBasti/SmartHashtag#357: Both PRs modify the manifest.json requirement for pysmarthashtag, bumping the pinned library version.
  • DasBasti/SmartHashtag#298: Both PRs increment the integration VERSION in const.py and manifest.json version.
  • DasBasti/SmartHashtag#244: Both PRs modify tests/test_config_flow.py; this PR extends tests with new connection-error scenarios and retry mocking.

Poem

🐰 A Smart #5 joins the pack,
Retry logic's got your back,
When the server's feeling slow,
Just wait a moment—off we go!
Five attempts to save the day,
#1, #3, #5—hip hooray! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the two main changes: adding Smart #5 support and improving the login flow resilience with retry logic.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🧹 Nitpick comments (3)
custom_components/smarthashtag/config_flow.py (2)

267-292: Error classification helpers are clear and correct.

The HTTP status codes {429, 500, 502, 503, 504} are appropriate choices for transient/retryable server errors.

Minor: ConnectionError is a subclass of OSError in Python, so listing both in the isinstance check is redundant. Not a bug, just a slight redundancy.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@custom_components/smarthashtag/config_flow.py` around lines 267 - 292, Remove
the redundant ConnectionError check in _is_connection_error: because
ConnectionError is a subclass of OSError, drop ConnectionError from the
isinstance tuple and keep OSError (and the other exceptions) so behavior is
unchanged; update only the tuple in _is_connection_error, leaving
_is_retryable_error and other logic intact.

226-265: Retry logic is well-implemented with appropriate backoff.

The retry mechanism correctly:

  • Retries up to 5 times with capped backoff (1s, 2s, 3s, 4s, 4s)
  • Distinguishes connection errors (→ CannotConnect) from auth errors (→ SmartAPIError)
  • Wraps unexpected HTTP errors appropriately

Line 265 appears unreachable given the current control flow (every iteration either returns on success or raises on the final failed attempt). Consider removing it or adding a comment explaining it's a defensive fallback:

Optional: Add clarifying comment
                 raise SmartAPIError(str(exception)) from exception

+        # Defensive fallback: should be unreachable as each iteration either returns or raises
         raise SmartAPIError("Could not validate credentials")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@custom_components/smarthashtag/config_flow.py` around lines 226 - 265, The
final line raising SmartAPIError("Could not validate credentials") is
unreachable because the for-loop either returns on success or raises on failure;
remove that unreachable raise and, if desired, add a brief comment above the
loop referencing that all failure paths raise within the loop (point to the
retry loop using self._LOGIN_RETRIES, SmartAccount(...).login(),
.get_vehicles(), and the exception handling branches using _is_retryable_error
and _is_connection_error) so future readers know the post-loop raise was
intentionally removed.
tests/test_config_flow.py (1)

104-145: Good test coverage for the new transient error path.

The test correctly:

  • Simulates an HTTP 500 server error using httpx.HTTPStatusError
  • Patches asyncio.sleep to avoid delays during retries
  • Verifies the flow returns {"base": "cannot_connect"} after retry exhaustion

Consider adding a test case for partial retry success (e.g., fail twice then succeed) to verify the retry mechanism works correctly when the backend recovers mid-retry. This would complement the exhausted-retries test.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_config_flow.py` around lines 104 - 145, Add a new async test
similar to test_form_with_transient_connection_error that confirms the retry
loop can succeed mid-retry: patch
custom_components.smarthashtag.config_flow.SmartAccount to return an instance
whose login AsyncMock first raises httpx.HTTPStatusError twice and then returns
successfully on the third call, patch asyncio.sleep to an AsyncMock to skip
delays, run the config flow via hass.config_entries.flow.async_init and
async_configure with the same credentials, and assert the final result is a
successful entry (type "create_entry") and contains expected data instead of
{"base":"cannot_connect"} to validate partial-retry success.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@custom_components/smarthashtag/__init__.py`:
- Line 1: Fix the typo in the module docstring by replacing "intergration" with
"integration" in the top-level string in __init__.py (the module docstring that
currently reads 'Smart `#1/`#3/#5 for intergration with Home Assistant.').

In `@custom_components/smarthashtag/sensor.py`:
- Line 1: Update the module docstring in sensor.py to correct the typo: change
"intergration" to "integration" in the top-line docstring so the file now reads:
"Sensor platform for Smart `#1/`#3/#5 integration."; no other code changes
required.

---

Nitpick comments:
In `@custom_components/smarthashtag/config_flow.py`:
- Around line 267-292: Remove the redundant ConnectionError check in
_is_connection_error: because ConnectionError is a subclass of OSError, drop
ConnectionError from the isinstance tuple and keep OSError (and the other
exceptions) so behavior is unchanged; update only the tuple in
_is_connection_error, leaving _is_retryable_error and other logic intact.
- Around line 226-265: The final line raising SmartAPIError("Could not validate
credentials") is unreachable because the for-loop either returns on success or
raises on failure; remove that unreachable raise and, if desired, add a brief
comment above the loop referencing that all failure paths raise within the loop
(point to the retry loop using self._LOGIN_RETRIES, SmartAccount(...).login(),
.get_vehicles(), and the exception handling branches using _is_retryable_error
and _is_connection_error) so future readers know the post-loop raise was
intentionally removed.

In `@tests/test_config_flow.py`:
- Around line 104-145: Add a new async test similar to
test_form_with_transient_connection_error that confirms the retry loop can
succeed mid-retry: patch custom_components.smarthashtag.config_flow.SmartAccount
to return an instance whose login AsyncMock first raises httpx.HTTPStatusError
twice and then returns successfully on the third call, patch asyncio.sleep to an
AsyncMock to skip delays, run the config flow via
hass.config_entries.flow.async_init and async_configure with the same
credentials, and assert the final result is a successful entry (type
"create_entry") and contains expected data instead of {"base":"cannot_connect"}
to validate partial-retry success.
🪄 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

Run ID: d1506c58-5b95-4f3f-8f52-6c1e5b1709da

📥 Commits

Reviewing files that changed from the base of the PR and between 7cb1889 and 474db1b.

📒 Files selected for processing (14)
  • README.md
  • custom_components/smarthashtag/__init__.py
  • custom_components/smarthashtag/climate.py
  • custom_components/smarthashtag/config_flow.py
  • custom_components/smarthashtag/const.py
  • custom_components/smarthashtag/coordinator.py
  • custom_components/smarthashtag/manifest.json
  • custom_components/smarthashtag/sensor.py
  • custom_components/smarthashtag/sensor_groups/__init__.py
  • custom_components/smarthashtag/switch.py
  • custom_components/smarthashtag/translations/de.json
  • custom_components/smarthashtag/translations/en.json
  • hacs.json
  • tests/test_config_flow.py

Comment thread custom_components/smarthashtag/__init__.py Outdated
Comment thread custom_components/smarthashtag/sensor.py Outdated
"requirements": ["pysmarthashtag==0.8.1"],
"version": "0.8.2"
"requirements": ["pysmarthashtag==0.8.2"],
"version": "0.8.3"

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

please don't increase the version. This change is not just a bugfix

"issue_tracker": "https://github.com/DasBasti/SmartHashtag/issues",
"requirements": ["pysmarthashtag==0.8.1"],
"version": "0.8.2"
"requirements": ["pysmarthashtag==0.8.2"],

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

do not make this update in a PR that does not relate to the updates in pysmarthashtag

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants