igloo
Docs / iglooworks / BLE SDK / Sync & Jobs

Sync & Jobs

Synchronize device state and execute pending jobs.


sync

Syncs the lock via Bluetooth: sets the lock's internal clock, retrieves battery level, and uploads activity logs to the server.

Signature

suspend fun sync(
    deviceId: String,
    key: String,
    timeInSeconds: Long? = null,
    accessToken: String? = null,
    operationId: Int? = null,
): SyncResult
func sync(
    _ deviceId: String,
    key: String,
    timeInSeconds time: Int? = nil,
    syncToken token: String? = nil
) async throws -> SyncResult

Parameters

Name Type Required Description
deviceId String Yes Bluetooth device name of the lock.
key String Yes Guest key with GET_TIME, SET_TIME, GET_BATTERY_LEVEL, GET_LOGS permissions.
timeInSeconds Long? No Unix timestamp (seconds) to set on the lock. If omitted, SDK uses last server time.
accessToken String? OAuth only OAuth access token for uploading activity logs.
operationId Int? No Operation ID for tracking.

Return Type

data class SyncResult(val batteryLevel: Int)
public struct SyncResult: Sendable {
    public let batteryLevel: Int
}
Field Description
batteryLevel Battery level as percentage (0–100).

Error Codes

Exception Code Description
BluetoothException 708 Bluetooth is off or unavailable.
ConnectionException 12 Device disconnected during sync.
TimeoutException 703 BLE operation exceeded the timeout.
ApiException varies Activity log upload failed.

Example

try {
    val result = sdk.sync(
        deviceId = "IGM4-XXXX",
        key = guestKey,
    )
    println("Battery: ${result.batteryLevel}%")
} catch (e: IglooWorksException.ConnectionException) {
    showError("unexpected bluetooth connection issue")
} catch (e: IglooWorksException.TimeoutException) {
    showError("Timed out, try again")
} catch (e: IglooWorksException) {
    showError("Sync failed: ${e.message}")
}
do {
    let result = try await sdk.sync(
        "IGM4-XXXX", 
        key: guestKey)

    print("Battery: \(result.batteryLevel)%")
} catch LockManagerError.deviceDisconnected {
    showError(message: "unexpected bluetooth connection issue")
} catch {
    showError(message: "Sync failed: \(error.localizedDescription)")
}

Notes

  • Sync reads all pending activity logs from the lock in a loop until no more remain.
  • Logs are stored in a local database first, then batch-uploaded to the server.
  • If timeInSeconds is omitted and no cached server time is available, the lock time is not updated.

syncJob

Executes pending jobs (created via the API or IglooWorks Dashboard) on the lock. Results are emitted as a Flow — one SyncJobResult per job.

Supported job types: CREATE_PIN, DELETE_PIN, ADD_GROUP_KEY, DELETE_GROUP_KEY.

Signature

fun syncJob(
    deviceId: String,
    key: String,
    jobIds: List<String>? = null,
    accessToken: String?,
): Flow<SyncJobResult>
func syncJobs(
    _ jobIds: [String] = [],
    from deviceId: String,
    key: String,
    syncJobToken token: String? = nil
) async -> AsyncThrowingStream<SyncJobResult, Error>

Parameters

Name Type Required Description
deviceId String Yes Bluetooth device name of the lock.
key String Yes Guest key with CREATE_PIN, DELETE_PIN, ADD_GROUP_KEY, DELETE_GROUP_KEY permissions.
jobIds List<String>? No Specific job IDs to execute. If null, all pending jobs are fetched and executed.
accessToken String? OAuth only OAuth access token for API calls.

Return Type

data class SyncJobResult(
    val jobId: String,
    val jobType: String,
    val status: JobStatus,
    val remainingJobs: Int,
    val reason: IglooWorksException? = null,
)
public struct SyncJobResult: Codable, Sendable {
    public let jobId: String
    public let jobType: String
    public let status: SyncJobStatus
    public let remainingJobs: Int
    public var reason: String? = nil
}

public enum SyncJobStatus: String, Codable, Sendable {
    case success = "success"
    case failed = "failed"
    case pending = "pending"
}
Field Description
jobId Server-assigned job identifier.
jobType Job description string (e.g. "CREATE_PIN").
status COMPLETE, FAILED, or PENDING.
remainingJobs Number of jobs remaining after this one.
reason Exception if the job failed or was left pending (e.g. timeout).

Error Codes

Exception Code Description
ApiException varies API call failed when fetching or updating job status.
GenericException 1 Unhandled error.

Example

try {
    sdk.syncJob(
        deviceId = "IGM4-XXXX",
        key = guestKey,
        accessToken = token,
    ).collect { result ->
        println("Job ${result.jobId}: ${result.status} (${result.remainingJobs} remaining)")
        if (result.status == JobStatus.FAILED) {
            println("  Reason: ${result.reason?.message}")
        }
    }
} catch (e: IglooWorksException.ApiException) {
    showError("Server error: ${e.message}")
} catch (e: IglooWorksException) {
    showError("Sync jobs failed: ${e.message}")
}
for try await result in await sdk.syncJobs(
    from: "IGM4-XXXX", 
    key: guestKey, 
    syncJobToken: token
) {
    print("Job \(result.jobId): \(result.status.rawValue) (\(result.remainingJobs) remaining)")
    if result.status == .failed {
        print("  Reason: \(result.reason ?? "unknown")")
    }
}

Notes

  • Jobs already marked COMPLETE or FAILED on the server are emitted as-is without re-execution.
  • If a BLE operation fails with TimeoutException, ConnectionException, or BluetoothException, the job is left as PENDING (not marked failed on the server).
  • DuplicatePinException during CREATE_PIN is treated as success (job marked COMPLETE).
  • PinNotFoundException during DELETE_PIN is treated as success (PIN already removed).
  • On iOS the method is named syncJobs (plural), takes job IDs as the first positional parameter (_ jobIds: [String] = [], empty means "all pending"), and reason on SyncJobResult is a plain String?, not an Error/exception type.