@tempivo/sensor-beacon 0.1.0 → 0.2.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 +209 -50
- package/android/build.gradle +4 -0
- package/android/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/android/gradle.properties +3 -0
- package/android/gradlew +251 -0
- package/android/library/build.gradle +30 -0
- package/android/library/consumer-rules.pro +1 -0
- package/android/library/src/main/AndroidManifest.xml +2 -0
- package/android/library/src/main/java/com/tempivo/sensor/beacon/PartnerBleRules.kt +279 -0
- package/android/library/src/main/java/com/tempivo/sensor/beacon/SensorBleRuntime.kt +168 -0
- package/android/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorBeaconDecoder.kt +389 -0
- package/android/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorBeaconScanner.kt +158 -0
- package/android/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorBeaconTypes.kt +40 -0
- package/android/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorCalibration.kt +36 -0
- package/android/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorError.kt +18 -0
- package/android/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorProfile.kt +171 -0
- package/android/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorQr.kt +82 -0
- package/android/library/src/main/java/com/tempivo/sensor/beacon/TempivoSensorSession.kt +114 -0
- package/android/settings.gradle +19 -0
- package/dist/calibration.d.ts +7 -0
- package/dist/calibration.js +42 -0
- package/dist/decoder.js +10 -18
- package/dist/errors.d.ts +6 -0
- package/dist/errors.js +8 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +5 -1
- 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/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/ios/Package.swift +22 -0
- package/ios/Sources/TempivoSensorBeacon/TempivoSensorBeaconDecoder.swift +409 -0
- package/ios/Sources/TempivoSensorBeacon/TempivoSensorBeaconScanner.swift +159 -0
- package/ios/Sources/TempivoSensorBeacon/TempivoSensorBeaconTypes.swift +103 -0
- 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/package.json +20 -6
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
package com.tempivo.sensor.beacon
|
|
2
|
+
|
|
3
|
+
import pl.efento.mobile.bluetooth.model.sensor.Action
|
|
4
|
+
import pl.efento.mobile.bluetooth.model.sensor.DisabledCondition
|
|
5
|
+
import pl.efento.mobile.bluetooth.model.sensor.HighThresholdCondition
|
|
6
|
+
import pl.efento.mobile.bluetooth.model.sensor.LogicOperator
|
|
7
|
+
import pl.efento.mobile.bluetooth.model.sensor.LogicOperatorCondition
|
|
8
|
+
import pl.efento.mobile.bluetooth.model.sensor.LogicOperatorConditionParameters
|
|
9
|
+
import pl.efento.mobile.bluetooth.model.sensor.LowThresholdCondition
|
|
10
|
+
import pl.efento.mobile.bluetooth.model.sensor.Rule
|
|
11
|
+
import pl.efento.mobile.bluetooth.model.sensor.RuleCalendar
|
|
12
|
+
import pl.efento.mobile.bluetooth.model.sensor.SensorConfiguration
|
|
13
|
+
import pl.efento.mobile.bluetooth.model.sensor.TemperatureLowHighThresholdConditionParameters
|
|
14
|
+
import pl.efento.mobile.bluetooth.model.sensor.TriggeringMode
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Compile Cellular API `temperatureAlerts` + `schedule` into device rules.
|
|
18
|
+
* Range + `transmitOnReturn` uses the same OR layout as POST /devices/config-profiles.
|
|
19
|
+
*/
|
|
20
|
+
internal object PartnerBleRules {
|
|
21
|
+
fun compile(cfg: TempivoSensorConfiguration): Pair<List<Rule>, List<RuleCalendar>> {
|
|
22
|
+
val rules = MutableList(12) { i ->
|
|
23
|
+
Rule(i + 1, emptySet(), DisabledCondition, Action.NO_ACTION)
|
|
24
|
+
}
|
|
25
|
+
val used = BooleanArray(12)
|
|
26
|
+
val returnLowSlots = mutableListOf<Int>()
|
|
27
|
+
|
|
28
|
+
for (alert in cfg.temperatureAlerts) {
|
|
29
|
+
val channel = if (alert.channel == "probe") 3 else 1
|
|
30
|
+
val hyst = alert.hysteresisC ?: 1.0
|
|
31
|
+
when (alert.type) {
|
|
32
|
+
"min" -> {
|
|
33
|
+
val slot = findFree(used) ?: continue
|
|
34
|
+
val t = alert.minC ?: continue
|
|
35
|
+
val action = if (alert.transmitOnBreach) Action.TRIGGER_TRANSMISSION_WITH_ACK else Action.NO_ACTION
|
|
36
|
+
putThreshold(rules, slot, channel, low = true, t, hyst, action)
|
|
37
|
+
used[slot] = true
|
|
38
|
+
}
|
|
39
|
+
"max" -> {
|
|
40
|
+
val slot = findFree(used) ?: continue
|
|
41
|
+
val t = alert.maxC ?: continue
|
|
42
|
+
val action = if (alert.transmitOnBreach) Action.TRIGGER_TRANSMISSION_WITH_ACK else Action.NO_ACTION
|
|
43
|
+
putThreshold(rules, slot, channel, low = false, t, hyst, action)
|
|
44
|
+
used[slot] = true
|
|
45
|
+
}
|
|
46
|
+
"range" -> {
|
|
47
|
+
val low = alert.lowC ?: continue
|
|
48
|
+
val high = alert.highC ?: continue
|
|
49
|
+
val pair = findPair(used) ?: continue
|
|
50
|
+
val ret = alert.transmitOnReturn
|
|
51
|
+
val action =
|
|
52
|
+
when {
|
|
53
|
+
ret -> Action.NO_ACTION
|
|
54
|
+
alert.transmitOnBreach -> Action.TRIGGER_TRANSMISSION_WITH_ACK
|
|
55
|
+
else -> Action.NO_ACTION
|
|
56
|
+
}
|
|
57
|
+
putThreshold(rules, pair, channel, low = true, low, hyst, action)
|
|
58
|
+
putThreshold(rules, pair + 1, channel, low = false, high, hyst, action)
|
|
59
|
+
used[pair] = true
|
|
60
|
+
used[pair + 1] = true
|
|
61
|
+
if (ret) returnLowSlots.add(pair)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
for (lowSlot in returnLowSlots) {
|
|
67
|
+
val logicSlot = findFree(used) ?: break
|
|
68
|
+
rules[logicSlot] =
|
|
69
|
+
Rule(
|
|
70
|
+
logicSlot + 1,
|
|
71
|
+
emptySet(),
|
|
72
|
+
LogicOperatorCondition(
|
|
73
|
+
LogicOperatorConditionParameters(
|
|
74
|
+
LogicOperator.OR,
|
|
75
|
+
setOf(lowSlot + 1, lowSlot + 2),
|
|
76
|
+
emptySet(),
|
|
77
|
+
0,
|
|
78
|
+
0,
|
|
79
|
+
),
|
|
80
|
+
),
|
|
81
|
+
Action.TRIGGER_TRANSMISSION_WITH_ACK,
|
|
82
|
+
)
|
|
83
|
+
used[logicSlot] = true
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
val active = rules.filter { it.condition !is DisabledCondition }.map { it.id }.toSet()
|
|
87
|
+
val calendars = MutableList(6) { i ->
|
|
88
|
+
RuleCalendar(i + 1, emptySet(), RuleCalendar.Type.Disabled)
|
|
89
|
+
}
|
|
90
|
+
if (active.isNotEmpty()) {
|
|
91
|
+
calendars[0] = RuleCalendar(1, active, weeklyType(cfg.schedule))
|
|
92
|
+
}
|
|
93
|
+
return rules to calendars
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
fun decompile(cfg: SensorConfiguration): TempivoSensorConfiguration {
|
|
97
|
+
val padded = MutableList(12) { i ->
|
|
98
|
+
cfg.rules.orEmpty().getOrNull(i)
|
|
99
|
+
?: Rule(i + 1, emptySet(), DisabledCondition, Action.NO_ACTION)
|
|
100
|
+
}
|
|
101
|
+
val orRefs = orSelectedPairs(padded)
|
|
102
|
+
val used = BooleanArray(12)
|
|
103
|
+
val alerts = mutableListOf<TempivoTemperatureAlert>()
|
|
104
|
+
|
|
105
|
+
for (i in 0..10) {
|
|
106
|
+
if (used[i] || used[i + 1]) continue
|
|
107
|
+
val low = padded[i]
|
|
108
|
+
val high = padded[i + 1]
|
|
109
|
+
if (low.condition !is LowThresholdCondition || high.condition !is HighThresholdCondition) continue
|
|
110
|
+
val lowCh = channelOf(low) ?: continue
|
|
111
|
+
val highCh = channelOf(high) ?: continue
|
|
112
|
+
if (lowCh != highCh) continue
|
|
113
|
+
val lowP = (low.condition as LowThresholdCondition).conditionParameters
|
|
114
|
+
val highP = (high.condition as HighThresholdCondition).conditionParameters
|
|
115
|
+
val hasReturn = orRefs.any { it.first == i + 1 && it.second == i + 2 }
|
|
116
|
+
val breachOnly =
|
|
117
|
+
!hasReturn && (low.action != Action.NO_ACTION || high.action != Action.NO_ACTION)
|
|
118
|
+
alerts.add(
|
|
119
|
+
TempivoTemperatureAlert(
|
|
120
|
+
type = "range",
|
|
121
|
+
channel = lowCh,
|
|
122
|
+
lowC = lowP.thresholdValue.toDouble(),
|
|
123
|
+
highC = highP.thresholdValue.toDouble(),
|
|
124
|
+
hysteresisC = lowP.hysteresisValue?.toDouble() ?: 1.0,
|
|
125
|
+
transmitOnBreach = hasReturn || breachOnly,
|
|
126
|
+
transmitOnReturn = hasReturn,
|
|
127
|
+
),
|
|
128
|
+
)
|
|
129
|
+
used[i] = true
|
|
130
|
+
used[i + 1] = true
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
for (i in 0 until 12) {
|
|
134
|
+
if (used[i]) continue
|
|
135
|
+
val r = padded[i]
|
|
136
|
+
when (val c = r.condition) {
|
|
137
|
+
is LowThresholdCondition -> {
|
|
138
|
+
val ch = channelOf(r) ?: continue
|
|
139
|
+
alerts.add(
|
|
140
|
+
TempivoTemperatureAlert(
|
|
141
|
+
type = "min",
|
|
142
|
+
channel = ch,
|
|
143
|
+
minC = c.conditionParameters.thresholdValue.toDouble(),
|
|
144
|
+
hysteresisC = c.conditionParameters.hysteresisValue?.toDouble() ?: 1.0,
|
|
145
|
+
transmitOnBreach = r.action != Action.NO_ACTION,
|
|
146
|
+
),
|
|
147
|
+
)
|
|
148
|
+
used[i] = true
|
|
149
|
+
}
|
|
150
|
+
is HighThresholdCondition -> {
|
|
151
|
+
val ch = channelOf(r) ?: continue
|
|
152
|
+
alerts.add(
|
|
153
|
+
TempivoTemperatureAlert(
|
|
154
|
+
type = "max",
|
|
155
|
+
channel = ch,
|
|
156
|
+
maxC = c.conditionParameters.thresholdValue.toDouble(),
|
|
157
|
+
hysteresisC = c.conditionParameters.hysteresisValue?.toDouble() ?: 1.0,
|
|
158
|
+
transmitOnBreach = r.action != Action.NO_ACTION,
|
|
159
|
+
),
|
|
160
|
+
)
|
|
161
|
+
used[i] = true
|
|
162
|
+
}
|
|
163
|
+
else -> Unit
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return TempivoSensorConfiguration(alerts, decompileSchedule(cfg.ruleCalendars))
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
private fun orSelectedPairs(rules: List<Rule>): List<Pair<Int, Int>> {
|
|
171
|
+
val out = mutableListOf<Pair<Int, Int>>()
|
|
172
|
+
for (r in rules) {
|
|
173
|
+
val c = r.condition as? LogicOperatorCondition ?: continue
|
|
174
|
+
val p = c.conditionParameters
|
|
175
|
+
if (p.logicOperator != LogicOperator.OR) continue
|
|
176
|
+
val ids = p.selectedRules.sorted()
|
|
177
|
+
if (ids.size == 2) out.add(ids[0] to ids[1])
|
|
178
|
+
}
|
|
179
|
+
return out
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private fun channelOf(rule: Rule): String? =
|
|
183
|
+
when {
|
|
184
|
+
rule.channels.contains(3) -> "probe"
|
|
185
|
+
rule.channels.contains(1) -> "ambient"
|
|
186
|
+
else -> null
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private fun findPair(used: BooleanArray): Int? {
|
|
190
|
+
for (i in 0..10) {
|
|
191
|
+
if (!used[i] && !used[i + 1]) return i
|
|
192
|
+
}
|
|
193
|
+
return null
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
private fun findFree(used: BooleanArray): Int? {
|
|
197
|
+
for (i in 0 until 12) if (!used[i]) return i
|
|
198
|
+
return null
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private fun putThreshold(
|
|
202
|
+
rules: MutableList<Rule>,
|
|
203
|
+
slot: Int,
|
|
204
|
+
channel: Int,
|
|
205
|
+
low: Boolean,
|
|
206
|
+
threshold: Double,
|
|
207
|
+
hyst: Double,
|
|
208
|
+
action: Action,
|
|
209
|
+
) {
|
|
210
|
+
if (slot !in 0..11) return
|
|
211
|
+
val params =
|
|
212
|
+
TemperatureLowHighThresholdConditionParameters(
|
|
213
|
+
threshold,
|
|
214
|
+
hyst,
|
|
215
|
+
TriggeringMode.CONSECUTIVE_SAMPLES,
|
|
216
|
+
1,
|
|
217
|
+
)
|
|
218
|
+
val condition = if (low) LowThresholdCondition(params) else HighThresholdCondition(params)
|
|
219
|
+
rules[slot] = Rule(slot + 1, setOf(channel), condition, action)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private fun decompileSchedule(calendars: List<RuleCalendar>?): TempivoSchedule {
|
|
223
|
+
val weekly = calendars.orEmpty().map { it.type }.filterIsInstance<RuleCalendar.Type.Weekly>().firstOrNull()
|
|
224
|
+
?: return TempivoSchedule(always = true)
|
|
225
|
+
val days = weekly.days.map { dayToPartnerIndex(it) }.sorted()
|
|
226
|
+
val always = days.size == 7 && weekly.fromTime == 0 && weekly.toTime >= 1439
|
|
227
|
+
return if (always) {
|
|
228
|
+
TempivoSchedule(always = true)
|
|
229
|
+
} else {
|
|
230
|
+
TempivoSchedule(
|
|
231
|
+
always = false,
|
|
232
|
+
weekdays = days,
|
|
233
|
+
from = TempivoSensorProfile.hhmmFromMinutes(weekly.fromTime),
|
|
234
|
+
to = TempivoSensorProfile.hhmmFromMinutes(weekly.toTime),
|
|
235
|
+
utcOffsetMinutes = weekly.timezoneOffset,
|
|
236
|
+
)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private fun weeklyType(schedule: TempivoSchedule): RuleCalendar.Type {
|
|
241
|
+
if (schedule.always) {
|
|
242
|
+
return RuleCalendar.Type.Weekly(
|
|
243
|
+
days = RuleCalendar.Type.Weekly.Day.entries.toSet(),
|
|
244
|
+
fromTime = 0,
|
|
245
|
+
toTime = 1439,
|
|
246
|
+
timezoneOffset = 0,
|
|
247
|
+
)
|
|
248
|
+
}
|
|
249
|
+
val days = schedule.weekdays.map { partnerIndexToDay(it) }.toSet()
|
|
250
|
+
return RuleCalendar.Type.Weekly(
|
|
251
|
+
days = days,
|
|
252
|
+
fromTime = TempivoSensorProfile.minutesFromHhmm(schedule.from ?: "00:00"),
|
|
253
|
+
toTime = TempivoSensorProfile.minutesFromHhmm(schedule.to ?: "23:59"),
|
|
254
|
+
timezoneOffset = schedule.utcOffsetMinutes,
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private fun partnerIndexToDay(index: Int): RuleCalendar.Type.Weekly.Day =
|
|
259
|
+
when (index) {
|
|
260
|
+
0 -> RuleCalendar.Type.Weekly.Day.MONDAY
|
|
261
|
+
1 -> RuleCalendar.Type.Weekly.Day.TUESDAY
|
|
262
|
+
2 -> RuleCalendar.Type.Weekly.Day.WEDNESDAY
|
|
263
|
+
3 -> RuleCalendar.Type.Weekly.Day.THURSDAY
|
|
264
|
+
4 -> RuleCalendar.Type.Weekly.Day.FRIDAY
|
|
265
|
+
5 -> RuleCalendar.Type.Weekly.Day.SATURDAY
|
|
266
|
+
else -> RuleCalendar.Type.Weekly.Day.SUNDAY
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private fun dayToPartnerIndex(day: RuleCalendar.Type.Weekly.Day): Int =
|
|
270
|
+
when (day) {
|
|
271
|
+
RuleCalendar.Type.Weekly.Day.MONDAY -> 0
|
|
272
|
+
RuleCalendar.Type.Weekly.Day.TUESDAY -> 1
|
|
273
|
+
RuleCalendar.Type.Weekly.Day.WEDNESDAY -> 2
|
|
274
|
+
RuleCalendar.Type.Weekly.Day.THURSDAY -> 3
|
|
275
|
+
RuleCalendar.Type.Weekly.Day.FRIDAY -> 4
|
|
276
|
+
RuleCalendar.Type.Weekly.Day.SATURDAY -> 5
|
|
277
|
+
RuleCalendar.Type.Weekly.Day.SUNDAY -> 6
|
|
278
|
+
}
|
|
279
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
package com.tempivo.sensor.beacon
|
|
2
|
+
|
|
3
|
+
import android.app.Application
|
|
4
|
+
import kotlinx.coroutines.delay
|
|
5
|
+
import pl.efento.mobile.bluetooth.EfentoBluetooth
|
|
6
|
+
import pl.efento.mobile.bluetooth.api.SensorConnection
|
|
7
|
+
import pl.efento.mobile.bluetooth.api.SensorLegacyConnection
|
|
8
|
+
import pl.efento.mobile.bluetooth.exception.InvalidResetCodeException
|
|
9
|
+
import pl.efento.mobile.bluetooth.exception.UnsupportedCommandException
|
|
10
|
+
import pl.efento.mobile.bluetooth.model.BluetoothMacAddress
|
|
11
|
+
import pl.efento.mobile.bluetooth.model.sensor.Rule
|
|
12
|
+
import pl.efento.mobile.bluetooth.model.sensor.RuleCalendar
|
|
13
|
+
import pl.efento.mobile.bluetooth.model.sensor.SensorConfiguration
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Internal adapter to the sensor BLE runtime. Keep vendor types inside this file.
|
|
17
|
+
*/
|
|
18
|
+
internal object SensorBleRuntime {
|
|
19
|
+
private val initMonitor = Any()
|
|
20
|
+
@Volatile private var initialized = false
|
|
21
|
+
|
|
22
|
+
fun initialize(application: Application) {
|
|
23
|
+
if (initialized) return
|
|
24
|
+
synchronized(initMonitor) {
|
|
25
|
+
if (initialized) return
|
|
26
|
+
EfentoBluetooth.init(application)
|
|
27
|
+
initialized = true
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
fun mapError(e: Throwable): TempivoSensorException {
|
|
32
|
+
if (e is TempivoSensorException) return e
|
|
33
|
+
val className = e.javaClass.name
|
|
34
|
+
return when {
|
|
35
|
+
e is InvalidResetCodeException || className.contains("InvalidResetCode") ->
|
|
36
|
+
TempivoSensorException(TempivoSensorException.Code.INVALID_PIN, "Invalid PIN.", e)
|
|
37
|
+
e is UnsupportedCommandException || className.contains("UnsupportedCommand") ->
|
|
38
|
+
TempivoSensorException(TempivoSensorException.Code.UNSUPPORTED_COMMAND, "Unsupported command.", e)
|
|
39
|
+
className.contains("NotConnected") ->
|
|
40
|
+
TempivoSensorException(TempivoSensorException.Code.NOT_CONNECTED, "Not connected.", e)
|
|
41
|
+
else ->
|
|
42
|
+
TempivoSensorException(TempivoSensorException.Code.UNKNOWN, "Sensor request failed.", e)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
suspend fun connect(
|
|
47
|
+
application: Application,
|
|
48
|
+
serial: String,
|
|
49
|
+
bluetoothMac: String,
|
|
50
|
+
pin: Int,
|
|
51
|
+
legacy: Boolean,
|
|
52
|
+
): Session {
|
|
53
|
+
initialize(application)
|
|
54
|
+
val mac = BluetoothMacAddress(bluetoothMac)
|
|
55
|
+
// PIN stays on the connection: set, trigger, calibration writes, and many other commands check it.
|
|
56
|
+
return if (legacy) {
|
|
57
|
+
val c =
|
|
58
|
+
EfentoBluetooth.sensorLegacyConnection(
|
|
59
|
+
deviceID = serial,
|
|
60
|
+
bluetoothMacAddress = mac,
|
|
61
|
+
resetCode = pin,
|
|
62
|
+
encryptionKey = "",
|
|
63
|
+
)
|
|
64
|
+
c.connect()
|
|
65
|
+
if (pin >= 0) c.resetCode = pin
|
|
66
|
+
Session.Legacy(c)
|
|
67
|
+
} else {
|
|
68
|
+
val c =
|
|
69
|
+
EfentoBluetooth.sensorConnection(
|
|
70
|
+
deviceID = serial,
|
|
71
|
+
bluetoothMacAddress = mac,
|
|
72
|
+
resetCode = pin,
|
|
73
|
+
encryptionKey = "",
|
|
74
|
+
)
|
|
75
|
+
c.connect()
|
|
76
|
+
if (pin >= 0) c.resetCode = pin
|
|
77
|
+
Session.Modern(c)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
suspend fun disconnect(session: Session?) {
|
|
82
|
+
when (session) {
|
|
83
|
+
is Session.Modern -> runCatching { session.connection.disconnect() }
|
|
84
|
+
is Session.Legacy -> runCatching { session.connection.disconnect() }
|
|
85
|
+
null -> Unit
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
suspend fun readConfiguration(session: Session): SensorConfiguration {
|
|
90
|
+
return when (session) {
|
|
91
|
+
is Session.Modern -> modernGetConfiguration(session.connection)
|
|
92
|
+
is Session.Legacy ->
|
|
93
|
+
throw TempivoSensorException(
|
|
94
|
+
TempivoSensorException.Code.UNSUPPORTED_COMMAND,
|
|
95
|
+
"Configuration rules require a current-generation sensor session.",
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
suspend fun writeRules(session: Session, rules: List<Rule>, calendars: List<RuleCalendar>) {
|
|
101
|
+
when (session) {
|
|
102
|
+
is Session.Modern -> {
|
|
103
|
+
val base = modernGetConfiguration(session.connection)
|
|
104
|
+
val next = base.copy(rules = rules.take(12), ruleCalendars = calendars.take(6))
|
|
105
|
+
session.connection.commands.setConfiguration(next) { }
|
|
106
|
+
}
|
|
107
|
+
is Session.Legacy ->
|
|
108
|
+
throw TempivoSensorException(
|
|
109
|
+
TempivoSensorException.Code.UNSUPPORTED_COMMAND,
|
|
110
|
+
"Configuration rules require a current-generation sensor session.",
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
suspend fun triggerTransmission(session: Session): TempivoSensorSession.TriggerResult {
|
|
116
|
+
return when (session) {
|
|
117
|
+
is Session.Modern -> {
|
|
118
|
+
session.connection.commands.triggerCommunicationWithServer { }
|
|
119
|
+
TempivoSensorSession.TriggerResult(ok = true, supported = true)
|
|
120
|
+
}
|
|
121
|
+
is Session.Legacy -> {
|
|
122
|
+
try {
|
|
123
|
+
session.connection.cellularCommands.triggerCommunicationWithServer { }
|
|
124
|
+
TempivoSensorSession.TriggerResult(ok = true, supported = true)
|
|
125
|
+
} catch (e: UnsupportedCommandException) {
|
|
126
|
+
TempivoSensorSession.TriggerResult(ok = false, supported = false)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
suspend fun readCalibration(session: Session): TempivoSensorCalibration {
|
|
133
|
+
return when (session) {
|
|
134
|
+
is Session.Modern -> {
|
|
135
|
+
val ext = session.connection.commands.getExtendedConfiguration { }
|
|
136
|
+
TempivoSensorCalibrationDecoder.decode(ext.laboratoryCalibrationTimestamp)
|
|
137
|
+
}
|
|
138
|
+
is Session.Legacy -> TempivoSensorCalibration(null, null)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
fun compileRules(cfg: TempivoSensorConfiguration): Pair<List<Rule>, List<RuleCalendar>> =
|
|
143
|
+
PartnerBleRules.compile(cfg)
|
|
144
|
+
|
|
145
|
+
fun decompile(cfg: SensorConfiguration): TempivoSensorConfiguration =
|
|
146
|
+
PartnerBleRules.decompile(cfg)
|
|
147
|
+
|
|
148
|
+
private suspend fun modernGetConfiguration(modern: SensorConnection): SensorConfiguration {
|
|
149
|
+
var last: Exception? = null
|
|
150
|
+
repeat(3) { attempt ->
|
|
151
|
+
try {
|
|
152
|
+
if (attempt > 0) {
|
|
153
|
+
runCatching { modern.commands.getDeviceInfo { } }
|
|
154
|
+
}
|
|
155
|
+
return modern.commands.getConfiguration { }
|
|
156
|
+
} catch (e: Exception) {
|
|
157
|
+
last = e
|
|
158
|
+
if (attempt < 2) delay(400)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
throw last ?: TempivoSensorException(TempivoSensorException.Code.UNKNOWN, "Could not read configuration.")
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
sealed class Session {
|
|
165
|
+
class Modern(val connection: SensorConnection) : Session()
|
|
166
|
+
class Legacy(val connection: SensorLegacyConnection) : Session()
|
|
167
|
+
}
|
|
168
|
+
}
|