

# Error Reference

All exceptions thrown by the iglooworks BLE SDK.

---

## Exception Hierarchy

All SDK exceptions extend `IglooWorksException`, a sealed class with `status`, `source`, and `message` properties.

```kotlin
sealed class IglooWorksException(
    val status: Int,
    val source: Throwable? = null,
    override val message: String? = null,
) : Exception(message, source)
```

On iOS, errors surface as one of two separate `Error` enums depending on which layer failed: `IglooworksError` (orchestration/server errors — `createPin`, `pair`, `link`, DFU, etc.) or `LockManagerError` (raw Bluetooth-layer errors — `lock`, `unlock`, `scan`, etc.). A given method may throw either type, so always end a `catch` chain with a bare `catch { }` to be safe. See per-method pages for the type(s) each one throws in practice.

```swift
public enum IglooworksError: Error {
    case bluetooth(source: Error?, message: String?)
    case connection(source: Error?, message: String?)
    case timeout(source: Error? = nil, message: String? = nil)
    case duplicatePin(source: Error? = nil, message: String? = nil)
    case pinNotFound(source: Error? = nil, message: String? = nil)
    case lockUidNotFound(source: Error? = nil, message: String? = nil)
    case bridgeOffline(source: Error? = nil, message: String? = nil)
    case invalidLockingDirection(source: Error? = nil, message: String? = nil)
    case calibrationFailed(source: Error? = nil, message: String? = nil)
    case lockStorageFull(source: Error? = nil, message: String? = nil)
    case maxScanAttempts(source: Error? = nil, message: String? = nil)
    case missingUid(source: Error? = nil, message: String? = nil)
    case batteryLow(source: Error? = nil, message: String? = nil)
    case dfuStateNotReady(source: Error? = nil, message: String? = nil)
    case dfuJobFailed(source: Error? = nil, message: String? = nil)
    case dfuJobExpired(source: Error? = nil, message: String? = nil)
    case devicePaired
    case hasLinkedDevice(linkedDeviceIds: [String])
    case api(status: Int, source: Error? = nil, message: String? = nil)
    case dfuLibrary(status: Int, source: Error? = nil, message: String? = nil)
    case genericError(_ status: Int, source: Error? = nil, message: String? = nil)
}
// Conforms to LocalizedError — use `error.localizedDescription` for a formatted message.
```

`IglooworksError.bluetooth` is a catch-all for a Bluetooth-layer failure with no closer
match among the other cases (usually a low-level transport error, or an unsupported
calibration model). Bluetooth-off, no-lock-found and pairing failures are raised as
`LockManagerError` instead — see below.

```swift
public enum LockManagerError: Error, Sendable {
    case noLockFound
    case bluetoothIsTurnedOff
    case invalidPairingData
    case pairingMissingCertificate
    case pairingFailed(String?)
    case invalidPairingFlow(String?)
    case deviceDisconnected
    case invalidKeycard
    case invalidCardUid
    case lockCalibrationFailed
    case invalidFirmwareVersion
    case invalidPinType
    case deviceAlreadyPaired
    case dfuStateNotReady
    case lockConnectionError(String?)
    case LockGenericFailureError(String?)
    case LockOperationInProgressError(String?)
    case LockPinNotFoundError(String?)
    case LockInvalidMasterPinLengthError(String?)
    case LockInvalidCustomPinLengthError(String?)
    case LockInvalidVolumeError(String?)
    case LockInvalidPinKeyLengthError(String?)
    case LockInvalidRights(String?)
    case LockPinOccupiedError(String?)
    case LockBlacklistGuestKeyNotFoundError(String?)
    case GenericNotFoundError(String?)
    case LockStorageError(String?)
    case LockReadError(String?)
    case LockTimeoutError(String?)
    case LockMaxAttemptError(String?)
    case CRCFailure(String?)
    case LockVerifyError(String?)
    case LockNotInSchedulerFailure(String?)
    case BluetoothError(String?)
}
// Conforms to LocalizedError — use `error.localizedDescription` for a formatted message.
```

---

## Error Codes

