@tempivo/sensor-beacon 0.1.1 → 0.3.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/README.md +222 -78
- package/android/build.gradle +38 -2
- package/android/src/main/AndroidManifest.xml +10 -0
- package/android/src/main/java/expo/modules/tempivosensorbeacon/SensorBeaconNative.java +233 -0
- package/android/src/main/java/expo/modules/tempivosensorbeacon/TempivoSensorBeaconModule.kt +122 -0
- package/android-aar/build.gradle +4 -0
- package/{android → android-aar}/library/build.gradle +3 -0
- package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/PartnerBleRules.kt +279 -0
- package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/SensorBleRuntime.kt +168 -0
- package/{android → android-aar}/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorBeaconDecoder.kt +6 -23
- package/{android → android-aar}/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorBeaconScanner.kt +0 -2
- package/{android → android-aar}/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorBeaconTypes.kt +2 -4
- package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorCalibration.kt +36 -0
- package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorError.kt +18 -0
- package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorProfile.kt +171 -0
- package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorQr.kt +82 -0
- package/android-aar/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorSession.kt +127 -0
- package/{android → android-aar}/settings.gradle +1 -0
- package/app.plugin.cjs +142 -0
- package/dist/calibration.d.ts +7 -0
- package/dist/calibration.js +42 -0
- package/dist/decoder.js +4 -12
- package/dist/errors.d.ts +6 -0
- package/dist/errors.js +8 -0
- package/dist/expo-modules-core.web-stub.d.ts +5 -0
- package/dist/expo-modules-core.web-stub.js +9 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.js +6 -1
- package/dist/native-bridge.d.ts +14 -0
- package/dist/native-bridge.js +51 -0
- package/dist/native-helpers.d.ts +5 -0
- package/dist/native-helpers.js +33 -0
- package/dist/native-module.d.ts +14 -0
- package/dist/native-module.js +104 -0
- package/dist/native-types.d.ts +41 -0
- package/dist/native-types.js +1 -0
- package/dist/profile.d.ts +10 -0
- package/dist/profile.js +226 -0
- package/dist/qr.d.ts +16 -0
- package/dist/qr.js +90 -0
- package/dist/react-native.d.ts +1 -0
- package/dist/react-native.js +1 -0
- package/dist/session-types.d.ts +64 -0
- package/dist/session-types.js +1 -0
- package/dist/tempivo-sensor-beacon.aar +0 -0
- package/dist/types.d.ts +3 -3
- package/expo-module.config.json +12 -0
- package/ios/Frameworks/TempivoSensorBridge.xcframework/Info.plist +43 -0
- package/ios/Frameworks/TempivoSensorBridge.xcframework/ios-arm64/TempivoSensorBridge.framework/Headers/TempivoSensorBridge.h +159 -0
- package/ios/Frameworks/TempivoSensorBridge.xcframework/ios-arm64/TempivoSensorBridge.framework/Info.plist +35 -0
- package/ios/Frameworks/TempivoSensorBridge.xcframework/ios-arm64/TempivoSensorBridge.framework/Modules/module.modulemap +8 -0
- 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 +159 -0
- package/ios/Frameworks/TempivoSensorBridge.xcframework/ios-arm64-simulator/TempivoSensorBridge.framework/Info.plist +31 -0
- package/ios/Frameworks/TempivoSensorBridge.xcframework/ios-arm64-simulator/TempivoSensorBridge.framework/Modules/module.modulemap +8 -0
- package/ios/Frameworks/TempivoSensorBridge.xcframework/ios-arm64-simulator/TempivoSensorBridge.framework/TempivoSensorBridge +0 -0
- package/ios/Sources/TempivoSensorBeacon/TempivoSensorBeaconDecoder.swift +4 -8
- package/ios/Sources/TempivoSensorBeacon/TempivoSensorBeaconTypes.swift +3 -6
- package/ios/Sources/TempivoSensorBeacon/TempivoSensorCalibration.swift +22 -0
- package/ios/Sources/TempivoSensorBeacon/TempivoSensorError.swift +24 -0
- package/ios/Sources/TempivoSensorBeacon/TempivoSensorProfile.swift +123 -0
- package/ios/Sources/TempivoSensorBeacon/TempivoSensorQr.swift +78 -0
- package/ios/Sources/TempivoSensorBeacon/TempivoSensorSession.swift +161 -0
- package/ios/Sources/TempivoSensorBeacon/TempivoSensorSessionTypes.swift +93 -0
- package/ios/TempivoSensorBeacon.podspec +35 -0
- package/ios/TempivoSensorBeaconModule.swift +208 -0
- package/package.json +55 -7
- /package/{android → android-aar}/gradle/wrapper/gradle-wrapper.jar +0 -0
- /package/{android → android-aar}/gradle/wrapper/gradle-wrapper.properties +0 -0
- /package/{android → android-aar}/gradle.properties +0 -0
- /package/{android → android-aar}/gradlew +0 -0
- /package/{android → android-aar}/library/consumer-rules.pro +0 -0
- /package/{android → android-aar}/library/src/main/AndroidManifest.xml +0 -0
package/README.md
CHANGED
|
@@ -1,129 +1,273 @@
|
|
|
1
1
|
# @tempivo/sensor-beacon
|
|
2
2
|
|
|
3
|
-
Tempivo
|
|
3
|
+
BLE for Tempivo sensors. One package for Expo, React Native, Android, iOS, and Node.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Expo and React Native
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|------|-----|
|
|
9
|
-
| `dist/` | Node / TypeScript decoder |
|
|
10
|
-
| `android/` + `dist/tempivo-sensor-beacon.aar` | Android scan + decode |
|
|
11
|
-
| `ios/` | iOS Swift Package (scan + decode) |
|
|
7
|
+
Needs a development build (or bare React Native). Expo Go is not supported.
|
|
12
8
|
|
|
13
9
|
```bash
|
|
14
|
-
|
|
10
|
+
npx expo install @tempivo/sensor-beacon
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
app.json:
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"expo": {
|
|
18
|
+
"plugins": ["@tempivo/sensor-beacon"]
|
|
19
|
+
}
|
|
20
|
+
}
|
|
15
21
|
```
|
|
16
22
|
|
|
17
|
-
|
|
23
|
+
Then rebuild native:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx expo prebuild
|
|
27
|
+
npx expo run:ios
|
|
28
|
+
# or
|
|
29
|
+
npx expo run:android
|
|
30
|
+
```
|
|
18
31
|
|
|
19
|
-
|
|
32
|
+
Android minSdk is 26. The plugin sets that on prebuild.
|
|
33
|
+
|
|
34
|
+
Bare React Native (no Expo app yet):
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx install-expo-modules@latest
|
|
38
|
+
npm install @tempivo/sensor-beacon
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Add `NSBluetoothAlwaysUsageDescription` on iOS if you do not use Expo prebuild.
|
|
20
42
|
|
|
21
43
|
```ts
|
|
22
44
|
import {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
45
|
+
parseSensorQrJson,
|
|
46
|
+
startScan,
|
|
47
|
+
stopScan,
|
|
48
|
+
addDeviceFoundListener,
|
|
49
|
+
connect,
|
|
50
|
+
setConfigurationJson,
|
|
51
|
+
getConfiguration,
|
|
52
|
+
triggerTransmission,
|
|
53
|
+
getCalibration,
|
|
54
|
+
disconnect,
|
|
26
55
|
} from '@tempivo/sensor-beacon';
|
|
27
56
|
|
|
28
|
-
const
|
|
57
|
+
const sub = await addDeviceFoundListener((device) => {
|
|
58
|
+
console.log(device.serialNumber, device.rssi, device.summary);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
await startScan();
|
|
62
|
+
|
|
63
|
+
const qr = parseSensorQrJson(stickerQrText);
|
|
64
|
+
await connect(qr);
|
|
65
|
+
await setConfigurationJson({
|
|
66
|
+
schedule: { always: true },
|
|
67
|
+
temperatureAlerts: [
|
|
68
|
+
{
|
|
69
|
+
type: 'range',
|
|
70
|
+
channel: 'probe',
|
|
71
|
+
lowC: -30,
|
|
72
|
+
highC: -10,
|
|
73
|
+
hysteresisC: 0.5,
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
});
|
|
77
|
+
await triggerTransmission();
|
|
78
|
+
const applied = await getConfiguration();
|
|
79
|
+
const cal = await getCalibration();
|
|
80
|
+
await disconnect();
|
|
81
|
+
stopScan();
|
|
82
|
+
sub.remove();
|
|
29
83
|
```
|
|
30
84
|
|
|
31
|
-
|
|
85
|
+
`startScan` and `connect` request Bluetooth permission. Sticker PIN is required on `connect`. Wrong PIN is `invalidPin`.
|
|
86
|
+
|
|
87
|
+
If a bundler misses native BLE, import `@tempivo/sensor-beacon/react-native`.
|
|
88
|
+
|
|
89
|
+
## Config JSON
|
|
90
|
+
|
|
91
|
+
Same as Cellular API config profiles (`temperatureAlerts` + `schedule`). You can build it yourself. Profiles are only for REST reuse.
|
|
92
|
+
|
|
93
|
+
```json
|
|
94
|
+
{
|
|
95
|
+
"schedule": { "always": true },
|
|
96
|
+
"temperatureAlerts": [
|
|
97
|
+
{
|
|
98
|
+
"type": "range",
|
|
99
|
+
"channel": "probe",
|
|
100
|
+
"lowC": -30,
|
|
101
|
+
"highC": -10,
|
|
102
|
+
"hysteresisC": 0.5,
|
|
103
|
+
"transmitOnBreach": true,
|
|
104
|
+
"transmitOnReturn": true
|
|
105
|
+
}
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
| Field | Meaning |
|
|
111
|
+
|------|---------|
|
|
112
|
+
| `temperatureAlerts` | Alert list. Empty array is valid. |
|
|
113
|
+
| `schedule` | `{ "always": true }` is 24/7. |
|
|
114
|
+
|
|
115
|
+
Ignored over BLE: `slug`, `name`, `measurementIntervalMinutes`, `transmissionIntervalSeconds`. Do not pass `alarmRules`.
|
|
116
|
+
|
|
117
|
+
**Each `temperatureAlerts[]` item**
|
|
118
|
+
|
|
119
|
+
| Field | Meaning |
|
|
120
|
+
|------|---------|
|
|
121
|
+
| `type` | `range` (band), `min` (below), or `max` (above). |
|
|
122
|
+
| `channel` | `ambient` or `probe`. |
|
|
123
|
+
| `lowC` / `highC` | Inclusive band for `type: "range"`. |
|
|
124
|
+
| `minC` | Threshold for `type: "min"`. |
|
|
125
|
+
| `maxC` | Threshold for `type: "max"`. |
|
|
126
|
+
| `hysteresisC` | Degrees of hysteresis. Omitted: **1**. |
|
|
127
|
+
| `transmitOnBreach` | Uplink when the alert trips. Omitted: **true**. |
|
|
128
|
+
| `transmitOnReturn` | Extra uplink when temp returns inside a `range`. Omitted: **true**. Range only. |
|
|
129
|
+
|
|
130
|
+
**`schedule` weekday window** (instead of `always`)
|
|
131
|
+
|
|
132
|
+
| Field | Meaning |
|
|
133
|
+
|------|---------|
|
|
134
|
+
| `weekdays` | **0 = Monday** … **6 = Sunday**. |
|
|
135
|
+
| `from` / `to` | 24h `HH:MM`. |
|
|
136
|
+
| `utcOffsetMinutes` | Offset from UTC for `from` / `to`. |
|
|
137
|
+
|
|
138
|
+
`GET /api/v1/devices/config-profiles/{slug}` returns `{ "profile": { … } }`. Pass that body or `profile` to `setConfigurationJson`.
|
|
32
139
|
|
|
33
|
-
|
|
140
|
+
## QR object
|
|
34
141
|
|
|
35
|
-
|
|
142
|
+
Sticker QR is JSON. Parse with `parseSensorQrJson`.
|
|
143
|
+
|
|
144
|
+
```json
|
|
145
|
+
{ "sn": "282C024F0012", "pin": "111111", "model": "HC7" }
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
| Field | Type | Meaning |
|
|
149
|
+
|------|------|---------|
|
|
150
|
+
| `serial` | string | Device serial, 12 hex chars, no colons. From QR `sn`. |
|
|
151
|
+
| `pin` | string | Sticker PIN. Required on `connect`. Also accepted as QR `resetCode`. |
|
|
152
|
+
| `model` | string | Sticker model. Omitted QR `model` becomes **HC7**. `HC5` selects legacy GATT. |
|
|
153
|
+
| `sessionType` | `modern` \| `legacy` | GATT path. HC7 / firmware 7+ is `modern`. HC5 / firmware 6.x is `legacy`. |
|
|
154
|
+
| `bluetoothMac` | string | BLE MAC with colons, derived from `serial`. |
|
|
155
|
+
|
|
156
|
+
## Scan result
|
|
157
|
+
|
|
158
|
+
Filter manufacturer data on company id **0x026C**. Active scanning is on so scan-response packets arrive.
|
|
159
|
+
|
|
160
|
+
| Field | Meaning |
|
|
161
|
+
|------|---------|
|
|
162
|
+
| `deviceId` | Platform BLE id. Often randomized. Not the sticker serial. |
|
|
163
|
+
| `bluetoothMacAddress` | MAC from the advertisement, with colons. |
|
|
164
|
+
| `serialNumber` | 12 hex chars from advertisement frame `0x03`. Same as QR `sn`. |
|
|
165
|
+
| `rssi` | Signal strength in dBm. |
|
|
166
|
+
| `summary` | Short display line. |
|
|
167
|
+
| `telemetry` | Decoded frames `0x03` + `0x04`, or null until enough bytes arrive. |
|
|
168
|
+
|
|
169
|
+
**`telemetry`**
|
|
170
|
+
|
|
171
|
+
| Field | Meaning |
|
|
172
|
+
|------|---------|
|
|
173
|
+
| `firmware` | `major.minor.patch`, e.g. `7.3.4`. |
|
|
174
|
+
| `batteryOk` | Battery status flag. |
|
|
175
|
+
| `encryptionEnabled` | Advertisement payload encryption flag. |
|
|
176
|
+
| `cellularStatus` | `ble_only`, `cell_ok`, `no_server`, or `net_issue`. |
|
|
177
|
+
| `readingTimestampUnix` | Sample time as unix seconds, or null. |
|
|
178
|
+
| `readingTimestampIso` | Same instant as UTC ISO-8601, or null. |
|
|
179
|
+
| `measurementIntervalSeconds` | Sample interval, in seconds. |
|
|
180
|
+
| `readings` | Decoded scan-response slots. |
|
|
181
|
+
| `readingsCount` | Number of measurement slots. |
|
|
182
|
+
| `summary` | Same short line as on the device object. |
|
|
183
|
+
| `measurementCounter` | Counter from frame `0x03`, or null. |
|
|
184
|
+
|
|
185
|
+
**Each `readings[]` slot**
|
|
186
|
+
|
|
187
|
+
| Field | Meaning |
|
|
188
|
+
|------|---------|
|
|
189
|
+
| `typeHex` | Slot type, e.g. `0x01` temperature, `0x02` humidity. |
|
|
190
|
+
| `raw24` | Undecoded 24-bit payload. |
|
|
191
|
+
| `text` | Display value with unit, e.g. `21.8 °C`. |
|
|
192
|
+
|
|
193
|
+
`triggerTransmission()` returns `{ ok, supported }`. `supported: false` is not an error.
|
|
194
|
+
|
|
195
|
+
`getCalibration()` returns `{ laboratoryCalibrationDate, laboratoryCalibrationTimestamp }`. Date is `YYYY-MM-DD`, or null if unset.
|
|
196
|
+
|
|
197
|
+
## Node / TypeScript helpers
|
|
198
|
+
|
|
199
|
+
Decode advertisements and parse QR / config without native BLE.
|
|
36
200
|
|
|
37
201
|
```ts
|
|
38
202
|
import {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
203
|
+
normalizeManufacturerBytes,
|
|
204
|
+
decodeSensorBeaconPayload,
|
|
205
|
+
parseSensorQrJson,
|
|
206
|
+
parseSensorConfigurationJson,
|
|
43
207
|
} from '@tempivo/sensor-beacon';
|
|
44
208
|
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
ingestManufacturerPayload(scanPayload, macKey, cache);
|
|
49
|
-
const reading = buildSensorBeaconReading(cache.last03.get(macKey), cache.last04.get(macKey));
|
|
209
|
+
const qr = parseSensorQrJson('{"sn":"282C024F0012","pin":"111111","model":"HC7"}');
|
|
210
|
+
const reading = decodeSensorBeaconPayload(normalizeManufacturerBytes(advertisementBytes));
|
|
211
|
+
const cfg = parseSensorConfigurationJson(/* config JSON above */);
|
|
50
212
|
```
|
|
51
213
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
| Export | Purpose |
|
|
55
|
-
|--------|---------|
|
|
56
|
-
| `TEMPVO_SENSOR_MANUFACTURER_ID` | BLE company id `0x026c` |
|
|
57
|
-
| `normalizeManufacturerBytes` | Strip company id prefix when present |
|
|
58
|
-
| `decodeSensorBeaconPayload` | One-shot decode |
|
|
59
|
-
| `ingestManufacturerPayload` | Update frame cache |
|
|
60
|
-
| `buildSensorBeaconReading` | Merge cached frames |
|
|
61
|
-
| `SensorBeaconReading` | Parsed device + measurements |
|
|
214
|
+
Invalid QR or config throws `TempivoSensorError` (`invalidQr`, `invalidPin`, `invalidConfig`).
|
|
62
215
|
|
|
63
|
-
|
|
216
|
+
## Android (Kotlin)
|
|
64
217
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
Published artifact: `dist/tempivo-sensor-beacon.aar` (built before npm publish).
|
|
218
|
+
AAR: `dist/tempivo-sensor-beacon.aar` after `npm run build:android`. Sources: `android-aar/library`. Request `BLUETOOTH_SCAN` and `BLUETOOTH_CONNECT` (API 31+) or location on older APIs.
|
|
68
219
|
|
|
69
220
|
```kotlin
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
)
|
|
221
|
+
val qr = TempivoSensorQrParser.parse(stickerQrText)
|
|
222
|
+
val session = TempivoSensorSession(context)
|
|
223
|
+
|
|
224
|
+
session.connect(qr)
|
|
225
|
+
try {
|
|
226
|
+
session.setConfigurationJson(profileJson)
|
|
227
|
+
val trigger = session.triggerTransmission()
|
|
228
|
+
val applied = session.getConfiguration()
|
|
229
|
+
val cal = session.getCalibration()
|
|
230
|
+
} finally {
|
|
231
|
+
session.disconnect()
|
|
77
232
|
}
|
|
78
233
|
```
|
|
79
234
|
|
|
80
|
-
Adjust the path to your `node_modules` layout.
|
|
81
|
-
|
|
82
235
|
```kotlin
|
|
83
|
-
import com.tempivo.sensor.beacon.TempivoSensorBeaconScanner
|
|
84
|
-
|
|
85
236
|
val scanner = TempivoSensorBeaconScanner(context)
|
|
86
237
|
scanner.startScan { device ->
|
|
87
|
-
|
|
238
|
+
Log.d("scan", "${device.serialNumber} rssi=${device.rssi} ${device.summary}")
|
|
88
239
|
}
|
|
89
240
|
```
|
|
90
241
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
## iOS
|
|
242
|
+
## iOS (Swift)
|
|
94
243
|
|
|
95
|
-
|
|
96
|
-
2. Path: `node_modules/@tempivo/sensor-beacon/ios`
|
|
97
|
-
3. Add product **TempivoSensorBeacon**
|
|
98
|
-
4. Set **`NSBluetoothAlwaysUsageDescription`** in Info.plist
|
|
244
|
+
Needs a physical device for GATT. Simulator can scan advertisements only. Set **`NSBluetoothAlwaysUsageDescription`** in Info.plist.
|
|
99
245
|
|
|
100
246
|
```swift
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
func start() { scanner.startScanning() }
|
|
247
|
+
let qr = try TempivoSensorQrParser.parse(stickerQrText)
|
|
248
|
+
let session = TempivoSensorSession()
|
|
249
|
+
try session.connect(qr: qr)
|
|
250
|
+
defer { session.disconnect() }
|
|
251
|
+
try session.setConfigurationJson(profileJson)
|
|
252
|
+
_ = try session.triggerTransmission()
|
|
253
|
+
let applied = try session.getConfiguration()
|
|
254
|
+
let cal = try session.getCalibration()
|
|
255
|
+
```
|
|
112
256
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
257
|
+
```swift
|
|
258
|
+
func sensorBeaconScanner(_ scanner: TempivoSensorBeaconScanner, didDiscover discovery: SensorBeaconDiscovery) {
|
|
259
|
+
print(discovery.serialNumber ?? "", discovery.rssi, discovery.reading?.summary ?? "")
|
|
116
260
|
}
|
|
117
261
|
```
|
|
118
262
|
|
|
119
|
-
|
|
263
|
+
## Errors
|
|
264
|
+
|
|
265
|
+
Native GATT maps to: `invalidPin`, `invalidQr`, `invalidConfig`, `notConnected`, `unsupportedCommand`, `connectFailed`, `runtimeUnavailable`, `unknown`.
|
|
120
266
|
|
|
121
|
-
##
|
|
267
|
+
## Tests
|
|
122
268
|
|
|
123
269
|
```bash
|
|
124
|
-
npm run
|
|
125
|
-
npm run build:android # requires Android SDK
|
|
126
|
-
npm run build:all # both
|
|
270
|
+
npm run test:unit
|
|
127
271
|
```
|
|
128
272
|
|
|
129
273
|
## License
|
package/android/build.gradle
CHANGED
|
@@ -1,4 +1,40 @@
|
|
|
1
1
|
plugins {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
id 'com.android.library'
|
|
3
|
+
id 'expo-module-gradle-plugin'
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
group = 'com.tempivo.sensor.beacon'
|
|
7
|
+
version = '0.3.0'
|
|
8
|
+
|
|
9
|
+
android {
|
|
10
|
+
namespace "expo.modules.tempivosensorbeacon"
|
|
11
|
+
defaultConfig {
|
|
12
|
+
minSdk 26
|
|
13
|
+
versionCode 1
|
|
14
|
+
versionName "0.3.0"
|
|
15
|
+
}
|
|
16
|
+
compileOptions {
|
|
17
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
18
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
19
|
+
}
|
|
20
|
+
kotlinOptions {
|
|
21
|
+
jvmTarget = "17"
|
|
22
|
+
// Native BLE runtime is Kotlin 2.3. Expo 57 compiles with Kotlin 2.1.
|
|
23
|
+
freeCompilerArgs += ["-Xskip-metadata-version-check"]
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
def aarFile = file("libs/tempivo-sensor-beacon.aar")
|
|
28
|
+
if (!aarFile.exists()) {
|
|
29
|
+
throw new GradleException("Missing android/libs/tempivo-sensor-beacon.aar. From the package root run: npm run build:android")
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
dependencies {
|
|
33
|
+
compileOnly files(aarFile)
|
|
34
|
+
compileOnly "pl.efento.mobile:bluetooth:2.3.0"
|
|
35
|
+
runtimeOnly files(aarFile)
|
|
36
|
+
runtimeOnly "pl.efento.mobile:bluetooth:2.3.0"
|
|
37
|
+
runtimeOnly "androidx.core:core-ktx:1.17.0"
|
|
38
|
+
runtimeOnly "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2"
|
|
39
|
+
runtimeOnly "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2"
|
|
4
40
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
2
|
+
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
|
|
3
|
+
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
|
|
4
|
+
<uses-permission
|
|
5
|
+
android:name="android.permission.BLUETOOTH_SCAN"
|
|
6
|
+
android:usesPermissionFlags="neverForLocation" />
|
|
7
|
+
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
|
8
|
+
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="30" />
|
|
9
|
+
<uses-feature android:name="android.hardware.bluetooth_le" android:required="false" />
|
|
10
|
+
</manifest>
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
package expo.modules.tempivosensorbeacon;
|
|
2
|
+
|
|
3
|
+
import android.content.Context;
|
|
4
|
+
import com.tempivo.sensor.beacon.TempivoSensorBeaconDevice;
|
|
5
|
+
import com.tempivo.sensor.beacon.TempivoSensorBeaconScanner;
|
|
6
|
+
import com.tempivo.sensor.beacon.TempivoSensorBeaconTelemetry;
|
|
7
|
+
import com.tempivo.sensor.beacon.TempivoSensorCalibration;
|
|
8
|
+
import com.tempivo.sensor.beacon.TempivoSensorException;
|
|
9
|
+
import com.tempivo.sensor.beacon.TempivoSensorQr;
|
|
10
|
+
import com.tempivo.sensor.beacon.TempivoSensorQrParser;
|
|
11
|
+
import com.tempivo.sensor.beacon.TempivoSensorSession;
|
|
12
|
+
import java.util.ArrayList;
|
|
13
|
+
import java.util.HashMap;
|
|
14
|
+
import java.util.List;
|
|
15
|
+
import java.util.Map;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Java facade so the Expo Kotlin module can compile with Kotlin 2.1 against a Kotlin 2.3 AAR.
|
|
19
|
+
*/
|
|
20
|
+
final class SensorBeaconNative {
|
|
21
|
+
interface DeviceCallback {
|
|
22
|
+
void onDevice(Map<String, Object> payload);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
static final class NativeError extends RuntimeException {
|
|
26
|
+
final String code;
|
|
27
|
+
|
|
28
|
+
NativeError(String code, String message, Throwable cause) {
|
|
29
|
+
super(message, cause);
|
|
30
|
+
this.code = code;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private TempivoSensorBeaconScanner scanner;
|
|
35
|
+
private TempivoSensorSession session;
|
|
36
|
+
|
|
37
|
+
void startScan(Context context, DeviceCallback callback) {
|
|
38
|
+
try {
|
|
39
|
+
requireScanner(context).startScan(device -> callback.onDevice(deviceMap(device)));
|
|
40
|
+
} catch (Throwable error) {
|
|
41
|
+
throw wrap(error);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
void stopScan() {
|
|
46
|
+
if (scanner != null) {
|
|
47
|
+
scanner.stopScan();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
boolean isScanning() {
|
|
52
|
+
return scanner != null && scanner.isScanning();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
Map<String, Object> connect(Context context, String qrJson) {
|
|
56
|
+
try {
|
|
57
|
+
TempivoSensorQr qr = TempivoSensorQrParser.INSTANCE.parse(qrJson);
|
|
58
|
+
requireSession(context).connectBlocking(qr);
|
|
59
|
+
return qrMap(qr);
|
|
60
|
+
} catch (Throwable error) {
|
|
61
|
+
throw wrap(error);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
void disconnect() {
|
|
66
|
+
try {
|
|
67
|
+
if (session != null) {
|
|
68
|
+
session.disconnectBlocking();
|
|
69
|
+
}
|
|
70
|
+
} catch (Throwable error) {
|
|
71
|
+
throw wrap(error);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
String getConfigurationJson() {
|
|
76
|
+
try {
|
|
77
|
+
return requireSessionConnected().getConfigurationJsonBlocking();
|
|
78
|
+
} catch (Throwable error) {
|
|
79
|
+
throw wrap(error);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
void setConfigurationJson(String json) {
|
|
84
|
+
try {
|
|
85
|
+
requireSessionConnected().setConfigurationJsonBlocking(json);
|
|
86
|
+
} catch (Throwable error) {
|
|
87
|
+
throw wrap(error);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
Map<String, Object> triggerTransmission() {
|
|
92
|
+
try {
|
|
93
|
+
TempivoSensorSession.TriggerResult result = requireSessionConnected().triggerTransmissionBlocking();
|
|
94
|
+
Map<String, Object> payload = new HashMap<>();
|
|
95
|
+
payload.put("ok", result.getOk());
|
|
96
|
+
payload.put("supported", result.getSupported());
|
|
97
|
+
return payload;
|
|
98
|
+
} catch (Throwable error) {
|
|
99
|
+
throw wrap(error);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
Map<String, Object> getCalibration() {
|
|
104
|
+
try {
|
|
105
|
+
return calibrationMap(requireSessionConnected().getCalibrationBlocking());
|
|
106
|
+
} catch (Throwable error) {
|
|
107
|
+
throw wrap(error);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private TempivoSensorBeaconScanner requireScanner(Context context) {
|
|
112
|
+
if (scanner == null) {
|
|
113
|
+
scanner = new TempivoSensorBeaconScanner(context);
|
|
114
|
+
}
|
|
115
|
+
return scanner;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private TempivoSensorSession requireSession(Context context) {
|
|
119
|
+
if (session == null) {
|
|
120
|
+
session = new TempivoSensorSession(context);
|
|
121
|
+
}
|
|
122
|
+
return session;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private TempivoSensorSession requireSessionConnected() {
|
|
126
|
+
if (session == null) {
|
|
127
|
+
throw new NativeError("notConnected", "Not connected.", null);
|
|
128
|
+
}
|
|
129
|
+
return session;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private static NativeError wrap(Throwable error) {
|
|
133
|
+
if (error instanceof NativeError) {
|
|
134
|
+
return (NativeError) error;
|
|
135
|
+
}
|
|
136
|
+
TempivoSensorException sensor = findSensor(error);
|
|
137
|
+
if (sensor != null) {
|
|
138
|
+
return new NativeError(toJs(sensor.getCode()), sensor.getMessage(), error);
|
|
139
|
+
}
|
|
140
|
+
if (error instanceof TempivoSensorBeaconScanner.ScanNotPermittedException) {
|
|
141
|
+
return new NativeError("runtimeUnavailable", error.getMessage(), error);
|
|
142
|
+
}
|
|
143
|
+
String message = error.getMessage();
|
|
144
|
+
return new NativeError("unknown", message != null ? message : "Sensor request failed.", error);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private static TempivoSensorException findSensor(Throwable error) {
|
|
148
|
+
Throwable current = error;
|
|
149
|
+
while (current != null) {
|
|
150
|
+
if (current instanceof TempivoSensorException) {
|
|
151
|
+
return (TempivoSensorException) current;
|
|
152
|
+
}
|
|
153
|
+
current = current.getCause();
|
|
154
|
+
}
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private static String toJs(TempivoSensorException.Code code) {
|
|
159
|
+
switch (code) {
|
|
160
|
+
case INVALID_PIN:
|
|
161
|
+
return "invalidPin";
|
|
162
|
+
case INVALID_QR:
|
|
163
|
+
return "invalidQr";
|
|
164
|
+
case INVALID_CONFIG:
|
|
165
|
+
return "invalidConfig";
|
|
166
|
+
case NOT_CONNECTED:
|
|
167
|
+
return "notConnected";
|
|
168
|
+
case UNSUPPORTED_COMMAND:
|
|
169
|
+
return "unsupportedCommand";
|
|
170
|
+
case RUNTIME_UNAVAILABLE:
|
|
171
|
+
return "runtimeUnavailable";
|
|
172
|
+
case CONNECT_FAILED:
|
|
173
|
+
return "connectFailed";
|
|
174
|
+
default:
|
|
175
|
+
return "unknown";
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private static Map<String, Object> qrMap(TempivoSensorQr qr) {
|
|
180
|
+
Map<String, Object> payload = new HashMap<>();
|
|
181
|
+
payload.put("serial", qr.getSerial());
|
|
182
|
+
payload.put("pin", qr.getPin());
|
|
183
|
+
payload.put("model", qr.getModel());
|
|
184
|
+
payload.put(
|
|
185
|
+
"sessionType",
|
|
186
|
+
qr.getSessionType() == TempivoSensorSession.SessionType.LEGACY ? "legacy" : "modern");
|
|
187
|
+
payload.put("bluetoothMac", qr.getBluetoothMac());
|
|
188
|
+
return payload;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private static Map<String, Object> calibrationMap(TempivoSensorCalibration cal) {
|
|
192
|
+
Map<String, Object> payload = new HashMap<>();
|
|
193
|
+
payload.put("laboratoryCalibrationDate", cal.getLaboratoryCalibrationDate());
|
|
194
|
+
payload.put("laboratoryCalibrationTimestamp", cal.getLaboratoryCalibrationTimestamp());
|
|
195
|
+
return payload;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private static Map<String, Object> deviceMap(TempivoSensorBeaconDevice device) {
|
|
199
|
+
Map<String, Object> payload = new HashMap<>();
|
|
200
|
+
payload.put("deviceId", device.getDeviceId());
|
|
201
|
+
payload.put("bluetoothMacAddress", device.getBluetoothMacAddress());
|
|
202
|
+
payload.put("serialNumber", device.getSerialNumber());
|
|
203
|
+
payload.put("rssi", device.getRssi());
|
|
204
|
+
payload.put("summary", device.getSummary());
|
|
205
|
+
TempivoSensorBeaconTelemetry telemetry = device.getTelemetry();
|
|
206
|
+
payload.put("telemetry", telemetry == null ? null : telemetryMap(telemetry));
|
|
207
|
+
return payload;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private static Map<String, Object> telemetryMap(TempivoSensorBeaconTelemetry telemetry) {
|
|
211
|
+
Map<String, Object> payload = new HashMap<>();
|
|
212
|
+
payload.put("firmware", telemetry.getFirmware());
|
|
213
|
+
payload.put("batteryOk", telemetry.getBatteryOk());
|
|
214
|
+
payload.put("encryptionEnabled", telemetry.getEncryptionEnabled());
|
|
215
|
+
payload.put("cellularStatus", telemetry.getCellularStatus());
|
|
216
|
+
payload.put("measurementCounter", telemetry.getMeasurementCounter());
|
|
217
|
+
payload.put("readingTimestampUnix", telemetry.getReadingTimestampUnix());
|
|
218
|
+
payload.put("readingTimestampIso", telemetry.getReadingTimestampIso());
|
|
219
|
+
payload.put("measurementIntervalSeconds", telemetry.getMeasurementIntervalSeconds());
|
|
220
|
+
payload.put("readingsCount", telemetry.getReadingsCount());
|
|
221
|
+
payload.put("summary", telemetry.getSummary());
|
|
222
|
+
List<Map<String, Object>> readings = new ArrayList<>();
|
|
223
|
+
telemetry.getReadings().forEach(reading -> {
|
|
224
|
+
Map<String, Object> row = new HashMap<>();
|
|
225
|
+
row.put("typeHex", reading.getTypeHex());
|
|
226
|
+
row.put("raw24", reading.getRaw24());
|
|
227
|
+
row.put("text", reading.getText());
|
|
228
|
+
readings.add(row);
|
|
229
|
+
});
|
|
230
|
+
payload.put("readings", readings);
|
|
231
|
+
return payload;
|
|
232
|
+
}
|
|
233
|
+
}
|