@tempivo/sensor-beacon 1.1.2 → 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 +29 -0
- package/README.md +35 -22
- 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 +124 -10
- package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/SensorBleRuntime.kt +19 -8
- 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-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 +33 -19
- 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
package/dist/native-module.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { requireOptionalNativeModule } from 'expo-modules-core';
|
|
2
2
|
import { TempivoSensorError } from './errors.js';
|
|
3
3
|
import { configToJson, mapNativeError, qrToConnectJson } from './native-helpers.js';
|
|
4
|
-
import {
|
|
4
|
+
import { partnerConfigFromProtoBase64 } from './proto/partner-from-proto.js';
|
|
5
|
+
import { partnerConfigToProtoBase64 } from './proto/partner-to-proto.js';
|
|
6
|
+
import { parseSensorConfigurationFromDevice } from './profile.js';
|
|
5
7
|
function getNative() {
|
|
6
8
|
const native = requireOptionalNativeModule('TempivoSensorBeacon');
|
|
7
9
|
if (!native) {
|
|
@@ -67,24 +69,65 @@ export async function connect(qr) {
|
|
|
67
69
|
}
|
|
68
70
|
export async function disconnect() {
|
|
69
71
|
try {
|
|
72
|
+
lastProtoBase64 = null;
|
|
70
73
|
await getNative().disconnect();
|
|
71
74
|
}
|
|
72
75
|
catch (error) {
|
|
73
76
|
throw mapNativeError(error);
|
|
74
77
|
}
|
|
75
78
|
}
|
|
79
|
+
/** Last ProtoConfig from iOS raw get; used as set baseline. Not part of the partner API. */
|
|
80
|
+
let lastProtoBase64 = null;
|
|
81
|
+
/**
|
|
82
|
+
* Partner configuration from the device.
|
|
83
|
+
* Return shape is only {@link TempivoSensorConfiguration} (no debug fields).
|
|
84
|
+
* iOS and Android use raw ProtoConfig GATT; return shape is only partner fields.
|
|
85
|
+
*/
|
|
76
86
|
export async function getConfiguration() {
|
|
77
87
|
try {
|
|
78
88
|
const json = await getNative().getConfigurationJson();
|
|
79
|
-
|
|
89
|
+
const parsed = JSON.parse(json);
|
|
90
|
+
const protoB64 = typeof parsed.protoBase64 === 'string' ? parsed.protoBase64 : null;
|
|
91
|
+
if (protoB64) {
|
|
92
|
+
lastProtoBase64 = protoB64;
|
|
93
|
+
return partnerConfigFromProtoBase64(protoB64);
|
|
94
|
+
}
|
|
95
|
+
lastProtoBase64 = null;
|
|
96
|
+
return parseSensorConfigurationFromDevice(parsed);
|
|
80
97
|
}
|
|
81
98
|
catch (error) {
|
|
82
99
|
throw mapNativeError(error);
|
|
83
100
|
}
|
|
84
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Write partner configuration to the device.
|
|
104
|
+
* Same public signature as before. Encodes partner config to ProtoConfig and writes raw GATT
|
|
105
|
+
* when `writeProtoConfigBase64` is available (iOS and Android).
|
|
106
|
+
*/
|
|
85
107
|
export async function setConfigurationJson(config) {
|
|
86
108
|
try {
|
|
87
|
-
|
|
109
|
+
const partner = typeof config === 'string'
|
|
110
|
+
? JSON.parse(config)
|
|
111
|
+
: config;
|
|
112
|
+
const native = getNative();
|
|
113
|
+
if (typeof native.writeProtoConfigBase64 === 'function') {
|
|
114
|
+
let baseline = lastProtoBase64;
|
|
115
|
+
if (!baseline) {
|
|
116
|
+
await getConfiguration();
|
|
117
|
+
baseline = lastProtoBase64;
|
|
118
|
+
}
|
|
119
|
+
if (!baseline) {
|
|
120
|
+
throw new TempivoSensorError('invalidConfig', 'Cannot set: missing baseline ProtoConfig from device get.');
|
|
121
|
+
}
|
|
122
|
+
const nextB64 = partnerConfigToProtoBase64(partner, baseline);
|
|
123
|
+
await native.writeProtoConfigBase64(nextB64);
|
|
124
|
+
lastProtoBase64 = nextB64;
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (typeof native.setConfigurationJson !== 'function') {
|
|
128
|
+
throw new TempivoSensorError('runtimeUnavailable', 'Native setConfigurationJson is unavailable in this build.');
|
|
129
|
+
}
|
|
130
|
+
await native.setConfigurationJson(configToJson(partner));
|
|
88
131
|
}
|
|
89
132
|
catch (error) {
|
|
90
133
|
throw mapNativeError(error);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Partner BLE dual-range + always payload used in regression tests.
|
|
3
|
+
* Before SDK 1.1.2, get could return empty alerts and/or Sunday-only schedule.
|
|
4
|
+
*/
|
|
5
|
+
export declare const PARTNER_DUAL_RANGE_ALWAYS_BODY: {
|
|
6
|
+
schedule: {
|
|
7
|
+
always: boolean;
|
|
8
|
+
};
|
|
9
|
+
temperatureAlerts: ({
|
|
10
|
+
type: "range";
|
|
11
|
+
channel: "ambient";
|
|
12
|
+
lowC: number;
|
|
13
|
+
highC: number;
|
|
14
|
+
hysteresisC: number;
|
|
15
|
+
transmitOnBreach: boolean;
|
|
16
|
+
transmitOnReturn: boolean;
|
|
17
|
+
} | {
|
|
18
|
+
type: "range";
|
|
19
|
+
channel: "probe";
|
|
20
|
+
lowC: number;
|
|
21
|
+
highC: number;
|
|
22
|
+
hysteresisC: number;
|
|
23
|
+
transmitOnBreach: boolean;
|
|
24
|
+
transmitOnReturn: boolean;
|
|
25
|
+
})[];
|
|
26
|
+
measurementIntervalMinutes: number;
|
|
27
|
+
transmissionIntervalSeconds: number;
|
|
28
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Partner BLE dual-range + always payload used in regression tests.
|
|
3
|
+
* Before SDK 1.1.2, get could return empty alerts and/or Sunday-only schedule.
|
|
4
|
+
*/
|
|
5
|
+
export const PARTNER_DUAL_RANGE_ALWAYS_BODY = {
|
|
6
|
+
schedule: { always: true },
|
|
7
|
+
temperatureAlerts: [
|
|
8
|
+
{
|
|
9
|
+
type: 'range',
|
|
10
|
+
channel: 'ambient',
|
|
11
|
+
lowC: 2,
|
|
12
|
+
highC: 8,
|
|
13
|
+
hysteresisC: 0.5,
|
|
14
|
+
transmitOnBreach: true,
|
|
15
|
+
transmitOnReturn: true,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
type: 'range',
|
|
19
|
+
channel: 'probe',
|
|
20
|
+
lowC: -30,
|
|
21
|
+
highC: -18,
|
|
22
|
+
hysteresisC: 0.5,
|
|
23
|
+
transmitOnBreach: true,
|
|
24
|
+
transmitOnReturn: true,
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
measurementIntervalMinutes: 5,
|
|
28
|
+
transmissionIntervalSeconds: 3600,
|
|
29
|
+
};
|
package/dist/profile.js
CHANGED
|
@@ -218,35 +218,49 @@ export const BLE_MAX_TRANSMISSION_INTERVAL_SECONDS = 604_800;
|
|
|
218
218
|
function parseOptionalIntervals(body, options = {}) {
|
|
219
219
|
const validatePartnerLimits = !options.fromDevice;
|
|
220
220
|
const out = {};
|
|
221
|
-
if (body.measurementIntervalMinutes !== undefined) {
|
|
221
|
+
if (body.measurementIntervalMinutes !== undefined && body.measurementIntervalMinutes !== null) {
|
|
222
222
|
const n = parseFiniteNumber(body.measurementIntervalMinutes);
|
|
223
223
|
if (n === undefined || !Number.isFinite(n) || n <= 0) {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
throw new TempivoSensorError('invalidConfig',
|
|
224
|
+
if (options.fromDevice) {
|
|
225
|
+
// Vendor may emit 0/null; omit rather than fail the whole get.
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
throw new TempivoSensorError('invalidConfig', 'measurementIntervalMinutes must be a positive number.');
|
|
229
229
|
}
|
|
230
|
-
|
|
231
|
-
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
if (validatePartnerLimits) {
|
|
233
|
+
if (n < BLE_MIN_MEASUREMENT_INTERVAL_MINUTES) {
|
|
234
|
+
throw new TempivoSensorError('invalidConfig', `measurementIntervalMinutes must be at least ${BLE_MIN_MEASUREMENT_INTERVAL_MINUTES}.`);
|
|
235
|
+
}
|
|
236
|
+
if (n > BLE_MAX_MEASUREMENT_INTERVAL_MINUTES) {
|
|
237
|
+
throw new TempivoSensorError('invalidConfig', `measurementIntervalMinutes must be at most ${BLE_MAX_MEASUREMENT_INTERVAL_MINUTES}.`);
|
|
238
|
+
}
|
|
232
239
|
}
|
|
240
|
+
out.measurementIntervalMinutes = n;
|
|
233
241
|
}
|
|
234
|
-
out.measurementIntervalMinutes = n;
|
|
235
242
|
}
|
|
236
|
-
if (body.transmissionIntervalSeconds !== undefined) {
|
|
243
|
+
if (body.transmissionIntervalSeconds !== undefined && body.transmissionIntervalSeconds !== null) {
|
|
237
244
|
const n = parseFiniteNumber(body.transmissionIntervalSeconds);
|
|
238
|
-
if (n === undefined || !Number.
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
throw new TempivoSensorError('invalidConfig',
|
|
245
|
+
if (n === undefined || !Number.isFinite(n) || n <= 0 || !Number.isInteger(n)) {
|
|
246
|
+
if (options.fromDevice) {
|
|
247
|
+
// omit
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
throw new TempivoSensorError('invalidConfig', 'transmissionIntervalSeconds must be a positive integer.');
|
|
244
251
|
}
|
|
245
|
-
|
|
246
|
-
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
if (validatePartnerLimits) {
|
|
255
|
+
if (n < BLE_MIN_TRANSMISSION_INTERVAL_SECONDS) {
|
|
256
|
+
throw new TempivoSensorError('invalidConfig', `transmissionIntervalSeconds must be at least ${BLE_MIN_TRANSMISSION_INTERVAL_SECONDS} (1 hour).`);
|
|
257
|
+
}
|
|
258
|
+
if (n > BLE_MAX_TRANSMISSION_INTERVAL_SECONDS) {
|
|
259
|
+
throw new TempivoSensorError('invalidConfig', `transmissionIntervalSeconds must be at most ${BLE_MAX_TRANSMISSION_INTERVAL_SECONDS}.`);
|
|
260
|
+
}
|
|
247
261
|
}
|
|
262
|
+
out.transmissionIntervalSeconds = n;
|
|
248
263
|
}
|
|
249
|
-
out.transmissionIntervalSeconds = n;
|
|
250
264
|
}
|
|
251
265
|
if (validatePartnerLimits &&
|
|
252
266
|
out.measurementIntervalMinutes != null &&
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decode Efento ProtoConfig bytes (same descriptor as web bluetooth module).
|
|
3
|
+
*/
|
|
4
|
+
import protobuf from 'protobufjs/light.js';
|
|
5
|
+
import descriptor from './efento-protobuf-descriptor.js';
|
|
6
|
+
const root = protobuf.Root.fromJSON(descriptor);
|
|
7
|
+
const ProtoConfig = root.lookupType('ProtoConfig');
|
|
8
|
+
export function decodeProtoConfigBytes(bytes) {
|
|
9
|
+
const msg = ProtoConfig.decode(bytes);
|
|
10
|
+
return ProtoConfig.toObject(msg, {
|
|
11
|
+
enums: String,
|
|
12
|
+
longs: Number,
|
|
13
|
+
bytes: Array,
|
|
14
|
+
arrays: true,
|
|
15
|
+
objects: true,
|
|
16
|
+
oneofs: true,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function base64ToBytes(b64) {
|
|
20
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
21
|
+
const clean = b64.replace(/=+$/, '').replace(/[^A-Za-z0-9+/]/g, '');
|
|
22
|
+
const out = new Uint8Array((clean.length * 3) >> 2);
|
|
23
|
+
let o = 0;
|
|
24
|
+
for (let i = 0; i < clean.length; i += 4) {
|
|
25
|
+
const a = chars.indexOf(clean[i] ?? 'A');
|
|
26
|
+
const b = chars.indexOf(clean[i + 1] ?? 'A');
|
|
27
|
+
const c = chars.indexOf(clean[i + 2] ?? 'A');
|
|
28
|
+
const d = chars.indexOf(clean[i + 3] ?? 'A');
|
|
29
|
+
out[o++] = (a << 2) | (b >> 4);
|
|
30
|
+
if (clean[i + 2] != null)
|
|
31
|
+
out[o++] = ((b & 15) << 4) | (c >> 2);
|
|
32
|
+
if (clean[i + 3] != null)
|
|
33
|
+
out[o++] = ((c & 3) << 6) | d;
|
|
34
|
+
}
|
|
35
|
+
return out.subarray(0, o);
|
|
36
|
+
}
|
|
37
|
+
export function decodeProtoConfigBase64(b64) {
|
|
38
|
+
if (typeof atob === 'function') {
|
|
39
|
+
const binary = atob(b64);
|
|
40
|
+
const bytes = new Uint8Array(binary.length);
|
|
41
|
+
for (let i = 0; i < binary.length; i++)
|
|
42
|
+
bytes[i] = binary.charCodeAt(i);
|
|
43
|
+
return decodeProtoConfigBytes(bytes);
|
|
44
|
+
}
|
|
45
|
+
return decodeProtoConfigBytes(base64ToBytes(b64));
|
|
46
|
+
}
|