@tempivo/sensor-beacon 0.3.3 → 0.4.7
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/CHANGELOG.md +29 -0
- package/README.md +159 -9
- package/android/build.gradle +2 -2
- package/android/src/main/AndroidManifest.xml +2 -1
- package/android/src/main/java/expo/modules/tempivosensorbeacon/SensorBeaconNative.java +18 -5
- package/android/src/main/java/expo/modules/tempivosensorbeacon/TempivoSensorBeaconModule.kt +11 -2
- package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/BleConnectCredentials.kt +25 -0
- package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/SdkMeasurementFormat.kt +111 -0
- package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/SensorBleRuntime.kt +39 -87
- package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/SensorBleVendor.kt +28 -0
- package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/SensorModelHint.kt +20 -0
- package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/SensorSdkScanTelemetry.kt +112 -0
- package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorBeaconDecoder.kt +54 -1
- package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorBeaconScanner.kt +160 -74
- package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorBeaconTypes.kt +2 -0
- package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorQr.kt +27 -9
- package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorSession.kt +20 -16
- package/dist/connect-helpers.d.ts +52 -0
- package/dist/connect-helpers.js +153 -0
- package/dist/decoder.d.ts +1 -2
- package/dist/decoder.js +7 -18
- package/dist/index.d.ts +7 -3
- package/dist/index.js +5 -3
- package/dist/model.d.ts +5 -0
- package/dist/model.js +25 -0
- package/dist/native-helpers.js +5 -2
- package/dist/native-module.js +7 -3
- package/dist/native-types.d.ts +2 -0
- package/dist/profile.d.ts +8 -1
- package/dist/profile.js +42 -19
- package/dist/qr.d.ts +3 -2
- package/dist/qr.js +8 -6
- package/dist/react-native.d.ts +28 -1
- package/dist/react-native.js +24 -1
- package/dist/session-types.d.ts +6 -3
- package/dist/tempivo-sensor-beacon.aar +0 -0
- package/ios/Frameworks/TempivoSensorBridge.xcframework/ios-arm64/TempivoSensorBridge.framework/Headers/TempivoSensorBridge.h +28 -1
- package/ios/Frameworks/TempivoSensorBridge.xcframework/ios-arm64/TempivoSensorBridge.framework/TempivoSensorBridge +0 -0
- package/ios/Frameworks/TempivoSensorBridge.xcframework/ios-arm64-simulator/TempivoSensorBridge.framework/Headers/TempivoSensorBridge.h +28 -1
- package/ios/Frameworks/TempivoSensorBridge.xcframework/ios-arm64-simulator/TempivoSensorBridge.framework/TempivoSensorBridge +0 -0
- package/ios/Sources/TempivoSensorBeacon/TempivoSensorBeaconDecoder.swift +8 -1
- package/ios/Sources/TempivoSensorBeacon/TempivoSensorBeaconScanner.swift +10 -1
- package/ios/Sources/TempivoSensorBeacon/TempivoSensorModelHint.swift +29 -0
- package/ios/Sources/TempivoSensorBeacon/TempivoSensorProfile.swift +37 -13
- package/ios/Sources/TempivoSensorBeacon/TempivoSensorQr.swift +48 -8
- package/ios/Sources/TempivoSensorBeacon/TempivoSensorSession.swift +24 -10
- package/ios/Sources/TempivoSensorBeacon/TempivoSensorSessionTypes.swift +1 -4
- package/ios/TempivoSensorBeaconModule.swift +214 -24
- package/package.json +3 -2
|
@@ -2,9 +2,7 @@ package com.tempivo.sensor.beacon
|
|
|
2
2
|
|
|
3
3
|
import android.app.Application
|
|
4
4
|
import kotlinx.coroutines.delay
|
|
5
|
-
import pl.efento.mobile.bluetooth.EfentoBluetooth
|
|
6
5
|
import pl.efento.mobile.bluetooth.api.SensorConnection
|
|
7
|
-
import pl.efento.mobile.bluetooth.api.SensorLegacyConnection
|
|
8
6
|
import pl.efento.mobile.bluetooth.exception.InvalidResetCodeException
|
|
9
7
|
import pl.efento.mobile.bluetooth.exception.UnsupportedCommandException
|
|
10
8
|
import pl.efento.mobile.bluetooth.model.BluetoothMacAddress
|
|
@@ -25,7 +23,7 @@ internal object SensorBleRuntime {
|
|
|
25
23
|
if (initialized) return
|
|
26
24
|
synchronized(initMonitor) {
|
|
27
25
|
if (initialized) return
|
|
28
|
-
|
|
26
|
+
SensorBleVendor.init(application)
|
|
29
27
|
initialized = true
|
|
30
28
|
}
|
|
31
29
|
}
|
|
@@ -47,106 +45,63 @@ internal object SensorBleRuntime {
|
|
|
47
45
|
|
|
48
46
|
suspend fun connect(
|
|
49
47
|
application: Application,
|
|
50
|
-
|
|
48
|
+
deviceId: String,
|
|
51
49
|
bluetoothMac: String,
|
|
52
|
-
pin:
|
|
53
|
-
legacy: Boolean,
|
|
50
|
+
pin: String,
|
|
54
51
|
): Session {
|
|
55
52
|
initialize(application)
|
|
53
|
+
val creds = BleConnectCredentialsResolver.fromPin(pin)
|
|
56
54
|
val mac = BluetoothMacAddress(bluetoothMac)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (pin >= 0) c.resetCode = pin
|
|
68
|
-
Session.Legacy(c)
|
|
69
|
-
} else {
|
|
70
|
-
val c =
|
|
71
|
-
EfentoBluetooth.sensorConnection(
|
|
72
|
-
deviceID = serial,
|
|
73
|
-
bluetoothMacAddress = mac,
|
|
74
|
-
resetCode = pin,
|
|
75
|
-
encryptionKey = "",
|
|
76
|
-
)
|
|
77
|
-
c.connect()
|
|
78
|
-
if (pin >= 0) c.resetCode = pin
|
|
79
|
-
Session.Modern(c)
|
|
80
|
-
}
|
|
55
|
+
val c =
|
|
56
|
+
SensorBleVendor.sensorConnection(
|
|
57
|
+
deviceId = deviceId,
|
|
58
|
+
bluetoothMac = mac,
|
|
59
|
+
resetCode = creds.resetCode,
|
|
60
|
+
encryptionKey = creds.encryptionKey,
|
|
61
|
+
)
|
|
62
|
+
c.connect()
|
|
63
|
+
applyConnectCredentials(c, creds)
|
|
64
|
+
return Session(c)
|
|
81
65
|
}
|
|
82
66
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
is Session.Legacy -> runCatching { session.connection.disconnect() }
|
|
87
|
-
null -> Unit
|
|
88
|
-
}
|
|
67
|
+
private fun applyConnectCredentials(connection: SensorConnection, creds: BleConnectCredentials) {
|
|
68
|
+
if (creds.resetCode >= 0) connection.resetCode = creds.resetCode
|
|
69
|
+
if (creds.encryptionKey.isNotEmpty()) connection.encryptionKey = creds.encryptionKey
|
|
89
70
|
}
|
|
90
71
|
|
|
91
|
-
suspend fun
|
|
92
|
-
|
|
93
|
-
is Session.Modern -> modernGetConfiguration(session.connection)
|
|
94
|
-
is Session.Legacy ->
|
|
95
|
-
throw TempivoSensorException(
|
|
96
|
-
TempivoSensorException.Code.UNSUPPORTED_COMMAND,
|
|
97
|
-
"Configuration rules require a current-generation sensor session.",
|
|
98
|
-
)
|
|
99
|
-
}
|
|
72
|
+
suspend fun disconnect(session: Session?) {
|
|
73
|
+
session?.let { runCatching { it.connection.disconnect() } }
|
|
100
74
|
}
|
|
101
75
|
|
|
76
|
+
suspend fun readConfiguration(session: Session): SensorConfiguration =
|
|
77
|
+
modernGetConfiguration(session.connection)
|
|
78
|
+
|
|
102
79
|
suspend fun writeConfiguration(session: Session, cfg: TempivoSensorConfiguration) {
|
|
103
80
|
val (rules, calendars) = compileRules(cfg)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
cfg.transmissionIntervalSeconds?.let { sec ->
|
|
113
|
-
next = next.copy(transmissionInterval = TransmissionInterval(sec))
|
|
114
|
-
}
|
|
115
|
-
session.connection.commands.setConfiguration(next) { }
|
|
116
|
-
}
|
|
117
|
-
is Session.Legacy ->
|
|
118
|
-
throw TempivoSensorException(
|
|
119
|
-
TempivoSensorException.Code.UNSUPPORTED_COMMAND,
|
|
120
|
-
"Configuration rules require a current-generation sensor session.",
|
|
121
|
-
)
|
|
81
|
+
val base = modernGetConfiguration(session.connection)
|
|
82
|
+
var next = base.copy(rules = rules.take(12), ruleCalendars = calendars.take(6))
|
|
83
|
+
cfg.measurementIntervalMinutes?.let { minutes ->
|
|
84
|
+
val baseSec = (minutes * 60.0).toInt().coerceAtLeast(1)
|
|
85
|
+
next = next.copy(measurementPeriod = MeasurementPeriod(baseSec, 1))
|
|
86
|
+
}
|
|
87
|
+
cfg.transmissionIntervalSeconds?.let { sec ->
|
|
88
|
+
next = next.copy(transmissionInterval = TransmissionInterval(sec))
|
|
122
89
|
}
|
|
90
|
+
session.connection.commands.setConfiguration(next) { }
|
|
123
91
|
}
|
|
124
92
|
|
|
125
93
|
suspend fun triggerTransmission(session: Session): TempivoSensorSession.TriggerResult {
|
|
126
|
-
return
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
is Session.Legacy -> {
|
|
132
|
-
try {
|
|
133
|
-
session.connection.cellularCommands.triggerCommunicationWithServer { }
|
|
134
|
-
TempivoSensorSession.TriggerResult(ok = true, supported = true)
|
|
135
|
-
} catch (e: UnsupportedCommandException) {
|
|
136
|
-
TempivoSensorSession.TriggerResult(ok = false, supported = false)
|
|
137
|
-
}
|
|
138
|
-
}
|
|
94
|
+
return try {
|
|
95
|
+
session.connection.commands.triggerCommunicationWithServer { }
|
|
96
|
+
TempivoSensorSession.TriggerResult(ok = true)
|
|
97
|
+
} catch (e: UnsupportedCommandException) {
|
|
98
|
+
TempivoSensorSession.TriggerResult(ok = false)
|
|
139
99
|
}
|
|
140
100
|
}
|
|
141
101
|
|
|
142
102
|
suspend fun readCalibration(session: Session): TempivoSensorCalibration {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
val ext = session.connection.commands.getExtendedConfiguration { }
|
|
146
|
-
TempivoSensorCalibrationDecoder.decode(ext.laboratoryCalibrationTimestamp)
|
|
147
|
-
}
|
|
148
|
-
is Session.Legacy -> TempivoSensorCalibration(null, null)
|
|
149
|
-
}
|
|
103
|
+
val ext = session.connection.commands.getExtendedConfiguration { }
|
|
104
|
+
return TempivoSensorCalibrationDecoder.decode(ext.laboratoryCalibrationTimestamp)
|
|
150
105
|
}
|
|
151
106
|
|
|
152
107
|
fun compileRules(cfg: TempivoSensorConfiguration): Pair<List<Rule>, List<RuleCalendar>> =
|
|
@@ -171,8 +126,5 @@ internal object SensorBleRuntime {
|
|
|
171
126
|
throw last ?: TempivoSensorException(TempivoSensorException.Code.UNKNOWN, "Could not read configuration.")
|
|
172
127
|
}
|
|
173
128
|
|
|
174
|
-
|
|
175
|
-
class Modern(val connection: SensorConnection) : Session()
|
|
176
|
-
class Legacy(val connection: SensorLegacyConnection) : Session()
|
|
177
|
-
}
|
|
129
|
+
class Session(val connection: SensorConnection)
|
|
178
130
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
package com.tempivo.sensor.beacon
|
|
2
|
+
|
|
3
|
+
import android.app.Application
|
|
4
|
+
import pl.efento.mobile.bluetooth.EfentoBluetooth
|
|
5
|
+
import pl.efento.mobile.bluetooth.api.SensorConnection
|
|
6
|
+
import pl.efento.mobile.bluetooth.model.BluetoothMacAddress
|
|
7
|
+
|
|
8
|
+
/** Single entry point to the bundled sensor BLE runtime (vendor types stay in this file). */
|
|
9
|
+
internal object SensorBleVendor {
|
|
10
|
+
fun init(application: Application) {
|
|
11
|
+
EfentoBluetooth.init(application)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
fun scanner() = EfentoBluetooth.scanner()
|
|
15
|
+
|
|
16
|
+
fun sensorConnection(
|
|
17
|
+
deviceId: String,
|
|
18
|
+
bluetoothMac: BluetoothMacAddress,
|
|
19
|
+
resetCode: Int,
|
|
20
|
+
encryptionKey: String,
|
|
21
|
+
): SensorConnection =
|
|
22
|
+
EfentoBluetooth.sensorConnection(
|
|
23
|
+
deviceID = deviceId,
|
|
24
|
+
bluetoothMacAddress = bluetoothMac,
|
|
25
|
+
resetCode = resetCode,
|
|
26
|
+
encryptionKey = encryptionKey,
|
|
27
|
+
)
|
|
28
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
package com.tempivo.sensor.beacon
|
|
2
|
+
|
|
3
|
+
/** Sticker model hint from advertisement firmware. Missing FW → HC7 (modern fleet default). */
|
|
4
|
+
internal object SensorModelHint {
|
|
5
|
+
const val DEFAULT_MODEL = TempivoSensorQrParser.DEFAULT_MODEL
|
|
6
|
+
|
|
7
|
+
fun isMissingFirmware(firmware: String?): Boolean {
|
|
8
|
+
val t = firmware?.trim().orEmpty()
|
|
9
|
+
return t.isEmpty() || t == "0.0.0"
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
fun fromFirmware(firmware: String?): String {
|
|
13
|
+
if (isMissingFirmware(firmware)) return DEFAULT_MODEL
|
|
14
|
+
val t = firmware!!.trim()
|
|
15
|
+
if (t.startsWith("FW 5", ignoreCase = true)) return "HC5"
|
|
16
|
+
val major = Regex("""^(?:FW\s*)?(\d+)\.\d+""", RegexOption.IGNORE_CASE).find(t)?.groupValues?.get(1)?.toIntOrNull()
|
|
17
|
+
?: return DEFAULT_MODEL
|
|
18
|
+
return if (major >= 7) "HC7" else if (major >= 1) "HC5" else DEFAULT_MODEL
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
package com.tempivo.sensor.beacon
|
|
2
|
+
|
|
3
|
+
import pl.efento.mobile.bluetooth.model.Sensor
|
|
4
|
+
|
|
5
|
+
/** Advertisement telemetry from runtime scan [Sensor]. */
|
|
6
|
+
internal object SensorSdkScanTelemetry {
|
|
7
|
+
fun fromSensor(sensor: Sensor): TempivoSensorBeaconTelemetry {
|
|
8
|
+
val readings = buildReadings(sensor)
|
|
9
|
+
val summary = sensorAdvertisementSummary(sensor, readings.size)
|
|
10
|
+
val fw = sensor.softwareVersion
|
|
11
|
+
val lts = fw.lts ?: 0
|
|
12
|
+
val periodBase = sensor.measurementPeriod.base
|
|
13
|
+
val periodFactor = sensor.measurementPeriod.factor
|
|
14
|
+
val intervalSec =
|
|
15
|
+
if (periodBase > 0) {
|
|
16
|
+
periodBase * (if (periodFactor == 0) 1 else periodFactor)
|
|
17
|
+
} else {
|
|
18
|
+
null
|
|
19
|
+
}
|
|
20
|
+
val ts = sensor.measurementsTimestamp
|
|
21
|
+
return TempivoSensorBeaconTelemetry(
|
|
22
|
+
firmware = "${fw.major}.${fw.minor}.$lts",
|
|
23
|
+
batteryOk = sensor.batteryStatus.name.contains("OK", ignoreCase = true),
|
|
24
|
+
encryptionEnabled = sensor.encryptionStatus.name.contains("ENABLED", ignoreCase = true),
|
|
25
|
+
cellularStatus = resolveCellularStatus(sensor),
|
|
26
|
+
measurementCounter = sensor.counter.takeIf { it > 0 }?.toLong(),
|
|
27
|
+
readingTimestampUnix = ts.takeIf { it > 0 },
|
|
28
|
+
measurementIntervalSeconds = intervalSec,
|
|
29
|
+
readings = readings,
|
|
30
|
+
readingsCount = readings.size,
|
|
31
|
+
summary = summary,
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
fun deviceFromSensor(sensor: Sensor): TempivoSensorBeaconDevice {
|
|
36
|
+
val mac = sensor.bluetoothMacAddress.withColons
|
|
37
|
+
val serial = TempivoSensorBeaconDecoder.macKey(mac)
|
|
38
|
+
val telemetry = fromSensor(sensor)
|
|
39
|
+
return TempivoSensorBeaconDevice(
|
|
40
|
+
deviceId = sensor.id,
|
|
41
|
+
bluetoothMacAddress = mac,
|
|
42
|
+
serialNumber = serial,
|
|
43
|
+
rssi = sensor.rssi,
|
|
44
|
+
model = SensorModelHint.fromFirmware(telemetry.firmware),
|
|
45
|
+
telemetry = telemetry,
|
|
46
|
+
summary = telemetry.summary ?: serial,
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private fun sensorAdvertisementSummary(
|
|
51
|
+
sensor: Sensor,
|
|
52
|
+
readingsCount: Int,
|
|
53
|
+
): String {
|
|
54
|
+
val parts = measurementSummaryParts(sensor)
|
|
55
|
+
if (parts.isNotEmpty()) return parts.take(4).joinToString(" · ")
|
|
56
|
+
val fw = sensor.softwareVersion
|
|
57
|
+
val lts = fw.lts ?: 0
|
|
58
|
+
return if (readingsCount == 0) {
|
|
59
|
+
"${fw.major}.${fw.minor}.$lts"
|
|
60
|
+
} else {
|
|
61
|
+
"${fw.major}.${fw.minor}.$lts"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private fun buildReadings(sensor: Sensor): List<TempivoSensorBeaconReading> {
|
|
66
|
+
val out = mutableListOf<TempivoSensorBeaconReading>()
|
|
67
|
+
for ((_, measurement) in sensor.measurements) {
|
|
68
|
+
if (!measurement.isValid) continue
|
|
69
|
+
out +=
|
|
70
|
+
TempivoSensorBeaconReading(
|
|
71
|
+
typeHex = SdkMeasurementFormat.typeHex(measurement),
|
|
72
|
+
raw24 = 0,
|
|
73
|
+
text = SdkMeasurementFormat.text(measurement),
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
return out
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private fun measurementSummaryParts(sensor: Sensor): List<String> {
|
|
80
|
+
val out = mutableListOf<String>()
|
|
81
|
+
for ((_, measurement) in sensor.measurements) {
|
|
82
|
+
if (!measurement.isValid) continue
|
|
83
|
+
out.add(SdkMeasurementFormat.text(measurement))
|
|
84
|
+
}
|
|
85
|
+
return out
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private fun resolveCellularStatus(sensor: Sensor): String? {
|
|
89
|
+
sensor.cellularStatus?.let { return mapCellularStatus(it) }
|
|
90
|
+
return mapConnectivityToCellular(sensor.connectivityStatus)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private fun mapCellularStatus(status: Sensor.CellularStatus): String =
|
|
94
|
+
when (status) {
|
|
95
|
+
Sensor.CellularStatus.COMMUNICATION_WITH_SERVER_DISABLED -> "ble_only"
|
|
96
|
+
Sensor.CellularStatus.OPERATING_CORRECTLY -> "cell_ok"
|
|
97
|
+
Sensor.CellularStatus.CLOUD_PROBLEM -> "no_server"
|
|
98
|
+
Sensor.CellularStatus.NETWORK_PROBLEM -> "net_issue"
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private fun mapConnectivityToCellular(status: Sensor.ConnectivityStatus): String? {
|
|
102
|
+
val n = status.name.uppercase()
|
|
103
|
+
return when {
|
|
104
|
+
n.contains("BLE_ONLY") || n.contains("BLUETOOTH_ONLY") -> "ble_only"
|
|
105
|
+
n.contains("SERVER") || n.contains("CLOUD") -> "no_server"
|
|
106
|
+
n.contains("NETWORK") || n.contains("NET") -> "net_issue"
|
|
107
|
+
n.contains("CELLULAR") && n.contains("OK") -> "cell_ok"
|
|
108
|
+
n.contains("WORKING") && n.contains("CORRECT") -> "cell_ok"
|
|
109
|
+
else -> null
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorBeaconDecoder.kt
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
package com.tempivo.sensor.beacon
|
|
2
2
|
|
|
3
|
+
import android.bluetooth.le.ScanRecord
|
|
3
4
|
import java.text.SimpleDateFormat
|
|
4
5
|
import java.util.Date
|
|
5
6
|
import java.util.Locale
|
|
@@ -116,6 +117,58 @@ object TempivoSensorBeaconDecoder {
|
|
|
116
117
|
return out
|
|
117
118
|
}
|
|
118
119
|
|
|
120
|
+
/**
|
|
121
|
+
* All Tempivo manufacturer payloads from a scan record.
|
|
122
|
+
*
|
|
123
|
+
* `getBytes()` may contain only the advertising `0x03` block while
|
|
124
|
+
* [ScanRecord.getManufacturerSpecificData] holds a **concatenated** `0x03` + `0x04` blob
|
|
125
|
+
* (Android merges both when they share company id `0x026C`). Always merge every source.
|
|
126
|
+
*/
|
|
127
|
+
fun manufacturerPayloadsFromScanRecord(record: ScanRecord): List<ByteArray> {
|
|
128
|
+
val out = mutableListOf<ByteArray>()
|
|
129
|
+
fun addUnique(payloads: List<ByteArray>) {
|
|
130
|
+
for (payload in payloads) {
|
|
131
|
+
if (payload.isEmpty()) continue
|
|
132
|
+
for (part in expandConcatenatedManufacturerPayload(payload)) {
|
|
133
|
+
if (out.none { it.contentEquals(part) }) {
|
|
134
|
+
out += part
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
addUnique(manufacturerPayloadsFromScanRecordBytes(record.bytes))
|
|
141
|
+
|
|
142
|
+
val sparse = record.manufacturerSpecificData
|
|
143
|
+
if (sparse != null) {
|
|
144
|
+
for (i in 0 until sparse.size()) {
|
|
145
|
+
if (sparse.keyAt(i) == MANUFACTURER_COMPANY_ID) {
|
|
146
|
+
sparse.valueAt(i)?.let { addUnique(listOf(it)) }
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
record.getManufacturerSpecificData(MANUFACTURER_COMPANY_ID)?.let { addUnique(listOf(it)) }
|
|
151
|
+
|
|
152
|
+
return out
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Android often returns one manufacturer blob with adv `0x03` immediately followed by
|
|
157
|
+
* scan-response `0x04`. Raw AD parsing may only surface the first block.
|
|
158
|
+
*/
|
|
159
|
+
private fun expandConcatenatedManufacturerPayload(payload: ByteArray): List<ByteArray> {
|
|
160
|
+
if (payload.size > ADV_FRAME_03_LEN && (payload[0].toInt() and 0xFF) == 0x03) {
|
|
161
|
+
val tail = payload.copyOfRange(ADV_FRAME_03_LEN, payload.size)
|
|
162
|
+
if (tail.isNotEmpty() && (tail[0].toInt() and 0xFF) == 0x04) {
|
|
163
|
+
return listOf(
|
|
164
|
+
payload.copyOfRange(0, ADV_FRAME_03_LEN),
|
|
165
|
+
tail,
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return listOf(payload)
|
|
170
|
+
}
|
|
171
|
+
|
|
119
172
|
/**
|
|
120
173
|
* @return true if any `0x03` / `0x04` frame was stored.
|
|
121
174
|
*/
|
|
@@ -236,7 +289,7 @@ object TempivoSensorBeaconDecoder {
|
|
|
236
289
|
summaryParts.add(text)
|
|
237
290
|
readings +=
|
|
238
291
|
TempivoSensorBeaconReading(
|
|
239
|
-
typeHex =
|
|
292
|
+
typeHex = SdkMeasurementFormat.labelForTypeId(mtype),
|
|
240
293
|
raw24 = raw24 and 0xFFFFFF,
|
|
241
294
|
text = text,
|
|
242
295
|
)
|