

# Scanning

Discover nearby iglooworks smart locks via Bluetooth.

---

## scanDevice

Scans for nearby Igloo locks via Bluetooth. Emits a `ScanResult` for each discovered device. The scan runs for as long as the Flow has active collectors.

### Signature

```kotlin
fun scanDevice(): Flow<ScanResult>
```

```swift
func scansLock() -> AsyncThrowingStream<ScanResult, Error>
func stopScan()
```

### Return Type

```kotlin
data class ScanResult(
    val deviceId: String,
    val isPaired: Boolean,
    val isActive: Boolean,
    val rssi: Int,
)
```

```swift
public struct ScanResult: Identifiable, Hashable, Sendable {
    public var id: String { bluetoothDeviceName }
    public let bluetoothDeviceName: String
    public let isActive: Bool
    public let isPaired: Bool
    public let isG3: Bool
}
```

| Field | Description |
|-------|-------------|
| `deviceId` | Bluetooth device name (e.g. `"IGM401abc123"`, `"SP2X01abc123"`). |
| `isPaired` | `true` if the device is already paired with an account. |
| `isActive` | `true` if the device is actively broadcasting. |
| `rssi` | Signal strength in dBm. Higher (closer to 0) means closer. |

### Error Codes

| Exception | Code | Description |
|-----------|------|-------------|
| `BluetoothException` | 708 | Bluetooth is off or unavailable. |

### Example

```kotlin
val scanJob = viewModelScope.launch {
    try {
        sdk.scanDevice().collect { result ->
            println("Found: ${result.deviceId}")
            println("  paired=${result.isPaired}, active=${result.isActive}, rssi=${result.rssi}")
        }
    } catch (e: IglooWorksException.BluetoothException) {
        showError("Please enable Bluetooth")
    } catch (e: IglooWorksException) {
        showError("Scan failed: ${e.message}")
    }
}

// Stop scanning
scanJob.cancel()
```

```swift
let scanTask = Task {
    do {
        for try await result in sdk.scansLock() {
            print("Found: \(result.bluetoothDeviceName)")
            print("  paired=\(result.isPaired), active=\(result.isActive), type=\(result.type)")
        }
    } catch LockManagerError.bluetoothIsTurnedOff {
        showError(message: "Please enable Bluetooth")
    } catch {
        showError(message: "Scan failed: \(error.localizedDescription)")
    }
}

// Stop scanning
sdk.stopScan()
scanTask.cancel()
```

### Notes

- The scan runs indefinitely until the collecting coroutine is cancelled (Android) or `stopScan()` is called and the enclosing `Task` is cancelled (iOS).
- Ensure BLE permissions are granted before calling `scanDevice()` / `scansLock()`. On iOS this means `NSBluetoothAlwaysUsageDescription` is set in `Info.plist` — there is no runtime permission prompt to request explicitly.
- The `deviceId` / `bluetoothDeviceName` prefix indicates the lock type (e.g. `IGM4` = mortise lock, `SP2X` = smart padlock, `EB1` = bridge). On iOS this is also exposed structurally via `ScanResult.type: LockType`.
- Unpaired devices (`isPaired = false`) can be registered via `pair()`.
- Filter by `rssi` to find nearby devices — typical threshold is `-70` for "close enough to connect". Not available on iOS (no `rssi` field).
