Independent Android/Kotlin SDK to communicate via BLE with user-owned glo devices. The project does not depend on Glow, Compose, or their ViewModels.
The first interoperable implementation targets the Boreas / glo Hyper Pro+ model.
Stable UUIDs, packets, and commands are derived from static analysis of the Android SDK and an HCI capture on the physical device. Uncaptured functions are separated and marked @ExperimentalGloApi.
glo-api: Kotlin/JVM JAR with public APIs, models, UUIDs, commands, codecs, and transport abstraction. Does not importandroid.*classes.glo-ble-android: AAR with Android BLE scanner, bonding, GATT connection, MTU negotiation, service discovery, serial operation queue, CCCD, and high-level client.
Coordinates:
com.anto426.glo:glo-api:0.1.0-SNAPSHOT
com.anto426.glo:glo-ble-android:0.1.0-SNAPSHOT
In the app's settings.gradle.kts:
includeBuild("../GloSdk")In the app's Android module:
dependencies {
implementation("com.anto426.glo:glo-ble-android:0.1.0-SNAPSHOT")
}Gradle automatically replaces coordinates with the modules of the separate project. Alternatively:
.\gradlew.bat publishToMavenLocaland the app can resolve the same coordinates from mavenLocal().
The host must request at runtime the permissions returned by AndroidBlePermissions.requiredRuntimePermissions(); the SDK does not display a UI and does not request permissions autonomously.
val manager = AndroidGloSdk.create(applicationContext)
val paired = manager.refreshPairedDevices().getOrNull().orEmpty()
val deviceId = paired.firstOrNull()?.id ?: run {
manager.startScan()
manager.discoveredDevices.first { it.isNotEmpty() }.first().id
}
when (val connected = manager.connect(deviceId)) {
is GloResult.Success -> {
val session = connected.value
session.readBattery()
session.setLock(LockState.LOCKED)
session.setHeatingProfile(HeatingProfile.BOOST)
session.startFind(durationSeconds = 60)
}
is GloResult.Failure -> handleError(connected.error)
}Reactive state is available via StateFlow:
GloDeviceManager.scanStateGloDeviceManager.discoveredDevicesGloDeviceManager.pairedDevicesGloDeviceManager.activeSessionGloDeviceSession.connectionStateGloDeviceSession.snapshotGloDeviceSession.events
The Android client executes in order:
- LE connection;
- creation or reuse of the bond;
- MTU 517 request and storage of the negotiated value;
- service discovery for UUIDs;
- write
01 00to the CCCDs of the status characteristics; - read and synchronization of the big-endian
uint32time; - initial read of device info, battery, lock, session status, FindGlo, LED, and profile.
Every GATT operation is serialized. For commands confirmed by notifications, the listener is registered before the write, and then both the write acknowledgement and the semantic notification are awaited.
- Ordinary commands use bonding and BLE link-layer encryption; no nonce, token, MAC, or additional application-layer encryption appeared in the captures.
- HCI captures and bonding keys are not included in this repository.
- Reset, Payload/Greetings upload, age verification, and OTA are not part of the stable path: complete dynamic captures are still missing.
- Observed GATT handles are not hardcoded: services and characteristics are always resolved via UUIDs.
- The list of paired devices only exposes names recognized as Boreas / Hyper Pro+; other Bluetooth bonds are excluded and unverified profiles cannot be connected.
Details: docs/PROTOCOL_SUPPORT.md.
.\gradlew.bat :glo-api:test :glo-ble-android:testDebugUnitTest :glo-ble-android:assembleRelease