

# Firmware Updates

Check for and perform firmware updates on iglooworks devices.

---

## checkFirmwareUpdate

Checks whether a firmware update is available for the device.

For BLE locks: reads the current firmware version from the lock over BLE, patches the server, then queries the DFU service.

For EB1 bridges: reads the server-stored firmware version (no BLE) and queries the DFU service.

### Signature

```kotlin
suspend fun checkFirmwareUpdate(
    deviceId: String,
    key: String,
    accessToken: String? = null,
): FirmwareUpdateInfo
```

```swift
func checkFirmwareUpdate(
    deviceId: String, 
    key: String
) async throws -> FirmwareUpdateInfo
```

### Parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `deviceId` | `String` | Yes | Bluetooth device name (or device ID for EB1). |
| `key` | `String` | Yes | eKey with `GET_FIRMWARE_VERSION` permission. |
| `accessToken` | `String?` | OAuth only | OAuth access token for server calls. |

### Return Type

```kotlin
data class FirmwareUpdateInfo(
    val hasUpdate: Boolean,
    val currentVersion: String,
    val firmwareUid: String?,
    val latestFirmwareUid: String?,
    val firmwareSizeBytes: Long?,
)
```

```swift
public struct FirmwareUpdateInfo {
    public let hasUpdate: Bool
    public let currentVersion: String
    public let firmwareUid: String?
    public let firmwareSizeBytes: Int?
}
```

| Field | Description |
|-------|-------------|
| `hasUpdate` | `true` if a newer firmware is available. |
| `currentVersion` | Current firmware version string on the device. |
| `firmwareUid` | UID of the available firmware package. `null` if no update. |
| `latestFirmwareUid` | UID of the latest available firmware. `null` if no update. |
| `firmwareSizeBytes` | Size of the firmware binary in bytes. `null` if no update. |

### Error Codes

| Exception | Code | Description |
|-----------|------|-------------|
| `BluetoothException` | 708 | Bluetooth is off or unavailable (BLE path). |
| `ConnectionException` | 12 | Device disconnected during version read. |
| `TimeoutException` | 703 | BLE operation exceeded the timeout. |
| `ApiException` | varies | Server API call failed. |

Both `checkFirmwareUpdate` and `performDfu` start by scanning for the device, which on
iOS throws `LockManagerError.noLockFound` (device did not respond) or
`.bluetoothIsTurnedOff` before anything else runs.

### Example

```kotlin
try {
    val info = sdk.checkFirmwareUpdate(
        deviceId = "IGM401abc123",
        key = guestKey,
    )
    if (info.hasUpdate) {
        println("Update available: ${info.firmwareUid}")
        println("Size: ${info.firmwareSizeBytes} bytes")
    } else {
        println("Firmware is up to date (${info.currentVersion})")
    }
} catch (e: IglooWorksException.ConnectionException) {
    showError("unexpected bluetooth connection issue")
} catch (e: IglooWorksException.TimeoutException) {
    showError("Timed out reading firmware version")
} catch (e: IglooWorksException) {
    showError("Check failed: ${e.message}")
}
```

```swift
do {
    let info = try await sdk.checkFirmwareUpdate(
        deviceId: "IGM401abc123", 
        key: guestKey)

    if info.hasUpdate {
        print("Update available: \(info.firmwareUid ?? "")")
        print("Size: \(info.firmwareSizeBytes ?? 0) bytes")
    } else {
        print("Firmware is up to date (\(info.currentVersion))")
    }
} catch {
    showError(message: "Check failed: \(error.localizedDescription)")
}
```

---

## performDfu

Performs a firmware update on the device. Returns a Flow of `DfuEvent` — progress updates then complete or failed.

For BLE locks: downloads firmware, executes BLE DFU transfer with progress (0–100%), then patches the server with the new version.

For SP2 devices: perform a Lock followed by an Unlock on the lock, then ensure the shackle is fully released (open position) before calling `performDfu`.

For EB1 bridges: creates a cloud firmware update job and polls until completion.

### Signature

