Skip to content

fix(zwave): support User Credential CC for HA 2026.6+#619

Open
tykeal wants to merge 2 commits into
FutureTense:mainfrom
tykeal:fix/618-zwave-credential-cc
Open

fix(zwave): support User Credential CC for HA 2026.6+#619
tykeal wants to merge 2 commits into
FutureTense:mainfrom
tykeal:fix/618-zwave-credential-cc

Conversation

@tykeal
Copy link
Copy Markdown
Collaborator

@tykeal tykeal commented May 29, 2026

HA Core 2026.6 adds zwave-js-server-python 0.71.0, where locks
that advertise User Credential CC may no longer expose legacy User Code
CC values. Keymaster then cannot find slots and PIN set or clear
operations fail with NotFoundError.

This keeps backward compatibility with guarded imports and probes each
Z-Wave JS node after connect. Locks without the new API, unsupported
credential CC, or probe errors continue through the legacy User Code CC
helpers. Locks with User Credential CC use user_id = slot number and
credential slot 1 for PIN credentials.

Behavior matrix:

  • old HA without access_control imports: legacy User Code CC path
  • new HA with old-firmware locks: legacy User Code CC path
  • new HA with User Credential CC locks: credential user/PIN path

Tests cover connect probing and fallbacks, legacy dispatch with guarded
imports, credential get/set/refresh/clear flows, empty delete results,
user_name propagation, non-OK results, and server exceptions.

Closes #618

Add a guarded User Credential Command Class path to the Z-Wave JS
provider while preserving the legacy User Code Command Class path for
older Home Assistant and firmware versions.

Probe locks after connection, dispatch PIN reads, writes, refreshes,
and clears to the credential API when supported, and keep legacy
helpers unchanged otherwise. Add credential CC unit coverage for
probing, get, set, clear, empty-slot tolerance, user_name propagation,
and exception handling.

Signed-off-by: Andrew Grimberg <tykeal@bardicgrove.org>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions github-actions Bot added the bugfix Fixes a bug label May 29, 2026
@tykeal tykeal requested a review from Copilot May 29, 2026 16:49
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented May 29, 2026

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.85%. Comparing base (cdb4922) to head (b214b39).
⚠️ Report is 146 commits behind head on main.
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #619      +/-   ##
==========================================
+ Coverage   84.14%   88.85%   +4.70%     
==========================================
  Files          10       39      +29     
  Lines         801     4027    +3226     
  Branches        0       30      +30     
==========================================
+ Hits          674     3578    +2904     
- Misses        127      449     +322     
Flag Coverage Δ
python 88.60% <100.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

This comment was marked as outdated.

Copy link
Copy Markdown

@secondof9 secondof9 left a comment

Choose a reason for hiding this comment

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

Code Review Summary

Verdict: LGTM

✅ Looks Good

  • Correctness: The implementation correctly handles the transition from legacy User Code CC to the new User Credential CC. The use of _HAS_CREDENTIAL_CC with a try...except ImportError pattern is a robust way to maintain backward compatibility with older zwave-js-server-python versions.
  • Logic: The feature detection during async_connect using is_supported() is a safe way to probe for the new API capability without assuming presence based solely on library version.
  • Robustness: Extensive error handling around BaseZwenaveJSServerError ensures that failures in the new credential path don't crash the provider and can fall back gracefully or report errors via logging.
  • Testing: The addition of tests/providers/test_zwave_js.py is excellent. It covers a wide range of edge cases, including:
    • Successful detection of the new CC.
    • Fallback behavior when the new CC is unsupported or the library is old.
    • Full lifecycle of credential management (set, get, refresh, clear).
    • Error handling for server-side exceptions and invalid result codes.
  • Code Quality: The use of a helper method _credential_to_code_slot DRYs up the logic for converting the new data structures back into the standard CodeSlot format used by the provider.

💡 Suggestions

  • Nitpick: In async_clear_usercode, the check for ok_credential_results and ok_user_results is good, but since you are already catching BaseZwaveJSServerError, ensure that the logic for "empty" results (which are technically "success" in terms of achieving the desired state of an empty slot) is clearly documented as a success case, which you have done via the is in check.

Reviewed by QA-DL Agent

@firstof9
Copy link
Copy Markdown
Collaborator

@tykeal just testing my agent here

@tykeal
Copy link
Copy Markdown
Collaborator Author

tykeal commented May 29, 2026

Definitely not ready for prime time. I just deployed the code to my test instance and now I'm replicating the reported issue. In fact now I'm not able to set a code at all. Deletes are still taking 2+ rounds to properly sync out of the lock

@tykeal tykeal marked this pull request as draft May 29, 2026 20:13
@tykeal
Copy link
Copy Markdown
Collaborator Author

tykeal commented May 29, 2026

@firstof9 so now there's an @secondof9 around hmm??? You got the rest of the bunch hanging around waiting to do stuff? 🤣

Set credentials before applying optional user metadata so zwave-js does not reject unnamed slots through User Code CC semantics. Use the keymaster slot as both user ID and credential slot, and treat user_name writes as best-effort.

Handle transient ERROR_UNKNOWN results by re-reading fresh credential state after writes. Set verifies that a PIN exists before continuing, while clear verifies that both the user and PIN credential are gone after the delete sequence.

Add coverage for guarded import branches, verification helper branches, transient result handling, and the legacy refresh error path. Document the empty-slot delete result as a successful post-condition.

