@tempivo/sensor-beacon 0.3.3 → 0.4.3
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/README.md +4 -4
- package/android/build.gradle +2 -2
- package/android/src/main/AndroidManifest.xml +2 -1
- package/android/src/main/java/expo/modules/tempivosensorbeacon/SensorBeaconNative.java +1 -3
- package/android/src/main/java/expo/modules/tempivosensorbeacon/TempivoSensorBeaconModule.kt +11 -2
- package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/SensorBleRuntime.kt +27 -84
- package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/SensorSdkScanTelemetry.kt +138 -0
- package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorBeaconDecoder.kt +53 -0
- package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorBeaconScanner.kt +161 -74
- package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorQr.kt +7 -8
- package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorSession.kt +2 -10
- package/dist/decoder.d.ts +0 -2
- package/dist/decoder.js +0 -17
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/native-module.js +5 -1
- package/dist/qr.d.ts +3 -2
- package/dist/qr.js +8 -6
- package/dist/react-native.d.ts +24 -1
- package/dist/react-native.js +22 -1
- package/dist/session-types.d.ts +4 -2
- package/dist/tempivo-sensor-beacon.aar +0 -0
- package/ios/Frameworks/TempivoSensorBridge.xcframework/ios-arm64/TempivoSensorBridge.framework/TempivoSensorBridge +0 -0
- package/ios/Sources/TempivoSensorBeacon/TempivoSensorQr.swift +10 -7
- package/ios/Sources/TempivoSensorBeacon/TempivoSensorSession.swift +5 -3
- package/ios/Sources/TempivoSensorBeacon/TempivoSensorSessionTypes.swift +0 -1
- package/ios/TempivoSensorBeaconModule.swift +137 -14
- package/package.json +1 -1
package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorBeaconScanner.kt
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package com.tempivo.sensor.beacon
|
|
2
2
|
|
|
3
3
|
import android.Manifest
|
|
4
|
+
import android.app.Application
|
|
4
5
|
import android.bluetooth.BluetoothManager
|
|
5
6
|
import android.bluetooth.le.ScanCallback
|
|
6
7
|
import android.bluetooth.le.ScanResult
|
|
@@ -11,12 +12,23 @@ import android.os.Build
|
|
|
11
12
|
import androidx.core.content.ContextCompat
|
|
12
13
|
import java.util.Locale
|
|
13
14
|
import java.util.concurrent.ConcurrentHashMap
|
|
15
|
+
import kotlinx.coroutines.CoroutineScope
|
|
16
|
+
import kotlinx.coroutines.Dispatchers
|
|
17
|
+
import kotlinx.coroutines.Job
|
|
18
|
+
import kotlinx.coroutines.SupervisorJob
|
|
19
|
+
import kotlinx.coroutines.cancel
|
|
20
|
+
import kotlinx.coroutines.delay
|
|
21
|
+
import kotlinx.coroutines.flow.catch
|
|
22
|
+
import kotlinx.coroutines.isActive
|
|
23
|
+
import kotlinx.coroutines.launch
|
|
24
|
+
import kotlinx.coroutines.withContext
|
|
25
|
+
import pl.efento.mobile.bluetooth.EfentoBluetooth
|
|
26
|
+
import pl.efento.mobile.bluetooth.model.Device as EfentoDevice
|
|
27
|
+
import pl.efento.mobile.bluetooth.model.Sensor
|
|
14
28
|
|
|
15
29
|
/**
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* The host app must declare and grant runtime BLE permissions (`BLUETOOTH_SCAN` on API 31+,
|
|
19
|
-
* or `ACCESS_FINE_LOCATION` on older APIs).
|
|
30
|
+
* Tempivo sensor discovery — same strategy as the Tempivo Capacitor app:
|
|
31
|
+
* alternate Efento SDK `deviceFlow()` with manufacturer `0x026C` adv-only windows.
|
|
20
32
|
*/
|
|
21
33
|
class TempivoSensorBeaconScanner(
|
|
22
34
|
context: Context,
|
|
@@ -35,31 +47,40 @@ class TempivoSensorBeaconScanner(
|
|
|
35
47
|
val encryptionEnabled: Boolean?,
|
|
36
48
|
)
|
|
37
49
|
|
|
38
|
-
|
|
50
|
+
companion object {
|
|
51
|
+
private const val SDK_SCAN_WINDOW_MS = 12_000L
|
|
52
|
+
private const val ADV_SCAN_WINDOW_MS = 5_000L
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private val application = context.applicationContext as Application
|
|
39
56
|
private val advLast03 = ConcurrentHashMap<String, ByteArray>()
|
|
40
57
|
private val advLast04 = ConcurrentHashMap<String, ByteArray>()
|
|
41
58
|
private val lastSeen = ConcurrentHashMap<String, SeenDevice>()
|
|
59
|
+
private val lastSensorBySerial = ConcurrentHashMap<String, Sensor>()
|
|
42
60
|
private var listener: Listener? = null
|
|
43
61
|
private var scanning = false
|
|
62
|
+
private var advOnlyWindowActive = false
|
|
63
|
+
private val scanScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
|
64
|
+
private var scanJob: Job? = null
|
|
44
65
|
|
|
45
66
|
private val leScanCallback =
|
|
46
67
|
object : ScanCallback() {
|
|
47
68
|
override fun onScanResult(callbackType: Int, result: ScanResult) {
|
|
48
|
-
|
|
69
|
+
handleAdvScanResult(result)
|
|
49
70
|
}
|
|
50
71
|
|
|
51
72
|
override fun onBatchScanResults(results: MutableList<ScanResult>) {
|
|
52
73
|
for (result in results) {
|
|
53
|
-
|
|
74
|
+
handleAdvScanResult(result)
|
|
54
75
|
}
|
|
55
76
|
}
|
|
56
77
|
}
|
|
57
78
|
|
|
58
|
-
/** Clears cached `0x03` / `0x04` frames. */
|
|
59
79
|
fun clearCache() {
|
|
60
80
|
advLast03.clear()
|
|
61
81
|
advLast04.clear()
|
|
62
82
|
lastSeen.clear()
|
|
83
|
+
lastSensorBySerial.clear()
|
|
63
84
|
}
|
|
64
85
|
|
|
65
86
|
fun lookup(serial: String): SeenDevice? {
|
|
@@ -68,75 +89,114 @@ class TempivoSensorBeaconScanner(
|
|
|
68
89
|
}
|
|
69
90
|
|
|
70
91
|
fun startScan(listener: Listener) {
|
|
71
|
-
if (scanning)
|
|
72
|
-
stopScan()
|
|
73
|
-
}
|
|
92
|
+
if (scanning) stopScan()
|
|
74
93
|
if (!hasBleScanPermission()) {
|
|
75
94
|
throw ScanNotPermittedException("BLE scan permission not granted")
|
|
76
95
|
}
|
|
77
|
-
|
|
78
|
-
?: throw IllegalStateException("Bluetooth LE scanner unavailable")
|
|
96
|
+
SensorBleRuntime.initialize(application)
|
|
79
97
|
this.listener = listener
|
|
80
|
-
val settings =
|
|
81
|
-
ScanSettings.Builder()
|
|
82
|
-
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
|
|
83
|
-
// Active scan: needed for scan-response frame (`0x04`) with measurements.
|
|
84
|
-
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
|
|
85
|
-
.build()
|
|
86
|
-
scanner.startScan(null, settings, leScanCallback)
|
|
87
98
|
scanning = true
|
|
99
|
+
advOnlyWindowActive = false
|
|
100
|
+
clearCache()
|
|
101
|
+
stopParallelAdvScan()
|
|
102
|
+
scanJob =
|
|
103
|
+
scanScope.launch {
|
|
104
|
+
val sdkScanner = EfentoBluetooth.scanner()
|
|
105
|
+
while (isActive && scanning) {
|
|
106
|
+
advOnlyWindowActive = false
|
|
107
|
+
val sdkCollect =
|
|
108
|
+
launch {
|
|
109
|
+
sdkScanner
|
|
110
|
+
.deviceFlow()
|
|
111
|
+
.catch { /* keep alternating windows alive */ }
|
|
112
|
+
.collect { device ->
|
|
113
|
+
emitSdkDevice(device)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
delay(SDK_SCAN_WINDOW_MS)
|
|
117
|
+
sdkCollect.cancel()
|
|
118
|
+
sdkCollect.join()
|
|
119
|
+
if (!isActive || !scanning) break
|
|
120
|
+
|
|
121
|
+
advOnlyWindowActive = true
|
|
122
|
+
withContext(Dispatchers.Main) { startParallelAdvScan() }
|
|
123
|
+
delay(ADV_SCAN_WINDOW_MS)
|
|
124
|
+
advOnlyWindowActive = false
|
|
125
|
+
withContext(Dispatchers.Main) { stopParallelAdvScan() }
|
|
126
|
+
}
|
|
127
|
+
}
|
|
88
128
|
}
|
|
89
129
|
|
|
90
130
|
fun stopScan() {
|
|
91
|
-
if (!scanning) return
|
|
92
|
-
val scanner = bluetoothLeScanner() ?: return
|
|
93
|
-
if (!hasBleScanPermission()) {
|
|
94
|
-
scanning = false
|
|
95
|
-
listener = null
|
|
96
|
-
return
|
|
97
|
-
}
|
|
98
|
-
try {
|
|
99
|
-
scanner.stopScan(leScanCallback)
|
|
100
|
-
} catch (_: Exception) {
|
|
101
|
-
}
|
|
102
131
|
scanning = false
|
|
132
|
+
advOnlyWindowActive = false
|
|
133
|
+
scanJob?.cancel()
|
|
134
|
+
scanJob = null
|
|
135
|
+
stopParallelAdvScan()
|
|
103
136
|
listener = null
|
|
104
137
|
}
|
|
105
138
|
|
|
106
139
|
fun isScanning(): Boolean = scanning
|
|
107
140
|
|
|
108
|
-
private fun
|
|
141
|
+
private fun emitSdkDevice(device: EfentoDevice) {
|
|
142
|
+
if (device !is Sensor) return
|
|
143
|
+
val mapped = SensorSdkScanTelemetry.deviceFromSensor(device)
|
|
144
|
+
val serial = mapped.serialNumber.uppercase(Locale.US)
|
|
145
|
+
lastSensorBySerial[serial] = device
|
|
146
|
+
rememberSeen(mapped)
|
|
147
|
+
listener?.onDeviceFound(mapped)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private fun handleAdvScanResult(result: ScanResult) {
|
|
151
|
+
if (!advOnlyWindowActive) return
|
|
109
152
|
val record = result.scanRecord ?: return
|
|
110
153
|
val addr = result.device?.address ?: return
|
|
111
154
|
val key = TempivoSensorBeaconDecoder.macKey(addr)
|
|
112
|
-
|
|
113
|
-
// getManufacturerSpecificData(0x026C) can retain only one of the two.
|
|
114
|
-
val payloads =
|
|
115
|
-
TempivoSensorBeaconDecoder.manufacturerPayloadsFromScanRecordBytes(record.bytes)
|
|
116
|
-
.ifEmpty {
|
|
117
|
-
val single =
|
|
118
|
-
record.getManufacturerSpecificData(TempivoSensorBeaconDecoder.MANUFACTURER_COMPANY_ID)
|
|
119
|
-
if (single != null) listOf(single) else emptyList()
|
|
120
|
-
}
|
|
155
|
+
val payloads = TempivoSensorBeaconDecoder.manufacturerPayloadsFromScanRecord(record)
|
|
121
156
|
if (payloads.isEmpty()) return
|
|
122
157
|
|
|
123
|
-
var changed = false
|
|
124
158
|
for (md in payloads) {
|
|
125
|
-
|
|
126
|
-
changed = true
|
|
127
|
-
}
|
|
159
|
+
TempivoSensorBeaconDecoder.ingestManufacturerPayload(md, key, advLast03, advLast04)
|
|
128
160
|
}
|
|
129
|
-
|
|
161
|
+
val serialHint =
|
|
162
|
+
payloads.asSequence()
|
|
163
|
+
.mapNotNull { TempivoSensorBeaconDecoder.serialKeyFromAdv03(it) }
|
|
164
|
+
.firstOrNull()
|
|
165
|
+
val lookupKeys = TempivoSensorBeaconDecoder.lookupKeys(key, serialHint)
|
|
166
|
+
if (lookupKeys.none { advLast03.containsKey(it) || advLast04.containsKey(it) }) {
|
|
130
167
|
return
|
|
131
168
|
}
|
|
132
|
-
|
|
169
|
+
|
|
170
|
+
val advSerial =
|
|
171
|
+
lookupKeys.asSequence()
|
|
172
|
+
.mapNotNull { advLast03[it] }
|
|
173
|
+
.mapNotNull { TempivoSensorBeaconDecoder.serialKeyFromAdv03(it) }
|
|
174
|
+
.firstOrNull()
|
|
175
|
+
val serial = (advSerial ?: key).uppercase(Locale.US)
|
|
176
|
+
|
|
177
|
+
lastSensorBySerial[serial]?.let { sensor ->
|
|
178
|
+
val refreshed = SensorSdkScanTelemetry.deviceFromSensor(sensor)
|
|
179
|
+
rememberSeen(refreshed)
|
|
180
|
+
listener?.onDeviceFound(refreshed)
|
|
181
|
+
return
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
val device = buildAdvDevice(result, key, lookupKeys) ?: return
|
|
185
|
+
rememberSeen(device)
|
|
133
186
|
listener?.onDeviceFound(device)
|
|
134
187
|
}
|
|
135
188
|
|
|
136
|
-
private fun
|
|
189
|
+
private fun buildAdvDevice(
|
|
190
|
+
result: ScanResult,
|
|
191
|
+
key: String,
|
|
192
|
+
lookupKeys: List<String>,
|
|
193
|
+
): TempivoSensorBeaconDevice? {
|
|
137
194
|
val addr = result.device?.address ?: return null
|
|
138
|
-
val
|
|
139
|
-
|
|
195
|
+
val advSerial =
|
|
196
|
+
lookupKeys.asSequence()
|
|
197
|
+
.mapNotNull { advLast03[it] }
|
|
198
|
+
.mapNotNull { TempivoSensorBeaconDecoder.serialKeyFromAdv03(it) }
|
|
199
|
+
.firstOrNull()
|
|
140
200
|
val serial = (advSerial ?: key).uppercase(Locale.US)
|
|
141
201
|
val macColons = macWithColonsFromKey(if (key.length == 12) key else serial)
|
|
142
202
|
val telemetry =
|
|
@@ -145,43 +205,70 @@ class TempivoSensorBeaconScanner(
|
|
|
145
205
|
advLast03,
|
|
146
206
|
advLast04,
|
|
147
207
|
)
|
|
148
|
-
val summary =
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
208
|
+
val summary = telemetry?.summary?.takeIf { it.isNotEmpty() } ?: serial
|
|
209
|
+
return TempivoSensorBeaconDevice(
|
|
210
|
+
deviceId = addr,
|
|
211
|
+
bluetoothMacAddress = macColons,
|
|
212
|
+
serialNumber = serial,
|
|
213
|
+
rssi = result.rssi,
|
|
214
|
+
telemetry = telemetry,
|
|
215
|
+
summary = summary,
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private fun rememberSeen(device: TempivoSensorBeaconDevice) {
|
|
160
220
|
val seen =
|
|
161
221
|
SeenDevice(
|
|
162
|
-
deviceId =
|
|
163
|
-
bluetoothMac =
|
|
164
|
-
serial =
|
|
165
|
-
firmware = telemetry?.firmware,
|
|
166
|
-
encryptionEnabled = telemetry?.encryptionEnabled,
|
|
222
|
+
deviceId = device.deviceId,
|
|
223
|
+
bluetoothMac = device.bluetoothMacAddress,
|
|
224
|
+
serial = device.serialNumber,
|
|
225
|
+
firmware = device.telemetry?.firmware,
|
|
226
|
+
encryptionEnabled = device.telemetry?.encryptionEnabled,
|
|
167
227
|
)
|
|
168
|
-
lastSeen[
|
|
169
|
-
lastSeen[
|
|
170
|
-
|
|
228
|
+
lastSeen[device.serialNumber.uppercase(Locale.US)] = seen
|
|
229
|
+
lastSeen[TempivoSensorBeaconDecoder.macKey(device.serialNumber)] = seen
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
private fun startParallelAdvScan() {
|
|
233
|
+
if (!hasBleScanPermission()) return
|
|
234
|
+
val scanner = bluetoothLeScanner() ?: return
|
|
235
|
+
val settings =
|
|
236
|
+
ScanSettings.Builder()
|
|
237
|
+
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
|
|
238
|
+
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
|
|
239
|
+
.build()
|
|
240
|
+
try {
|
|
241
|
+
scanner.startScan(null, settings, leScanCallback)
|
|
242
|
+
} catch (_: Exception) {
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private fun stopParallelAdvScan() {
|
|
247
|
+
if (!hasBleScanPermission()) return
|
|
248
|
+
val scanner = bluetoothLeScanner() ?: return
|
|
249
|
+
try {
|
|
250
|
+
scanner.stopScan(leScanCallback)
|
|
251
|
+
} catch (_: Exception) {
|
|
252
|
+
}
|
|
171
253
|
}
|
|
172
254
|
|
|
173
255
|
private fun bluetoothLeScanner() =
|
|
174
|
-
(
|
|
256
|
+
(application.getSystemService(Context.BLUETOOTH_SERVICE) as? BluetoothManager)
|
|
175
257
|
?.adapter
|
|
176
258
|
?.bluetoothLeScanner
|
|
177
259
|
|
|
178
260
|
private fun hasBleScanPermission(): Boolean {
|
|
179
261
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
|
180
|
-
|
|
181
|
-
|
|
262
|
+
val scanGranted =
|
|
263
|
+
ContextCompat.checkSelfPermission(application, Manifest.permission.BLUETOOTH_SCAN) ==
|
|
264
|
+
PackageManager.PERMISSION_GRANTED
|
|
265
|
+
val locationGranted =
|
|
266
|
+
ContextCompat.checkSelfPermission(application, Manifest.permission.ACCESS_FINE_LOCATION) ==
|
|
267
|
+
PackageManager.PERMISSION_GRANTED
|
|
268
|
+
return scanGranted && locationGranted
|
|
182
269
|
}
|
|
183
270
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
184
|
-
return ContextCompat.checkSelfPermission(
|
|
271
|
+
return ContextCompat.checkSelfPermission(application, Manifest.permission.ACCESS_FINE_LOCATION) ==
|
|
185
272
|
PackageManager.PERMISSION_GRANTED
|
|
186
273
|
}
|
|
187
274
|
return true
|
|
@@ -7,7 +7,6 @@ data class TempivoSensorQr(
|
|
|
7
7
|
val serial: String,
|
|
8
8
|
val pin: String,
|
|
9
9
|
val model: String?,
|
|
10
|
-
val sessionType: TempivoSensorSession.SessionType,
|
|
11
10
|
val bluetoothMac: String,
|
|
12
11
|
)
|
|
13
12
|
|
|
@@ -30,13 +29,13 @@ object TempivoSensorQrParser {
|
|
|
30
29
|
}
|
|
31
30
|
}
|
|
32
31
|
|
|
33
|
-
fun
|
|
34
|
-
if (model.isNullOrBlank()) return TempivoSensorSession.SessionType.MODERN
|
|
32
|
+
fun assertSupportedModel(model: String) {
|
|
35
33
|
val m = model.trim().uppercase(Locale.US).replace("-", "")
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
if (m == "HC5") {
|
|
35
|
+
throw TempivoSensorException(
|
|
36
|
+
TempivoSensorException.Code.INVALID_QR,
|
|
37
|
+
"HC5 is not supported by the partner SDK. Use HC7 sensors.",
|
|
38
|
+
)
|
|
40
39
|
}
|
|
41
40
|
}
|
|
42
41
|
|
|
@@ -61,12 +60,12 @@ object TempivoSensorQrParser {
|
|
|
61
60
|
throw TempivoSensorException(TempivoSensorException.Code.INVALID_QR, "QR is missing PIN.")
|
|
62
61
|
}
|
|
63
62
|
val model = rec.optString("model").trim().ifEmpty { DEFAULT_MODEL }
|
|
63
|
+
assertSupportedModel(model)
|
|
64
64
|
val hex = if (serial.length == 12) serial else serial.takeLast(12)
|
|
65
65
|
return TempivoSensorQr(
|
|
66
66
|
serial = serial,
|
|
67
67
|
pin = pin,
|
|
68
68
|
model = model,
|
|
69
|
-
sessionType = sessionTypeFromModel(model),
|
|
70
69
|
bluetoothMac = bluetoothMacFromSerial(hex),
|
|
71
70
|
)
|
|
72
71
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
package com.tempivo.sensor.beacon
|
|
2
2
|
|
|
3
|
-
import android.app.Application
|
|
4
3
|
import android.content.Context
|
|
5
4
|
import kotlinx.coroutines.Dispatchers
|
|
6
5
|
import kotlinx.coroutines.runBlocking
|
|
@@ -14,17 +13,12 @@ import kotlinx.coroutines.withContext
|
|
|
14
13
|
class TempivoSensorSession(
|
|
15
14
|
context: Context,
|
|
16
15
|
) {
|
|
17
|
-
enum class SessionType {
|
|
18
|
-
MODERN,
|
|
19
|
-
LEGACY,
|
|
20
|
-
}
|
|
21
|
-
|
|
22
16
|
data class TriggerResult(
|
|
23
17
|
val ok: Boolean,
|
|
24
18
|
val supported: Boolean,
|
|
25
19
|
)
|
|
26
20
|
|
|
27
|
-
private val application = context.applicationContext as Application
|
|
21
|
+
private val application = context.applicationContext as android.app.Application
|
|
28
22
|
private val mutex = Mutex()
|
|
29
23
|
private var session: SensorBleRuntime.Session? = null
|
|
30
24
|
|
|
@@ -32,7 +26,6 @@ class TempivoSensorSession(
|
|
|
32
26
|
serial: String,
|
|
33
27
|
bluetoothMac: String,
|
|
34
28
|
pin: String,
|
|
35
|
-
sessionType: SessionType = SessionType.MODERN,
|
|
36
29
|
) {
|
|
37
30
|
val pinInt =
|
|
38
31
|
pin.trim().toIntOrNull()
|
|
@@ -48,7 +41,6 @@ class TempivoSensorSession(
|
|
|
48
41
|
serial = TempivoSensorQrParser.normalizeSerial(serial),
|
|
49
42
|
bluetoothMac = bluetoothMac,
|
|
50
43
|
pin = pinInt,
|
|
51
|
-
legacy = sessionType == SessionType.LEGACY,
|
|
52
44
|
)
|
|
53
45
|
}
|
|
54
46
|
}
|
|
@@ -64,7 +56,7 @@ class TempivoSensorSession(
|
|
|
64
56
|
}
|
|
65
57
|
|
|
66
58
|
suspend fun connect(qr: TempivoSensorQr) {
|
|
67
|
-
connect(qr.serial, qr.bluetoothMac, qr.pin
|
|
59
|
+
connect(qr.serial, qr.bluetoothMac, qr.pin)
|
|
68
60
|
}
|
|
69
61
|
|
|
70
62
|
suspend fun disconnect() {
|
package/dist/decoder.d.ts
CHANGED
|
@@ -11,5 +11,3 @@ export declare function splitSensorBeaconFrames(payload: Uint8Array): Uint8Array
|
|
|
11
11
|
export declare function buildSensorBeaconReading(adv03: Uint8Array | null | undefined, adv04: Uint8Array | null | undefined, rawHex?: string): SensorBeaconReading | null;
|
|
12
12
|
/** One-shot decode of manufacturer payload (after company id strip). */
|
|
13
13
|
export declare function decodeSensorBeaconPayload(data: Uint8Array): SensorBeaconReading | null;
|
|
14
|
-
/** FW 7+ → modern (`false`); FW 6-/5.x → legacy (`true`). */
|
|
15
|
-
export declare function legacyHintFromFirmware(firmware: string | null | undefined): boolean | null;
|
package/dist/decoder.js
CHANGED
|
@@ -306,20 +306,3 @@ export function decodeSensorBeaconPayload(data) {
|
|
|
306
306
|
const merged03 = adv03 ?? adv02;
|
|
307
307
|
return buildSensorBeaconReading(merged03, adv04, bufferToHex(data));
|
|
308
308
|
}
|
|
309
|
-
/** FW 7+ → modern (`false`); FW 6-/5.x → legacy (`true`). */
|
|
310
|
-
export function legacyHintFromFirmware(firmware) {
|
|
311
|
-
if (!firmware?.trim())
|
|
312
|
-
return null;
|
|
313
|
-
const t = firmware.trim();
|
|
314
|
-
if (/^FW 5/i.test(t))
|
|
315
|
-
return true;
|
|
316
|
-
const m = /^(\d+)\.\d+/.exec(t);
|
|
317
|
-
if (!m)
|
|
318
|
-
return null;
|
|
319
|
-
const major = parseInt(m[1], 10);
|
|
320
|
-
if (major >= 7)
|
|
321
|
-
return false;
|
|
322
|
-
if (major >= 1)
|
|
323
|
-
return true;
|
|
324
|
-
return null;
|
|
325
|
-
}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { dataViewToUint8Array, manufacturerPayloadsFromScanRecordBytes, normaliz
|
|
|
4
4
|
export { MEASURE_SPECS, type MeasureSpec } from './measure-specs.js';
|
|
5
5
|
export { createSensorBeaconFrameCache, type SensorBeaconFrameCache, type SensorBeaconMeasurement, type SensorBeaconReading, } from './types.js';
|
|
6
6
|
export { TempivoSensorError, type TempivoSensorErrorCode } from './errors.js';
|
|
7
|
-
export { DEFAULT_SENSOR_MODEL, bluetoothMacFromSerial, normalizeSensorSerial, encodeSensorPinPayload, parseSensorQrJson, sessionTypeFromModel, } from './qr.js';
|
|
7
|
+
export { DEFAULT_SENSOR_MODEL, assertSupportedSensorModel, bluetoothMacFromSerial, normalizeSensorSerial, encodeSensorPinPayload, parseSensorQrJson, sessionTypeFromModel, } from './qr.js';
|
|
8
8
|
export { calibrationFromExtendedJson, decodeLaboratoryCalibrationTimestamp, } from './calibration.js';
|
|
9
9
|
export { BLE_MAX_MEASUREMENT_INTERVAL_MINUTES, BLE_MAX_TRANSMISSION_INTERVAL_SECONDS, BLE_MIN_MEASUREMENT_INTERVAL_MINUTES, BLE_MIN_TRANSMISSION_INTERVAL_SECONDS, DEFAULT_ALERT_HYSTERESIS_C, compileSensorProfileToBleJson, configurationFromApiProfile, parseSensorConfiguration, parseSensorConfigurationJson, pickPartnerConfigurationJson, } from './profile.js';
|
|
10
10
|
export type { TempivoSchedule, TempivoScheduleAlways, TempivoScheduleWeek, TempivoSensorCalibration, TempivoSensorConfiguration, TempivoSensorQr, TempivoSensorSessionType, TempivoTemperatureAlert, TempivoTemperatureChannel, TempivoTemperatureMaxAlert, TempivoTemperatureMinAlert, TempivoTemperatureRangeAlert, TempivoTriggerTransmissionResult, } from './session-types.js';
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ export { dataViewToUint8Array, manufacturerPayloadsFromScanRecordBytes, normaliz
|
|
|
4
4
|
export { MEASURE_SPECS } from './measure-specs.js';
|
|
5
5
|
export { createSensorBeaconFrameCache, } from './types.js';
|
|
6
6
|
export { TempivoSensorError } from './errors.js';
|
|
7
|
-
export { DEFAULT_SENSOR_MODEL, bluetoothMacFromSerial, normalizeSensorSerial, encodeSensorPinPayload, parseSensorQrJson, sessionTypeFromModel, } from './qr.js';
|
|
7
|
+
export { DEFAULT_SENSOR_MODEL, assertSupportedSensorModel, bluetoothMacFromSerial, normalizeSensorSerial, encodeSensorPinPayload, parseSensorQrJson, sessionTypeFromModel, } from './qr.js';
|
|
8
8
|
export { calibrationFromExtendedJson, decodeLaboratoryCalibrationTimestamp, } from './calibration.js';
|
|
9
9
|
export { BLE_MAX_MEASUREMENT_INTERVAL_MINUTES, BLE_MAX_TRANSMISSION_INTERVAL_SECONDS, BLE_MIN_MEASUREMENT_INTERVAL_MINUTES, BLE_MIN_TRANSMISSION_INTERVAL_SECONDS, DEFAULT_ALERT_HYSTERESIS_C, compileSensorProfileToBleJson, configurationFromApiProfile, parseSensorConfiguration, parseSensorConfigurationJson, pickPartnerConfigurationJson, } from './profile.js';
|
|
10
10
|
export { addDeviceFoundListener, connect, disconnect, getCalibration, getConfiguration, isNativeSensorBeaconAvailable, isScanning, requestPermissions, setConfigurationJson, startScan, stopScan, triggerTransmission, } from './native-bridge.js';
|
package/dist/native-module.js
CHANGED
|
@@ -50,7 +50,11 @@ export function isScanning() {
|
|
|
50
50
|
return getNative().isScanning();
|
|
51
51
|
}
|
|
52
52
|
export function addDeviceFoundListener(listener) {
|
|
53
|
-
|
|
53
|
+
const native = getNative();
|
|
54
|
+
if (typeof native.addListener !== 'function') {
|
|
55
|
+
throw new TempivoSensorError('runtimeUnavailable', 'Native scan events are unavailable in this build.');
|
|
56
|
+
}
|
|
57
|
+
return native.addListener('onDeviceFound', listener);
|
|
54
58
|
}
|
|
55
59
|
export async function connect(qr) {
|
|
56
60
|
await ensureBlePermission();
|
package/dist/qr.d.ts
CHANGED
|
@@ -2,9 +2,10 @@ import type { TempivoSensorQr, TempivoSensorSessionType } from './session-types.
|
|
|
2
2
|
export declare function normalizeSensorSerial(value: string): string;
|
|
3
3
|
/** `282C024F0012` → `28:2C:02:4F:00:12`. */
|
|
4
4
|
export declare function bluetoothMacFromSerial(serial: string): string;
|
|
5
|
-
/** BLE default when QR omits `model`.
|
|
5
|
+
/** BLE default when QR omits `model`. */
|
|
6
6
|
export declare const DEFAULT_SENSOR_MODEL = "HC7";
|
|
7
|
-
export declare function
|
|
7
|
+
export declare function assertSupportedSensorModel(model: string): void;
|
|
8
|
+
export declare function sessionTypeFromModel(_model: string | undefined | null): TempivoSensorSessionType;
|
|
8
9
|
/**
|
|
9
10
|
* 3-byte big-endian numeric PIN for GATT command payloads that check a reset code
|
|
10
11
|
* (`setConfiguration`, trigger, and many other privileged writes).
|
package/dist/qr.js
CHANGED
|
@@ -13,14 +13,15 @@ export function bluetoothMacFromSerial(serial) {
|
|
|
13
13
|
parts.push(key.slice(i, i + 2));
|
|
14
14
|
return parts.join(':');
|
|
15
15
|
}
|
|
16
|
-
/** BLE default when QR omits `model`.
|
|
16
|
+
/** BLE default when QR omits `model`. */
|
|
17
17
|
export const DEFAULT_SENSOR_MODEL = 'HC7';
|
|
18
|
-
export function
|
|
19
|
-
if (model == null || model.trim() === '')
|
|
20
|
-
return 'modern';
|
|
18
|
+
export function assertSupportedSensorModel(model) {
|
|
21
19
|
const m = model.trim().toUpperCase().replace(/-/g, '');
|
|
22
|
-
if (m === 'HC5'
|
|
23
|
-
|
|
20
|
+
if (m === 'HC5') {
|
|
21
|
+
throw new TempivoSensorError('invalidQr', 'HC5 is not supported by the partner SDK. Use HC7 sensors.');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function sessionTypeFromModel(_model) {
|
|
24
25
|
return 'modern';
|
|
25
26
|
}
|
|
26
27
|
/**
|
|
@@ -80,6 +81,7 @@ export function parseSensorQrJson(text) {
|
|
|
80
81
|
}
|
|
81
82
|
const rawModel = typeof rec.model === 'string' ? rec.model.trim() : '';
|
|
82
83
|
const model = rawModel || DEFAULT_SENSOR_MODEL;
|
|
84
|
+
assertSupportedSensorModel(model);
|
|
83
85
|
return {
|
|
84
86
|
serial,
|
|
85
87
|
pin,
|
package/dist/react-native.d.ts
CHANGED
|
@@ -1 +1,24 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { ADV_FRAME_03_LEN, TEMPVO_SENSOR_MANUFACTURER_ID, buildSensorBeaconReading, decodeMeasurementSlot, decodeSensorBeaconPayload, lookupDeviceKeys, macKeyFromAddress, serialKeyFromAdv03Frame, splitSensorBeaconFrames, } from './decoder.js';
|
|
2
|
+
export { ingestManufacturerPayload } from './ingest.js';
|
|
3
|
+
export { dataViewToUint8Array, manufacturerPayloadsFromScanRecordBytes, normalizeManufacturerBytes } from './normalize.js';
|
|
4
|
+
export { MEASURE_SPECS, type MeasureSpec } from './measure-specs.js';
|
|
5
|
+
export { createSensorBeaconFrameCache, type SensorBeaconFrameCache, type SensorBeaconMeasurement, type SensorBeaconReading, } from './types.js';
|
|
6
|
+
export { TempivoSensorError, type TempivoSensorErrorCode } from './errors.js';
|
|
7
|
+
export { DEFAULT_SENSOR_MODEL, bluetoothMacFromSerial, normalizeSensorSerial, encodeSensorPinPayload, parseSensorQrJson, sessionTypeFromModel, } from './qr.js';
|
|
8
|
+
export { calibrationFromExtendedJson, decodeLaboratoryCalibrationTimestamp, } from './calibration.js';
|
|
9
|
+
export { BLE_MAX_MEASUREMENT_INTERVAL_MINUTES, BLE_MAX_TRANSMISSION_INTERVAL_SECONDS, BLE_MIN_MEASUREMENT_INTERVAL_MINUTES, BLE_MIN_TRANSMISSION_INTERVAL_SECONDS, DEFAULT_ALERT_HYSTERESIS_C, compileSensorProfileToBleJson, configurationFromApiProfile, parseSensorConfiguration, parseSensorConfigurationJson, pickPartnerConfigurationJson, } from './profile.js';
|
|
10
|
+
export type { TempivoSchedule, TempivoScheduleAlways, TempivoScheduleWeek, TempivoSensorCalibration, TempivoSensorConfiguration, TempivoSensorQr, TempivoSensorSessionType, TempivoTemperatureAlert, TempivoTemperatureChannel, TempivoTemperatureMaxAlert, TempivoTemperatureMinAlert, TempivoTemperatureRangeAlert, TempivoTriggerTransmissionResult, } from './session-types.js';
|
|
11
|
+
export type { TempivoDeviceFoundSubscription, TempivoNativeCalibration, TempivoNativeMeasurement, TempivoNativePermissionResponse, TempivoNativePermissionStatus, TempivoNativeQr, TempivoNativeTelemetry, TempivoSensorBeaconDevice, } from './native-types.js';
|
|
12
|
+
import * as native from './native-module.js';
|
|
13
|
+
export declare const isNativeSensorBeaconAvailable: typeof native.isNativeSensorBeaconAvailable;
|
|
14
|
+
export declare const requestPermissions: typeof native.requestPermissions;
|
|
15
|
+
export declare const startScan: typeof native.startScan;
|
|
16
|
+
export declare const stopScan: typeof native.stopScan;
|
|
17
|
+
export declare const isScanning: typeof native.isScanning;
|
|
18
|
+
export declare const addDeviceFoundListener: typeof native.addDeviceFoundListener;
|
|
19
|
+
export declare const connect: typeof native.connect;
|
|
20
|
+
export declare const disconnect: typeof native.disconnect;
|
|
21
|
+
export declare const getConfiguration: typeof native.getConfiguration;
|
|
22
|
+
export declare const setConfigurationJson: typeof native.setConfigurationJson;
|
|
23
|
+
export declare const triggerTransmission: typeof native.triggerTransmission;
|
|
24
|
+
export declare const getCalibration: typeof native.getCalibration;
|
package/dist/react-native.js
CHANGED
|
@@ -1 +1,22 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { ADV_FRAME_03_LEN, TEMPVO_SENSOR_MANUFACTURER_ID, buildSensorBeaconReading, decodeMeasurementSlot, decodeSensorBeaconPayload, lookupDeviceKeys, macKeyFromAddress, serialKeyFromAdv03Frame, splitSensorBeaconFrames, } from './decoder.js';
|
|
2
|
+
export { ingestManufacturerPayload } from './ingest.js';
|
|
3
|
+
export { dataViewToUint8Array, manufacturerPayloadsFromScanRecordBytes, normalizeManufacturerBytes } from './normalize.js';
|
|
4
|
+
export { MEASURE_SPECS } from './measure-specs.js';
|
|
5
|
+
export { createSensorBeaconFrameCache, } from './types.js';
|
|
6
|
+
export { TempivoSensorError } from './errors.js';
|
|
7
|
+
export { DEFAULT_SENSOR_MODEL, bluetoothMacFromSerial, normalizeSensorSerial, encodeSensorPinPayload, parseSensorQrJson, sessionTypeFromModel, } from './qr.js';
|
|
8
|
+
export { calibrationFromExtendedJson, decodeLaboratoryCalibrationTimestamp, } from './calibration.js';
|
|
9
|
+
export { BLE_MAX_MEASUREMENT_INTERVAL_MINUTES, BLE_MAX_TRANSMISSION_INTERVAL_SECONDS, BLE_MIN_MEASUREMENT_INTERVAL_MINUTES, BLE_MIN_TRANSMISSION_INTERVAL_SECONDS, DEFAULT_ALERT_HYSTERESIS_C, compileSensorProfileToBleJson, configurationFromApiProfile, parseSensorConfiguration, parseSensorConfigurationJson, pickPartnerConfigurationJson, } from './profile.js';
|
|
10
|
+
import * as native from './native-module.js';
|
|
11
|
+
export const isNativeSensorBeaconAvailable = native.isNativeSensorBeaconAvailable;
|
|
12
|
+
export const requestPermissions = native.requestPermissions;
|
|
13
|
+
export const startScan = native.startScan;
|
|
14
|
+
export const stopScan = native.stopScan;
|
|
15
|
+
export const isScanning = native.isScanning;
|
|
16
|
+
export const addDeviceFoundListener = native.addDeviceFoundListener;
|
|
17
|
+
export const connect = native.connect;
|
|
18
|
+
export const disconnect = native.disconnect;
|
|
19
|
+
export const getConfiguration = native.getConfiguration;
|
|
20
|
+
export const setConfigurationJson = native.setConfigurationJson;
|
|
21
|
+
export const triggerTransmission = native.triggerTransmission;
|
|
22
|
+
export const getCalibration = native.getCalibration;
|
package/dist/session-types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
/** Partner SDK: modern GATT only (HC7 / firmware 7+). */
|
|
2
|
+
export type TempivoSensorSessionType = 'modern';
|
|
2
3
|
export type TempivoTemperatureChannel = 'ambient' | 'probe';
|
|
3
4
|
export type TempivoTemperatureRangeAlert = {
|
|
4
5
|
type: 'range';
|
|
@@ -54,8 +55,9 @@ export type TempivoSensorConfiguration = {
|
|
|
54
55
|
export type TempivoSensorQr = {
|
|
55
56
|
serial: string;
|
|
56
57
|
pin: string;
|
|
57
|
-
/** Optional sticker field.
|
|
58
|
+
/** Optional sticker field. Defaults to HC7. HC5 is rejected. */
|
|
58
59
|
model?: string;
|
|
60
|
+
/** Always `modern` (HC7 GATT). */
|
|
59
61
|
sessionType: TempivoSensorSessionType;
|
|
60
62
|
bluetoothMac: string;
|
|
61
63
|
};
|
|
Binary file
|
|
Binary file
|
|
@@ -29,13 +29,15 @@ public enum TempivoSensorQrParser {
|
|
|
29
29
|
|
|
30
30
|
public static let defaultModel = "HC7"
|
|
31
31
|
|
|
32
|
-
public static func
|
|
33
|
-
guard let model, !model.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
|
|
34
|
-
return .modern
|
|
35
|
-
}
|
|
32
|
+
public static func assertSupportedModel(_ model: String) throws {
|
|
36
33
|
let m = model.trimmingCharacters(in: .whitespacesAndNewlines).uppercased().replacingOccurrences(of: "-", with: "")
|
|
37
|
-
if m == "HC5"
|
|
38
|
-
|
|
34
|
+
if m == "HC5" {
|
|
35
|
+
throw TempivoSensorError(.invalidQr, "HC5 is not supported by the partner SDK. Use HC7 sensors.")
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public static func sessionType(fromModel _: String?) -> TempivoSensorSessionType {
|
|
40
|
+
.modern
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
public static func parse(_ json: String) throws -> TempivoSensorQr {
|
|
@@ -59,12 +61,13 @@ public enum TempivoSensorQrParser {
|
|
|
59
61
|
}
|
|
60
62
|
let rawModel = (obj["model"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
|
61
63
|
let model = rawModel.isEmpty ? defaultModel : rawModel
|
|
64
|
+
try assertSupportedModel(model)
|
|
62
65
|
let hex = serial.count == 12 ? serial : String(serial.suffix(12))
|
|
63
66
|
return TempivoSensorQr(
|
|
64
67
|
serial: serial,
|
|
65
68
|
pin: pin,
|
|
66
69
|
model: model,
|
|
67
|
-
sessionType:
|
|
70
|
+
sessionType: .modern,
|
|
68
71
|
bluetoothMac: try bluetoothMacFromSerial(hex)
|
|
69
72
|
)
|
|
70
73
|
}
|