1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
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)
}
}
|