@tempivo/sensor-beacon 0.2.0 → 0.3.2

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 (61) hide show
  1. package/README.md +142 -114
  2. package/android/build.gradle +38 -2
  3. package/android/src/main/AndroidManifest.xml +10 -0
  4. package/android/src/main/java/expo/modules/tempivosensorbeacon/SensorBeaconNative.java +241 -0
  5. package/android/src/main/java/expo/modules/tempivosensorbeacon/TempivoSensorBeaconModule.kt +124 -0
  6. package/android-aar/build.gradle +4 -0
  7. package/{android → android-aar}/library/src/main/java/com/tempivo/sensor/beacon/PartnerBleRules.kt +6 -3
  8. package/{android → android-aar}/library/src/main/java/com/tempivo/sensor/beacon/SensorBleRuntime.kt +81 -38
  9. package/{android → android-aar}/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorBeaconDecoder.kt +14 -0
  10. package/{android → android-aar}/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorBeaconScanner.kt +40 -11
  11. package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorError.kt +35 -0
  12. package/{android → android-aar}/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorQr.kt +9 -0
  13. package/{android → android-aar}/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorSession.kt +40 -7
  14. package/app.plugin.cjs +142 -0
  15. package/dist/expo-modules-core.web-stub.d.ts +5 -0
  16. package/dist/expo-modules-core.web-stub.js +9 -0
  17. package/dist/index.d.ts +3 -1
  18. package/dist/index.js +2 -1
  19. package/dist/native-bridge.d.ts +14 -0
  20. package/dist/native-bridge.js +51 -0
  21. package/dist/native-helpers.d.ts +6 -0
  22. package/dist/native-helpers.js +48 -0
  23. package/dist/native-module.d.ts +14 -0
  24. package/dist/native-module.js +105 -0
  25. package/dist/native-types.d.ts +41 -0
  26. package/dist/native-types.js +1 -0
  27. package/dist/qr.d.ts +4 -0
  28. package/dist/qr.js +17 -0
  29. package/dist/react-native.d.ts +1 -0
  30. package/dist/react-native.js +1 -0
  31. package/dist/tempivo-sensor-beacon.aar +0 -0
  32. package/expo-module.config.json +12 -0
  33. package/ios/Frameworks/TempivoSensorBridge.xcframework/Info.plist +43 -0
  34. package/ios/Frameworks/TempivoSensorBridge.xcframework/ios-arm64/TempivoSensorBridge.framework/Headers/TempivoSensorBridge.h +161 -0
  35. package/ios/Frameworks/TempivoSensorBridge.xcframework/ios-arm64/TempivoSensorBridge.framework/Info.plist +35 -0
  36. package/ios/Frameworks/TempivoSensorBridge.xcframework/ios-arm64/TempivoSensorBridge.framework/Modules/module.modulemap +8 -0
  37. package/ios/Frameworks/TempivoSensorBridge.xcframework/ios-arm64/TempivoSensorBridge.framework/TempivoSensorBridge +0 -0
  38. package/ios/Frameworks/TempivoSensorBridge.xcframework/ios-arm64-simulator/TempivoSensorBridge.framework/Headers/TempivoSensorBridge.h +161 -0
  39. package/ios/Frameworks/TempivoSensorBridge.xcframework/ios-arm64-simulator/TempivoSensorBridge.framework/Info.plist +31 -0
  40. package/ios/Frameworks/TempivoSensorBridge.xcframework/ios-arm64-simulator/TempivoSensorBridge.framework/Modules/module.modulemap +8 -0
  41. package/ios/Frameworks/TempivoSensorBridge.xcframework/ios-arm64-simulator/TempivoSensorBridge.framework/TempivoSensorBridge +0 -0
  42. package/ios/Sources/TempivoSensorBeacon/TempivoSensorBeaconScanner.swift +80 -4
  43. package/ios/Sources/TempivoSensorBeacon/TempivoSensorBeaconTypes.swift +15 -0
  44. package/ios/Sources/TempivoSensorBeacon/TempivoSensorError.swift +15 -1
  45. package/ios/Sources/TempivoSensorBeacon/TempivoSensorQr.swift +17 -0
  46. package/ios/Sources/TempivoSensorBeacon/TempivoSensorSession.swift +36 -8
  47. package/ios/TempivoSensorBeacon.podspec +35 -0
  48. package/ios/TempivoSensorBeaconModule.swift +217 -0
  49. package/package.json +48 -7
  50. package/android/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorError.kt +0 -18
  51. /package/{android → android-aar}/gradle/wrapper/gradle-wrapper.jar +0 -0
  52. /package/{android → android-aar}/gradle/wrapper/gradle-wrapper.properties +0 -0
  53. /package/{android → android-aar}/gradle.properties +0 -0
  54. /package/{android → android-aar}/gradlew +0 -0
  55. /package/{android → android-aar}/library/build.gradle +0 -0
  56. /package/{android → android-aar}/library/consumer-rules.pro +0 -0
  57. /package/{android → android-aar}/library/src/main/AndroidManifest.xml +0 -0
  58. /package/{android → android-aar}/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorBeaconTypes.kt +0 -0
  59. /package/{android → android-aar}/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorCalibration.kt +0 -0
  60. /package/{android → android-aar}/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorProfile.kt +0 -0
  61. /package/{android → android-aar}/settings.gradle +0 -0
