@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.
Files changed (49) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +159 -9
  3. package/android/build.gradle +2 -2
  4. package/android/src/main/AndroidManifest.xml +2 -1
  5. package/android/src/main/java/expo/modules/tempivosensorbeacon/SensorBeaconNative.java +18 -5
  6. package/android/src/main/java/expo/modules/tempivosensorbeacon/TempivoSensorBeaconModule.kt +11 -2
  7. package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/BleConnectCredentials.kt +25 -0
  8. package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/SdkMeasurementFormat.kt +111 -0
  9. package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/SensorBleRuntime.kt +39 -87
  10. package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/SensorBleVendor.kt +28 -0
  11. package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/SensorModelHint.kt +20 -0
  12. package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/SensorSdkScanTelemetry.kt +112 -0
  13. package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorBeaconDecoder.kt +54 -1
  14. package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorBeaconScanner.kt +160 -74
  15. package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorBeaconTypes.kt +2 -0
  16. package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorQr.kt +27 -9
  17. package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorSession.kt +20 -16
  18. package/dist/connect-helpers.d.ts +52 -0
  19. package/dist/connect-helpers.js +153 -0
  20. package/dist/decoder.d.ts +1 -2
  21. package/dist/decoder.js +7 -18
  22. package/dist/index.d.ts +7 -3
  23. package/dist/index.js +5 -3
  24. package/dist/model.d.ts +5 -0
  25. package/dist/model.js +25 -0
  26. package/dist/native-helpers.js +5 -2
  27. package/dist/native-module.js +7 -3
  28. package/dist/native-types.d.ts +2 -0
  29. package/dist/profile.d.ts +8 -1
  30. package/dist/profile.js +42 -19
  31. package/dist/qr.d.ts +3 -2
  32. package/dist/qr.js +8 -6
  33. package/dist/react-native.d.ts +28 -1
  34. package/dist/react-native.js +24 -1
  35. package/dist/session-types.d.ts +6 -3
  36. package/dist/tempivo-sensor-beacon.aar +0 -0
  37. package/ios/Frameworks/TempivoSensorBridge.xcframework/ios-arm64/TempivoSensorBridge.framework/Headers/TempivoSensorBridge.h +28 -1
  38. package/ios/Frameworks/TempivoSensorBridge.xcframework/ios-arm64/TempivoSensorBridge.framework/TempivoSensorBridge +0 -0
  39. package/ios/Frameworks/TempivoSensorBridge.xcframework/ios-arm64-simulator/TempivoSensorBridge.framework/Headers/TempivoSensorBridge.h +28 -1
  40. package/ios/Frameworks/TempivoSensorBridge.xcframework/ios-arm64-simulator/TempivoSensorBridge.framework/TempivoSensorBridge +0 -0
  41. package/ios/Sources/TempivoSensorBeacon/TempivoSensorBeaconDecoder.swift +8 -1
  42. package/ios/Sources/TempivoSensorBeacon/TempivoSensorBeaconScanner.swift +10 -1
  43. package/ios/Sources/TempivoSensorBeacon/TempivoSensorModelHint.swift +29 -0
  44. package/ios/Sources/TempivoSensorBeacon/TempivoSensorProfile.swift +37 -13
  45. package/ios/Sources/TempivoSensorBeacon/TempivoSensorQr.swift +48 -8
  46. package/ios/Sources/TempivoSensorBeacon/TempivoSensorSession.swift +24 -10
  47. package/ios/Sources/TempivoSensorBeacon/TempivoSensorSessionTypes.swift +1 -4
  48. package/ios/TempivoSensorBeaconModule.swift +214 -24
  49. package/package.json +3 -2
@@ -1,8 +1,20 @@
1
1
  import CoreBluetooth
2
2
  import ExpoModulesCore
3
+ import Foundation
3
4
 
