@tempivo/sensor-beacon 0.4.3 → 1.1.0
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 +38 -0
- package/README.md +156 -6
- package/android/src/main/java/expo/modules/tempivosensorbeacon/SensorBeaconNative.java +17 -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 +21 -12
- 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 +5 -31
- package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorBeaconDecoder.kt +1 -1
- package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorBeaconScanner.kt +5 -6
- 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 +20 -1
- package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorSession.kt +19 -7
- package/dist/connect-helpers.d.ts +52 -0
- package/dist/connect-helpers.js +153 -0
- package/dist/decoder.d.ts +1 -0
- package/dist/decoder.js +7 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.js +4 -2
- 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 +2 -2
- package/dist/native-types.d.ts +2 -0
- package/dist/profile.d.ts +8 -1
- package/dist/profile.js +42 -19
- package/dist/react-native.d.ts +5 -1
- package/dist/react-native.js +3 -1
- package/dist/session-types.d.ts +2 -1
- package/dist/tempivo-sensor-beacon.aar +0 -0
- package/ios/Frameworks/TempivoSensorBridge.xcframework/Info.plist +4 -4
- 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 +38 -1
- package/ios/Sources/TempivoSensorBeacon/TempivoSensorSession.swift +24 -12
- package/ios/Sources/TempivoSensorBeacon/TempivoSensorSessionTypes.swift +1 -3
- package/ios/TempivoSensorBeaconModule.swift +83 -16
- package/package.json +4 -3
package/dist/react-native.d.ts
CHANGED
|
@@ -5,8 +5,12 @@ 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
7
|
export { DEFAULT_SENSOR_MODEL, bluetoothMacFromSerial, normalizeSensorSerial, encodeSensorPinPayload, parseSensorQrJson, sessionTypeFromModel, } from './qr.js';
|
|
8
|
+
export { isMissingAdvertisementFirmware, sensorModelFromFirmware } from './model.js';
|
|
9
|
+
export type { SensorStickerModel } from './model.js';
|
|
10
|
+
export { buildSensorQrConnectJson, draftSensorQrConnectJsonFromScanDevice, formatSensorReadingLine, mergeScanTargetIntoSensorQr, normalizeSensorStickerModel, parseSensorQrForConnect, pinFromSensorQrRecord, resolveSensorBleConnectCredentialsFromPin, sensorQrConnectJsonFromLoose, sensorQrSerialMatchesSelected, serialHexFromSensorQrScan, tryParseSensorQrJson, } from './connect-helpers.js';
|
|
11
|
+
export type { ParsedSensorQrLoose, SensorBleConnectCredentials } from './connect-helpers.js';
|
|
8
12
|
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';
|
|
13
|
+
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, parseSensorConfigurationFromDevice, parseSensorConfigurationFromDeviceJson, parseSensorConfigurationJson, pickPartnerConfigurationJson, } from './profile.js';
|
|
10
14
|
export type { TempivoSchedule, TempivoScheduleAlways, TempivoScheduleWeek, TempivoSensorCalibration, TempivoSensorConfiguration, TempivoSensorQr, TempivoSensorSessionType, TempivoTemperatureAlert, TempivoTemperatureChannel, TempivoTemperatureMaxAlert, TempivoTemperatureMinAlert, TempivoTemperatureRangeAlert, TempivoTriggerTransmissionResult, } from './session-types.js';
|
|
11
15
|
export type { TempivoDeviceFoundSubscription, TempivoNativeCalibration, TempivoNativeMeasurement, TempivoNativePermissionResponse, TempivoNativePermissionStatus, TempivoNativeQr, TempivoNativeTelemetry, TempivoSensorBeaconDevice, } from './native-types.js';
|
|
12
16
|
import * as native from './native-module.js';
|
package/dist/react-native.js
CHANGED
|
@@ -5,8 +5,10 @@ export { MEASURE_SPECS } from './measure-specs.js';
|
|
|
5
5
|
export { createSensorBeaconFrameCache, } from './types.js';
|
|
6
6
|
export { TempivoSensorError } from './errors.js';
|
|
7
7
|
export { DEFAULT_SENSOR_MODEL, bluetoothMacFromSerial, normalizeSensorSerial, encodeSensorPinPayload, parseSensorQrJson, sessionTypeFromModel, } from './qr.js';
|
|
8
|
+
export { isMissingAdvertisementFirmware, sensorModelFromFirmware } from './model.js';
|
|
9
|
+
export { buildSensorQrConnectJson, draftSensorQrConnectJsonFromScanDevice, formatSensorReadingLine, mergeScanTargetIntoSensorQr, normalizeSensorStickerModel, parseSensorQrForConnect, pinFromSensorQrRecord, resolveSensorBleConnectCredentialsFromPin, sensorQrConnectJsonFromLoose, sensorQrSerialMatchesSelected, serialHexFromSensorQrScan, tryParseSensorQrJson, } from './connect-helpers.js';
|
|
8
10
|
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';
|
|
11
|
+
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, parseSensorConfigurationFromDevice, parseSensorConfigurationFromDeviceJson, parseSensorConfigurationJson, pickPartnerConfigurationJson, } from './profile.js';
|
|
10
12
|
import * as native from './native-module.js';
|
|
11
13
|
export const isNativeSensorBeaconAvailable = native.isNativeSensorBeaconAvailable;
|
|
12
14
|
export const requestPermissions = native.requestPermissions;
|
package/dist/session-types.d.ts
CHANGED
|
@@ -60,6 +60,8 @@ export type TempivoSensorQr = {
|
|
|
60
60
|
/** Always `modern` (HC7 GATT). */
|
|
61
61
|
sessionType: TempivoSensorSessionType;
|
|
62
62
|
bluetoothMac: string;
|
|
63
|
+
/** SDK scan `deviceId` when it differs from sticker serial. */
|
|
64
|
+
deviceId?: string;
|
|
63
65
|
};
|
|
64
66
|
export type TempivoSensorCalibration = {
|
|
65
67
|
laboratoryCalibrationDate: string | null;
|
|
@@ -67,5 +69,4 @@ export type TempivoSensorCalibration = {
|
|
|
67
69
|
};
|
|
68
70
|
export type TempivoTriggerTransmissionResult = {
|
|
69
71
|
ok: boolean;
|
|
70
|
-
supported: boolean;
|
|
71
72
|
};
|
|
Binary file
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<key>BinaryPath</key>
|
|
9
9
|
<string>TempivoSensorBridge.framework/TempivoSensorBridge</string>
|
|
10
10
|
<key>LibraryIdentifier</key>
|
|
11
|
-
<string>ios-arm64</string>
|
|
11
|
+
<string>ios-arm64-simulator</string>
|
|
12
12
|
<key>LibraryPath</key>
|
|
13
13
|
<string>TempivoSensorBridge.framework</string>
|
|
14
14
|
<key>SupportedArchitectures</key>
|
|
@@ -17,12 +17,14 @@
|
|
|
17
17
|
</array>
|
|
18
18
|
<key>SupportedPlatform</key>
|
|
19
19
|
<string>ios</string>
|
|
20
|
+
<key>SupportedPlatformVariant</key>
|
|
21
|
+
<string>simulator</string>
|
|
20
22
|
</dict>
|
|
21
23
|
<dict>
|
|
22
24
|
<key>BinaryPath</key>
|
|
23
25
|
<string>TempivoSensorBridge.framework/TempivoSensorBridge</string>
|
|
24
26
|
<key>LibraryIdentifier</key>
|
|
25
|
-
<string>ios-arm64
|
|
27
|
+
<string>ios-arm64</string>
|
|
26
28
|
<key>LibraryPath</key>
|
|
27
29
|
<string>TempivoSensorBridge.framework</string>
|
|
28
30
|
<key>SupportedArchitectures</key>
|
|
@@ -31,8 +33,6 @@
|
|
|
31
33
|
</array>
|
|
32
34
|
<key>SupportedPlatform</key>
|
|
33
35
|
<string>ios</string>
|
|
34
|
-
<key>SupportedPlatformVariant</key>
|
|
35
|
-
<string>simulator</string>
|
|
36
36
|
</dict>
|
|
37
37
|
</array>
|
|
38
38
|
<key>CFBundlePackageType</key>
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
#import <Foundation/NSString.h>
|
|
7
7
|
#import <Foundation/NSValue.h>
|
|
8
8
|
|
|
9
|
+
@class TSBSensorDeviceScanHandler;
|
|
10
|
+
|
|
9
11
|
NS_ASSUME_NONNULL_BEGIN
|
|
10
12
|
#pragma clang diagnostic push
|
|
11
13
|
#pragma clang diagnostic ignored "-Wunknown-warning-option"
|
|
@@ -140,17 +142,42 @@ __attribute__((swift_name("KotlinBoolean")))
|
|
|
140
142
|
+ (instancetype)numberWithBool:(BOOL)value;
|
|
141
143
|
@end
|
|
142
144
|
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Swift receives [TempivoSensorBridge.startDeviceScan] events here.
|
|
148
|
+
* JSON matches the inner device object from Android scan (MAC, serial, advertisementTelemetry).
|
|
149
|
+
*/
|
|
150
|
+
__attribute__((swift_name("SensorDeviceScanHandler")))
|
|
151
|
+
@interface TSBSensorDeviceScanHandler : TSBBase
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Swift receives [TempivoSensorBridge.startDeviceScan] events here.
|
|
155
|
+
* JSON matches the inner device object from Android scan (MAC, serial, advertisementTelemetry).
|
|
156
|
+
*/
|
|
157
|
+
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Swift receives [TempivoSensorBridge.startDeviceScan] events here.
|
|
161
|
+
* JSON matches the inner device object from Android scan (MAC, serial, advertisementTelemetry).
|
|
162
|
+
*/
|
|
163
|
+
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
|
|
164
|
+
- (void)onDeviceDeviceJson:(NSString *)deviceJson __attribute__((swift_name("onDevice(deviceJson:)")));
|
|
165
|
+
@end
|
|
166
|
+
|
|
143
167
|
__attribute__((objc_subclassing_restricted))
|
|
144
168
|
__attribute__((swift_name("TempivoSensorBridge")))
|
|
145
169
|
@interface TSBTempivoSensorBridge : TSBBase
|
|
146
170
|
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
|
|
147
171
|
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
|
|
148
|
-
- (NSString *)
|
|
172
|
+
- (NSString *)connectPayloadJsonDeviceId:(NSString *)deviceId bluetoothMac:(NSString *)bluetoothMac pin:(NSString *)pin legacy:(BOOL)legacy __attribute__((swift_name("connectPayloadJson(deviceId:bluetoothMac:pin:legacy:)")));
|
|
149
173
|
- (void)disconnect __attribute__((swift_name("disconnect()")));
|
|
150
174
|
- (NSString *)getCalibrationPayloadJson __attribute__((swift_name("getCalibrationPayloadJson()")));
|
|
151
175
|
- (NSString *)getConfigurationPayloadJson __attribute__((swift_name("getConfigurationPayloadJson()")));
|
|
152
176
|
- (void)initialize __attribute__((swift_name("initialize()")));
|
|
153
177
|
- (NSString *)setConfigurationPayloadJsonJson:(NSString *)json __attribute__((swift_name("setConfigurationPayloadJson(json:)")));
|
|
178
|
+
- (void)setDeviceScanHandlerHandler:(TSBSensorDeviceScanHandler * _Nullable)handler __attribute__((swift_name("setDeviceScanHandler(handler:)")));
|
|
179
|
+
- (void)startDeviceScan __attribute__((swift_name("startDeviceScan()")));
|
|
180
|
+
- (void)stopDeviceScan __attribute__((swift_name("stopDeviceScan()")));
|
|
154
181
|
- (NSString *)triggerTransmissionPayloadJson __attribute__((swift_name("triggerTransmissionPayloadJson()")));
|
|
155
182
|
@end
|
|
156
183
|
|
|
Binary file
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
#import <Foundation/NSString.h>
|
|
7
7
|
#import <Foundation/NSValue.h>
|
|
8
8
|
|
|
9
|
+
@class TSBSensorDeviceScanHandler;
|
|
10
|
+
|
|
9
11
|
NS_ASSUME_NONNULL_BEGIN
|
|
10
12
|
#pragma clang diagnostic push
|
|
11
13
|
#pragma clang diagnostic ignored "-Wunknown-warning-option"
|
|
@@ -140,17 +142,42 @@ __attribute__((swift_name("KotlinBoolean")))
|
|
|
140
142
|
+ (instancetype)numberWithBool:(BOOL)value;
|
|
141
143
|
@end
|
|
142
144
|
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Swift receives [TempivoSensorBridge.startDeviceScan] events here.
|
|
148
|
+
* JSON matches the inner device object from Android scan (MAC, serial, advertisementTelemetry).
|
|
149
|
+
*/
|
|
150
|
+
__attribute__((swift_name("SensorDeviceScanHandler")))
|
|
151
|
+
@interface TSBSensorDeviceScanHandler : TSBBase
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Swift receives [TempivoSensorBridge.startDeviceScan] events here.
|
|
155
|
+
* JSON matches the inner device object from Android scan (MAC, serial, advertisementTelemetry).
|
|
156
|
+
*/
|
|
157
|
+
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Swift receives [TempivoSensorBridge.startDeviceScan] events here.
|
|
161
|
+
* JSON matches the inner device object from Android scan (MAC, serial, advertisementTelemetry).
|
|
162
|
+
*/
|
|
163
|
+
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
|
|
164
|
+
- (void)onDeviceDeviceJson:(NSString *)deviceJson __attribute__((swift_name("onDevice(deviceJson:)")));
|
|
165
|
+
@end
|
|
166
|
+
|
|
143
167
|
__attribute__((objc_subclassing_restricted))
|
|
144
168
|
__attribute__((swift_name("TempivoSensorBridge")))
|
|
145
169
|
@interface TSBTempivoSensorBridge : TSBBase
|
|
146
170
|
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
|
|
147
171
|
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
|
|
148
|
-
- (NSString *)
|
|
172
|
+
- (NSString *)connectPayloadJsonDeviceId:(NSString *)deviceId bluetoothMac:(NSString *)bluetoothMac pin:(NSString *)pin legacy:(BOOL)legacy __attribute__((swift_name("connectPayloadJson(deviceId:bluetoothMac:pin:legacy:)")));
|
|
149
173
|
- (void)disconnect __attribute__((swift_name("disconnect()")));
|
|
150
174
|
- (NSString *)getCalibrationPayloadJson __attribute__((swift_name("getCalibrationPayloadJson()")));
|
|
151
175
|
- (NSString *)getConfigurationPayloadJson __attribute__((swift_name("getConfigurationPayloadJson()")));
|
|
152
176
|
- (void)initialize __attribute__((swift_name("initialize()")));
|
|
153
177
|
- (NSString *)setConfigurationPayloadJsonJson:(NSString *)json __attribute__((swift_name("setConfigurationPayloadJson(json:)")));
|
|
178
|
+
- (void)setDeviceScanHandlerHandler:(TSBSensorDeviceScanHandler * _Nullable)handler __attribute__((swift_name("setDeviceScanHandler(handler:)")));
|
|
179
|
+
- (void)startDeviceScan __attribute__((swift_name("startDeviceScan()")));
|
|
180
|
+
- (void)stopDeviceScan __attribute__((swift_name("stopDeviceScan()")));
|
|
154
181
|
- (NSString *)triggerTransmissionPayloadJson __attribute__((swift_name("triggerTransmissionPayloadJson()")));
|
|
155
182
|
@end
|
|
156
183
|
|
|
Binary file
|
|
@@ -250,6 +250,13 @@ public enum TempivoSensorBeaconDecoder {
|
|
|
250
250
|
return (n >> 1) ^ (-(n & 1))
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
+
private static func measurementTypeLabel(_ mtype: UInt8) -> String {
|
|
254
|
+
guard let spec = measureTypes[mtype] else {
|
|
255
|
+
return "type_\(String(format: "%02x", mtype))"
|
|
256
|
+
}
|
|
257
|
+
return spec.name.lowercased()
|
|
258
|
+
}
|
|
259
|
+
|
|
253
260
|
private static func decodeSlot(mtype: UInt8, raw24: UInt32) -> String {
|
|
254
261
|
guard let spec = measureTypes[mtype] else {
|
|
255
262
|
return String(format: "0x%02X: 0x%06X", mtype, raw24)
|
|
@@ -356,7 +363,7 @@ public enum TempivoSensorBeaconDecoder {
|
|
|
356
363
|
summaryParts.append(text)
|
|
357
364
|
readings.append(
|
|
358
365
|
SensorBeaconMeasurement(
|
|
359
|
-
typeHex:
|
|
366
|
+
typeHex: measurementTypeLabel(mtype),
|
|
360
367
|
raw24: Int(raw24 & 0xFFFFFF),
|
|
361
368
|
text: text
|
|
362
369
|
)
|
|
@@ -17,6 +17,7 @@ public final class TempivoSensorBeaconScanner: NSObject {
|
|
|
17
17
|
|
|
18
18
|
private var central: CBCentralManager!
|
|
19
19
|
private var frameCache = SensorBeaconFrameCache()
|
|
20
|
+
private var peripheralIdToSerialKey: [String: String] = [:]
|
|
20
21
|
private var isScanning = false
|
|
21
22
|
private var pendingStart = false
|
|
22
23
|
|
|
@@ -36,6 +37,7 @@ public final class TempivoSensorBeaconScanner: NSObject {
|
|
|
36
37
|
/// Clears cached `0x03` / `0x04` frames. Does not stop an active scan.
|
|
37
38
|
public func clearCache() {
|
|
38
39
|
frameCache.clear()
|
|
40
|
+
peripheralIdToSerialKey.removeAll(keepingCapacity: false)
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
/// Starts an active scan (duplicate advertisements allowed). Requires Bluetooth powered on.
|
|
@@ -109,13 +111,20 @@ public final class TempivoSensorBeaconScanner: NSObject {
|
|
|
109
111
|
if let mac = TempivoSensorBeaconDecoder.macFromManufacturerData(mdata) {
|
|
110
112
|
bluetoothMacAddress = mac
|
|
111
113
|
serialNumber = TempivoSensorBeaconDecoder.macKey(mac)
|
|
114
|
+
if let serialNumber {
|
|
115
|
+
peripheralIdToSerialKey[deviceId] = serialNumber.uppercased()
|
|
116
|
+
}
|
|
112
117
|
}
|
|
113
|
-
let
|
|
118
|
+
let serialHint = serialNumber ?? peripheralIdToSerialKey[deviceId]
|
|
119
|
+
let primaryKey = serialHint ?? deviceId
|
|
114
120
|
_ = TempivoSensorBeaconDecoder.ingestManufacturerData(
|
|
115
121
|
mdata,
|
|
116
122
|
primaryMacKey: primaryKey,
|
|
117
123
|
cache: &frameCache
|
|
118
124
|
)
|
|
125
|
+
if serialNumber == nil {
|
|
126
|
+
serialNumber = serialHint
|
|
127
|
+
}
|
|
119
128
|
}
|
|
120
129
|
|
|
121
130
|
let keys = cacheKeys(serial: serialNumber, macWithColons: bluetoothMacAddress, deviceId: deviceId)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
enum TempivoSensorModelHint {
|
|
4
|
+
static let defaultModel = "HC7"
|
|
5
|
+
|
|
6
|
+
static func isMissingFirmware(_ firmware: String?) -> Bool {
|
|
7
|
+
let t = firmware?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
|
8
|
+
return t.isEmpty || t == "0.0.0"
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
static func fromFirmware(_ firmware: String?) -> String {
|
|
12
|
+
if isMissingFirmware(firmware) { return defaultModel }
|
|
13
|
+
let t = firmware!.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
14
|
+
if t.range(of: "^FW 5", options: [.regularExpression, .caseInsensitive]) != nil {
|
|
15
|
+
return "HC5"
|
|
16
|
+
}
|
|
17
|
+
let pattern = #"^(?:FW\s*)?(\d+)\.\d+"#
|
|
18
|
+
guard let regex = try? NSRegularExpression(pattern: pattern, options: .caseInsensitive),
|
|
19
|
+
let match = regex.firstMatch(in: t, range: NSRange(t.startIndex..., in: t)),
|
|
20
|
+
match.numberOfRanges > 1,
|
|
21
|
+
let majorRange = Range(match.range(at: 1), in: t),
|
|
22
|
+
let major = Int(t[majorRange]) else {
|
|
23
|
+
return defaultModel
|
|
24
|
+
}
|
|
25
|
+
if major >= 7 { return "HC7" }
|
|
26
|
+
if major >= 1 { return "HC5" }
|
|
27
|
+
return defaultModel
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -10,7 +10,24 @@ public enum TempivoSensorProfile {
|
|
|
10
10
|
return try parse(obj)
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
public static func parseFromDevice(_ obj: [String: Any]) throws -> TempivoSensorConfiguration {
|
|
14
|
+
try parseBody(obj, validatePartnerLimits: false)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public static func parseFromDevice(json: String) throws -> TempivoSensorConfiguration {
|
|
18
|
+
guard let data = json.data(using: .utf8),
|
|
19
|
+
let obj = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
|
|
20
|
+
else {
|
|
21
|
+
throw TempivoSensorError(.invalidConfig, "Configuration is not valid JSON.")
|
|
22
|
+
}
|
|
23
|
+
return try parseFromDevice(obj)
|
|
24
|
+
}
|
|
25
|
+
|
|
13
26
|
public static func parse(_ obj: [String: Any]) throws -> TempivoSensorConfiguration {
|
|
27
|
+
try parseBody(obj, validatePartnerLimits: true)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private static func parseBody(_ obj: [String: Any], validatePartnerLimits: Bool) throws -> TempivoSensorConfiguration {
|
|
14
31
|
if obj["profiles"] is [Any] {
|
|
15
32
|
throw TempivoSensorError(.invalidConfig, "Pass one profile from GET /devices/config-profiles/{slug}, not the list.")
|
|
16
33
|
}
|
|
@@ -30,7 +47,7 @@ public enum TempivoSensorProfile {
|
|
|
30
47
|
}
|
|
31
48
|
let rawAlerts = body["temperatureAlerts"] as? [Any] ?? []
|
|
32
49
|
let alerts = try rawAlerts.enumerated().map { try parseAlert($0.element, index: $0.offset) }
|
|
33
|
-
let intervals = try parseOptionalIntervals(body)
|
|
50
|
+
let intervals = try parseOptionalIntervals(body, validatePartnerLimits: validatePartnerLimits)
|
|
34
51
|
return TempivoSensorConfiguration(
|
|
35
52
|
temperatureAlerts: alerts,
|
|
36
53
|
schedule: try parseSchedule(body["schedule"] as? [String: Any]),
|
|
@@ -78,18 +95,23 @@ public enum TempivoSensorProfile {
|
|
|
78
95
|
private static let minTransmissionSeconds = 3600
|
|
79
96
|
private static let maxTransmissionSeconds = 604_800
|
|
80
97
|
|
|
81
|
-
private static func parseOptionalIntervals(
|
|
98
|
+
private static func parseOptionalIntervals(
|
|
99
|
+
_ body: [String: Any],
|
|
100
|
+
validatePartnerLimits: Bool
|
|
101
|
+
) throws -> (measurement: Double?, transmission: Int?) {
|
|
82
102
|
var measurement: Double?
|
|
83
103
|
var transmission: Int?
|
|
84
104
|
if body["measurementIntervalMinutes"] != nil {
|
|
85
105
|
guard let n = number(body["measurementIntervalMinutes"]), n > 0 else {
|
|
86
106
|
throw TempivoSensorError(.invalidConfig, "measurementIntervalMinutes must be a positive number.")
|
|
87
107
|
}
|
|
88
|
-
if
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
108
|
+
if validatePartnerLimits {
|
|
109
|
+
if n < minMeasurementMinutes {
|
|
110
|
+
throw TempivoSensorError(.invalidConfig, "measurementIntervalMinutes must be at least \(Int(minMeasurementMinutes)) (1 hour).")
|
|
111
|
+
}
|
|
112
|
+
if n > maxMeasurementMinutes {
|
|
113
|
+
throw TempivoSensorError(.invalidConfig, "measurementIntervalMinutes must be at most \(Int(maxMeasurementMinutes)).")
|
|
114
|
+
}
|
|
93
115
|
}
|
|
94
116
|
measurement = n
|
|
95
117
|
}
|
|
@@ -98,15 +120,17 @@ public enum TempivoSensorProfile {
|
|
|
98
120
|
throw TempivoSensorError(.invalidConfig, "transmissionIntervalSeconds must be a positive integer.")
|
|
99
121
|
}
|
|
100
122
|
let sec = Int(n)
|
|
101
|
-
if
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
123
|
+
if validatePartnerLimits {
|
|
124
|
+
if sec < minTransmissionSeconds {
|
|
125
|
+
throw TempivoSensorError(.invalidConfig, "transmissionIntervalSeconds must be at least \(minTransmissionSeconds) (1 hour).")
|
|
126
|
+
}
|
|
127
|
+
if sec > maxTransmissionSeconds {
|
|
128
|
+
throw TempivoSensorError(.invalidConfig, "transmissionIntervalSeconds must be at most \(maxTransmissionSeconds).")
|
|
129
|
+
}
|
|
106
130
|
}
|
|
107
131
|
transmission = sec
|
|
108
132
|
}
|
|
109
|
-
if let measurement, let transmission {
|
|
133
|
+
if validatePartnerLimits, let measurement, let transmission {
|
|
110
134
|
let maxTx = measurement * 60 * 60
|
|
111
135
|
if Double(transmission) > maxTx {
|
|
112
136
|
throw TempivoSensorError(.invalidConfig, "transmissionIntervalSeconds must be at most 60 times the measurement interval.")
|
|
@@ -6,6 +6,23 @@ public struct TempivoSensorQr: Sendable, Equatable {
|
|
|
6
6
|
public let model: String?
|
|
7
7
|
public let sessionType: TempivoSensorSessionType
|
|
8
8
|
public let bluetoothMac: String
|
|
9
|
+
public let deviceId: String?
|
|
10
|
+
|
|
11
|
+
public init(
|
|
12
|
+
serial: String,
|
|
13
|
+
pin: String,
|
|
14
|
+
model: String?,
|
|
15
|
+
sessionType: TempivoSensorSessionType,
|
|
16
|
+
bluetoothMac: String,
|
|
17
|
+
deviceId: String? = nil
|
|
18
|
+
) {
|
|
19
|
+
self.serial = serial
|
|
20
|
+
self.pin = pin
|
|
21
|
+
self.model = model
|
|
22
|
+
self.sessionType = sessionType
|
|
23
|
+
self.bluetoothMac = bluetoothMac
|
|
24
|
+
self.deviceId = deviceId
|
|
25
|
+
}
|
|
9
26
|
}
|
|
10
27
|
|
|
11
28
|
public enum TempivoSensorQrParser {
|
|
@@ -27,6 +44,14 @@ public enum TempivoSensorQrParser {
|
|
|
27
44
|
}.joined(separator: ":")
|
|
28
45
|
}
|
|
29
46
|
|
|
47
|
+
public static func normalizeMac(_ value: String) throws -> String {
|
|
48
|
+
let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
49
|
+
if trimmed.contains(":") {
|
|
50
|
+
return try bluetoothMacFromSerial(trimmed.replacingOccurrences(of: ":", with: ""))
|
|
51
|
+
}
|
|
52
|
+
return try bluetoothMacFromSerial(trimmed)
|
|
53
|
+
}
|
|
54
|
+
|
|
30
55
|
public static let defaultModel = "HC7"
|
|
31
56
|
|
|
32
57
|
public static func assertSupportedModel(_ model: String) throws {
|
|
@@ -63,12 +88,24 @@ public enum TempivoSensorQrParser {
|
|
|
63
88
|
let model = rawModel.isEmpty ? defaultModel : rawModel
|
|
64
89
|
try assertSupportedModel(model)
|
|
65
90
|
let hex = serial.count == 12 ? serial : String(serial.suffix(12))
|
|
91
|
+
let macOverride =
|
|
92
|
+
(["bluetoothMac", "bluetoothMacAddress", "mac"] as [String])
|
|
93
|
+
.compactMap { key -> String? in
|
|
94
|
+
guard let raw = obj[key] as? String else { return nil }
|
|
95
|
+
let t = raw.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
96
|
+
return t.isEmpty ? nil : t
|
|
97
|
+
}
|
|
98
|
+
.first
|
|
99
|
+
let deviceIdRaw = (obj["deviceId"] as? String)?
|
|
100
|
+
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
101
|
+
let deviceId = (deviceIdRaw?.isEmpty == false) ? deviceIdRaw : nil
|
|
66
102
|
return TempivoSensorQr(
|
|
67
103
|
serial: serial,
|
|
68
104
|
pin: pin,
|
|
69
105
|
model: model,
|
|
70
106
|
sessionType: .modern,
|
|
71
|
-
bluetoothMac: try bluetoothMacFromSerial(hex)
|
|
107
|
+
bluetoothMac: try macOverride.map(normalizeMac) ?? bluetoothMacFromSerial(hex),
|
|
108
|
+
deviceId: deviceId
|
|
72
109
|
)
|
|
73
110
|
}
|
|
74
111
|
|
|
@@ -15,22 +15,30 @@ public final class TempivoSensorSession: @unchecked Sendable {
|
|
|
15
15
|
|
|
16
16
|
public init() {}
|
|
17
17
|
|
|
18
|
-
public func connect(
|
|
19
|
-
|
|
18
|
+
public func connect(
|
|
19
|
+
serial: String,
|
|
20
|
+
bluetoothMac: String,
|
|
21
|
+
pin: String,
|
|
22
|
+
deviceId: String? = nil
|
|
23
|
+
) throws {
|
|
24
|
+
let pinValue = pin.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
25
|
+
guard !pinValue.isEmpty, pinValue.range(of: "^[0-9]+$", options: .regularExpression) != nil else {
|
|
20
26
|
throw TempivoSensorError(.invalidPin, "PIN must be numeric.")
|
|
21
27
|
}
|
|
28
|
+
let trimmedDeviceId = deviceId?.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
29
|
+
let resolvedDeviceId =
|
|
30
|
+
(trimmedDeviceId?.isEmpty == false ? trimmedDeviceId : nil)
|
|
31
|
+
?? TempivoSensorQrParser.normalizeSerial(serial)
|
|
22
32
|
#if canImport(TempivoSensorBridge)
|
|
23
33
|
lock.lock()
|
|
24
34
|
defer { lock.unlock() }
|
|
25
35
|
bridge.initialize()
|
|
26
36
|
let result = parseBridgeJson(
|
|
27
37
|
bridge.connectPayloadJson(
|
|
28
|
-
|
|
38
|
+
deviceId: resolvedDeviceId,
|
|
29
39
|
bluetoothMac: bluetoothMac,
|
|
30
|
-
pin:
|
|
31
|
-
legacy: false
|
|
32
|
-
deviceId: TempivoSensorQrParser.normalizeSerial(serial),
|
|
33
|
-
encryptionKey: ""
|
|
40
|
+
pin: pinValue,
|
|
41
|
+
legacy: false
|
|
34
42
|
)
|
|
35
43
|
)
|
|
36
44
|
try throwIfBridgeError(result)
|
|
@@ -40,8 +48,13 @@ public final class TempivoSensorSession: @unchecked Sendable {
|
|
|
40
48
|
#endif
|
|
41
49
|
}
|
|
42
50
|
|
|
43
|
-
public func connect(qr: TempivoSensorQr) throws {
|
|
44
|
-
try connect(
|
|
51
|
+
public func connect(qr: TempivoSensorQr, deviceId: String? = nil, bluetoothMac: String? = nil) throws {
|
|
52
|
+
try connect(
|
|
53
|
+
serial: qr.serial,
|
|
54
|
+
bluetoothMac: bluetoothMac ?? qr.bluetoothMac,
|
|
55
|
+
pin: qr.pin,
|
|
56
|
+
deviceId: deviceId ?? qr.deviceId
|
|
57
|
+
)
|
|
45
58
|
}
|
|
46
59
|
|
|
47
60
|
public func disconnect() {
|
|
@@ -58,7 +71,7 @@ public final class TempivoSensorSession: @unchecked Sendable {
|
|
|
58
71
|
}
|
|
59
72
|
|
|
60
73
|
public func getConfiguration() throws -> TempivoSensorConfiguration {
|
|
61
|
-
try TempivoSensorProfile.
|
|
74
|
+
try TempivoSensorProfile.parseFromDevice(json: getConfigurationJson())
|
|
62
75
|
}
|
|
63
76
|
|
|
64
77
|
public func getConfigurationJson() throws -> String {
|
|
@@ -98,8 +111,7 @@ public final class TempivoSensorSession: @unchecked Sendable {
|
|
|
98
111
|
try throwIfBridgeError(result)
|
|
99
112
|
}
|
|
100
113
|
return TempivoTriggerTransmissionResult(
|
|
101
|
-
ok: (result["ok"] as? Bool) ?? false
|
|
102
|
-
supported: (result["supported"] as? Bool) ?? false
|
|
114
|
+
ok: (result["ok"] as? Bool) ?? false
|
|
103
115
|
)
|
|
104
116
|
#else
|
|
105
117
|
throw TempivoSensorError(.runtimeUnavailable, "Sensor GATT requires a physical device with the native runtime.")
|
|
@@ -92,10 +92,8 @@ public struct TempivoSensorCalibration: Sendable, Equatable {
|
|
|
92
92
|
|
|
93
93
|
public struct TempivoTriggerTransmissionResult: Sendable, Equatable {
|
|
94
94
|
public var ok: Bool
|
|
95
|
-
public var supported: Bool
|
|
96
95
|
|
|
97
|
-
public init(ok: Bool
|
|
96
|
+
public init(ok: Bool) {
|
|
98
97
|
self.ok = ok
|
|
99
|
-
self.supported = supported
|
|
100
98
|
}
|
|
101
99
|
}
|