

# Error Reference

All exceptions thrown by the igloohome BLE SDK.

---

## Exception Hierarchy

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

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

On iOS, every method throws a single `Error` enum: `IgloohomeError`, so a single `catch IgloohomeError` (or a bare `catch { }`) is enough for every method.

```swift
public enum IgloohomeError: Error {
    case bluetooth(source: Error?, message: String?)
    case bluetoothIsTurnedOff(source: Error? = nil, message: String? = nil)
    case lockNotFound(source: Error? = nil, message: String? = nil)
    case pairingFailed(source: Error? = nil, message: String? = nil)
    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.
```

---

## 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 `IgloohomeError.api`, `.dfuLibrary`, and `.genericError` carry a `status: Int` (HTTP-style status from the Partner API or DFU library). The rest of the mapping is by case name:

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

---

## Error Handling Patterns

### Basic

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

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

> Every method — `lock`/`unlock` included — throws `IgloohomeError`, whether the failure came from the Bluetooth layer or the server, so one exhaustive `catch` list covers both. Add more `catch IgloohomeError.xxx { }` clauses for the cases a given method's own page calls out (`pair`, `createPin`, `link`, DFU, …).

### 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 {
    for try await event in sdk.performDfu(deviceId: deviceId, key: key, firmwareUid: firmwareUid, accessToken: accessToken) {
        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 | `IgloohomeError.timeout`, `.connection`, `.lockNotFound` | Retry the operation (user may need to move closer, or wake the lock). |
| Retryable | `IgloohomeError.api` (5xx) | Retry after delay. |
| Retryable | `IgloohomeError.pairingFailed` | Retry pairing with the lock awake and close by. |
| Terminal | `IgloohomeError.bluetoothIsTurnedOff` | Prompt user to enable Bluetooth. |
| Terminal | `IgloohomeError.devicePaired` | Device already registered — no action needed. |
| Terminal | `IgloohomeError.hasLinkedDevice` | Unlink accessories first using `unlink()`. |
| Terminal | `IgloohomeError.duplicatePin` | Choose a different PIN. |
| Terminal | `IgloohomeError.lockStorageFull` | Delete existing access before adding new ones. |
| Terminal | `IgloohomeError.batteryLow` | Wait for battery to charge before DFU. |
| Terminal | `IgloohomeError.calibrationFailed` | Hardware issue — retry calibration from scratch. |
| Non-fatal | `IgloohomeError.lockUidNotFound`, `.pinNotFound` | SDK treats these as "already removed" during delete operations. |
