Skip to content

fix: wahoo setSimGrade sent before setSimMode (KICKR CORE race condition) - #4814

Open
cagnulein wants to merge 1 commit into
masterfrom
fix/wahoo-simmode-before-simgrade-clean
Open

fix: wahoo setSimGrade sent before setSimMode (KICKR CORE race condition)#4814
cagnulein wants to merge 1 commit into
masterfrom
fix/wahoo-simmode-before-simgrade-clean

Conversation

@cagnulein

Copy link
Copy Markdown
Owner

Problem

On KICKR CORE (and similar Wahoo devices), setSimGrade was being sent to the trainer before setSimMode had ever been sent.

Root cause

stateChanged() subscribes to all notifiable/indicatable characteristics across all BLE services. On a KICKR CORE this amounts to 17 descriptor writes (notificationSubscribed = 17). The descriptorWritten callbacks arrive slowly from the BLE stack — in the reported log only 9 of the 17 had arrived by the time Zwift sent its first indoor bike simulation parameters FTMS command (~30 seconds after connection).

Since notificationSubscribed never reached 0, initRequest was never set to true, so the update() init block (which sends unlocksetSimModesetWheelCircumference) never ran.

Meanwhile Zwift's FTMS write flows through a direct signal/slot path:
CharacteristicWriteProcessor2AD9 → changeSlope → inclinationChanged → setSimGrade
…which bypasses initRequest entirely, sending setSimGrade to a trainer that had never received setSimMode.

Fix

Subscribe only to CyclingPowerMeasurement during stateChanged() — at most 1 descriptor write — so descriptorWritten fires almost immediately and the init sequence (including setSimMode) completes before Zwift can send the first slope command.

A fallback is also added: if no CyclingPowerMeasurement subscription was established at all, initRequest is set directly from stateChanged() so the init always runs.

This is a minimal, targeted fix. A previous attempt (PR #4530, closed as wontfix) addressed the same issue with additional unrelated changes; this PR contains only the essential change.

Test plan

  • Connect KICKR CORE and verify setSimMode appears in the log before the first setSimGrade
  • Ride in Zwift: resistance/slope changes should work correctly from the start
  • Verify no regression for KICKR SNAP and KICKR BIKE (ERG mode, gear mode)

🤖 Generated with Claude Code

…tor subscriptions

On KICKR CORE, stateChanged subscribed to all 17 notifiable/indicatable
characteristics across all services. The descriptorWritten callbacks
trickle in slowly, so notificationSubscribed never reached zero before
Zwift sent its first indoor-bike-simulation-parameters write. That write
triggered inclinationChanged -> setSimGrade directly, bypassing the
initRequest guard and the setSimMode that must precede it.

Fix: subscribe only to CyclingPowerMeasurement (at most 1 descriptor
write needed), so the init sequence fires almost immediately after
service discovery. Add a fallback to fire init directly from stateChanged
if no subscription was established at all.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@stale

stale Bot commented Jul 30, 2026

Copy link
Copy Markdown

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale Bot added the wontfix This will not be worked on label Jul 30, 2026
@stale stale Bot closed this Aug 7, 2026
@cagnulein cagnulein reopened this Aug 8, 2026
@stale stale Bot removed the wontfix This will not be worked on label Aug 8, 2026
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.

1 participant