4
- public class TempivoSensorBeaconModule: Module, TempivoSensorBeaconScannerDelegate {
5
+ #if canImport(TempivoSensorBridge)
6
+ import TempivoSensorBridge
7
+ #endif
8
+
9
+ public class TempivoSensorBeaconModule: Module {
10
+ #if canImport(TempivoSensorBridge)
11
+ private let scanBridge = TempivoSensorBridge()
12
+ private var sdkScanHandler: SensorSdkScanHandler?
13
+ fileprivate var sdkScanActive = false
14
+ private var lastSeenBySerial: [String: (deviceId: String, mac: String)] = [:]
15
+ #else
5
16
  private var scanner: TempivoSensorBeaconScanner?
17
+ #endif
6
18
  private let session = TempivoSensorSession()
7
19
 
8
20
  public func definition() -> ModuleDefinition {
@@ -10,26 +22,60 @@ public class TempivoSensorBeaconModule: Module, TempivoSensorBeaconScannerDelega
10
22
  Events("onDeviceFound")
11
23
 
12
24
  AsyncFunction("requestPermissions") { () -> [String: Any] in
13
- _ = self.requireScanner()
25
+ #if canImport(TempivoSensorBridge)
26
+ self.scanBridge.initialize()
27
+ #else
28
+ _ = self.requireCbScanner()
29
+ #endif
14
30
  return self.permissionPayload()
15
31
  }
16
32
 
17
33
  AsyncFunction("startScan") {
18
- self.requireScanner().startScanning()
34
+ #if canImport(TempivoSensorBridge)
35
+ self.scanBridge.initialize()
36
+ self.sdkScanActive = true
37
+ if self.sdkScanHandler == nil {
38
+ self.sdkScanHandler = SensorSdkScanHandler(module: self)
39
+ }
40
+ self.scanBridge.setDeviceScanHandler(handler: self.sdkScanHandler)
41
+ self.scanBridge.startDeviceScan()
42
+ #else
43
+ self.requireCbScanner().startScanning()
44
+ #endif
19
45
  }
20
46
 
21
47
  Function("stopScan") {
48
+ #if canImport(TempivoSensorBridge)
49
+ self.sdkScanActive = false
50
+ self.scanBridge.stopDeviceScan()
51
+ self.scanBridge.setDeviceScanHandler(handler: nil)
52
+ #else
22
53
  self.scanner?.stopScanning()
54
+ #endif
23
55
  }
24
56
 
25
57
  Function("isScanning") { () -> Bool in
26
- self.scanner?.scanning ?? false
58
+ #if canImport(TempivoSensorBridge)
59
+ return self.sdkScanActive
60
+ #else
61
+ return self.scanner?.scanning ?? false
62
+ #endif
27
63
  }
28
64
 
29
65
  AsyncFunction("connect") { (qrJson: String) -> [String: Any] in
30
66
  do {
67
+ #if canImport(TempivoSensorBridge)
68
+ self.pauseSdkScanForGatt()
69
+ #else
70
+ self.scanner?.stopScanning()
71
+ #endif
31
72
  let qr = try TempivoSensorQrParser.parse(qrJson)
32
- try self.session.connect(qr: qr)
73
+ let resolved = self.resolveConnectTarget(qr: qr)
74
+ try self.session.connect(
75
+ qr: qr,
76
+ deviceId: resolved.deviceId,
77
+ bluetoothMac: resolved.bluetoothMac
78
+ )
33
79
  return self.qrPayload(qr)
34
80
  } catch {
35
81
  throw self.mapError(error)
@@ -59,7 +105,7 @@ public class TempivoSensorBeaconModule: Module, TempivoSensorBeaconScannerDelega
59
105
  AsyncFunction("triggerTransmission") { () -> [String: Any] in
60
106
  do {
61
107
  let result = try self.session.triggerTransmission()
62
- return ["ok": result.ok, "supported": result.supported]
108
+ return ["ok": result.ok]
63
109
  } catch {
64
110
  throw self.mapError(error)
65
111
  }
@@ -68,33 +114,29 @@ public class TempivoSensorBeaconModule: Module, TempivoSensorBeaconScannerDelega
68
114
  AsyncFunction("getCalibration") { () -> [String: Any] in
69
115
  do {
70
116
  let cal = try self.session.getCalibration()
71
- var payload: [String: Any] = [:]
72
- if let date = cal.laboratoryCalibrationDate {
73
- payload["laboratoryCalibrationDate"] = date
74
- }
75
- if let ts = cal.laboratoryCalibrationTimestamp {
76
- payload["laboratoryCalibrationTimestamp"] = Int(ts)
77
- }
78
- return payload
117
+ return [
118
+ "laboratoryCalibrationDate": cal.laboratoryCalibrationDate as Any,
119
+ "laboratoryCalibrationTimestamp": cal.laboratoryCalibrationTimestamp.map { Int($0) } as Any,
120
+ ]
79
121
  } catch {
80
122
  throw self.mapError(error)
81
123
  }
82
124
  }
83
125
 
84
126
  OnDestroy {
127
+ #if canImport(TempivoSensorBridge)
128
+ self.sdkScanActive = false
129
+ self.scanBridge.stopDeviceScan()
130
+ self.scanBridge.setDeviceScanHandler(handler: nil)
131
+ #else
85
132
  self.scanner?.stopScanning()
133
+ #endif
86
134
  self.session.disconnect()
87
135
  }
88
136
  }
89
137
 
90
- public func sensorBeaconScanner(
91
- _ scanner: TempivoSensorBeaconScanner,
92
- didDiscover discovery: SensorBeaconDiscovery
93
- ) {
94
- sendEvent("onDeviceFound", devicePayload(discovery))
95
- }
96
-
97
- private func requireScanner() -> TempivoSensorBeaconScanner {
138
+ #if !canImport(TempivoSensorBridge)
139
+ private func requireCbScanner() -> TempivoSensorBeaconScanner {
98
140
  if let scanner {
99
141
  return scanner
100
142
  }
@@ -103,11 +145,129 @@ public class TempivoSensorBeaconModule: Module, TempivoSensorBeaconScannerDelega
103
145
  scanner = created
104
146
  return created
105
147
  }
148
+ #endif
149
+
150
+ #if canImport(TempivoSensorBridge)
151
+ fileprivate func rememberSeenDevice(_ payload: [String: Any]) {
152
+ guard let deviceId = payload["deviceId"] as? String,
153
+ let mac = payload["bluetoothMacAddress"] as? String else { return }
154
+ let serial = (payload["serialNumber"] as? String)?
155
+ .trimmingCharacters(in: .whitespacesAndNewlines)
156
+ .uppercased()
157
+ if let serial, !serial.isEmpty {
158
+ lastSeenBySerial[serial] = (deviceId, mac)
159
+ }
160
+ let macKey = mac.replacingOccurrences(of: ":", with: "").uppercased()
161
+ if macKey.count == 12 {
162
+ lastSeenBySerial[macKey] = (deviceId, mac)
163
+ }
164
+ }
165
+
166
+ fileprivate func pauseSdkScanForGatt() {
167
+ sdkScanActive = false
168
+ scanBridge.stopDeviceScan()
169
+ scanBridge.setDeviceScanHandler(handler: nil)
170
+ }
171
+
172
+ private func resolveConnectTarget(qr: TempivoSensorQr) -> (deviceId: String, bluetoothMac: String) {
173
+ let serialKey = TempivoSensorQrParser.normalizeSerial(qr.serial)
174
+ let seen = lastSeenBySerial[serialKey] ?? lastSeenBySerial[String(serialKey.suffix(12))]
175
+ let deviceId = qr.deviceId ?? seen?.deviceId ?? serialKey
176
+ let mac = seen?.mac ?? qr.bluetoothMac
177
+ return (deviceId, mac)
178
+ }
179
+
180
+ fileprivate func devicePayloadFromBridgeJson(_ json: String) -> [String: Any]? {
181
+ guard let data = json.data(using: .utf8),
182
+ let dict = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
183
+ return nil
184
+ }
185
+ guard let deviceId = dict["deviceId"] as? String else { return nil }
186
+ var payload: [String: Any] = ["deviceId": deviceId]
187
+ if let rssi = dict["rssi"] as? Int {
188
+ payload["rssi"] = rssi
189
+ } else if let rssi = dict["rssi"] as? Double {
190
+ payload["rssi"] = Int(rssi)
191
+ }
192
+ if let serial = dict["serialNumber"] as? String {
193
+ payload["serialNumber"] = serial
194
+ }
195
+ if let mac = dict["bluetoothMacAddress"] as? String {
196
+ payload["bluetoothMacAddress"] = mac
197
+ }
198
+ if let model = dict["model"] as? String, !model.isEmpty {
199
+ payload["model"] = model
200
+ } else if let telem = dict["advertisementTelemetry"] as? [String: Any],
201
+ let firmware = telem["firmware"] as? String {
202
+ payload["model"] = TempivoSensorModelHint.fromFirmware(firmware)
203
+ } else {
204
+ payload["model"] = TempivoSensorModelHint.defaultModel
205
+ }
206
+ if let summary = dict["advertisementSummary"] as? String, !summary.isEmpty {
207
+ payload["summary"] = summary
208
+ }
209
+ if let telem = dict["advertisementTelemetry"] as? [String: Any] {
210
+ payload["telemetry"] = telemetryPayloadFromBridge(telem)
211
+ if payload["summary"] == nil, let s = telem["summary"] as? String, !s.isEmpty {
212
+ payload["summary"] = s
213
+ }
214
+ }
215
+ if payload["summary"] == nil {
216
+ payload["summary"] = payload["serialNumber"] as? String ?? deviceId
217
+ }
218
+ rememberSeenDevice(payload)
219
+ return payload
220
+ }
221
+
222
+ private func telemetryPayloadFromBridge(_ telem: [String: Any]) -> [String: Any] {
223
+ var payload: [String: Any] = [:]
224
+ if let firmware = telem["firmware"] as? String { payload["firmware"] = firmware }
225
+ if let batteryOk = telem["batteryOk"] as? Bool { payload["batteryOk"] = batteryOk }
226
+ if let encryptionEnabled = telem["encryptionEnabled"] as? Bool {
227
+ payload["encryptionEnabled"] = encryptionEnabled
228
+ }
229
+ if let cellularStatus = telem["cellularStatus"] as? String {
230
+ payload["cellularStatus"] = cellularStatus
231
+ }
232
+ if let measurementCounter = telem["measurementCounter"] as? Int {
233
+ payload["measurementCounter"] = measurementCounter
234
+ } else if let measurementCounter = telem["measurementCounter"] as? Int64 {
235
+ payload["measurementCounter"] = Int(measurementCounter)
236
+ }
237
+ if let readingTimestampUnix = telem["readingTimestampUnix"] as? Int {
238
+ payload["readingTimestampUnix"] = readingTimestampUnix
239
+ } else if let readingTimestampUnix = telem["readingTimestampUnix"] as? Int64 {
240
+ payload["readingTimestampUnix"] = Int(readingTimestampUnix)
241
+ }
242
+ if let periodBase = telem["periodBaseSeconds"] as? Int,
243
+ let periodFactor = telem["periodFactor"] as? Int,
244
+ periodBase > 0 {
245
+ payload["measurementIntervalSeconds"] = periodBase * max(periodFactor, 1)
246
+ }
247
+ if let readings = telem["readings"] as? [[String: Any]] {
248
+ payload["readings"] = readings.map { row in
249
+ var out: [String: Any] = [:]
250
+ if let typeHex = row["typeHex"] as? String { out["typeHex"] = typeHex }
251
+ if let text = row["text"] as? String { out["text"] = text }
252
+ if let raw24 = row["raw24"] as? Int { out["raw24"] = raw24 }
253
+ return out
254
+ }
255
+ } else {
256
+ payload["readings"] = [] as [[String: Any]]
257
+ }
258
+ if let readingsCount = telem["readingsCount"] as? Int {
259
+ payload["readingsCount"] = readingsCount
260
+ }
261
+ if let summary = telem["summary"] as? String {
262
+ payload["summary"] = summary
263
+ }
264
+ return payload
265
+ }
266
+ #endif
106
267
 
107
268
  private func permissionPayload() -> [String: Any] {
108
269
  if #available(iOS 13.1, *) {
109
270
  let status = CBManager.authorization
110
- let granted = status == .allowedAlways
111
271
  let statusName: String
112
272
  switch status {
113
273
  case .allowedAlways: statusName = "granted"
@@ -115,7 +275,7 @@ public class TempivoSensorBeaconModule: Module, TempivoSensorBeaconScannerDelega
115
275
  default: statusName = "denied"
116
276
  }
117
277
  return [
118
- "granted": granted,
278
+ "granted": status == .allowedAlways || status == .notDetermined,
119
279
  "status": statusName,
120
280
  "canAskAgain": status == .notDetermined,
121
281
  "expires": "never",
@@ -156,6 +316,9 @@ public class TempivoSensorBeaconModule: Module, TempivoSensorBeaconScannerDelega
156
316
  if let reading = discovery.reading {
157
317
  payload["summary"] = reading.summary
158
318
  payload["telemetry"] = telemetryPayload(reading)
319
+ payload["model"] = TempivoSensorModelHint.fromFirmware(reading.firmware)
320
+ } else {
321
+ payload["model"] = TempivoSensorModelHint.defaultModel
159
322
  }
160
323
  return payload
161
324
  }
@@ -193,6 +356,33 @@ public class TempivoSensorBeaconModule: Module, TempivoSensorBeaconScannerDelega
193
356
  }
194
357
  }
195
358
 
359
+ #if canImport(TempivoSensorBridge)
360
+ private final class SensorSdkScanHandler: SensorDeviceScanHandler {
361
+ private weak var module: TempivoSensorBeaconModule?
362
+
363
+ init(module: TempivoSensorBeaconModule) {
364
+ self.module = module
365
+ }
366
+
367
+ override func onDevice(deviceJson: String) {
368
+ guard let module, module.sdkScanActive else { return }
369
+ guard let payload = module.devicePayloadFromBridgeJson(deviceJson) else { return }
370
+ module.sendEvent("onDeviceFound", payload)
371
+ }
372
+ }
373
+ #endif
374
+
375
+ #if !canImport(TempivoSensorBridge)
376
+ extension TempivoSensorBeaconModule: TempivoSensorBeaconScannerDelegate {
377
+ public func sensorBeaconScanner(
378
+ _ scanner: TempivoSensorBeaconScanner,
379
+ didDiscover discovery: SensorBeaconDiscovery
380
+ ) {
381
+ sendEvent("onDeviceFound", devicePayload(discovery))
382
+ }
383
+ }
384
+ #endif
385
+
196
386
  internal final class SensorBeaconException: Exception {
197
387
  private let customCode: String
198
388
  private let customReason: String
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tempivo/sensor-beacon",
3
- "version": "0.3.3",
3
+ "version": "0.4.7",
4
4
  "description": "Tempivo sensor BLE SDK for Expo, React Native, Android, iOS, and Node.",
5
5
  "homepage": "https://app.tempivo.com/integrations",
6
6
  "repository": {
@@ -40,7 +40,8 @@
40
40
  "expo-module.config.json",
41
41
  "ios",
42
42
  "LICENSE",
43
- "README.md"
43
+ "README.md",
44
+ "CHANGELOG.md"
44
45
  ],
45
46
  "publishConfig": {
46
47
  "access": "public"