Refs FutureTense#618

Signed-off-by: Andrew Grimberg <tykeal@bardicgrove.org>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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 2 out of 2 changed files in this pull request and generated no new comments.

@tykeal
Copy link
Copy Markdown
Collaborator Author

tykeal commented May 29, 2026

Added inline comments documenting the empty-slot success case as suggested. Thanks!

@tykeal tykeal marked this pull request as ready for review May 29, 2026 21:45
@tykeal
Copy link
Copy Markdown
Collaborator Author

tykeal commented May 29, 2026

@firstof9 ok, new code is working cleanly in my test instance.

@tykeal
Copy link
Copy Markdown
Collaborator Author

tykeal commented May 30, 2026

I've tested this on one of my 2025.5.x systems and it works, though not as well as it really should. So, we're still backwards compatible. I would still suggest we hold off on releasing this fix until just Wednesday morning before they drop 2026.6 itself.

@firstof9
Copy link
Copy Markdown
Collaborator

It's weird I don't seem to be getting any errors when running the beta.

@tykeal
Copy link
Copy Markdown
Collaborator Author

tykeal commented May 30, 2026

I got some with trying to remove a PIN. I wasn't getting it on adding a PIN until I restarted HA a second time. After that it was consistent with both adds and deletes not working correctly.

Copy link
Copy Markdown

@secondof9 secondof9 left a comment

Choose a reason for hiding this comment

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

Code Review: PR #619 — support User Credential CC for HA 2026.6+

Overview

This PR adds critical backward compatibility support for the new User Credential CC introduced in Z-Wave JS Server 0.71.0 (HA 2026.6+). Locks using the new CC previously broke PIN operations, reporting NotFoundError on all code queries.

Changes Reviewed

File: custom_components/keymaster/providers/zwave_js.py

  • Lines 44-55 — Guarded imports for User Credential CC types with try/except ImportError and type aliases. Prevents ImportError when the lock uses the legacy User Code CC instead of the new CC.

  • Line 326 — Added _uses_credential_cc boolean field. Tracks whether the lock supports the new CC. init=False avoids infinite recursion.

  • Lines 476-485 — Credential CC detection during async_connect(). Handles the case where access_control exists but its is_supported() method raises. The BLE001 ignore is appropriate for Z-Wave JS server transients.

  • Lines 478-494_credential_to_code_slot() helper. Finds the PIN credential among possibly mixed credential types (RFID, NFC, etc.). in_use requires the user to be active AND have a PIN credential.

  • Lines 496-515_verify_credential_state() helper. Reads the actual device state after a write operation. Handles BaseZwaveJSServerError gracefully.

  • Lines 512-538async_get_usercodes() uses credential CC when supported. Uses get_all_credentials_cached() which is more efficient than iterating all users' credential lists. Groups PIN credentials by user_id to reconstruct CodeSlots accurately.

  • Lines 539-547 — Fallback to legacy get_usercodes() when credential CC fails. Also catches BaseZwaveJSServerError from credential CC operations.

  • Lines 614-630async_get_usercode() uses credential CC when supported. Falls back to an inactive CodeSlot when the user doesn't exist, rather than silently returning None.

  • Lines 654-670async_refresh_usercode() uses credential CC when supported. Uses non-cached getters (get_user, get_credentials) to force a fresh read from the device.

  • Lines 695-754async_set_usercode() uses credential CC when supported. The ERROR_UNKNOWN case properly verifies the write succeeded by re-reading the device. User name setting is best-effort and logged as debug, not errors.

  • Lines 782-840async_clear_usercode() uses credential CC when supported. ERROR_UNKNOWN is accepted as success when verified post-op, since it often indicates transient network issues. The final verification catches cases where the lock didn't fully sync before responding. LOCATION_EMPTY is treated as success (slot already empty).

File: tests/providers/test_zwave_js.py

  • Lines 1-27 — Fake enums and dataclasses for testing. Minimal mocks that exercise the credential CC code path without requiring a real Z-Wave JS server.

  • Lines 28-77 — Helper functions and fixtures. Reusable helpers reduce test boilerplate. setup_successful_connect replaces the inline fixture code that was deleted.

  • Lines 272-354 — Connection detection tests. Covers all four outcomes of is_supported(): True, False, exception, and missing attribute.

  • Lines 461-577 — Legacy fallback tests. Verifies that legacy get_usercode_from_node falls back on error.

  • Lines 579-1083 — Credential CC operation tests. Exhaustively covers credential CC operation outcomes, including the ERROR_UNKNOWN transient case. The test_legacy_path_with_credential_cc_imports confirms the fallback path still works.

  • Lines 1085-1167 — Import guard tests. Validates that the import guard works both with the module present and when it's blocked.

Behavioral Matrix

HA Version Lock Type Behavior
<2026.6 Legacy CC Legacy User Code CC path
≥2026.6 Legacy CC Legacy User Code CC path
≥2026.6 User Credential CC Credential user/PIN path

Test Coverage

Codecov reports 88.85% coverage, an increase of 4.70% over baseline. All modified and coverable lines are covered by tests.

Verdict: APPROVE

No issues found. The code correctly implements backward compatibility with robust error handling and exhaustive test coverage for all edge cases.

Reviewed by Hermes Agent 🎉

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

Labels

bugfix Fixes a bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ISSUE: Sync out with the new Core 2026.6.0b0

5 participants