@tempivo/sensor-beacon 1.1.1 → 1.1.4
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 +43 -0
- package/README.md +38 -23
- package/android/build.gradle +2 -2
- package/android/src/main/java/expo/modules/tempivosensorbeacon/SensorBeaconNative.java +8 -0
- package/android/src/main/java/expo/modules/tempivosensorbeacon/TempivoSensorBeaconModule.kt +8 -0
- package/android-aar/library/build.gradle +5 -0
- package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/EfentoRawConfigTransfer.kt +672 -0
- package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/PartnerBleRules.kt +188 -52
- package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/SensorBleRuntime.kt +20 -0
- package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorProfile.kt +28 -0
- package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorSession.kt +142 -11
- package/android-aar/library/src/test/java/com/tempivo/sensor/beacon/PartnerBleRulesTest.kt +209 -0
- package/dist/native-helpers.js +3 -1
- package/dist/native-module.d.ts +10 -0
- package/dist/native-module.js +46 -3
- package/dist/partner-dual-range-fixture.d.ts +28 -0
- package/dist/partner-dual-range-fixture.js +29 -0
- package/dist/profile.js +66 -32
- package/dist/proto/decode-proto-config.d.ts +3 -0
- package/dist/proto/decode-proto-config.js +46 -0
- package/dist/proto/efento-protobuf-descriptor.d.ts +964 -0
- package/dist/proto/efento-protobuf-descriptor.js +727 -0
- package/dist/proto/partner-from-proto.d.ts +9 -0
- package/dist/proto/partner-from-proto.js +187 -0
- package/dist/proto/partner-to-proto.d.ts +12 -0
- package/dist/proto/partner-to-proto.js +211 -0
- package/dist/tempivo-sensor-beacon.aar +0 -0
- package/ios/Frameworks/TempivoSensorBridge.xcframework/ios-arm64/TempivoSensorBridge.framework/TempivoSensorBridge +0 -0
- package/ios/Sources/TempivoSensorBeacon/EfentoRawConfigTransfer.swift +601 -0
- package/ios/Sources/TempivoSensorBeacon/TempivoSensorSession.swift +99 -29
- package/ios/TempivoSensorBeaconModule.swift +9 -0
- package/package.json +6 -2
|
@@ -4,10 +4,16 @@ import Foundation
|
|
|
4
4
|
import TempivoSensorBridge
|
|
5
5
|
#endif
|
|
6
6
|
|
|
7
|
-
/// GATT session: connect,
|
|
7
|
+
/// GATT session: connect (SDK), alert rules via raw ProtoConfig 0x0C/0x0D, trigger/calibration via SDK.
|
|
8
8
|
public final class TempivoSensorSession: @unchecked Sendable {
|
|
9
9
|
private let lock = NSLock()
|
|
10
|
-
|
|
10
|
+
/// Vendor SDK GATT held (needed for trigger / calibration).
|
|
11
|
+
private var sdkConnected = false
|
|
12
|
+
/// Credentials from last successful `connect` (enough for raw config).
|
|
13
|
+
private var lastSerial = ""
|
|
14
|
+
private var lastMac = ""
|
|
15
|
+
private var lastPin = ""
|
|
16
|
+
private var lastDeviceId = ""
|
|
11
17
|
|
|
12
18
|
#if canImport(TempivoSensorBridge)
|
|
13
19
|
private let bridge = TempivoSensorBridge()
|
|
@@ -42,7 +48,11 @@ public final class TempivoSensorSession: @unchecked Sendable {
|
|
|
42
48
|
)
|
|
43
49
|
)
|
|
44
50
|
try throwIfBridgeError(result)
|
|
45
|
-
|
|
51
|
+
sdkConnected = true
|
|
52
|
+
lastSerial = serial
|
|
53
|
+
lastMac = bluetoothMac
|
|
54
|
+
lastPin = pinValue
|
|
55
|
+
lastDeviceId = resolvedDeviceId
|
|
46
56
|
#else
|
|
47
57
|
throw TempivoSensorError(.runtimeUnavailable, "Sensor GATT requires a physical device with the native runtime.")
|
|
48
58
|
#endif
|
|
@@ -58,14 +68,19 @@ public final class TempivoSensorSession: @unchecked Sendable {
|
|
|
58
68
|
}
|
|
59
69
|
|
|
60
70
|
public func disconnect() {
|
|
71
|
+
EfentoRawConfigTransfer.closeLink()
|
|
61
72
|
#if canImport(TempivoSensorBridge)
|
|
62
73
|
lock.lock()
|
|
63
74
|
defer { lock.unlock() }
|
|
64
75
|
bridge.disconnect()
|
|
65
|
-
|
|
76
|
+
sdkConnected = false
|
|
77
|
+
lastSerial = ""
|
|
78
|
+
lastMac = ""
|
|
79
|
+
lastPin = ""
|
|
80
|
+
lastDeviceId = ""
|
|
66
81
|
#else
|
|
67
82
|
lock.lock()
|
|
68
|
-
|
|
83
|
+
sdkConnected = false
|
|
69
84
|
lock.unlock()
|
|
70
85
|
#endif
|
|
71
86
|
}
|
|
@@ -74,38 +89,51 @@ public final class TempivoSensorSession: @unchecked Sendable {
|
|
|
74
89
|
try TempivoSensorProfile.parseFromDevice(json: getConfigurationJson())
|
|
75
90
|
}
|
|
76
91
|
|
|
92
|
+
/// Always raw GATT ProtoConfig get (0x0C). Vendor SDK getConfiguration is not used for rules.
|
|
77
93
|
public func getConfigurationJson() throws -> String {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
94
|
+
let proto = try withRawConfigLink {
|
|
95
|
+
try EfentoRawConfigTransfer.readProtoConfig(
|
|
96
|
+
peripheralId: $0,
|
|
97
|
+
bluetoothMac: lastMac,
|
|
98
|
+
pin: lastPin
|
|
99
|
+
)
|
|
84
100
|
}
|
|
85
|
-
|
|
86
|
-
#
|
|
87
|
-
throw TempivoSensorError(.runtimeUnavailable, "Sensor GATT requires a physical device with the native runtime.")
|
|
88
|
-
#endif
|
|
101
|
+
let b64 = proto.base64EncodedString()
|
|
102
|
+
return #"{"temperatureAlerts":[],"schedule":{"always":true},"protoBase64":"\#(b64)","configSource":"rawGatt"}"#
|
|
89
103
|
}
|
|
90
104
|
|
|
91
105
|
public func setConfiguration(_ configuration: TempivoSensorConfiguration) throws {
|
|
92
106
|
try setConfigurationJson(TempivoSensorProfile.jsonString(configuration))
|
|
93
107
|
}
|
|
94
108
|
|
|
109
|
+
/// Partner JSON is not written via vendor SDK. Use `writeProtoConfigBase64` from JS after encode.
|
|
95
110
|
public func setConfigurationJson(_ json: String) throws {
|
|
96
111
|
_ = try TempivoSensorProfile.parse(json: json)
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
112
|
+
throw TempivoSensorError(
|
|
113
|
+
.unsupportedCommand,
|
|
114
|
+
"Use writeProtoConfigBase64 after encoding partner config to ProtoConfig."
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/// Raw GATT set (0x0D): full ProtoConfig bytes as base64.
|
|
119
|
+
public func writeProtoConfigBase64(_ base64: String) throws {
|
|
120
|
+
guard let proto = Data(base64Encoded: base64), !proto.isEmpty else {
|
|
121
|
+
throw TempivoSensorError(.invalidConfig, "protoBase64 is missing or invalid.")
|
|
122
|
+
}
|
|
123
|
+
_ = try withRawConfigLink {
|
|
124
|
+
try EfentoRawConfigTransfer.writeProtoConfig(
|
|
125
|
+
peripheralId: $0,
|
|
126
|
+
bluetoothMac: lastMac,
|
|
127
|
+
pin: lastPin,
|
|
128
|
+
proto: proto
|
|
129
|
+
)
|
|
130
|
+
return Data()
|
|
131
|
+
}
|
|
104
132
|
}
|
|
105
133
|
|
|
106
134
|
public func triggerTransmission() throws -> TempivoTriggerTransmissionResult {
|
|
107
135
|
#if canImport(TempivoSensorBridge)
|
|
108
|
-
try
|
|
136
|
+
try ensureSdkConnected()
|
|
109
137
|
let result = parseBridgeJson(bridge.triggerTransmissionPayloadJson())
|
|
110
138
|
if let code = intValue(result["errorCode"]), code != 0 {
|
|
111
139
|
try throwIfBridgeError(result)
|
|
@@ -120,7 +148,7 @@ public final class TempivoSensorSession: @unchecked Sendable {
|
|
|
120
148
|
|
|
121
149
|
public func getCalibration() throws -> TempivoSensorCalibration {
|
|
122
150
|
#if canImport(TempivoSensorBridge)
|
|
123
|
-
try
|
|
151
|
+
try ensureSdkConnected()
|
|
124
152
|
let result = parseBridgeJson(bridge.getCalibrationPayloadJson())
|
|
125
153
|
try throwIfBridgeError(result)
|
|
126
154
|
let ts = int64Value(result["laboratoryCalibrationTimestamp"])
|
|
@@ -130,14 +158,48 @@ public final class TempivoSensorSession: @unchecked Sendable {
|
|
|
130
158
|
#endif
|
|
131
159
|
}
|
|
132
160
|
|
|
133
|
-
|
|
161
|
+
// MARK: - Raw link helpers
|
|
162
|
+
|
|
163
|
+
private func withRawConfigLink(_ body: (UUID) throws -> Data) throws -> Data {
|
|
164
|
+
guard !lastDeviceId.isEmpty, !lastMac.isEmpty, !lastPin.isEmpty,
|
|
165
|
+
let uuid = UUID(uuidString: lastDeviceId)
|
|
166
|
+
else {
|
|
167
|
+
throw TempivoSensorError(.notConnected, "Not connected. Scan and connect first.")
|
|
168
|
+
}
|
|
169
|
+
releaseSdkForRaw()
|
|
170
|
+
return try body(uuid)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private func releaseSdkForRaw() {
|
|
174
|
+
#if canImport(TempivoSensorBridge)
|
|
175
|
+
lock.lock()
|
|
176
|
+
let didDisconnect = sdkConnected
|
|
177
|
+
if sdkConnected {
|
|
178
|
+
bridge.disconnect()
|
|
179
|
+
sdkConnected = false
|
|
180
|
+
}
|
|
181
|
+
lock.unlock()
|
|
182
|
+
// Only wait when the SDK actually held the link; the raw link is reused after that.
|
|
183
|
+
if didDisconnect { Thread.sleep(forTimeInterval: 1.0) }
|
|
184
|
+
#endif
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
#if canImport(TempivoSensorBridge)
|
|
188
|
+
private func ensureSdkConnected() throws {
|
|
134
189
|
lock.lock()
|
|
135
|
-
let ok =
|
|
190
|
+
let ok = sdkConnected
|
|
191
|
+
let serial = lastSerial
|
|
192
|
+
let mac = lastMac
|
|
193
|
+
let pin = lastPin
|
|
194
|
+
let deviceId = lastDeviceId
|
|
136
195
|
lock.unlock()
|
|
137
|
-
if
|
|
196
|
+
if ok { return }
|
|
197
|
+
guard !serial.isEmpty, !mac.isEmpty, !pin.isEmpty, !deviceId.isEmpty else {
|
|
138
198
|
throw TempivoSensorError(.notConnected, "Not connected.")
|
|
139
199
|
}
|
|
200
|
+
try connect(serial: serial, bluetoothMac: mac, pin: pin, deviceId: deviceId)
|
|
140
201
|
}
|
|
202
|
+
#endif
|
|
141
203
|
|
|
142
204
|
private func parseBridgeJson(_ raw: String) -> [String: Any] {
|
|
143
205
|
guard let data = raw.data(using: .utf8),
|
|
@@ -163,13 +225,21 @@ public final class TempivoSensorSession: @unchecked Sendable {
|
|
|
163
225
|
private func throwIfBridgeError(_ result: [String: Any]) throws {
|
|
164
226
|
guard let code = intValue(result["errorCode"]), code != 0 else { return }
|
|
165
227
|
switch code {
|
|
166
|
-
case 1:
|
|
228
|
+
case 1:
|
|
229
|
+
throw TempivoSensorError(
|
|
230
|
+
.invalidPin,
|
|
231
|
+
"Invalid PIN. Forget this sensor in iOS Settings → Bluetooth, then scan and connect again."
|
|
232
|
+
)
|
|
167
233
|
case 2: throw TempivoSensorError(.notConnected, "Not connected.")
|
|
168
234
|
case 3: throw TempivoSensorError(.unsupportedCommand, "Unsupported command.")
|
|
169
235
|
case 4: throw TempivoSensorError(.runtimeUnavailable, "Sensor GATT is not available on this device.")
|
|
170
236
|
case 5: throw TempivoSensorError(.connectFailed, "Could not connect.")
|
|
171
237
|
case 6: throw TempivoSensorError(.invalidConfig, (result["message"] as? String) ?? "Invalid configuration.")
|
|
172
|
-
default:
|
|
238
|
+
default:
|
|
239
|
+
throw TempivoSensorError(
|
|
240
|
+
.unknown,
|
|
241
|
+
(result["message"] as? String) ?? "Sensor request failed."
|
|
242
|
+
)
|
|
173
243
|
}
|
|
174
244
|
}
|
|
175
245
|
}
|
|
@@ -94,8 +94,17 @@ public class TempivoSensorBeaconModule: Module {
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
AsyncFunction("writeProtoConfigBase64") { (base64: String) in
|
|
98
|
+
do {
|
|
99
|
+
try self.session.writeProtoConfigBase64(base64)
|
|
100
|
+
} catch {
|
|
101
|
+
throw self.mapError(error)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
97
105
|
AsyncFunction("setConfigurationJson") { (json: String) in
|
|
98
106
|
do {
|
|
107
|
+
// Legacy name: partner JSON must be encoded in JS then written via writeProtoConfigBase64.
|
|
99
108
|
try self.session.setConfigurationJson(json)
|
|
100
109
|
} catch {
|
|
101
110
|
throw self.mapError(error)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tempivo/sensor-beacon",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"description": "Tempivo sensor BLE SDK. Native scan and GATT for Expo, React Native, Android, and iOS. Node helpers decode advertisements and parse QR or config (no radio).",
|
|
5
5
|
"homepage": "https://app.tempivo.com/integrations",
|
|
6
6
|
"repository": {
|
|
@@ -58,7 +58,8 @@
|
|
|
58
58
|
"build:all": "npm run build:ts && npm run build:android",
|
|
59
59
|
"prepare": "npm run build:ts",
|
|
60
60
|
"test": "npm run test:all",
|
|
61
|
-
"test:unit": "node --experimental-vm-modules node_modules/jest/bin/jest.js --config jest.config.mjs",
|
|
61
|
+
"test:unit": "node --experimental-vm-modules node_modules/jest/bin/jest.js --config jest.config.mjs --testPathIgnorePatterns=ble.integration",
|
|
62
|
+
"test:ble": "TEMPIVO_BLE_INTEGRATION=1 node --experimental-vm-modules node_modules/jest/bin/jest.js --config jest.config.mjs --testPathPattern=ble.integration",
|
|
62
63
|
"test:python": "PYTHONPATH=python python3 -m pytest python/tests -q",
|
|
63
64
|
"test:all": "npm run test:unit && npm run test:python",
|
|
64
65
|
"test:smoke": "node scripts/smoke-test.mjs",
|
|
@@ -98,6 +99,9 @@
|
|
|
98
99
|
"optional": true
|
|
99
100
|
}
|
|
100
101
|
},
|
|
102
|
+
"dependencies": {
|
|
103
|
+
"protobufjs": "^7.6.6"
|
|
104
|
+
},
|
|
101
105
|
"devDependencies": {
|
|
102
106
|
"@types/jest": "^29.5.14",
|
|
103
107
|
"jest": "^29.7.0",
|