Docs / iglooworks / 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? = null,
): 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? |
OAuth only |
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,
}
public enum LockingDirection: String, Sendable {
case leftHanded = "left"
case rightHanded = "right"
}
public enum CylinderRotation: Sendable {
case single
case double
}
public enum CalibrationSignal: Sendable {
case proceed
case doorSettingsSelected(lockingDirection: LockingDirection, doorType: DoorType)
}
public 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,
).collect { step ->
when (step) {
is CalibrationStep.Complete -> {
showSuccess("Calibration complete: ${step.lockingDirection}")
}
else -> { /* Only OE1 emits intermediate steps */ }
}
}
} catch (e: IglooWorksException.CalibrationFailedException) {
showError("Calibration failed — try again")
} catch (e: IglooWorksException.ConnectionException) {
showError("unexpected bluetooth connection issue")
} catch (e: IglooWorksException) {
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 LockManagerError.lockCalibrationFailed {
showError(message: "Calibration failed — try again")
} catch LockManagerError.deviceDisconnected {
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,
).collect { step ->
when (step) {
is CalibrationStep.Complete -> showSuccess("Calibrated: ${step.lockingDirection}")
else -> {}
}
}
} catch (e: IglooWorksException.InvalidLockingDirectionException) {
showError("Could not determine locking direction")
} catch (e: IglooWorksException.CalibrationFailedException) {
showError("Calibration failed — try again")
} catch (e: IglooWorksException) {
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 IglooworksError.invalidLockingDirection {
showError(message: "Could not determine locking direction")
} catch LockManagerError.lockCalibrationFailed {
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,
).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: IglooWorksException.CalibrationFailedException) {
showError("Calibration failed — try again")
} catch (e: IglooWorksException.ConnectionException) {
showError("unexpected bluetooth connection issue")
} catch (e: IglooWorksException) {
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 LockManagerError.lockCalibrationFailed {
showError(message: "Calibration failed — try again")
} catch LockManagerError.deviceDisconnected {
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}.
- A
deviceId prefix that matches no supported model (not IGB4, DAX, DBX or OE1) throws IglooworksError.calibrationFailed immediately, before any Bluetooth command is sent. The same case also covers a DAX/DBX calibration timeout and an unrecognised rotation or locking-direction result from the lock.