| Exception | Code | Description |
|-----------|------|-------------|
| `GenericException` | 1 | Catch-all for unhandled errors. |
| `ConnectionException` | 12 | BLE connection failed or device disconnected unexpectedly. |
| `BridgeOfflineException` | 406 | Bridge device is offline (cloud DFU / link operations). |
| `TimeoutException` | 703 | Operation exceeded the time limit. Also used for `DfuJobFailedException` and `DfuJobExpiredException`. |
| `DfuJobFailedException` | 703 | Cloud firmware update job failed. |
| `DfuJobExpiredException` | 703 | Cloud firmware update job expired before completion. |
| `BluetoothException` | 708 | Bluetooth is off or unavailable on the device. |
| `DuplicatePinException` | 880 | PIN already exists on the lock. |
| `PinNotFoundException` | 890 | PIN not found on the lock. |
| `InvalidLockingDirectionException` | 901 | Auto-detected locking direction is ambiguous during calibration. |
| `CalibrationFailedException` | 902 | BLE calibration command failed. |
| `DevicePairedException` | 910 | Device is already paired. |
| `HasLinkedDeviceException` | 910 | Device has linked accessories — unlink them first. Carries `linkedDeviceIds: List<String>`. |
| `LockStorageFullException` | 912 | Lock storage exhausted (no room for more PINs, keycards, or fingerprints). |
| `MaxScanAttemptsException` | 972 | BLE scan retry limit exceeded. |
| `LockUidNotFoundException` | 980 | Device UID not found on the lock (card/fingerprint already removed). |
| `DfuStateNotReadyException` | 1003 | Device not ready for firmware update (door open, DFU mode not entered). |
| `BatteryLowException` | 1010 | Battery too low for firmware update (< 20%, or < 31% for SP2X/SP2E). |
| `DfuLibraryException` | 1100+ | Low-level DFU library error. Status varies. |
| `ApiException` | varies | HTTP error from server API. Status matches the HTTP response code. |

iOS does not use numeric status codes for most cases — only `IglooworksError.api`, `.dfuLibrary`, and `.genericError` carry a `status: Int`. The rest of the mapping is by case name:

| Android exception | iOS equivalent |
|---|---|
| `GenericException` | `IglooworksError.genericError` |
| `ConnectionException` | `LockManagerError.deviceDisconnected` |
| `BridgeOfflineException` | `IglooworksError.bridgeOffline` |
| `TimeoutException` | `IglooworksError.timeout` or `LockManagerError.LockTimeoutError` (BLE-layer) |
| `DfuJobFailedException` | `IglooworksError.dfuJobFailed` |
| `DfuJobExpiredException` | `IglooworksError.dfuJobExpired` |
| `BluetoothException` | `LockManagerError.bluetoothIsTurnedOff` |
| `DuplicatePinException` | `IglooworksError.duplicatePin` |
| `PinNotFoundException` | `IglooworksError.pinNotFound` |
| `InvalidLockingDirectionException` | `IglooworksError.invalidLockingDirection` |
| `CalibrationFailedException` | `IglooworksError.calibrationFailed` / `LockManagerError.lockCalibrationFailed` |
| `DevicePairedException` | `IglooworksError.devicePaired` / `LockManagerError.deviceAlreadyPaired` |
| `HasLinkedDeviceException` | `IglooworksError.hasLinkedDevice(linkedDeviceIds:)` |
| `LockStorageFullException` | `IglooworksError.lockStorageFull` |
| `MaxScanAttemptsException` | `IglooworksError.maxScanAttempts` |
| `LockUidNotFoundException` | `IglooworksError.lockUidNotFound` |
| `DfuStateNotReadyException` | `IglooworksError.dfuStateNotReady` / `LockManagerError.dfuStateNotReady` |
| `BatteryLowException` | `IglooworksError.batteryLow` |
| `DfuLibraryException` | `IglooworksError.dfuLibrary(status:)` |
| `ApiException` | `IglooworksError.api(status:)` |

---

## Error Handling Patterns

### Basic

```kotlin
try {
    sdk.lock(deviceId, key)
} catch (e: IglooWorksException) {
    showError("Error ${e.status}: ${e.message}")
}
```

```swift
do {
    try await sdk.lock(deviceId, key: key)
} catch {
    showError(message: error.localizedDescription)
}
```

### Exhaustive

