@capacitor-community/bluetooth-le 8.0.0-0 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CapacitorCommunityBluetoothLe.podspec +17 -17
- package/LICENSE +21 -21
- package/Package.swift +27 -27
- package/README.md +2 -1
- package/android/build.gradle +73 -71
- package/android/src/main/AndroidManifest.xml +22 -22
- package/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/BluetoothLe.kt +1094 -1094
- package/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/Conversion.kt +51 -51
- package/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/Device.kt +771 -771
- package/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/DeviceList.kt +28 -28
- package/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/DeviceScanner.kt +189 -189
- package/ios/Sources/BluetoothLe/Conversion.swift +83 -83
- package/ios/Sources/BluetoothLe/Device.swift +423 -423
- package/ios/Sources/BluetoothLe/DeviceListView.swift +121 -121
- package/ios/Sources/BluetoothLe/DeviceManager.swift +503 -503
- package/ios/Sources/BluetoothLe/Logging.swift +8 -8
- package/ios/Sources/BluetoothLe/Plugin.swift +775 -775
- package/ios/Sources/BluetoothLe/ThreadSafeDictionary.swift +15 -15
- package/ios/Tests/BluetoothLeTests/ConversionTests.swift +55 -55
- package/ios/Tests/BluetoothLeTests/PluginTests.swift +27 -27
- package/package.json +115 -115
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
package com.capacitorjs.community.plugins.bluetoothle
|
|
2
|
-
|
|
3
|
-
import android.bluetooth.BluetoothDevice
|
|
4
|
-
|
|
5
|
-
class DeviceList {
|
|
6
|
-
private val devices: ArrayList<BluetoothDevice> = ArrayList()
|
|
7
|
-
|
|
8
|
-
fun addDevice(device: BluetoothDevice): Boolean {
|
|
9
|
-
// contains compares devices by their address
|
|
10
|
-
if (!devices.contains(device)) {
|
|
11
|
-
devices.add(device)
|
|
12
|
-
return true
|
|
13
|
-
}
|
|
14
|
-
return false
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
fun getDevice(index: Int): BluetoothDevice {
|
|
18
|
-
return devices[index]
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
fun getCount(): Int {
|
|
22
|
-
return devices.size
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
fun clear() {
|
|
26
|
-
devices.clear()
|
|
27
|
-
}
|
|
28
|
-
}
|
|
1
|
+
package com.capacitorjs.community.plugins.bluetoothle
|
|
2
|
+
|
|
3
|
+
import android.bluetooth.BluetoothDevice
|
|
4
|
+
|
|
5
|
+
class DeviceList {
|
|
6
|
+
private val devices: ArrayList<BluetoothDevice> = ArrayList()
|
|
7
|
+
|
|
8
|
+
fun addDevice(device: BluetoothDevice): Boolean {
|
|
9
|
+
// contains compares devices by their address
|
|
10
|
+
if (!devices.contains(device)) {
|
|
11
|
+
devices.add(device)
|
|
12
|
+
return true
|
|
13
|
+
}
|
|
14
|
+
return false
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
fun getDevice(index: Int): BluetoothDevice {
|
|
18
|
+
return devices[index]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
fun getCount(): Int {
|
|
22
|
+
return devices.size
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
fun clear() {
|
|
26
|
+
devices.clear()
|
|
27
|
+
}
|
|
28
|
+
}
|
package/android/src/main/java/com/capacitorjs/community/plugins/bluetoothle/DeviceScanner.kt
CHANGED
|
@@ -1,190 +1,190 @@
|
|
|
1
|
-
package com.capacitorjs.community.plugins.bluetoothle
|
|
2
|
-
|
|
3
|
-
import android.annotation.SuppressLint
|
|
4
|
-
import android.app.AlertDialog
|
|
5
|
-
import android.bluetooth.BluetoothAdapter
|
|
6
|
-
import android.bluetooth.BluetoothDevice
|
|
7
|
-
import android.bluetooth.le.ScanCallback
|
|
8
|
-
import android.bluetooth.le.ScanFilter
|
|
9
|
-
import android.bluetooth.le.ScanResult
|
|
10
|
-
import android.bluetooth.le.ScanSettings
|
|
11
|
-
import android.content.Context
|
|
12
|
-
import android.os.Handler
|
|
13
|
-
import android.os.Looper
|
|
14
|
-
import android.widget.ArrayAdapter
|
|
15
|
-
import com.getcapacitor.Logger
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class ScanResponse(
|
|
19
|
-
val success: Boolean,
|
|
20
|
-
val message: String?,
|
|
21
|
-
val device: BluetoothDevice?,
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
class DisplayStrings(
|
|
25
|
-
val scanning: String,
|
|
26
|
-
val cancel: String,
|
|
27
|
-
val availableDevices: String,
|
|
28
|
-
val noDeviceFound: String,
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
@SuppressLint("MissingPermission")
|
|
32
|
-
class DeviceScanner(
|
|
33
|
-
private val context: Context,
|
|
34
|
-
bluetoothAdapter: BluetoothAdapter,
|
|
35
|
-
private val scanDuration: Long?,
|
|
36
|
-
private val displayStrings: DisplayStrings,
|
|
37
|
-
private val showDialog: Boolean,
|
|
38
|
-
) {
|
|
39
|
-
companion object {
|
|
40
|
-
private val TAG = DeviceScanner::class.java.simpleName
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
private var isScanning = false
|
|
44
|
-
private val bluetoothLeScanner = bluetoothAdapter.bluetoothLeScanner
|
|
45
|
-
private var savedCallback: ((ScanResponse) -> Unit)? = null
|
|
46
|
-
private var scanResultCallback: ((ScanResult) -> Unit)? = null
|
|
47
|
-
private var adapter: ArrayAdapter<String>? = null
|
|
48
|
-
private val deviceList = DeviceList()
|
|
49
|
-
private var deviceStrings: ArrayList<String> = ArrayList()
|
|
50
|
-
private var dialog: AlertDialog? = null
|
|
51
|
-
private var dialogHandler: Handler? = null
|
|
52
|
-
private var stopScanHandler: Handler? = null
|
|
53
|
-
private var allowDuplicates: Boolean = false
|
|
54
|
-
private var namePrefix: String = ""
|
|
55
|
-
|
|
56
|
-
private val scanCallback: ScanCallback = object : ScanCallback() {
|
|
57
|
-
override fun onScanResult(callbackType: Int, result: ScanResult) {
|
|
58
|
-
super.onScanResult(callbackType, result)
|
|
59
|
-
if (namePrefix.isNotEmpty()) {
|
|
60
|
-
if (result.device.name == null || !result.device.name.startsWith(namePrefix)) {
|
|
61
|
-
return
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
val isNew = deviceList.addDevice(result.device)
|
|
65
|
-
if (showDialog) {
|
|
66
|
-
if (isNew) {
|
|
67
|
-
dialogHandler?.post {
|
|
68
|
-
deviceStrings.add("[${result.device.address}] ${result.device.name ?: "Unknown"}")
|
|
69
|
-
adapter?.notifyDataSetChanged()
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
} else {
|
|
73
|
-
if (allowDuplicates || isNew) {
|
|
74
|
-
scanResultCallback?.invoke(result)
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
fun startScanning(
|
|
81
|
-
scanFilters: List<ScanFilter>,
|
|
82
|
-
scanSettings: ScanSettings,
|
|
83
|
-
allowDuplicates: Boolean,
|
|
84
|
-
namePrefix: String,
|
|
85
|
-
callback: (ScanResponse) -> Unit,
|
|
86
|
-
scanResultCallback: ((ScanResult) -> Unit)?
|
|
87
|
-
) {
|
|
88
|
-
this.savedCallback = callback
|
|
89
|
-
this.scanResultCallback = scanResultCallback
|
|
90
|
-
this.allowDuplicates = allowDuplicates
|
|
91
|
-
this.namePrefix = namePrefix
|
|
92
|
-
|
|
93
|
-
deviceStrings.clear()
|
|
94
|
-
deviceList.clear()
|
|
95
|
-
if (!isScanning) {
|
|
96
|
-
setTimeoutForStopScanning()
|
|
97
|
-
Logger.debug(TAG, "Start scanning.")
|
|
98
|
-
isScanning = true
|
|
99
|
-
bluetoothLeScanner?.startScan(scanFilters, scanSettings, scanCallback)
|
|
100
|
-
if (showDialog) {
|
|
101
|
-
dialogHandler = Handler(Looper.getMainLooper())
|
|
102
|
-
showDeviceList()
|
|
103
|
-
} else {
|
|
104
|
-
savedCallback?.invoke(
|
|
105
|
-
ScanResponse(
|
|
106
|
-
true, "Started scanning.", null
|
|
107
|
-
)
|
|
108
|
-
)
|
|
109
|
-
savedCallback = null
|
|
110
|
-
}
|
|
111
|
-
} else {
|
|
112
|
-
stopScanning()
|
|
113
|
-
savedCallback?.invoke(
|
|
114
|
-
ScanResponse(
|
|
115
|
-
false, "Already scanning. Stopping now.", null
|
|
116
|
-
)
|
|
117
|
-
)
|
|
118
|
-
savedCallback = null
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
fun stopScanning() {
|
|
123
|
-
stopScanHandler?.removeCallbacksAndMessages(null)
|
|
124
|
-
stopScanHandler = null
|
|
125
|
-
if (showDialog) {
|
|
126
|
-
dialogHandler?.post {
|
|
127
|
-
if (deviceList.getCount() == 0) {
|
|
128
|
-
dialog?.setTitle(displayStrings.noDeviceFound)
|
|
129
|
-
} else {
|
|
130
|
-
dialog?.setTitle(displayStrings.availableDevices)
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
Logger.debug(TAG, "Stop scanning.")
|
|
135
|
-
isScanning = false
|
|
136
|
-
bluetoothLeScanner?.stopScan(scanCallback)
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
private fun showDeviceList() {
|
|
140
|
-
dialogHandler?.post {
|
|
141
|
-
val builder = AlertDialog.Builder(context)
|
|
142
|
-
builder.setTitle(displayStrings.scanning)
|
|
143
|
-
builder.setCancelable(true)
|
|
144
|
-
adapter = ArrayAdapter(
|
|
145
|
-
context, android.R.layout.simple_selectable_list_item, deviceStrings
|
|
146
|
-
)
|
|
147
|
-
builder.setAdapter(adapter) { dialog, index ->
|
|
148
|
-
stopScanning()
|
|
149
|
-
dialog.dismiss()
|
|
150
|
-
val device = deviceList.getDevice(index)
|
|
151
|
-
savedCallback?.invoke(ScanResponse(true, device.address, device))
|
|
152
|
-
savedCallback = null
|
|
153
|
-
}
|
|
154
|
-
builder.setNegativeButton(displayStrings.cancel) { dialog, _ ->
|
|
155
|
-
stopScanning()
|
|
156
|
-
dialog.cancel()
|
|
157
|
-
savedCallback?.invoke(
|
|
158
|
-
ScanResponse(
|
|
159
|
-
false, "requestDevice cancelled.", null
|
|
160
|
-
)
|
|
161
|
-
)
|
|
162
|
-
savedCallback = null
|
|
163
|
-
}
|
|
164
|
-
builder.setOnCancelListener { dialog ->
|
|
165
|
-
stopScanning()
|
|
166
|
-
dialog.cancel()
|
|
167
|
-
savedCallback?.invoke(
|
|
168
|
-
ScanResponse(
|
|
169
|
-
false, "requestDevice cancelled.", null
|
|
170
|
-
)
|
|
171
|
-
)
|
|
172
|
-
savedCallback = null
|
|
173
|
-
}
|
|
174
|
-
dialog = builder.create()
|
|
175
|
-
dialog?.show()
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
private fun setTimeoutForStopScanning() {
|
|
180
|
-
if (scanDuration != null) {
|
|
181
|
-
stopScanHandler = Handler(Looper.getMainLooper())
|
|
182
|
-
stopScanHandler?.postDelayed(
|
|
183
|
-
{
|
|
184
|
-
stopScanning()
|
|
185
|
-
}, scanDuration
|
|
186
|
-
)
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
1
|
+
package com.capacitorjs.community.plugins.bluetoothle
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint
|
|
4
|
+
import android.app.AlertDialog
|
|
5
|
+
import android.bluetooth.BluetoothAdapter
|
|
6
|
+
import android.bluetooth.BluetoothDevice
|
|
7
|
+
import android.bluetooth.le.ScanCallback
|
|
8
|
+
import android.bluetooth.le.ScanFilter
|
|
9
|
+
import android.bluetooth.le.ScanResult
|
|
10
|
+
import android.bluetooth.le.ScanSettings
|
|
11
|
+
import android.content.Context
|
|
12
|
+
import android.os.Handler
|
|
13
|
+
import android.os.Looper
|
|
14
|
+
import android.widget.ArrayAdapter
|
|
15
|
+
import com.getcapacitor.Logger
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ScanResponse(
|
|
19
|
+
val success: Boolean,
|
|
20
|
+
val message: String?,
|
|
21
|
+
val device: BluetoothDevice?,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
class DisplayStrings(
|
|
25
|
+
val scanning: String,
|
|
26
|
+
val cancel: String,
|
|
27
|
+
val availableDevices: String,
|
|
28
|
+
val noDeviceFound: String,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
@SuppressLint("MissingPermission")
|
|
32
|
+
class DeviceScanner(
|
|
33
|
+
private val context: Context,
|
|
34
|
+
bluetoothAdapter: BluetoothAdapter,
|
|
35
|
+
private val scanDuration: Long?,
|
|
36
|
+
private val displayStrings: DisplayStrings,
|
|
37
|
+
private val showDialog: Boolean,
|
|
38
|
+
) {
|
|
39
|
+
companion object {
|
|
40
|
+
private val TAG = DeviceScanner::class.java.simpleName
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private var isScanning = false
|
|
44
|
+
private val bluetoothLeScanner = bluetoothAdapter.bluetoothLeScanner
|
|
45
|
+
private var savedCallback: ((ScanResponse) -> Unit)? = null
|
|
46
|
+
private var scanResultCallback: ((ScanResult) -> Unit)? = null
|
|
47
|
+
private var adapter: ArrayAdapter<String>? = null
|
|
48
|
+
private val deviceList = DeviceList()
|
|
49
|
+
private var deviceStrings: ArrayList<String> = ArrayList()
|
|
50
|
+
private var dialog: AlertDialog? = null
|
|
51
|
+
private var dialogHandler: Handler? = null
|
|
52
|
+
private var stopScanHandler: Handler? = null
|
|
53
|
+
private var allowDuplicates: Boolean = false
|
|
54
|
+
private var namePrefix: String = ""
|
|
55
|
+
|
|
56
|
+
private val scanCallback: ScanCallback = object : ScanCallback() {
|
|
57
|
+
override fun onScanResult(callbackType: Int, result: ScanResult) {
|
|
58
|
+
super.onScanResult(callbackType, result)
|
|
59
|
+
if (namePrefix.isNotEmpty()) {
|
|
60
|
+
if (result.device.name == null || !result.device.name.startsWith(namePrefix)) {
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
val isNew = deviceList.addDevice(result.device)
|
|
65
|
+
if (showDialog) {
|
|
66
|
+
if (isNew) {
|
|
67
|
+
dialogHandler?.post {
|
|
68
|
+
deviceStrings.add("[${result.device.address}] ${result.device.name ?: "Unknown"}")
|
|
69
|
+
adapter?.notifyDataSetChanged()
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
if (allowDuplicates || isNew) {
|
|
74
|
+
scanResultCallback?.invoke(result)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fun startScanning(
|
|
81
|
+
scanFilters: List<ScanFilter>,
|
|
82
|
+
scanSettings: ScanSettings,
|
|
83
|
+
allowDuplicates: Boolean,
|
|
84
|
+
namePrefix: String,
|
|
85
|
+
callback: (ScanResponse) -> Unit,
|
|
86
|
+
scanResultCallback: ((ScanResult) -> Unit)?
|
|
87
|
+
) {
|
|
88
|
+
this.savedCallback = callback
|
|
89
|
+
this.scanResultCallback = scanResultCallback
|
|
90
|
+
this.allowDuplicates = allowDuplicates
|
|
91
|
+
this.namePrefix = namePrefix
|
|
92
|
+
|
|
93
|
+
deviceStrings.clear()
|
|
94
|
+
deviceList.clear()
|
|
95
|
+
if (!isScanning) {
|
|
96
|
+
setTimeoutForStopScanning()
|
|
97
|
+
Logger.debug(TAG, "Start scanning.")
|
|
98
|
+
isScanning = true
|
|
99
|
+
bluetoothLeScanner?.startScan(scanFilters, scanSettings, scanCallback)
|
|
100
|
+
if (showDialog) {
|
|
101
|
+
dialogHandler = Handler(Looper.getMainLooper())
|
|
102
|
+
showDeviceList()
|
|
103
|
+
} else {
|
|
104
|
+
savedCallback?.invoke(
|
|
105
|
+
ScanResponse(
|
|
106
|
+
true, "Started scanning.", null
|
|
107
|
+
)
|
|
108
|
+
)
|
|
109
|
+
savedCallback = null
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
stopScanning()
|
|
113
|
+
savedCallback?.invoke(
|
|
114
|
+
ScanResponse(
|
|
115
|
+
false, "Already scanning. Stopping now.", null
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
savedCallback = null
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
fun stopScanning() {
|
|
123
|
+
stopScanHandler?.removeCallbacksAndMessages(null)
|
|
124
|
+
stopScanHandler = null
|
|
125
|
+
if (showDialog) {
|
|
126
|
+
dialogHandler?.post {
|
|
127
|
+
if (deviceList.getCount() == 0) {
|
|
128
|
+
dialog?.setTitle(displayStrings.noDeviceFound)
|
|
129
|
+
} else {
|
|
130
|
+
dialog?.setTitle(displayStrings.availableDevices)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
Logger.debug(TAG, "Stop scanning.")
|
|
135
|
+
isScanning = false
|
|
136
|
+
bluetoothLeScanner?.stopScan(scanCallback)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private fun showDeviceList() {
|
|
140
|
+
dialogHandler?.post {
|
|
141
|
+
val builder = AlertDialog.Builder(context)
|
|
142
|
+
builder.setTitle(displayStrings.scanning)
|
|
143
|
+
builder.setCancelable(true)
|
|
144
|
+
adapter = ArrayAdapter(
|
|
145
|
+
context, android.R.layout.simple_selectable_list_item, deviceStrings
|
|
146
|
+
)
|
|
147
|
+
builder.setAdapter(adapter) { dialog, index ->
|
|
148
|
+
stopScanning()
|
|
149
|
+
dialog.dismiss()
|
|
150
|
+
val device = deviceList.getDevice(index)
|
|
151
|
+
savedCallback?.invoke(ScanResponse(true, device.address, device))
|
|
152
|
+
savedCallback = null
|
|
153
|
+
}
|
|
154
|
+
builder.setNegativeButton(displayStrings.cancel) { dialog, _ ->
|
|
155
|
+
stopScanning()
|
|
156
|
+
dialog.cancel()
|
|
157
|
+
savedCallback?.invoke(
|
|
158
|
+
ScanResponse(
|
|
159
|
+
false, "requestDevice cancelled.", null
|
|
160
|
+
)
|
|
161
|
+
)
|
|
162
|
+
savedCallback = null
|
|
163
|
+
}
|
|
164
|
+
builder.setOnCancelListener { dialog ->
|
|
165
|
+
stopScanning()
|
|
166
|
+
dialog.cancel()
|
|
167
|
+
savedCallback?.invoke(
|
|
168
|
+
ScanResponse(
|
|
169
|
+
false, "requestDevice cancelled.", null
|
|
170
|
+
)
|
|
171
|
+
)
|
|
172
|
+
savedCallback = null
|
|
173
|
+
}
|
|
174
|
+
dialog = builder.create()
|
|
175
|
+
dialog?.show()
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private fun setTimeoutForStopScanning() {
|
|
180
|
+
if (scanDuration != null) {
|
|
181
|
+
stopScanHandler = Handler(Looper.getMainLooper())
|
|
182
|
+
stopScanHandler?.postDelayed(
|
|
183
|
+
{
|
|
184
|
+
stopScanning()
|
|
185
|
+
}, scanDuration
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
190
|
}
|
|
@@ -1,83 +1,83 @@
|
|
|
1
|
-
import Foundation
|
|
2
|
-
import CoreBluetooth
|
|
3
|
-
|
|
4
|
-
func descriptorValueToString(_ value: Any) -> String {
|
|
5
|
-
// https://developer.apple.com/documentation/corebluetooth/cbdescriptor
|
|
6
|
-
if let str = value as? String {
|
|
7
|
-
return dataToString(Data(str.utf8))
|
|
8
|
-
}
|
|
9
|
-
if let data = value as? Data {
|
|
10
|
-
return dataToString(data)
|
|
11
|
-
}
|
|
12
|
-
if let num = value as? UInt16 {
|
|
13
|
-
return dataToString(Data([UInt8(truncatingIfNeeded: num), UInt8(truncatingIfNeeded: num >> 8)]))
|
|
14
|
-
}
|
|
15
|
-
return ""
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
extension Data {
|
|
19
|
-
func toHexString() -> String {
|
|
20
|
-
let hexChars = Array("0123456789abcdef".utf8)
|
|
21
|
-
if #available(iOS 14, *) {
|
|
22
|
-
return String(unsafeUninitializedCapacity: self.count*2) { (ptr) -> Int in
|
|
23
|
-
var strp = ptr.baseAddress!
|
|
24
|
-
for byte in self {
|
|
25
|
-
strp[0] = hexChars[Int(byte >> 4)]
|
|
26
|
-
strp[1] = hexChars[Int(byte & 0xF)]
|
|
27
|
-
strp += 2
|
|
28
|
-
}
|
|
29
|
-
return 2 * self.count
|
|
30
|
-
}
|
|
31
|
-
} else {
|
|
32
|
-
// Fallback implementation for iOS < 14, a bit slower
|
|
33
|
-
var result = ""
|
|
34
|
-
result.reserveCapacity(self.count * 2)
|
|
35
|
-
for byte in self {
|
|
36
|
-
let high = Int(byte >> 4)
|
|
37
|
-
let low = Int(byte & 0xF)
|
|
38
|
-
result.append(Character(UnicodeScalar(hexChars[high])))
|
|
39
|
-
result.append(Character(UnicodeScalar(hexChars[low])))
|
|
40
|
-
}
|
|
41
|
-
return result
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
func dataToString(_ data: Data) -> String {
|
|
47
|
-
return data.toHexString()
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
func stringToData(_ dataString: String) -> Data {
|
|
51
|
-
guard dataString.count % 2 == 0 else {
|
|
52
|
-
fatalError("Input string must have an even length, not \(dataString.count)")
|
|
53
|
-
}
|
|
54
|
-
var data = Data(capacity: dataString.count / 2)
|
|
55
|
-
for i in stride(from: 0, to: dataString.count, by: 2) {
|
|
56
|
-
let start = dataString.index(dataString.startIndex, offsetBy: i)
|
|
57
|
-
let end = dataString.index(start, offsetBy: 2)
|
|
58
|
-
let hexPair = dataString[start..<end]
|
|
59
|
-
if let byte = UInt8(hexPair, radix: 16) {
|
|
60
|
-
data.append(byte)
|
|
61
|
-
} else {
|
|
62
|
-
fatalError("Invalid hexadecimal value: \(hexPair)")
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return data
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
func cbuuidToString(_ uuid: CBUUID) -> String {
|
|
69
|
-
// declare as optional because of https://github.com/capacitor-community/bluetooth-le/issues/170
|
|
70
|
-
let uuidString: String? = uuid.uuidString
|
|
71
|
-
var str = uuidString!.lowercased()
|
|
72
|
-
if str.count == 4 {
|
|
73
|
-
str = "0000\(str)-0000-1000-8000-00805f9b34fb"
|
|
74
|
-
} else if str.count == 8 {
|
|
75
|
-
str = "\(str)-0000-1000-8000-00805f9b34fb"
|
|
76
|
-
}
|
|
77
|
-
return str
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
func cbuuidToStringUppercase(_ uuid: CBUUID) -> String {
|
|
81
|
-
let str = cbuuidToString(uuid)
|
|
82
|
-
return str.uppercased()
|
|
83
|
-
}
|
|
1
|
+
import Foundation
|
|
2
|
+
import CoreBluetooth
|
|
3
|
+
|
|
4
|
+
func descriptorValueToString(_ value: Any) -> String {
|
|
5
|
+
// https://developer.apple.com/documentation/corebluetooth/cbdescriptor
|
|
6
|
+
if let str = value as? String {
|
|
7
|
+
return dataToString(Data(str.utf8))
|
|
8
|
+
}
|
|
9
|
+
if let data = value as? Data {
|
|
10
|
+
return dataToString(data)
|
|
11
|
+
}
|
|
12
|
+
if let num = value as? UInt16 {
|
|
13
|
+
return dataToString(Data([UInt8(truncatingIfNeeded: num), UInt8(truncatingIfNeeded: num >> 8)]))
|
|
14
|
+
}
|
|
15
|
+
return ""
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
extension Data {
|
|
19
|
+
func toHexString() -> String {
|
|
20
|
+
let hexChars = Array("0123456789abcdef".utf8)
|
|
21
|
+
if #available(iOS 14, *) {
|
|
22
|
+
return String(unsafeUninitializedCapacity: self.count*2) { (ptr) -> Int in
|
|
23
|
+
var strp = ptr.baseAddress!
|
|
24
|
+
for byte in self {
|
|
25
|
+
strp[0] = hexChars[Int(byte >> 4)]
|
|
26
|
+
strp[1] = hexChars[Int(byte & 0xF)]
|
|
27
|
+
strp += 2
|
|
28
|
+
}
|
|
29
|
+
return 2 * self.count
|
|
30
|
+
}
|
|
31
|
+
} else {
|
|
32
|
+
// Fallback implementation for iOS < 14, a bit slower
|
|
33
|
+
var result = ""
|
|
34
|
+
result.reserveCapacity(self.count * 2)
|
|
35
|
+
for byte in self {
|
|
36
|
+
let high = Int(byte >> 4)
|
|
37
|
+
let low = Int(byte & 0xF)
|
|
38
|
+
result.append(Character(UnicodeScalar(hexChars[high])))
|
|
39
|
+
result.append(Character(UnicodeScalar(hexChars[low])))
|
|
40
|
+
}
|
|
41
|
+
return result
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
func dataToString(_ data: Data) -> String {
|
|
47
|
+
return data.toHexString()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
func stringToData(_ dataString: String) -> Data {
|
|
51
|
+
guard dataString.count % 2 == 0 else {
|
|
52
|
+
fatalError("Input string must have an even length, not \(dataString.count)")
|
|
53
|
+
}
|
|
54
|
+
var data = Data(capacity: dataString.count / 2)
|
|
55
|
+
for i in stride(from: 0, to: dataString.count, by: 2) {
|
|
56
|
+
let start = dataString.index(dataString.startIndex, offsetBy: i)
|
|
57
|
+
let end = dataString.index(start, offsetBy: 2)
|
|
58
|
+
let hexPair = dataString[start..<end]
|
|
59
|
+
if let byte = UInt8(hexPair, radix: 16) {
|
|
60
|
+
data.append(byte)
|
|
61
|
+
} else {
|
|
62
|
+
fatalError("Invalid hexadecimal value: \(hexPair)")
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return data
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
func cbuuidToString(_ uuid: CBUUID) -> String {
|
|
69
|
+
// declare as optional because of https://github.com/capacitor-community/bluetooth-le/issues/170
|
|
70
|
+
let uuidString: String? = uuid.uuidString
|
|
71
|
+
var str = uuidString!.lowercased()
|
|
72
|
+
if str.count == 4 {
|
|
73
|
+
str = "0000\(str)-0000-1000-8000-00805f9b34fb"
|
|
74
|
+
} else if str.count == 8 {
|
|
75
|
+
str = "\(str)-0000-1000-8000-00805f9b34fb"
|
|
76
|
+
}
|
|
77
|
+
return str
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
func cbuuidToStringUppercase(_ uuid: CBUUID) -> String {
|
|
81
|
+
let str = cbuuidToString(uuid)
|
|
82
|
+
return str.uppercased()
|
|
83
|
+
}
|