diff options
| author | Arslaan Pathan <[email protected]> | 2026-03-30 22:13:08 +1300 |
|---|---|---|
| committer | Arslaan Pathan <[email protected]> | 2026-03-30 22:13:08 +1300 |
| commit | e92a1f4a6babca0827410426ae59cc7b24f401da (patch) | |
| tree | a9e93b75c41876368a4f66a7e4e9665c6832388f /app/src/main/java/com/arslaancodes/zwznfreefit/BleManager.kt | |
| download | zwzn-freefit-android-e92a1f4a6babca0827410426ae59cc7b24f401da.tar.xz zwzn-freefit-android-e92a1f4a6babca0827410426ae59cc7b24f401da.zip | |
chore: Initial commit
Diffstat (limited to 'app/src/main/java/com/arslaancodes/zwznfreefit/BleManager.kt')
| -rw-r--r-- | app/src/main/java/com/arslaancodes/zwznfreefit/BleManager.kt | 121 |
1 files changed, 121 insertions, 0 deletions
diff --git a/app/src/main/java/com/arslaancodes/zwznfreefit/BleManager.kt b/app/src/main/java/com/arslaancodes/zwznfreefit/BleManager.kt new file mode 100644 index 0000000..c138ead --- /dev/null +++ b/app/src/main/java/com/arslaancodes/zwznfreefit/BleManager.kt @@ -0,0 +1,121 @@ +package com.arslaancodes.zwznfreefit + +import android.bluetooth.* +import android.bluetooth.le.* +import android.content.Context +import android.os.ParcelUuid +import java.util.UUID + +val SERVICE_UUID = UUID.fromString("6E40FC00-B5A3-F393-E0A9-E50E24DCCA9E") +val WRITE_UUID = UUID.fromString("6E40FC20-B5A3-F393-E0A9-E50E24DCCA9E") +val NOTIFY_UUID = UUID.fromString("6E40FC21-B5A3-F393-E0A9-E50E24DCCA9E") + +class BleManager(private val context: Context) { + companion object { + lateinit var instance: BleManager + } + + init { + instance = this + } + + private val adapter = BluetoothAdapter.getDefaultAdapter() + private var gatt: BluetoothGatt? = null + private var onNotify: ((ByteArray) -> Unit)? = null + + fun scan(onFound: (BluetoothDevice) -> Unit) { + val scanner = adapter.bluetoothLeScanner + android.util.Log.d("BLE", "Starting scan, scanner=$scanner") + if (scanner == null) { + android.util.Log.e("BLE", "Scanner is null! Bluetooth off?") + return + } + val settings = ScanSettings.Builder().build() + scanner.startScan(null, settings, object : ScanCallback() { + override fun onScanResult(callbackType: Int, result: ScanResult) { + android.util.Log.d("BLE", "Found: ${result.device.name} ${result.device.address}") + onFound(result.device) + } + override fun onScanFailed(errorCode: Int) { + android.util.Log.e("BLE", "Scan failed: $errorCode") + } + }) + } + + fun connect(device: BluetoothDevice, onConnected: (BluetoothGatt) -> Unit) { + gatt = device.connectGatt(context, false, object : BluetoothGattCallback() { + override fun onConnectionStateChange(g: BluetoothGatt, status: Int, newState: Int) { + if (newState == BluetoothProfile.STATE_CONNECTED) { + g.discoverServices() + } + } + override fun onServicesDiscovered(g: BluetoothGatt, status: Int) { + val notifyChar = g.getService(SERVICE_UUID)?.getCharacteristic(NOTIFY_UUID) + if (notifyChar != null) { + g.setCharacteristicNotification(notifyChar, true) + val descriptor = notifyChar.getDescriptor( + UUID.fromString("00002902-0000-1000-8000-00805f9b34fb") + ) + descriptor?.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE + g.writeDescriptor(descriptor) + } + onConnected(g) + } + override fun onCharacteristicChanged(g: BluetoothGatt, characteristic: BluetoothGattCharacteristic) { + if (characteristic.uuid == NOTIFY_UUID) { + onNotify?.invoke(characteristic.value) + } + } + }) + } + + fun write(data: ByteArray) { + val char = gatt?.getService(SERVICE_UUID)?.getCharacteristic(WRITE_UUID) + char?.value = data + gatt?.writeCharacteristic(char) + } + + fun syncTime() { + val now = System.currentTimeMillis() / 1000 + val tzOffset = java.util.TimeZone.getDefault().getOffset(System.currentTimeMillis()) / 1000 + + val packet = ByteArray(12) + packet[0] = 0x01 + packet[1] = (now shr 24).toByte() + packet[2] = (now shr 16).toByte() + packet[3] = (now shr 8).toByte() + packet[4] = now.toByte() + packet[5] = (tzOffset shr 24).toByte() + packet[6] = (tzOffset shr 16).toByte() + packet[7] = (tzOffset shr 8).toByte() + packet[8] = tzOffset.toByte() + packet[9] = 0x00 + packet[10] = 0x00 // language code, 0 = english + packet[11] = 0x00 + + write(packet) + } + + fun switchFindBand(flag: Int) { + // find/vibrate band + // these functions are usually named after their freefit/ferefit counterparts + val packet = ByteArray(2) + packet[0] = 0x51 // command + packet[1] = flag.toByte() // 0x01 = start, 0x00 = stop + write(packet) + } + + fun configRealTimeMeasure(type: Int, start: Boolean, onResult: (ByteArray) -> Unit) { + // measure heart rate/other stuff + // confirmed working types so far is just 0x00 for heart rate, will RE more later + // yet again, function named after freefit counterparts from RE'd protocol/decompiled APKs + // all code in this file is intellectual property of Arslaan Pathan, no pieces of decompiled freefit code have been used anywhere in the app + // freefit decompilation was merely used as a reference for reverse-engineering the protocol + onNotify = onResult + val packet = ByteArray(3) + packet[0] = 0x60.toByte() + packet[1] = type.toByte() + packet[2] = if (start) 0x01.toByte() else 0x00.toByte() + write(packet) + } +} |
