igloo
Docs / igloohome / BLE SDK / Calibration

Calibration

Calibrate Igloohome locks to tune motor direction and door settings.


calibrate

Calibrates a lock via Bluetooth. Lock type is resolved from the deviceId prefix and routes to the appropriate BLE flow. Returns a Flow<CalibrationStep> — non-OE1 locks emit only Complete, OE1 emits interactive steps requiring user signals.

After BLE calibration succeeds, the SDK updates the server with the calibration result.

Signature

fun calibrate(
    deviceId: String,
    key: String,
    lockingDirection: LockingDirection? = null,
    signals: ReceiveChannel<CalibrationSignal>? = null,
    accessToken: String,
): Flow<CalibrationStep>
func calibrate(
    deviceId: String,
    key: String,
    lockingDirection: LockingDirection? = nil,
    signals: AsyncStream<CalibrationSignal>? = nil,
    accessToken token: String
) async throws -> AsyncThrowingStream<CalibrationStep, Error>

Parameters

Name Type Required Description
deviceId String Yes Bluetooth device name of the lock.
key String Yes Guest key with CALIBRATE and SET_BRIGHTNESS permissions.
lockingDirection LockingDirection? IGB4 only Locking direction. Required for IGB4 locks, ignored for others.
signals ReceiveChannel<CalibrationSignal>? OE1 only Channel for interactive OE1 flow. Required for OE1, ignored for others.
accessToken String Yes OAuth access token for server calls.

Return Type — CalibrationStep

sealed class CalibrationStep {
    /** OE1: User should open door ajar. Send CalibrationSignal.Proceed to continue. */
    object OpenPosition : CalibrationStep()

    /** OE1: User should close door. Send CalibrationSignal.Proceed to continue. */
    object ClosePosition : CalibrationStep()

    /** OE1: User should select locking direction and door type. Send DoorSettingsSelected. */
    object DoorSettings : CalibrationStep()

    /** All BLE steps complete. */
    data class Complete(
        val lockingDirection: LockingDirection,
        val cylinderRotation: CylinderRotation?,
        val unlockHoldEnabled: Boolean?,
    ) : CalibrationStep()
}
public enum CalibrationStep : Equatable, Sendable {
    case openPosition
    case closePosition
    case doorSettings
    case complete(CalibrateResult)
}

public struct CalibrateResult : Sendable {
    public let deviceId: String
    public let lockingDirection: LockingDirection
    public let calibratedAt: String
    public let cylinderRotation: CylinderRotation?
}

Supporting Types

enum class LockingDirection {
    LEFT_HANDED, RIGHT_HANDED, UNKNOWN,
}

enum class CylinderRotation {
    SINGLE, DOUBLE,
}

sealed class CalibrationSignal {
    /** User is ready — proceed with current step. */
    object Proceed : CalibrationSignal()

    /** User has selected door settings. */
    data class DoorSettingsSelected(
        val lockingDirection: LockingDirection,
        val doorType: DoorType,
    ) : CalibrationSignal()
}

enum class DoorType {
    /** Both sides have a handle — unlockHoldEnabled = false */
    BOTH_SIDES_HAVE_HANDLE,

    /** One side has no handle — unlockHoldEnabled = true */
    ONE_SIDE_NO_HANDLE,
}
enum LockingDirection : String, Sendable {
    case leftHanded = "left"
    case rightHanded = "right"
}

enum CylinderRotation : Sendable {
    case single
    case double
}

public enum CalibrationSignal : Sendable {
    case proceed
    case doorSettingsSelected(lockingDirection: LockingDirection, doorType: DoorType)
}

enum DoorType : Sendable {
    case bothSidesHaveHandle
    case oneSideNoHandle
}

Error Codes

Exception Code Description
BluetoothException 708 Bluetooth is off or unavailable.
ConnectionException 12 Device disconnected during calibration.
TimeoutException 703 BLE operation exceeded the timeout.
InvalidLockingDirectionException 901 Auto-detected locking direction is ambiguous.
CalibrationFailedException 902 BLE calibration command failed.
ApiException varies Server calibration update failed.

Example — Standard Lock (DAX, DBX)

try {
    sdk.calibrate(
        deviceId = "DAX5-XXXX",
        key = guestKey,
        accessToken = accessToken,
    ).collect { step ->
        when (step) {
            is CalibrationStep.Complete -> {
                showSuccess("Calibration complete: ${step.lockingDirection}")
            }
            else -> { /* Only OE1 emits intermediate steps */ }
        }
    }
} catch (e: IglooHomeException.CalibrationFailedException) {
    showError("Calibration failed — reposition the lock and try again")
} catch (e: IglooHomeException.ConnectionException) {
    showError("unexpected bluetooth connection issue")
} catch (e: IglooHomeException) {
    showError("Calibration failed: ${e.message}")
}
do {
    let steps = try await sdk.calibrate(
        deviceId: "DAX5-XXXX",
        key: guestKey,
        accessToken: accessToken)
    for try await step in steps {
        switch step {
        case let .complete(result):
            showSuccess(message: "Calibration complete: \(result.lockingDirection.rawValue)")
        default:
            break // Only OE1 emits intermediate steps
        }
    }
} catch IgloohomeError.calibrationFailed {
    showError(message: "Calibration failed — reposition the lock and try again")
} catch IgloohomeError.connection {
    showError(message: "unexpected bluetooth connection issue")
} catch {
    showError(message: "Calibration failed: \(error.localizedDescription)")
}

