Releases: DerekSeaman/irk-capture
v1.5.8
Release Notes v1.5.8
This release note reflects changes from v1.5.7 to v1.5.8.
Bug Fix
- Fixed spurious encryption timeout on first connect in
retry_security_if_needed(): Whensec_init_time_ms_was 0 (first connection since boot/reset), the local snapshotsec_init_time_copywas left as 0 after settingsec_init_time_ms_ = now. The subsequent timeout check(now - sec_init_time_copy) > SEC_TIMEOUT_MSevaluated as(uptime_ms - 0), which immediately exceededSEC_TIMEOUT_MS(20 000 ms) on any device that had been running longer than 20 seconds. This caused every first pairing attempt to be aborted with a spurious "Encryption timeout" disconnect before the peer could respond. Fixed by also updatingsec_init_time_copy = nowafter initializingsec_init_time_ms_.
Files Updated
components/irk_capture/irk_capture.cppESPHome Devices/irk-capture-base.yamlESPHome Devices/irk-capture-full.yamlRELEASE_NOTES_v1.5.8.md
v1.5.7
Release Notes v1.5.7
Boot Stability and GATT Reliability
- Fixed Keyboard profile boot crash: The Keyboard BLE profile was causing GATT registration to fail at boot because NimBLE rejects HID services with no characteristics. Added a minimal
HID Protocol Modecharacteristic (0x2A4E) to make the HID service structurally valid. - Added Keyboard→Heart Sensor GATT fallback: If Keyboard GATT registration fails, the component now automatically falls back to the Heart Sensor profile instead of hard-failing. The fallback is persisted to NVS so subsequent boots avoid the same failure path.
- Graceful no-GATT continuation: If all GATT profile registrations fail, the component continues operating without custom GATT services rather than calling
mark_failed(), preventing unnecessary boot failures. - Relaxed non-fatal boot checks: NVS health-check failures and
ble_store_config_init()failures are now non-fatal warnings rather than hard stops, improving resilience on flash-stressed hardware. - Setup flow hardening:
setup()exits early ifsetup_ble()marks the component failed, preventing entity/UI initialization on a broken BLE stack. - Explicitly zero GATT handle globals before registration so handle state is predictable regardless of which profile or fallback path is taken.
Thread Safety and Concurrency
- Serialized BLE control operations: Added a dedicated BLE operation mutex (
ble_op_mutex_) with RAII guard (BleOpGuard) to serialize sensitive NimBLE calls (advertising start/stop, device name updates, MAC rotation, connection termination) across the ESPHome main loop and NimBLE callback contexts. - Fixed
sec_retry_done_race condition: The security retry flag is now written under mutex before callingble_gap_security_initiate(), closing a window where a NimBLE callback could observe the stalefalsevalue and trigger a double retry. - Fixed
irk_last_try_ms_first-poll bypass: The IRK poll interval guard was bypassed on the first poll because the timestamp initialized to 0. It is now set tonow_ms()alongsideenc_ready_when encryption completes. - Fixed
mac_rotation_retries_andmac_rotation_ready_time_unprotected writes: These members are now reset inside existingMutexGuardblocks, consistent with the documented threading model. - Made getters thread-safe:
get_ble_profile()andget_ble_name()now acquirestate_mutex_before reading shared state. - Fixed
host_synced_cross-core visibility: Usesstd::atomic<bool>to ensure the NimBLE task's write is visible to the ESPHome main loop on dual-core ESP32 variants.
Correctness Fixes
- Fixed duplicate Home Assistant state events on auto-stop:
publish_and_log_irk()now checksis_advertising()before callingstop_advertising(), preventing a double call and doublepublish_state(false)when the disconnect handler has already stopped advertising. - Fixed IRK re-publish deduplication bypass: The reconnect path in
on_connect()previously calledpublish_irk_to_sensors()directly, bypassing the 60-second rate limit, deduplication cache, and capture counter. It now routes throughpublish_and_log_irk(). - Fixed BLE name length silent truncation: The
name_lenfield is now clamped to 29 bytes with a warning log before theuint8_tcast. - Fixed
commit_errmisleading log: NVS set and commit errors are now tracked with separate variables so the log accurately reports which operation failed. - Advertising switch state now reflects reality: The advertising switch publishes the actual runtime state after start/stop attempts rather than echoing only the requested state.
- NVS profile range validation: Persisted BLE profile values are range-checked before
static_cast, preventing undefined behavior from corrupted NVS data. - BLE name whitespace trimming:
sanitize_ble_name()trims leading and trailing spaces after character filtering.
Observability and Diagnostics
- Diagnostic log on spurious double-disconnect:
on_disconnect()now logs at DEBUG level if called while already disconnected, making NimBLE duplicate events visible in serial output. - Diagnostic log on L2CAP parameter update acceptance: Logs at DEBUG level when a peer-requested connection parameter update is accepted.
- Diagnostic log on Keyboard→Heart Sensor fallback: Logs handle values after fallback registration to clarify GATT state in diagnostics.
- Hardened NVS initialization logging: Added strict return-code checks and logging for
nvs_flash_init(), erase/re-init paths, and NVS health-check operations. - Consistent failure logging:
ble_store_config_init(), boot-timeble_store_clear(),ble_svc_gap_device_name_set(), and all terminate/start/stop paths now log failures consistently.
Code Quality
- Removed dead code: Unused helper
read_peer_bond_by_conn(), unused constantIRK_MIN_POLL_INTERVAL_MS, unused localadv_success. - Replaced magic GAP event numbers with named constants for readability and safer maintenance across ESP-IDF/NimBLE SDK variants.
- Added explicit standard library includes: Added
<string>,<vector>to header and<cstring>to implementation, removing reliance on transitive includes.
Files Updated
components/irk_capture/irk_capture.cppcomponents/irk_capture/irk_capture.hESPHome Devices/irk-capture-base.yamlESPHome Devices/irk-capture-full.yamlESPHome Devices/irk-capture-device-remote.yamlRELEASE_NOTES_v1.5.7.md(this file; replacesRELEASE_NOTES_v1.5.6.md)
v1.5.6
Reliability Fixes
- Fixed MAC rotation race condition:
refresh_mac()now sets thesuppress_next_adv_flag before triggering disconnect, preventinghandle_gap_disconnectfrom restarting advertising while the MAC rotation is still in progress. - Component fails cleanly on critical init errors:
setup()now callsmark_failed()and returns early if the FreeRTOS mutex cannot be created, andregister_gatt_services()marks the component as failed if GATT registration fails. - NVS profile range validation: Persisted BLE profile values are now range-checked before
static_cast, preventing undefined behavior from corrupted NVS data. - BLE name whitespace trimming:
sanitize_ble_name()now trims leading and trailing spaces after character filtering, preventing names like" IRK "from wasting bytes in the advertising packet. - Fixed
host_synced_cross-core visibility: Changed from plainbooltostd::atomic<bool>to ensure the NimBLE task's write is visible to the ESPHome main loop on dual-core ESP32 variants.
Code Quality
- Added explicit standard library includes: Added
<string>,<vector>to header and<cstring>to implementation, removing reliance on transitive includes from ESPHome/NimBLE headers.
v1.5.5
Bug Fixes
- Fixed thread safety race conditions: Six pairing/polling state fields were accessed across threads without mutex protection, risking missed IRK captures on dual-core ESP32 variants.
- Fixed 32-bit timer overflow: Absolute timer comparisons would fail after ~49.5 days of uptime. All timer checks now use wraparound-safe arithmetic.
- Fixed post-disconnect IRK lookup: Used the event's embedded connection descriptor instead of
ble_gap_conn_find, which could fail after NimBLE removed the descriptor. - Fixed config defaults mismatch: Python schema defaults now match the intended C++ behavior (
continuous_mode=true,max_captures=10). - Fixed BLE name validation: Compile-time limit reduced from 29 to 12 bytes to match the runtime Samsung S24/S25 compatibility constraint.
Improvements
- IRK cache size now scales with
max_capturessetting instead of being hardcoded to 10. - Replaced
esp_restart()withApp.safe_reboot()to avoid watchdog timeouts on profile change. - Consolidated
dump_configoutput into a single log line, removingvTaskDelayworkarounds. - Replaced magic number for NimBLE DHKey error with a named constant.
- Simplified
is_valid_irk, MAC generation, and NVS health check logic. - Added input validation for BLE profile select to reject invalid values.
- Removed unused
on_auth_completemethod andto_hex_fwddead code.
v1.5.4
Bug Fixes
-
Fixed BLE advertising not starting on boot: Resolved an issue where BLE advertising would not automatically start when
start_on_boot: truewas configured. The advertising initialization now correctly waits for the NimBLE host to sync before starting. -
Fixed rc=21 (BLE_HS_EALREADY) error: Added defensive handling to prevent advertising start failures when the BLE GAP was in a transitional state.
Documentation
- Various documentation updates including improved installation instructions, ESP32 variant reference images, and browser compatibility notes for serial flashing.
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.5 noreply@anthropic.com
v1.5.3
v1.5.3 - Documentation Improvements & Remote Installation Option
This release adds a new remote installation method and improves documentation clarity.
New Features
Remote Installation Option (Recommended)
- Added new Option 1 - Remote installation method that pulls both YAML package and IRK Capture component directly from GitHub
- No local file downloads required - simplest installation method
- Automatically gets latest version at build time
- New template file:
irk-capture-device-remote.yaml
Documentation Updates
Main README
- Restructured installation section with three clear options:
- Option 1 - Remote (Recommended): Pulls everything from GitHub
- Option 2 - Local Package: Uses base YAML + device-specific YAML
- Option 3 - Local Standalone: Single self-contained YAML file
- Added step-by-step instructions for remote installation
- Added clarification that IRK Capture cannot act as a Bluetooth proxy
- Added note about Bermuda BLE Trilateration integration compatibility
ESPHome Devices README
- Added documentation for new
irk-capture-device-remote.yamltemplate - Updated "Which Configuration Should I Use" section with all three options
- Clarified that standalone config still pulls component from GitHub at build time
- Added note about accessing secrets via ESPHome Device Builder UI
Compatibility
- ESP32 Variants: All (ESP32, C3, C6, S3, etc.)
- Framework: ESP-IDF (required)
- ESPHome: 2024.x or newer
- Home Assistant: Optional (recommended for Private BLE Device integration)
Installation
See the README for detailed installation instructions.
For the simplest installation, use the new remote method:
external_components:
- source:
type: git
url: https://github.com/DerekSeaman/irk-capture
ref: v1.5.3
components: [irk_capture]
refresh: 1min🤖 Generated with Claude Code
Co-Authored-By: Claude noreply@anthropic.com
v1.5.2
Overview
v1.5.2 is a major update focused on Android device support and reliability improvements. This release introduces a new BLE profile system that enables IRK capture from Samsung Galaxy phones and other Android devices that previously needed workarounds.
New Features
BLE Profile Selector
A new dropdown in the ESPHome device page lets you switch between two BLE advertising profiles:
| Profile | Best For | Advertised Name | Service |
|---|---|---|---|
| Heart Sensor | Apple devices (iPhone, iPad, Apple Watch), Android Watches | Your configured name | Heart Rate (0x180D) |
| Keyboard | Android Phones (Samsung Galaxy, Pixel, etc.) | "Logitech K380" | HID (0x1812) |
Why this matters: Samsung One UI 7 and other Android versions aggressively filter BLE devices in Bluetooth settings. The Keyboard profile advertises as a familiar Logitech keyboard, bypassing these filters and making the ESP32 visible for pairing.
Effective MAC Sensor
A new text sensor displays the current BLE MAC address being advertised by the ESP32.
Improvements
- Thread Safety Overhaul - FreeRTOS mutex protection for all shared state
- Non-Blocking MAC Rotation - Event-driven state machine, instant button response
- Bond Table Management - Cleared on every boot for fresh pairing sessions
- Memory Safety - IRK cache with hard cap of 10 entries and FIFO eviction
- BLE Name Validation - 12-character limit enforced for Samsung compatibility
- NimBLE Stack Optimization - Increased task stack size to 5KB
Bug Fixes
- Fixed race condition in IRK cache causing potential heap corruption
- Fixed deadlock when duplicate IRKs triggered advertising stop
- Fixed zombie connection state where UI showed "connected" but no data flowed
- Fixed heap fragmentation on extended capture sessions
- Fixed pairing timeout handling with proper cooldown
Breaking Changes
None. All existing YAML configurations work without modification.
Tested Devices
| Device | OS | Profile | Status |
|---|---|---|---|
| iPhone | iOS 26 | Heart Sensor | Working |
| Apple Watch | watchOS 26 | Heart Sensor | Working |
| iPad | iPadOS 26 | Heart Sensor | Working |
| Samsung Galaxy S25+ | One UI 7 | Keyboard | Working |
| Samsung Galaxy Watch8 Classic | Wear OS 6 | Heart Sensor | Working |
| Google Pixel 9 | Android 15 | Keyboard | Working |
| Amazon Echo Show | LineageOS 18.1 | Heart Sensor | Working |
🤖 Generated with Claude Code
Co-Authored-By: Claude noreply@anthropic.com
v1.5.1 - Android Watch Support
v1.5.1 - Android Watch Support
This release adds support for capturing IRKs from Android watches using the Gear Tracker II app.
New Features
Android Watch Support
- Added new documentation section for capturing IRKs from Android watches
- Gear Tracker II app enables pairing with heart rate sensor profile on watches that aggressively filter BLE devices
- Added Samsung Galaxy Watch7 to tested devices
Bug Fixes
Configuration Fixes
- Fixed invalid
initial_optionin select schema that caused ESPHome build failures - Updated external component URLs from
irk-capture-bludroidtoirk-capture
Documentation Updates
- Consolidated Heart Sensor Profile description for Apple devices and Android watches
- Renamed "Android devices" to "Android phones" where appropriate
- Added troubleshooting section for Android watches
- Added note about third-party app requirement for Android watches
Compatibility
- ESP32 Variants: All (ESP32, C3, C6, S3, etc.)
- Framework: ESP-IDF (required)
- ESPHome: 2024.x or newer
- Home Assistant: Optional (recommended for Private BLE Device integration)
Tested Devices
- ✓ Apple devices (iPhone, Apple Watch, iPad) - iOS 18+
- ✓ Android phones (Samsung Galaxy S25+, Google Pixel 9)
- ✓ Samsung Galaxy Watch7
Installation
Use this in your ESPHome YAML:
external_components:
- source:
type: git
url: https://github.com/DerekSeaman/irk-capture
ref: v1.5.1
components: [irk_capture]
refresh: 1minSee the README for detailed installation instructions.
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.5 noreply@anthropic.com
v1.5.0
IRK Capture v1.5.0 Release Notes
Overview
v1.5.0 is a major update focused on Android device support and reliability improvements. This release introduces a new BLE profile system that enables IRK capture from Samsung Galaxy phones and other Android devices that previously needed workarounds.
New Features
BLE Profile Selector
A new dropdown in the ESPHome device page lets you switch between two BLE advertising profiles:
| Profile | Best For | Advertised Name | Service |
|---|---|---|---|
| Heart Sensor | Apple devices (iPhone, iPad, Apple Watch) | Your configured name | Heart Rate (0x180D) |
| Keyboard | Android devices (Samsung Galaxy, Pixel, etc.) | "Logitech K380" | HID (0x1812) |
Why this matters: Samsung One UI 7 and other Android versions aggressively filter BLE devices in Bluetooth settings. The Keyboard profile advertises as a familiar Logitech keyboard, bypassing these filters and making the ESP32 visible for pairing.
Usage:
- Select the profile matching your target device
- The ESP32 will automatically reboot to apply the new GATT services
- Wait ~30 seconds for the device to be ready (watch for the Effective MAC sensor to update)
Effective MAC Sensor
A new text sensor displays the current BLE MAC address being advertised by the ESP32. This helps you:
- Verify MAC rotation is working after pressing "Generate New MAC"
- Confirm the device is ready after a profile change or reboot
- Troubleshoot pairing issues by confirming the advertised address
Improvements
Thread Safety Overhaul
The entire codebase has been refactored for production-grade thread safety on ESP32 single-core devices:
- Added FreeRTOS mutex protection for all shared state between the NimBLE task and ESPHome main task
- Implemented RAII
MutexGuardclass for exception-safe lock/unlock - Eliminated race conditions that could cause intermittent pairing failures
Non-Blocking MAC Rotation
The "Generate New MAC" button now uses an event-driven state machine instead of blocking delays:
- Instant button response (no UI freeze)
- Reliable MAC changes even during active connections
- Automatic retry logic for transient errors
Bond Table Management
- Bond table is now cleared on every boot, ensuring a fresh pairing session
- Eliminates "phantom" pairing failures from stale bond data
- Improves privacy by not persisting old IRKs across reboots
Memory Safety
- IRK cache now has a hard cap of 10 entries with FIFO eviction
- Pre-allocated vector capacity prevents heap fragmentation on ESP32-C3
- In-place updates for duplicate IRKs (no memory bloat from reconnecting devices)
BLE Name Validation
- Runtime sanitization of BLE device names from Home Assistant
- Enforces 12-character limit for Samsung compatibility
- Automatic fallback to "IRK Capture" for invalid input
NimBLE Stack Optimization
- Increased NimBLE task stack size from 4KB to 5KB for logging safety
- Prevents stack overflow during debug logging in GAP event callbacks
Bug Fixes
- Fixed race condition in IRK cache causing potential heap corruption during rapid reconnections
- Fixed deadlock when duplicate IRKs triggered advertising stop
- Fixed zombie connection state where the UI showed "connected" but no data flowed
- Fixed heap fragmentation on extended capture sessions with pre-allocated vectors
- Fixed pairing timeout handling with proper cooldown to prevent rapid-fire reconnection loops
Breaking Changes
None. All existing YAML configurations work without modification.
Behavioral Changes
- Bond table cleared on boot - Devices must re-pair after ESP32 reboot. This is intentional for reliability.
- Profile change triggers reboot - Switching between Heart Sensor and Keyboard profiles requires a reboot to update GATT services (NimBLE limitation).
Tested Devices
| Device | OS | Profile | Status |
|---|---|---|---|
| iPhone | iOS 26 | Heart Sensor | Working |
| Apple Watch | watchOS 26 | Heart Sensor | Working |
| iPad | iPadOS 26 | Heart Sensor | Working |
| Samsung Galaxy S25+ | One UI 7 | Keyboard | Working |
| Samsung Galaxy Watch8 Classic | Wear OS 6 | Both | Not Working |
| Google Pixel 9 | Android 15 | Keyboard | Working |
| Amazon Echo Show | LineageOS 18.1 | Heart Sensor | Working |
Migration Guide
From v1.4.x
Add the new optional entities to your configuration (if desired):
select:
- platform: irk_capture
irk_capture_id: irk
ble_profile:
id: ble_profile_select
name: "BLE Profile"
text_sensor:
- platform: irk_capture
irk_capture_id: irk
effective_mac:
name: "Effective MAC"Then compile, flash, and power cycle your ESP32.
v1.4.5 - CI/CD Linting Fixes & README Badge
v1.4.5 - CI/CD Linting Fixes & README Badge
This maintenance release fixes CI/CD linting compatibility issues and adds a status badge to the README. No functional changes to IRK capture implementation.
Changes
Linting Status Badge
- Added GitHub Actions linting workflow status badge to README
- Badge displays real-time code quality check status
- Green badge indicates all linting checks pass
Black Formatter Fixes
- Fixed Python code formatting to match CI/CD Black configuration
- Changed from line length 100 to Black default (88 characters)
- Updated pre-commit hook to match GitHub Actions settings
- All Python files now pass Black formatter validation
Files Fixed
button.py- Multi-line class declaration formattingswitch.py- Multi-line class declaration formattingtext_sensor.py- Multi-line schema formatting- Pre-commit hook updated for consistency
Technical Details
The pre-commit hook was using --line-length 100 while the GitHub Actions workflow used Black's default line length (88). This caused CI/CD failures even though code was formatted locally. This release aligns both configurations.
CI/CD Status: All linting checks now pass
- ✅ yamllint - YAML configuration validation
- ✅ clang-format - C++ code formatting
- ✅ Black - Python code formatting
Compatibility
- ESP32 Variants: All (ESP32, C3, C6, S3, etc.)
- Framework: ESP-IDF (required)
- ESPHome: 2024.x or newer
- Home Assistant: Optional (recommended for Private BLE Device integration)
Tested Devices
- ✓ Apple devices (iPhone, Apple Watch, iPad) - iOS 18+
- ✓ Android devices (LineageOS, standard Android)
Installation
See the README for detailed installation instructions.
Use this in your ESPHome YAML:
external_components:
- source:
type: git
url: https://github.com/DerekSeaman/irk-capture
ref: v1.4.5
components: [irk_capture]
refresh: 1min
irk_capture:
id: irk
ble_name: "IRK Capture"
start_on_boot: trueCommits Since v1.4.4
- Fix Black formatting to match CI/CD default line length
- Apply Black formatter to fix CI/CD linting errors
- Add linting status badge to README
Upgrade Notes
If upgrading from v1.4.4 or earlier, simply update the ref: line in your ESPHome YAML configuration to v1.4.5. This is a drop-in replacement with no configuration changes required.
🤖 Generated with Claude Code
Co-Authored-By: Claude noreply@anthropic.com