

# 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

```kotlin
fun calibrate(
    deviceId: String,
    key: String,
    lockingDirection: LockingDirection? = null,
    signals: ReceiveChannel<CalibrationSignal>? = null,
    accessToken: String,
): Flow<CalibrationStep>
```

```swift
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 | eKey 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

```kotlin
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()
}
```

```swift
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

```kotlin
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,
}
```

```swift
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)

```kotlin
try {
    sdk.calibrate(
        deviceId = "DBX101abc123",
        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}")
}
```

```swift
do {
    let steps = try await sdk.calibrate(
        deviceId: "DBX101abc123",
        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)

```kotlin
try {
    sdk.calibrate(
        deviceId = "IGB403fe3b9d",
        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}")
}
```

```swift
do {
    let steps = try await sdk.calibrate(
        deviceId: "IGB403fe3b9d",
        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)

```kotlin
val signalChannel = Channel<CalibrationSignal>()

// Collect in one coroutine
launch {
    try {
        sdk.calibrate(
            deviceId = "OE1X01abc123",
            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}")
    }
}
```

```swift
let (signalStream, signalContinuation) = AsyncStream<CalibrationSignal>.makeStream()

do {
    let steps = try await sdk.calibrate(
        deviceId: "OE1X01abc123",
        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}`.