Example — IGB4 Lock (requires lockingDirection)

try {
    sdk.calibrate(
        deviceId = "IGB4-XXXX",
        key = guestKey,
        lockingDirection = LockingDirection.LEFT_HANDED,
        accessToken = accessToken,
    ).collect { step ->
        when (step) {
            is CalibrationStep.Complete -> showSuccess("Calibrated: ${step.lockingDirection}")
            else -> {}
        }
    }
} catch (e: IglooHomeException.InvalidLockingDirectionException) {
    showError("Locking direction is ambiguous — select manually")
} catch (e: IglooHomeException.CalibrationFailedException) {
    showError("Calibration failed — try again")
} catch (e: IglooHomeException) {
    showError("Calibration failed: ${e.message}")
}
do {
    let steps = try await sdk.calibrate(
        deviceId: "IGB4-XXXX",
        key: guestKey,
        lockingDirection: .leftHanded,
        accessToken: accessToken)
    for try await step in steps {
        switch step {
        case let .complete(result):
            showSuccess(message: "Calibrated: \(result.lockingDirection.rawValue)")
        default:
            break
        }
    }
} catch IgloohomeError.invalidLockingDirection {
    showError(message: "Locking direction is ambiguous — select manually")
} catch IgloohomeError.calibrationFailed {
    showError(message: "Calibration failed — try again")
} catch {
    showError(message: "Calibration failed: \(error.localizedDescription)")
}

Example — OE1 Lock (interactive flow)

val signalChannel = Channel<CalibrationSignal>()

// Collect in one coroutine
launch {
    try {
        sdk.calibrate(
            deviceId = "OE1-XXXX",
            key = guestKey,
            signals = signalChannel,
            accessToken = accessToken,
        ).collect { step ->
            when (step) {
                is CalibrationStep.OpenPosition -> {
                    showPrompt("Open the door ajar, then tap Continue")
                    // User taps Continue button:
                    signalChannel.send(CalibrationSignal.Proceed)
                }
                is CalibrationStep.ClosePosition -> {
                    showPrompt("Close the door, then tap Continue")
                    signalChannel.send(CalibrationSignal.Proceed)
                }
                is CalibrationStep.DoorSettings -> {
                    showPrompt("Select locking direction and door type")
                    signalChannel.send(
                        CalibrationSignal.DoorSettingsSelected(
                            lockingDirection = LockingDirection.LEFT_HANDED,
                            doorType = DoorType.ONE_SIDE_NO_HANDLE,
                        )
                    )
                }
                is CalibrationStep.Complete -> {
                    showSuccess("Calibration complete")
                    signalChannel.close()
                }
            }
        }
    } catch (e: IglooHomeException.CalibrationFailedException) {
        showError("Calibration failed — reposition the door and try again")
    } catch (e: IglooHomeException.ConnectionException) {
        showError("unexpected bluetooth connection issue")
    } catch (e: IglooHomeException) {
        showError("Calibration failed: ${e.message}")
    }
}
let (signalStream, signalContinuation) = AsyncStream<CalibrationSignal>.makeStream()

do {
    let steps = try await sdk.calibrate(
        deviceId: "OE1-XXXX",
        key: guestKey,
        signals: signalStream,
        accessToken: accessToken)
    for try await step in steps {
        switch step {
        case .openPosition:
            showPrompt(message: "Open the door ajar, then proceed")
            signalContinuation.yield(.proceed)
        case .closePosition:
            showPrompt(message: "Close the door, then proceed")
            signalContinuation.yield(.proceed)
        case .doorSettings:
            showPrompt(message: "Select locking direction and door type, then confirm")
            signalContinuation.yield(
                .doorSettingsSelected(lockingDirection: .leftHanded, doorType: .oneSideNoHandle))
        case let .complete(result):
            showSuccess(message: "Calibrated '\(result.deviceId)' — \(result.lockingDirection.rawValue)")
            signalContinuation.finish()
        }
    }
} catch IgloohomeError.calibrationFailed {
    showError(message: "Calibration failed — reposition the door and try again")
} catch IgloohomeError.connection {
    showError(message: "unexpected bluetooth connection issue")
} catch {
    showError(message: "Calibration failed: \(error.localizedDescription)")
}

Notes

  • The SDK disconnects from the lock automatically after calibration completes (success or failure).
  • Non-OE1 locks auto-detect locking direction during calibration (except IGB4 which requires it as a parameter).
  • OE1 locks require an interactive flow: the user positions the door and selects settings at each step.
  • After BLE calibration, the SDK sends the result to the server via PATCH /devices/{deviceId}.