```kotlin
fun performDfu(
    deviceId: String,
    key: String,
    firmwareUid: String,
    accessToken: String? = null,
): Flow<DfuEvent>
```

```swift
func performDfu(
    deviceId: String, 
    key: String, 
    firmwareUid: String
) async throws -> AsyncThrowingStream<DfuEvent, Error>
```

There's also a public `performDfuJob(deviceId:)` for driving a Bridge's server-job DFU flow directly — `performDfu` calls into it automatically when the target device is a Bridge.

### Parameters

| Name | Type | Required | Description |
|------|------|----------|-------------|
| `deviceId` | `String` | Yes | Bluetooth device name (or device ID for EB1). |
| `key` | `String` | Yes | eKey with `ENABLE_DFU` permission. |
| `firmwareUid` | `String` | Yes | Firmware package UID from `FirmwareUpdateInfo.firmwareUid`. |
| `accessToken` | `String?` | OAuth only | OAuth access token for server calls. |

### Return Type — DfuEvent

```kotlin
sealed class DfuEvent {
    /** Firmware transfer progress (0–100). */
    data class Progress(val percent: Int) : DfuEvent()

    /** Firmware update completed. */
    data class Complete(val newFirmwareVersion: String) : DfuEvent()

    /** Firmware update failed. */
    data class Failed(val error: IglooWorksException) : DfuEvent()
}
```

```swift
public enum DfuEvent {
    case progress(Int)
    case complete(newFirmwareVersion: String)
}
```

### Example

```kotlin
try {
    val info = sdk.checkFirmwareUpdate(deviceId, key)
    if (!info.hasUpdate) return

    sdk.performDfu(
        deviceId = deviceId,
        key = key,
        firmwareUid = info.firmwareUid!!,
    ).collect { event ->
        when (event) {
            is DfuEvent.Progress -> updateProgressBar(event.percent)
            is DfuEvent.Complete -> showSuccess("Updated to ${event.newFirmwareVersion}")
            is DfuEvent.Failed -> {
                when (event.error) {
                    is IglooWorksException.BatteryLowException ->
                        showError("Battery too low for update")
                    is IglooWorksException.DfuStateNotReadyException ->
                        showError("Lock not ready — close the door and try again")
                    else -> showError("DFU failed: ${event.error.message}")
                }
            }
        }
    }
} catch (e: IglooWorksException.ConnectionException) {
    showError("unexpected bluetooth connection issue")
} catch (e: IglooWorksException.ApiException) {
    showError("Server error: ${e.message}")
} catch (e: IglooWorksException) {
    showError("Firmware update failed: ${e.message}")
}
```

```swift
let info = try await sdk.checkFirmwareUpdate(deviceId: deviceId, key: key)
guard info.hasUpdate, let firmwareUid = info.firmwareUid else { return }

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 IglooworksError.batteryLow {
    showError(message: "Battery too low for update")
} catch IglooworksError.dfuStateNotReady {
    showError(message: "Lock not ready — close the door and try again")
} catch {
    showError(message: "Firmware update failed: \(error.localizedDescription)")
}
```

### Notes

- **Battery check:** The SDK checks battery before starting DFU. Minimum 20% (31% for SP2X/SP2E). Throws `BatteryLowException` if too low.
- **DFU state check:** The SDK verifies the lock is ready for DFU (door closed, not in active state). Throws `DfuStateNotReadyException` if not ready. Skipped for EK and IEF SKU.
- **SP2 SKU:** Before performing DFU on an SP2 device, perform a Lock followed by an Unlock on the lock, then ensure the shackle is fully released (open position) before starting the update.
- **Progress:** BLE DFU emits progress 0–90% during transfer, animates 90–99% during reconnect, then 100% after reading the new firmware version.
- **Bridge DFU:** For EB1 bridges, the SDK creates a cloud job and polls every 3 seconds. `DfuJobFailedException` if the job fails, `DfuJobExpiredException` if it expires.
- **Keep screen alive:** DFU can take several minutes. Keep the screen on and display a progress indicator.
- Errors are emitted as `DfuEvent.Failed` rather than thrown as exceptions.
