@tempivo/sensor-beacon 0.2.0 → 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 +142 -114
- 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/src/main/java/com/tempivo/sensor/beacon/TempivoSensorSession.kt +13 -0
- package/app.plugin.cjs +142 -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 +2 -0
- package/dist/index.js +1 -0
- 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/react-native.d.ts +1 -0
- package/dist/react-native.js +1 -0
- package/dist/tempivo-sensor-beacon.aar +0 -0
- 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/TempivoSensorBeacon.podspec +35 -0
- package/ios/TempivoSensorBeaconModule.swift +208 -0
- package/package.json +48 -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/build.gradle +0 -0
- /package/{android → android-aar}/library/consumer-rules.pro +0 -0
- /package/{android → android-aar}/library/src/main/AndroidManifest.xml +0 -0
- /package/{android → android-aar}/library/src/main/java/com/tempivo/sensor/beacon/PartnerBleRules.kt +0 -0
- /package/{android → android-aar}/library/src/main/java/com/tempivo/sensor/beacon/SensorBleRuntime.kt +0 -0
- /package/{android → android-aar}/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorBeaconDecoder.kt +0 -0
- /package/{android → android-aar}/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorBeaconScanner.kt +0 -0
- /package/{android → android-aar}/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorBeaconTypes.kt +0 -0
- /package/{android → android-aar}/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorCalibration.kt +0 -0
- /package/{android → android-aar}/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorError.kt +0 -0
- /package/{android → android-aar}/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorProfile.kt +0 -0
- /package/{android → android-aar}/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorQr.kt +0 -0
- /package/{android → android-aar}/settings.gradle +0 -0
package/README.md
CHANGED
|
@@ -1,42 +1,94 @@
|
|
|
1
1
|
# @tempivo/sensor-beacon
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
BLE for Tempivo sensors. One package for Expo, React Native, Android, iOS, and Node.
|
|
4
|
+
|
|
5
|
+
## Expo and React Native
|
|
6
|
+
|
|
7
|
+
Needs a development build (or bare React Native). Expo Go is not supported.
|
|
4
8
|
|
|
5
9
|
```bash
|
|
6
|
-
|
|
10
|
+
npx expo install @tempivo/sensor-beacon
|
|
7
11
|
```
|
|
8
12
|
|
|
9
|
-
|
|
10
|
-
|------|-----|
|
|
11
|
-
| `dist/` | Node / TypeScript: advertising decode, QR parse, config JSON |
|
|
12
|
-
| `android/` + `dist/tempivo-sensor-beacon.aar` | Android scan + GATT |
|
|
13
|
-
| `ios/` | iOS Swift Package (scan + session) |
|
|
13
|
+
app.json:
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"expo": {
|
|
18
|
+
"plugins": ["@tempivo/sensor-beacon"]
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
```
|
|
16
22
|
|
|
17
|
-
|
|
23
|
+
Then rebuild native:
|
|
18
24
|
|
|
19
|
-
|
|
25
|
+
```bash
|
|
26
|
+
npx expo prebuild
|
|
27
|
+
npx expo run:ios
|
|
28
|
+
# or
|
|
29
|
+
npx expo run:android
|
|
30
|
+
```
|
|
20
31
|
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
23
39
|
```
|
|
24
40
|
|
|
41
|
+
Add `NSBluetoothAlwaysUsageDescription` on iOS if you do not use Expo prebuild.
|
|
42
|
+
|
|
25
43
|
```ts
|
|
44
|
+
import {
|
|
45
|
+
parseSensorQrJson,
|
|
46
|
+
startScan,
|
|
47
|
+
stopScan,
|
|
48
|
+
addDeviceFoundListener,
|
|
49
|
+
connect,
|
|
50
|
+
setConfigurationJson,
|
|
51
|
+
getConfiguration,
|
|
52
|
+
triggerTransmission,
|
|
53
|
+
getCalibration,
|
|
54
|
+
disconnect,
|
|
55
|
+
} from '@tempivo/sensor-beacon';
|
|
56
|
+
|
|
57
|
+
const sub = await addDeviceFoundListener((device) => {
|
|
58
|
+
console.log(device.serialNumber, device.rssi, device.summary);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
await startScan();
|
|
62
|
+
|
|
26
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();
|
|
27
83
|
```
|
|
28
84
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
| `pin` | string | Sticker PIN. Required on `connect`. Also accepted as QR `resetCode`. |
|
|
33
|
-
| `model` | string | Sticker model. Omitted QR `model` becomes **HC7**. `HC5` selects legacy GATT. |
|
|
34
|
-
| `sessionType` | `modern` \| `legacy` | GATT path. HC7 / firmware 7+ is `modern`. HC5 / firmware 6.x is `legacy`. |
|
|
35
|
-
| `bluetoothMac` | string | BLE MAC with colons, derived from `serial`. |
|
|
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`.
|
|
36
88
|
|
|
37
|
-
## Config JSON
|
|
89
|
+
## Config JSON
|
|
38
90
|
|
|
39
|
-
|
|
91
|
+
Same as Cellular API config profiles (`temperatureAlerts` + `schedule`). You can build it yourself. Profiles are only for REST reuse.
|
|
40
92
|
|
|
41
93
|
```json
|
|
42
94
|
{
|
|
@@ -55,19 +107,17 @@ const qr = parseSensorQrJson(stickerQrText);
|
|
|
55
107
|
}
|
|
56
108
|
```
|
|
57
109
|
|
|
58
|
-
**Root**
|
|
59
|
-
|
|
60
110
|
| Field | Meaning |
|
|
61
|
-
|
|
62
|
-
| `temperatureAlerts` | Alert list. Empty array is valid
|
|
63
|
-
| `schedule` |
|
|
111
|
+
|------|---------|
|
|
112
|
+
| `temperatureAlerts` | Alert list. Empty array is valid. |
|
|
113
|
+
| `schedule` | `{ "always": true }` is 24/7. |
|
|
64
114
|
|
|
65
|
-
Ignored over BLE: `slug`, `name`, `measurementIntervalMinutes`, `transmissionIntervalSeconds`. Do not pass `
|
|
115
|
+
Ignored over BLE: `slug`, `name`, `measurementIntervalMinutes`, `transmissionIntervalSeconds`. Do not pass `alarmRules`.
|
|
66
116
|
|
|
67
117
|
**Each `temperatureAlerts[]` item**
|
|
68
118
|
|
|
69
119
|
| Field | Meaning |
|
|
70
|
-
|
|
120
|
+
|------|---------|
|
|
71
121
|
| `type` | `range` (band), `min` (below), or `max` (above). |
|
|
72
122
|
| `channel` | `ambient` or `probe`. |
|
|
73
123
|
| `lowC` / `highC` | Inclusive band for `type: "range"`. |
|
|
@@ -80,38 +130,74 @@ Ignored over BLE: `slug`, `name`, `measurementIntervalMinutes`, `transmissionInt
|
|
|
80
130
|
**`schedule` weekday window** (instead of `always`)
|
|
81
131
|
|
|
82
132
|
| Field | Meaning |
|
|
83
|
-
|
|
133
|
+
|------|---------|
|
|
84
134
|
| `weekdays` | **0 = Monday** … **6 = Sunday**. |
|
|
85
135
|
| `from` / `to` | 24h `HH:MM`. |
|
|
86
136
|
| `utcOffsetMinutes` | Offset from UTC for `from` / `to`. |
|
|
87
137
|
|
|
88
|
-
|
|
138
|
+
`GET /api/v1/devices/config-profiles/{slug}` returns `{ "profile": { … } }`. Pass that body or `profile` to `setConfigurationJson`.
|
|
89
139
|
|
|
90
|
-
|
|
140
|
+
## QR object
|
|
91
141
|
|
|
92
|
-
|
|
93
|
-
import { parseSensorQrJson, configurationFromApiProfile } from '@tempivo/sensor-beacon';
|
|
142
|
+
Sticker QR is JSON. Parse with `parseSensorQrJson`.
|
|
94
143
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
`https://app.tempivo.com/api/v1/devices/config-profiles/${slug}`,
|
|
98
|
-
{ headers: { Authorization: `Bearer ${apiKey}` } }
|
|
99
|
-
);
|
|
100
|
-
const body = await res.json();
|
|
101
|
-
const cfg = configurationFromApiProfile(body); // or body.profile
|
|
144
|
+
```json
|
|
145
|
+
{ "sn": "282C024F0012", "pin": "111111", "model": "HC7" }
|
|
102
146
|
```
|
|
103
147
|
|
|
104
|
-
|
|
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
|
|
105
157
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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. |
|
|
110
168
|
|
|
111
|
-
|
|
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.
|
|
112
196
|
|
|
113
197
|
## Node / TypeScript helpers
|
|
114
198
|
|
|
199
|
+
Decode advertisements and parse QR / config without native BLE.
|
|
200
|
+
|
|
115
201
|
```ts
|
|
116
202
|
import {
|
|
117
203
|
normalizeManufacturerBytes,
|
|
@@ -127,9 +213,9 @@ const cfg = parseSensorConfigurationJson(/* config JSON above */);
|
|
|
127
213
|
|
|
128
214
|
Invalid QR or config throws `TempivoSensorError` (`invalidQr`, `invalidPin`, `invalidConfig`).
|
|
129
215
|
|
|
130
|
-
## Android (
|
|
216
|
+
## Android (Kotlin)
|
|
131
217
|
|
|
132
|
-
`
|
|
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.
|
|
133
219
|
|
|
134
220
|
```kotlin
|
|
135
221
|
val qr = TempivoSensorQrParser.parse(stickerQrText)
|
|
@@ -146,23 +232,16 @@ try {
|
|
|
146
232
|
}
|
|
147
233
|
```
|
|
148
234
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
**`getCalibration()`**
|
|
157
|
-
|
|
158
|
-
| Field | Meaning |
|
|
159
|
-
|-------|---------|
|
|
160
|
-
| `laboratoryCalibrationDate` | ISO date (`YYYY-MM-DD`), or null if unset. |
|
|
161
|
-
| `laboratoryCalibrationTimestamp` | Same instant as unix seconds, or null. No expiry field on the device. |
|
|
235
|
+
```kotlin
|
|
236
|
+
val scanner = TempivoSensorBeaconScanner(context)
|
|
237
|
+
scanner.startScan { device ->
|
|
238
|
+
Log.d("scan", "${device.serialNumber} rssi=${device.rssi} ${device.summary}")
|
|
239
|
+
}
|
|
240
|
+
```
|
|
162
241
|
|
|
163
|
-
## iOS (
|
|
242
|
+
## iOS (Swift)
|
|
164
243
|
|
|
165
|
-
Needs a physical device
|
|
244
|
+
Needs a physical device for GATT. Simulator can scan advertisements only. Set **`NSBluetoothAlwaysUsageDescription`** in Info.plist.
|
|
166
245
|
|
|
167
246
|
```swift
|
|
168
247
|
let qr = try TempivoSensorQrParser.parse(stickerQrText)
|
|
@@ -175,57 +254,6 @@ let applied = try session.getConfiguration()
|
|
|
175
254
|
let cal = try session.getCalibration()
|
|
176
255
|
```
|
|
177
256
|
|
|
178
|
-
## Advertising (no pairing)
|
|
179
|
-
|
|
180
|
-
Filter manufacturer data on company id **0x026C**. Use **active scanning** so scan-response packets arrive. Product model is **not** in the broadcast. Connect uses QR `model` (HC7 is modern GATT).
|
|
181
|
-
|
|
182
|
-
```kotlin
|
|
183
|
-
val scanner = TempivoSensorBeaconScanner(context)
|
|
184
|
-
scanner.startScan { device ->
|
|
185
|
-
Log.d("scan", "${device.serialNumber} rssi=${device.rssi} ${device.summary}")
|
|
186
|
-
}
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
**Android `TempivoSensorBeaconDevice`**
|
|
190
|
-
|
|
191
|
-
| Field | Meaning |
|
|
192
|
-
|-------|---------|
|
|
193
|
-
| `deviceId` | Android BLE address. Often randomized. Not the sticker serial. |
|
|
194
|
-
| `bluetoothMacAddress` | MAC from the advertisement, with colons. |
|
|
195
|
-
| `serialNumber` | 12 hex chars from advertisement frame `0x03`. Same as QR `sn`. |
|
|
196
|
-
| `rssi` | Signal strength in dBm. |
|
|
197
|
-
| `summary` | Short display line. After scan response: measurement texts joined with ` · `. Before that: serial, or `Adv OK. Wait for scan response (measurements).` |
|
|
198
|
-
| `telemetry` | Decoded frames `0x03` + `0x04`, or null until enough bytes arrive. |
|
|
199
|
-
|
|
200
|
-
**`telemetry` (Android) / `reading` (iOS and Node)**
|
|
201
|
-
|
|
202
|
-
| Field | Meaning |
|
|
203
|
-
|-------|---------|
|
|
204
|
-
| `firmware` | `major.minor.patch` from the advertisement, e.g. `7.3.4`. |
|
|
205
|
-
| `batteryOk` | Battery status flag from the advertisement. |
|
|
206
|
-
| `encryptionEnabled` | Advertisement payload encryption flag. |
|
|
207
|
-
| `cellularStatus` | `ble_only`, `cell_ok`, `no_server`, or `net_issue`. |
|
|
208
|
-
| `readingTimestampUnix` | Sample time as unix seconds, or null. |
|
|
209
|
-
| `readingTimestampIso` | Same instant as UTC ISO-8601, or null. |
|
|
210
|
-
| `measurementIntervalSeconds` | Sample interval from the advertisement, in seconds. |
|
|
211
|
-
| `readings` | Decoded scan-response slots (see below). |
|
|
212
|
-
| `readingsCount` | Number of measurement slots. |
|
|
213
|
-
| `summary` | Same short line as on the device object. |
|
|
214
|
-
| `measurementCounter` | Counter from frame `0x03`, or null. |
|
|
215
|
-
|
|
216
|
-
Node also exposes `serialMac`, `temperatures[]`, `humidity`, and `rawHex`.
|
|
217
|
-
|
|
218
|
-
**Each `readings[]` / `measurements[]` slot**
|
|
219
|
-
|
|
220
|
-
| Field | Meaning |
|
|
221
|
-
|-------|---------|
|
|
222
|
-
| `typeHex` | Slot type, e.g. `0x01` temperature, `0x02` humidity. |
|
|
223
|
-
| `raw24` | Undecoded 24-bit payload. |
|
|
224
|
-
| `text` | Display value with unit, e.g. `21.8 °C`. No type name in the string. |
|
|
225
|
-
| `temperatureC` / `humidityPct` | Node only: numeric value when the slot is that type. |
|
|
226
|
-
|
|
227
|
-
iOS `SensorBeaconDiscovery` has `deviceId`, `rssi`, `serialNumber`, `bluetoothMacAddress`, and `reading` (same telemetry fields).
|
|
228
|
-
|
|
229
257
|
```swift
|
|
230
258
|
func sensorBeaconScanner(_ scanner: TempivoSensorBeaconScanner, didDiscover discovery: SensorBeaconDiscovery) {
|
|
231
259
|
print(discovery.serialNumber ?? "", discovery.rssi, discovery.reading?.summary ?? "")
|
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
|
+
}
|