```kotlin
try {
    sdk.lock(deviceId, key)
} catch (e: IglooWorksException) {
    when (e) {
        is IglooWorksException.BluetoothException ->
            showError("Please enable Bluetooth")
        is IglooWorksException.ConnectionException ->
            showError("unexpected bluetooth connection issue")
        is IglooWorksException.TimeoutException ->
            showError("Operation timed out, try again")
        else ->
            showError("Error: ${e.message}")
    }
}
```

```swift
do {
    try await sdk.lock(deviceId, key: key)
} catch LockManagerError.bluetoothIsTurnedOff {
    showError(message: "Please enable Bluetooth")
} catch LockManagerError.deviceDisconnected {
    showError(message: "unexpected bluetooth connection issue")
} catch LockManagerError.LockTimeoutError {
    showError(message: "Operation timed out, try again")
} catch {
    showError(message: "Error: \(error.localizedDescription)")
}
```

> Because `lock`/`unlock` throw `LockManagerError` directly (not `IglooworksError`), the exhaustive `catch` list above only needs to match `LockManagerError` cases. Methods that orchestrate a server call too (`pair`, `createPin`, `link`, DFU, …) can throw `IglooworksError` cases as well — add `catch IglooworksError.xxx { }` clauses for those as shown on each method's own page.

### Flow-Based (DFU, Fingerprint)

```kotlin
sdk.performDfu(deviceId, key, firmwareUid).collect { event ->
    when (event) {
        is DfuEvent.Progress -> updateProgressBar(event.percent)
        is DfuEvent.Complete -> showSuccess("Updated to ${event.newFirmwareVersion}")
        is DfuEvent.Failed -> showError("DFU failed: ${event.error.message}")
    }
}
```

```swift
do {
    let events = try await sdk.performDfu(deviceId: deviceId, key: key, firmwareUid: firmwareUid)
    for try await event in events {
        switch event {
        case let .progress(percent): updateProgressBar(percent)
        case let .complete(newFirmwareVersion): showSuccess(message: "Updated to \(newFirmwareVersion)")
        }
    }
} catch {
    showError(message: "DFU failed: \(error.localizedDescription)")
}
```

---

## Retryable vs Terminal

| Type | Exceptions | Recommended Action |
|------|-----------|-------------------|
| Retryable | `TimeoutException`, `ConnectionException` | Retry the operation (user may need to move closer). |
| Retryable | `ApiException` (5xx) | Retry after delay. |
| Terminal | `BluetoothException` | Prompt user to enable Bluetooth. |
| Terminal | `DevicePairedException` | Device already registered — no action needed. |
| Terminal | `HasLinkedDeviceException` | Unlink accessories first using `unlink()`. |
| Terminal | `DuplicatePinException` | Choose a different PIN. |
| Terminal | `LockStorageFullException` | Delete existing access before adding new ones. |
| Terminal | `BatteryLowException` | Wait for battery to charge before DFU. |
| Terminal | `CalibrationFailedException` | Hardware issue — retry calibration from scratch. |
| Non-fatal | `LockUidNotFoundException`, `PinNotFoundException` | SDK treats these as "already removed" during delete operations. |

The same table on iOS, by case name:

| Type | iOS cases | Recommended Action |
|------|-----------|-------------------|
| Retryable | `IglooworksError.timeout`, `LockManagerError.LockTimeoutError`, `LockManagerError.deviceDisconnected` | Retry the operation (user may need to move closer). |
| Retryable | `IglooworksError.api` (5xx) | Retry after delay. |
| Terminal | `LockManagerError.bluetoothIsTurnedOff` | Prompt user to enable Bluetooth. |
| Terminal | `IglooworksError.devicePaired`, `LockManagerError.deviceAlreadyPaired` | Device already registered — no action needed. |
| Terminal | `IglooworksError.hasLinkedDevice` | Unlink accessories first using `unlink()`. |
| Terminal | `IglooworksError.duplicatePin` | Choose a different PIN. |
| Terminal | `IglooworksError.lockStorageFull` | Delete existing access before adding new ones. |
| Terminal | `IglooworksError.batteryLow` | Wait for battery to charge before DFU. |
| Terminal | `IglooworksError.calibrationFailed`, `LockManagerError.lockCalibrationFailed` | Hardware issue — retry calibration from scratch. |
| Non-fatal | `IglooworksError.lockUidNotFound`, `.pinNotFound` | SDK treats these as "already removed" during delete operations. |
