Acknowledged
CIQQA-4662

Edge Explore 2 FW 31.33: Ble.registerProfile() with a custom 128-bit service UUID crashes the data field with a System Error (regression from FW 30.23)

Device: Edge Explore 2 (Part-Number 006-B4169-00)
Firmware: 31.33 — broken. 30.23 — worked.
Connect IQ on device: 6.0.2 (API 6.0.0)
SDK: 9.2.0 (2026-06-09)
App type: data field, permissions BluetoothLowEnergy + FitContributor

Summary

On firmware 31.33, calling BluetoothLowEnergy.registerProfile() with a custom 128-bit service UUID kills the data field from inside the call. The failure is a System Error: 'Failed invoking <symbol>', which cannot be caught by try/catch, so an application has no way to recover or degrade gracefully.

The same code runs correctly on firmware 30.23 on the same device with the same peripheral, so this is a regression.

Profile being registered

Ble.registerProfile({
    :uuid => Ble.stringToUuid("000018ef-5348-494d-414e-4f5f424c4500"),
    :characteristics => [
        {
            :uuid => Ble.stringToUuid("00002ac1-5348-494d-414e-4f5f424c4500"),
            :descriptors => [Ble.cccdUuid()]
        }
    ]
});

Registering the same profile without the explicit CCCD descriptor crashes as well.

Crash log (GARMIN/APPS/LOGS/CIQ_LOG.YML)

Error: System Error
Details: 'Failed invoking <symbol>'
Time: 2026-08-22T12:42:54Z
Part-Number: 006-B4169-00
Firmware-Version: '31.33'
ConnectIQ-Version: 6.0.2
Stack:
  - pc: 0x100028a6
    File: 'Di2BleDelegate.mc'
    Line: 396          # the Ble.registerProfile(...) call itself
    Function: registerModeProfile
  - pc: 0x1000325d
    File: 'Di2BleDelegate.mc'
    Line: 358
    Function: registerProfiles
  - pc: 0x10003682
    File: 'Di2BleDelegate.mc'
    Line: 143
    Function: start
  - pc: 0x1000041a
    File: 'Di2FieldApp.mc'
    Line: 33
    Function: onStart

A standard service registers fine in the same run

Registering the standard Battery Service in the same application succeeds, so registerProfile() itself works — only the custom 128-bit UUID above is fatal:

register mode profile skipped after 2 crashes
profile register 180F:ok
services n=1 [0000180F-0000-1000-8000-00805F9B34FB ]

Earlier symptom on the same firmware

Before the call became fatal, an earlier build received an undocumented status = 2 in onProfileRegister() for this profile, while the battery profile got STATUS_SUCCESS. 2 is not among the documented Status values (0 SUCCESS, 1 NOT_ENOUGH_RESOURCES, 12 READ_FAIL, 14 WRITE_FAIL, 18/19 authentication/encryption). A similar undocumented status = 2 on profile registration was reported for VivoActive 4 and fixed in FW 5.63, so this may be the same defect resurfacing on Edge.

Minimal reproduction

A stand-alone data field (~60 lines) that only registers the profile and draws the result reproduces it. No peripheral is required — the crash happens during registration, before any scan or connection. I can attach the full project if useful.

function onStart(state) {
    var d = new ReproDelegate();
    Ble.setDelegate(d);

    var chr = WITH_CCCD
        ? { :uuid => Ble.stringToUuid(CHAR_UUID), :descriptors => [Ble.cccdUuid()] }
        : { :uuid => Ble.stringToUuid(CHAR_UUID) };
    try {
        // FW 31.33: the application dies here. No exception is delivered.
        Ble.registerProfile({
            :uuid => Ble.stringToUuid(SERVICE_UUID),
            :characteristics => [chr]
        });
    } catch (e) {
        d.status = "exception";   // never reached
    }
}

On FW 30.23 the field draws "registered"; on FW 31.33 it is replaced by the Connect IQ error icon.

Steps to reproduce

  1. Build a data field with the BluetoothLowEnergy permission.
  2. In AppBase.onStart(), call Ble.setDelegate(...) and then Ble.registerProfile(...) with the custom profile shown above.
  3. Install on an Edge Explore 2 running FW 31.33 and add the field to a data screen.
  4. The field shows the Connect IQ error icon immediately; CIQ_LOG.YML contains the stack above.

Only one BLE profile is registered at this point, so this is not the "3 profiles maximum" limit. Reordering registrations, dropping the descriptor and registering before/after setDelegate make no difference.

Impact

Third-party fields that talk to a peripheral over a vendor-specific GATT service cannot run at all on this firmware — the app dies at startup and cannot detect, catch or work around the failure. On 30.23 the identical build connects, subscribes to the characteristic and receives notifications normally.

