igloo
Docs / iglooworks / BLE SDK / Firmware Updates

Firmware Updates (DFU)

Check for and perform firmware updates on Igloohome 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

suspend fun checkFirmwareUpdate(
    deviceId: String,
    key: String,
    accessToken: String? = null,
): FirmwareUpdateInfo
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 Guest key with GET_FIRMWARE_VERSION permission.
accessToken String? OAuth only OAuth access token for server calls.

Return Type

data class FirmwareUpdateInfo(
    val hasUpdate: Boolean,
    val currentVersion: String,
    val firmwareUid: String?,
    val latestFirmwareUid: String?,
    val firmwareSizeBytes: Long?,
)
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

try {
    val info = sdk.checkFirmwareUpdate(
        deviceId = "IGM4-XXXX",
        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}")
}
do {
    let info = try await sdk.checkFirmwareUpdate(
        deviceId: "IGM4-XXXX", 
        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

fun performDfu(
    deviceId: String,
    key: String,
    firmwareUid: String,
    accessToken: String? = null,
): Flow<DfuEvent>
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 Guest key 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

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()
}
public enum DfuEvent {
    case progress(Int)
    case complete(newFirmwareVersion: String)
}

Example

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}")
}
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.