@@ -15,10 +15,19 @@ public final class TempivoSensorSession: @unchecked Sendable {
15
15
 
16
16
  public init() {}
17
17
 
18
- public func connect(serial: String, bluetoothMac: String, pin: String, sessionType: TempivoSensorSessionType = .modern) throws {
19
- guard let pinInt = Int32(pin.trimmingCharacters(in: .whitespacesAndNewlines)) else {
18
+ public func connect(
19
+ serial: String,
20
+ bluetoothMac: String,
21
+ pin: String,
22
+ sessionType: TempivoSensorSessionType = .modern,
23
+ deviceId: String? = nil,
24
+ firmware: String? = nil
25
+ ) throws {
26
+ let trimmedPin = pin.trimmingCharacters(in: .whitespacesAndNewlines)
27
+ guard let pinInt = Int32(trimmedPin) else {
20
28
  throw TempivoSensorError(.invalidPin, "PIN must be numeric.")
21
29
  }
30
+ let resolved = TempivoSensorQrParser.sessionType(fromModel: sessionType == .legacy ? "HC5" : "HC7", firmware: firmware)
22
31
  #if canImport(TempivoSensorBridge)
23
32
  lock.lock()
24
33
  defer { lock.unlock() }
@@ -28,7 +37,9 @@ public final class TempivoSensorSession: @unchecked Sendable {
28
37
  serial: TempivoSensorQrParser.normalizeSerial(serial),
29
38
  bluetoothMac: bluetoothMac,
30
39
  pin: pinInt,
31
- legacy: sessionType == .legacy
40
+ legacy: resolved == .legacy,
41
+ deviceId: deviceId?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "",
42
+ encryptionKey: trimmedPin
32
43
  )
33
44
  )
34
45
  try throwIfBridgeError(result)
@@ -38,8 +49,22 @@ public final class TempivoSensorSession: @unchecked Sendable {
38
49
  #endif
39
50
  }
40
51
 
41
- public func connect(qr: TempivoSensorQr) throws {
42
- try connect(serial: qr.serial, bluetoothMac: qr.bluetoothMac, pin: qr.pin, sessionType: qr.sessionType)
52
+ public func connect(qr: TempivoSensorQr, seen: TempivoSensorSeenDevice? = nil) throws {
53
+ let deviceId = seen?.deviceId.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
54
+ if deviceId.isEmpty {
55
+ throw TempivoSensorError(
56
+ .connectFailed,
57
+ "Sensor not in last scan. Scan first, then connect."
58
+ )
59
+ }
60
+ try connect(
61
+ serial: qr.serial,
62
+ bluetoothMac: seen?.bluetoothMac ?? qr.bluetoothMac,
63
+ pin: qr.pin,
64
+ sessionType: qr.sessionType,
65
+ deviceId: deviceId,
66
+ firmware: seen?.firmware
67
+ )
43
68
  }
44
69
 
45
70
  public func disconnect() {
@@ -148,14 +173,17 @@ public final class TempivoSensorSession: @unchecked Sendable {
148
173
 
149
174
  private func throwIfBridgeError(_ result: [String: Any]) throws {
150
175
  guard let code = intValue(result["errorCode"]), code != 0 else { return }
176
+ let detail = TempivoSensorError.sanitizeClientMessage(
177
+ (result["message"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
178
+ )
151
179
  switch code {
152
180
  case 1: throw TempivoSensorError(.invalidPin, "Invalid PIN.")
153
181
  case 2: throw TempivoSensorError(.notConnected, "Not connected.")
154
182
  case 3: throw TempivoSensorError(.unsupportedCommand, "Unsupported command.")
155
183
  case 4: throw TempivoSensorError(.runtimeUnavailable, "Sensor GATT is not available on this device.")
156
- case 5: throw TempivoSensorError(.connectFailed, "Could not connect.")
157
- case 6: throw TempivoSensorError(.invalidConfig, (result["message"] as? String) ?? "Invalid configuration.")
158
- default: throw TempivoSensorError(.unknown, "Sensor request failed.")
184
+ case 5: throw TempivoSensorError(.connectFailed, detail.isEmpty ? "Could not connect." : "Could not connect. \(detail)")
185
+ case 6: throw TempivoSensorError(.invalidConfig, detail.isEmpty ? "Invalid configuration." : detail)
186
+ default: throw TempivoSensorError(.unknown, detail.isEmpty ? "Sensor request failed." : "Sensor request failed. \(detail)")
159
187
  }
160
188
  }
161
189
  }
@@ -0,0 +1,35 @@
1
+ require 'json'
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = 'TempivoSensorBeacon'
7
+ s.version = package['version']
8
+ s.summary = package['description']
9
+ s.description = package['description']
10
+ s.license = package['license']
11
+ s.author = package['author'] || 'Tempivo'
12
+ s.homepage = package['homepage']
13
+ s.platforms = { :ios => '15.1' }
14
+ s.swift_version = '5.9'
15
+ s.source = { git: 'https://github.com/tempivo/tempivo-app.git' }
16
+ s.static_framework = true
17
+ s.dependency 'ExpoModulesCore'
18
+ s.frameworks = 'CoreBluetooth'
19
+
20
+ s.pod_target_xcconfig = {
21
+ 'DEFINES_MODULE' => 'YES',
22
+ 'SWIFT_COMPILATION_MODE' => 'wholemodule',
23
+ 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'x86_64'
24
+ }
25
+ s.user_target_xcconfig = {
26
+ 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'x86_64'
27
+ }
28
+
29
+ s.source_files = 'TempivoSensorBeaconModule.swift', 'Sources/TempivoSensorBeacon/**/*.swift'
30
+
31
+ bridge = File.join(__dir__, 'Frameworks/TempivoSensorBridge.xcframework')
32
+ if File.directory?(bridge)
33
+ s.vendored_frameworks = 'Frameworks/TempivoSensorBridge.xcframework'
34
+ end
35
+ end
@@ -0,0 +1,217 @@
1
+ import CoreBluetooth
2
+ import ExpoModulesCore
3
+
4
+ public class TempivoSensorBeaconModule: Module, TempivoSensorBeaconScannerDelegate {
5
+ private var scanner: TempivoSensorBeaconScanner?
6
+ private let session = TempivoSensorSession()
7
+
8
+ public func definition() -> ModuleDefinition {
9
+ Name("TempivoSensorBeacon")
10
+ Events("onDeviceFound")
11
+
12
+ AsyncFunction("requestPermissions") { () -> [String: Any] in
13
+ _ = self.requireScanner()
14
+ return self.permissionPayload()
15
+ }
16
+
17
+ AsyncFunction("startScan") {
18
+ self.requireScanner().startScanning()
19
+ }
20
+
21
+ Function("stopScan") {
22
+ self.scanner?.stopScanning()
23
+ }
24
+
25
+ Function("isScanning") { () -> Bool in
26
+ self.scanner?.scanning ?? false
27
+ }
28
+
29
+ AsyncFunction("connect") { (qrJson: String) -> [String: Any] in
30
+ do {
31
+ self.scanner?.stopScanning()
32
+ if !Thread.isMainThread {
33
+ Thread.sleep(forTimeInterval: 0.2)
34
+ }
35
+ let qr = try TempivoSensorQrParser.parse(qrJson)
36
+ let seen = self.scanner?.lookup(serial: qr.serial)
37
+ try self.session.connect(qr: qr, seen: seen)
38
+ return self.qrPayload(qr)
39
+ } catch {
40
+ throw self.mapError(error)
41
+ }
42
+ }
43
+
44
+ AsyncFunction("disconnect") {
45
+ self.session.disconnect()
46
+ }
47
+
48
+ AsyncFunction("getConfigurationJson") { () -> String in
49
+ do {
50
+ return try self.session.getConfigurationJson()
51
+ } catch {
52
+ throw self.mapError(error)
53
+ }
54
+ }
55
+
56
+ AsyncFunction("setConfigurationJson") { (json: String) in
57
+ do {
58
+ try self.session.setConfigurationJson(json)
59
+ } catch {
60
+ throw self.mapError(error)
61
+ }
62
+ }
63
+
64
+ AsyncFunction("triggerTransmission") { () -> [String: Any] in
65
+ do {
66
+ let result = try self.session.triggerTransmission()
67
+ return ["ok": result.ok, "supported": result.supported]
68
+ } catch {
69
+ throw self.mapError(error)
70
+ }
71
+ }
72
+
73
+ AsyncFunction("getCalibration") { () -> [String: Any] in
74
+ do {
75
+ let cal = try self.session.getCalibration()
76
+ var payload: [String: Any] = [:]
77
+ if let date = cal.laboratoryCalibrationDate {
78
+ payload["laboratoryCalibrationDate"] = date
79
+ }
80
+ if let ts = cal.laboratoryCalibrationTimestamp {
81
+ payload["laboratoryCalibrationTimestamp"] = Int(ts)
82
+ }
83
+ return payload
84
+ } catch {
85
+ throw self.mapError(error)
86
+ }
87
+ }
88
+
89
+ OnDestroy {
90
+ self.scanner?.stopScanning()
91
+ self.session.disconnect()
92
+ }
93
+ }
94
+
95
+ public func sensorBeaconScanner(
96
+ _ scanner: TempivoSensorBeaconScanner,
97
+ didDiscover discovery: SensorBeaconDiscovery
98
+ ) {
99
+ sendEvent("onDeviceFound", devicePayload(discovery))
100
+ }
101
+
102
+ private func requireScanner() -> TempivoSensorBeaconScanner {
103
+ if let scanner {
104
+ return scanner
105
+ }
106
+ let created = TempivoSensorBeaconScanner()
107
+ created.delegate = self
108
+ scanner = created
109
+ return created
110
+ }
111
+
112
+ private func permissionPayload() -> [String: Any] {
113
+ if #available(iOS 13.1, *) {
114
+ let status = CBManager.authorization
115
+ let granted = status == .allowedAlways
116
+ let statusName: String
117
+ switch status {
118
+ case .allowedAlways: statusName = "granted"
119
+ case .notDetermined: statusName = "undetermined"
120
+ default: statusName = "denied"
121
+ }
122
+ return [
123
+ "granted": granted,
124
+ "status": statusName,
125
+ "canAskAgain": status == .notDetermined,
126
+ "expires": "never",
127
+ ]
128
+ }
129
+ return [
130
+ "granted": true,
131
+ "status": "granted",
132
+ "canAskAgain": true,
133
+ "expires": "never",
134
+ ]
135
+ }
136
+
137
+ private func qrPayload(_ qr: TempivoSensorQr) -> [String: Any] {
138
+ var payload: [String: Any] = [
139
+ "serial": qr.serial,
140
+ "pin": qr.pin,
141
+ "sessionType": qr.sessionType.rawValue,
142
+ "bluetoothMac": qr.bluetoothMac,
143
+ ]
144
+ if let model = qr.model {
145
+ payload["model"] = model
146
+ }
147
+ return payload
148
+ }
149
+
150
+ private func devicePayload(_ discovery: SensorBeaconDiscovery) -> [String: Any] {
151
+ var payload: [String: Any] = [
152
+ "deviceId": discovery.deviceId,
153
+ "rssi": discovery.rssi,
154
+ ]
155
+ if let serial = discovery.serialNumber {
156
+ payload["serialNumber"] = serial
157
+ }
158
+ if let mac = discovery.bluetoothMacAddress {
159
+ payload["bluetoothMacAddress"] = mac
160
+ }
161
+ if let reading = discovery.reading {
162
+ payload["summary"] = reading.summary
163
+ payload["telemetry"] = telemetryPayload(reading)
164
+ }
165
+ return payload
166
+ }
167
+
168
+ private func telemetryPayload(_ reading: SensorBeaconReading) -> [String: Any] {
169
+ var payload: [String: Any] = [
170
+ "readingsCount": reading.readingsCount,
171
+ "summary": reading.summary,
172
+ "readings": reading.readings.map { slot in
173
+ [
174
+ "typeHex": slot.typeHex,
175
+ "raw24": slot.raw24,
176
+ "text": slot.text,
177
+ ] as [String: Any]
178
+ },
179
+ ]
180
+ if let firmware = reading.firmware { payload["firmware"] = firmware }
181
+ if let batteryOk = reading.batteryOk { payload["batteryOk"] = batteryOk }
182
+ if let encryptionEnabled = reading.encryptionEnabled { payload["encryptionEnabled"] = encryptionEnabled }
183
+ if let cellularStatus = reading.cellularStatus { payload["cellularStatus"] = cellularStatus }
184
+ if let measurementCounter = reading.measurementCounter { payload["measurementCounter"] = measurementCounter }
185
+ if let readingTimestampUnix = reading.readingTimestampUnix { payload["readingTimestampUnix"] = readingTimestampUnix }
186
+ if let readingTimestampIso = reading.readingTimestampIso { payload["readingTimestampIso"] = readingTimestampIso }
187
+ if let measurementIntervalSeconds = reading.measurementIntervalSeconds {
188
+ payload["measurementIntervalSeconds"] = measurementIntervalSeconds
189
+ }
190
+ return payload
191
+ }
192
+
193
+ private func mapError(_ error: Error) -> SensorBeaconException {
194
+ if let sensor = error as? TempivoSensorError {
195
+ return SensorBeaconException(code: sensor.code.rawValue, reason: sensor.message)
196
+ }
197
+ let reason = TempivoSensorError.sanitizeClientMessage(error.localizedDescription)
198
+ return SensorBeaconException(
199
+ code: "unknown",
200
+ reason: reason.isEmpty ? "Sensor request failed." : reason
201
+ )
202
+ }
203
+ }
204
+
205
+ internal final class SensorBeaconException: Exception {
206
+ private let customCode: String
207
+ private let customReason: String
208
+
209
+ init(code: String, reason: String) {
210
+ self.customCode = code
211
+ self.customReason = reason
212
+ super.init()
213
+ }
214
+
215
+ override var code: String { customCode }
216
+ override var reason: String { customReason }
217
+ }
package/package.json CHANGED
@@ -1,20 +1,38 @@
1
1
  {
2
2
  "name": "@tempivo/sensor-beacon",
3
- "version": "0.2.0",
4
- "description": "Tempivo sensor BLE SDK: advertisement decode plus GATT session (config rules, transmission trigger, lab calibration date).",
3
+ "version": "0.3.2",
4
+ "description": "Tempivo sensor BLE SDK for Expo, React Native, Android, iOS, and Node.",
5
5
  "homepage": "https://app.tempivo.com/integrations",
6
+ "author": "Tempivo",
6
7
  "type": "module",
7
8
  "main": "dist/index.js",
8
9
  "types": "dist/index.d.ts",
10
+ "react-native": "./dist/react-native.js",
9
11
  "exports": {
10
12
  ".": {
13
+ "react-native": {
14
+ "types": "./dist/react-native.d.ts",
15
+ "default": "./dist/react-native.js"
16
+ },
11
17
  "types": "./dist/index.d.ts",
12
- "import": "./dist/index.js"
13
- }
18
+ "import": "./dist/index.js",
19
+ "default": "./dist/index.js"
20
+ },
21
+ "./react-native": {
22
+ "types": "./dist/react-native.d.ts",
23
+ "default": "./dist/react-native.js"
24
+ },
25
+ "./app.plugin": "./app.plugin.cjs",
26
+ "./app.plugin.js": "./app.plugin.cjs",
27
+ "./app.plugin.cjs": "./app.plugin.cjs",
28
+ "./package.json": "./package.json"
14
29
  },
15
30
  "files": [
31
+ "app.plugin.cjs",
16
32
  "dist",
17
33
  "android",
34
+ "android-aar",
35
+ "expo-module.config.json",
18
36
  "ios",
19
37
  "LICENSE",
20
38
  "README.md"
@@ -29,7 +47,8 @@
29
47
  "build": "npm run build:ts",
30
48
  "build:ts": "tsc",
31
49
  "build:android": "node scripts/build-aar.mjs",
32
- "build:ios-bridge": "bash scripts/build-ios-bridge.sh",
50
+ "build:ios-bridge": "bash scripts/build-ios-bridge.sh && node scripts/copy-ios-bridge.mjs",
51
+ "copy:ios-bridge": "node scripts/copy-ios-bridge.mjs",
33
52
  "build:all": "npm run build:ts && npm run build:android",
34
53
  "prepare": "npm run build:ts",
35
54
  "test": "npm run test:all",
@@ -37,7 +56,7 @@
37
56
  "test:python": "PYTHONPATH=python python3 -m pytest python/tests -q",
38
57
  "test:all": "npm run test:unit && npm run test:python",
39
58
  "test:smoke": "node scripts/smoke-test.mjs",
40
- "prepublishOnly": "npm run build:all"
59
+ "prepublishOnly": "npm run build:all && npm run copy:ios-bridge"
41
60
  },
42
61
  "keywords": [
43
62
  "tempivo",
@@ -48,9 +67,31 @@
48
67
  "sensor",
49
68
  "manufacturer-data",
50
69
  "android",
51
- "ios"
70
+ "ios",
71
+ "expo",
72
+ "react-native"
52
73
  ],
53
74
  "license": "MIT",
75
+ "peerDependencies": {
76
+ "expo": "*",
77
+ "expo-modules-core": "*",
78
+ "react": "*",
79
+ "react-native": "*"
80
+ },
81
+ "peerDependenciesMeta": {
82
+ "expo": {
83
+ "optional": true
84
+ },
85
+ "expo-modules-core": {
86
+ "optional": true
87
+ },
88
+ "react": {
89
+ "optional": true
90
+ },
91
+ "react-native": {
92
+ "optional": true
93
+ }
94
+ },
54
95
  "devDependencies": {
55
96
  "@types/jest": "^29.5.14",
56
97
  "jest": "^29.7.0",
@@ -1,18 +0,0 @@
1
- package com.tempivo.sensor.beacon
2
-
3
- class TempivoSensorException(
4
- val code: Code,
5
- message: String,
6
- cause: Throwable? = null,
7
- ) : Exception(message, cause) {
8
- enum class Code {
9
- INVALID_PIN,
10
- INVALID_QR,
11
- INVALID_CONFIG,
12
- NOT_CONNECTED,
13
- UNSUPPORTED_COMMAND,
14
- RUNTIME_UNAVAILABLE,
15
- CONNECT_FAILED,
16
- UNKNOWN,
17
- }
18
- }
File without changes
File without changes
File without changes
File without changes