Request

  1. Restore the ability to register custom 128-bit service UUIDs, as on FW 30.23.
  2. If registration must fail, please fail through onProfileRegister(uuid, status) with a documented status instead of terminating the application from inside the call — an app cannot catch a System Error.
  3. Please document status = 2.
  • After updating the Garmin Edge Explore 2 firmware to version 31.33, my Connect IQ app "Di2 Gear & Battery Viewer" can no longer communicate correctly with the Shimano Di2 system via the Toybox.BluetoothLowEnergy API.

    With the previous firmware version 30.23, the app works correctly: the Shimano Di2 service is registered, discovered, and shifting information is received normally.

    On firmware 31.33, the following occurs:

    • The battery service 0000180f-0000-1000-8000-00805f9b34fb is registered successfully (Status = 0).

    • The Shimano Di2 service 000018ef-5348-494d-414e-4f5f424c4500 is rejected during registerProfile(), before any scan or connection.

    • The returned error is Status = 2, but this error code is not documented in the Garmin Connect IQ API.

    • If the base UUID 000018ef-0000-1000-8000-00805f9b34fb is used, registration returns Status = 0, but the Shimano Di2 service does not appear later when enumerating the device’s services.

    • Using the same code with the Connect IQ SDK 9.2.0 Simulator, both UUIDs are registered correctly, so the issue is not reproducible in the simulator.

    The observed behavior on the actual device is:

    Firmware 30.23
    Shimano DI2 UUID: 000018EF-5348-494D-414E-4F5F424C4500

    • registerProfile() → Status 0

    • DI2 service → found

    • DI2 characteristics → found

    • DI2 communication → works

    Firmware 31.33
    Shimano DI2 UUID: 000018EF-5348-494D-414E-4F5F424C4500

    • registerProfile() → Status 2

    • DI2 service → not available

    • DI2 communication → does not work

    The battery service continues to function normally on firmware 31.33.

    I kindly request that a possible change/regression in the BLE implementation of firmware 31.33 be investigated, specifically regarding the registration and discovery of BLE services with custom UUIDs, particularly the Shimano Di2 service.

    The same code and device work correctly with firmware 30.23, while the issue is reproducible on the actual device with firmware 31.33.

  • Update: this is not limited to one app, and it is not limited to one call site.

    Since the original report I have run a series of instrumented builds on the same device (Edge Explore 2, FW 31.33, CIQ 6.0.0). Findings:

    1. Only one Ble.registerProfile() per session survives. The first call is tolerated. Any second call in the same session kills the data field with System Error: Failed invoking <symbol> — both when made from inside the onProfileRegister callback and when deferred to a later timer tick, outside any stack callback. On FW 30.23 the same code registers two profiles without trouble.

    2. The custom 128-bit service is refused rather than accepted. Registering 000018ef-5348-494d-414e-4f5f424c4500 with a plain characteristic (no explicit CCCD descriptor) survives the call but returns status = 2 in onProfileRegister — a value not documented in the SDK. Declaring the same characteristic with an explicit CCCD descriptor crashes the field instead of returning a status. The standard 0000180f battery profile registers successfully in the same session, so the API itself works.

    3. A third-party app hits the same wall. The device's own CIQ_LOG.BAK on this unit contains a crash from an unrelated Connect IQ app, "Di2 Dash" (Store-Id f02d57dd-e582-44aa-888e-12c928122eee), on the same firmware:

    Error: System Error
    Details: Profile Registration Unhandled Error
    Firmware-Version: '31.33'

    A further Di2 data field for this device, "Di2 Gear & Battery Viewer" (asgferreira), currently carries a notice in its store listing warning users about a Bluetooth communication problem on Edge Explore 2 after updating to 31.33, and advising them not to update.

    So at least three independent apps in the same category are broken by this regression — every Connect IQ app that talks to Shimano Di2 over BLE on this device.

    4. There is no workaround available to app developers. AntPlus.Shifting is not an option: the class is declared in the SDK device profile for Edge Explore 2, but the symbols are absent from the actual firmware, and instantiating it raises Symbol Not Found Error. Reading gear state from the advertisement is not an option either: the manufacturer-specific data broadcast by the shifter (company ID 0x044A, payload 10 00) is static and carries no gear information. The BLE gear service is the only channel, and it is exactly the one this regression closes.

    Happy to provide the diagnostic builds or additional logs if that helps reproduce.

  • di2-ble-repro.zip

    Di2App-0_0_45-diag.TXT

    Two attachments:

    • di2-ble-repro.zip — the minimal reproduction project (SDK 9.2.0, data field, ~60 lines, builds with monkeyc -d edgeexplore2). It only registers the custom profile and draws the result. No peripheral is required: the crash happens during registration, before any scan or connection, so nothing has to be paired or powered on to reproduce it. A constant at the top switches between the two profile forms (characteristic with an explicit CCCD descriptor, and characteristic without any descriptor) — both kill the application.
    • Di2App-0_0_45-diag.TXT — application log of one run on FW 31.33.

    The crash log itself (CIQ_LOG.YML) is quoted in full in the report above; the copy on the device has since rotated, but I can capture a fresh one on request.

    What the application log shows, in order:

    previous register attempt crashed (1), switching profile form
    previous register attempt crashed (2), switching profile form
    register mode profile skipped after 2 crashes
    profile register 180F:ok
    services n=1 [0000180F-0000-1000-8000-00805F9B34FB ]

    Because a System Error cannot be caught, the only workaround I found is to record the pending attempt in Storage before calling registerProfile, switch to the other profile form on the next start if the flag survived, and after two crashes stop requesting the custom profile altogether. The field then stays alive, but of course receives no data from the peripheral.

    Immediately after that skip, the standard Battery Service registers successfully in the same run (180F:ok) and is the only service the stack reports (services n=1). So registerProfile() itself works on this firmware — the failure is specific to the custom 128-bit service UUID.

    Happy to provide anything else that helps: a build with different UUIDs, fresh logs, or testing a candidate firmware.