-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathApiSaga.ts
408 lines (352 loc) · 16 KB
/
ApiSaga.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
import { put, call, select, all, take } from 'redux-saga/effects'
import ApiActions from 'App/Stores/Api/Actions'
import BeepBaseActions from 'App/Stores/BeepBase/Actions'
import UserActions from 'App/Stores/User/Actions'
import AuthActions from 'App/Stores/Auth/Actions'
import api from 'App/Services/ApiService'
import { DeviceModel } from '../Models/DeviceModel'
import { FirmwareModel } from '../Models/FirmwareModel'
import { SensorDefinitionModel } from '../Models/SensorDefinitionModel'
import { getDevice, getHardwareId, getLoRaWanState, getPairedPeripheral, getTemperatureSensorDefinitions, getWeightSensorDefinitions } from '../Stores/BeepBase/Selectors'
import BleHelpers, { COMMANDS } from '../Helpers/BleHelpers'
import { PairedPeripheralModel } from '../Models/PairedPeripheralModel'
import { BITMASK_ADAPTIVE_DATA_RATE, BITMASK_DUTY_CYCLE_LIMITATION, BITMASK_DISABLED, BITMASK_ENABLED, LoRaWanStateModel } from '../Models/LoRaWanStateModel'
import { APP_EUI, TTNModel } from '../Models/TTNModel'
import { BeepBaseTypes } from '../Stores/BeepBase/Actions'
import { CHANNELS } from '../Models/AudioModel'
import { getRefreshToken } from '../Stores/User/Selectors'
import { navigate } from '../Services/NavigationService'
function* guardedRequest<Fn extends (...args: any[]) => any>(fn: Fn, ...args: Parameters<Fn>) {
const response = yield fn(...args)
if (response.status == 401 || response.status == 403) {
//token expired
console.log("token expired!", response)
//refresh token
const refreshToken = yield select(getRefreshToken)
if (!refreshToken) {
//no refresh token, logout user
console.log("no refresh token")
//yield put(AuthActions.logout())
return { ok: false, data: "Authorization token expired" }
}
//Beep API has no support for refresh tokens
// const refreshResponse = yield call(api.refreshToken, refreshToken)
const refreshResponse = { ok: false }
if (refreshResponse.ok && refreshResponse.data?.token) {
//refresh successful
console.log("refresh successful!", refreshResponse)
const { token, refresh_token } = refreshResponse.data
yield call(api.setKey, token)
yield put(UserActions.setToken(token, refresh_token))
//redo request with new token
const redoResponse = yield fn(...args)
if (redoResponse.code == 401) {
//request failed after refresh, logout user
// yield put(AuthActions.logout())
}
return redoResponse
} else {
//refresh failed, logout user
console.log("refresh failed")
// yield put(AuthActions.logout())
}
}
else if(response.status == 500 || response.status == 400 ){
console.error("Server error")
console.log("server error", response)
}
return response
}
export function* getDevices(action: any) {
const response = yield guardedRequest(api.getDevices)
if (response && response.ok) {
const devices: Array<DeviceModel> = []
response.data?.map((item: any) => devices.push(new DeviceModel(item)))
yield put(UserActions.setDevices(devices))
} else {
yield put(ApiActions.apiFailure(response))
}
}
export function* checkDeviceRegistration(action: any) {
yield put(ApiActions.setRegisterState("checking"))
const { peripheralId, hardwareId } = action
//search for existing device
const deviceResponse = yield guardedRequest(api.getDevice, hardwareId.id)
if (deviceResponse && deviceResponse.ok && deviceResponse.data) {
if (deviceResponse.data.info) {
//info field has error code
switch (deviceResponse.data.info) {
case "device_not_yours":
//cancel wizard
yield put(ApiActions.setRegisterState("deviceAlreadyLinkedToAnotherAccount"))
break;
}
} else {
//no info field means we have a search result
if (Array.isArray(deviceResponse.data) && deviceResponse.data.length > 0) {
// device found but may not have a devEUI
if (deviceResponse.devEUI == null)
{
yield put(ApiActions.setRegisterState("failed"))
yield put(ApiActions.setRegisterState("notYetRegistered"))
console.log("Registration failed (device exists but devEUI is not defined)")
}
yield put(ApiActions.setRegisterState("alreadyRegistered"))
const device = new DeviceModel(deviceResponse.data[0])
yield put(BeepBaseActions.setDevice(device))
//update firmware with LoRa devEUI. This will also rename the BLE name
yield call(BleHelpers.write, peripheralId, COMMANDS.WRITE_LORAWAN_DEVEUI, device.devEUI)
} else {
//device not found
yield put(ApiActions.setRegisterState("notYetRegistered"))
}
}
} else {
yield put(ApiActions.setRegisterState("failed"))
yield put(ApiActions.apiFailure(deviceResponse))
}
}
export function* registerDevice(action: any) {
yield put(ApiActions.setRegisterState("registering"))
const { peripheralId, requestParams } = action
const registerResponse = yield guardedRequest(api.registerDevice, requestParams)
if (registerResponse && registerResponse.ok) {
yield put(ApiActions.setRegisterState("registered"))
const device = new DeviceModel(registerResponse.data)
yield put(BeepBaseActions.setDevice(device))
//update firmware with LoRa devEUI. This will also rename the BLE name
yield call(BleHelpers.write, peripheralId, COMMANDS.WRITE_LORAWAN_DEVEUI, device.devEUI)
//reset device to factory defaults (as specified here)
//ENERGY
let params = Buffer.alloc(3)
let i = 0
params.writeUint8(1, i++) //message to send ratio
params.writeUInt16BE(15, i++) //interval in minutes
yield call(BleHelpers.write, peripheralId, COMMANDS.WRITE_APPLICATION_CONFIG, params)
//LORA
yield call(BleHelpers.write, peripheralId, COMMANDS.READ_LORAWAN_STATE)
yield take(BeepBaseTypes.SET_LO_RA_WAN_STATE)
const loRaWanState: LoRaWanStateModel = getLoRaWanState(yield select())
let newState = BITMASK_ADAPTIVE_DATA_RATE | BITMASK_DUTY_CYCLE_LIMITATION
if (loRaWanState.hasJoined) {
newState |= BITMASK_ENABLED
}
yield call(BleHelpers.write, peripheralId, COMMANDS.WRITE_LORAWAN_STATE, newState)
//AUDIO
params = Buffer.alloc(6)
i = 0
params.writeUint8(CHANNELS[0].bitmask, i++) //IN3LM
params.writeUint8(20, i++) //gain
params.writeInt8(0, i++) //volume
params.writeUint8(10, i++) //number of bins
params.writeUint8(9, i++) //start bin
params.writeUint8(70, i++) //stop bin
yield call(BleHelpers.write, peripheralId, COMMANDS.WRITE_AUDIO_ADC_CONFIG, params)
//CLOCK TODO: check if feature is supported in firmware
params = Buffer.alloc(4)
params.writeUint32BE((new Date().valueOf() + 1300) / 1000, 0)
yield call(BleHelpers.write, peripheralId, COMMANDS.WRITE_CLOCK, params)
//refresh user device list
yield call(getDevices, null)
} else {
yield put(ApiActions.setRegisterState("failed"))
yield put(ApiActions.apiFailure(registerResponse))
}
}
export function* readLoraState(action: any) {
const peripheral: PairedPeripheralModel = getPairedPeripheral(yield select())
yield call(BleHelpers.write, peripheral.id, COMMANDS.READ_LORAWAN_STATE)
yield call(BleHelpers.write, peripheral.id, COMMANDS.READ_LORAWAN_DEVEUI)
yield call(BleHelpers.write, peripheral.id, COMMANDS.READ_LORAWAN_APPEUI)
yield call(BleHelpers.write, peripheral.id, COMMANDS.READ_LORAWAN_APPKEY)
}
export function* configureLoRaAutomatic(action: any) {
yield put(ApiActions.setLoRaConfigState("registeringApi"))
const { appKey, devEUI } = action
const hardwareId: string = getHardwareId(yield select())
const device: DeviceModel = getDevice(yield select())
const peripheral: PairedPeripheralModel = getPairedPeripheral(yield select())
const requestParams = {
lorawan_device: {
dev_eui: devEUI,
app_key: appKey,
}
}
const response = yield guardedRequest(api.createTtnDevice, hardwareId.toString(), requestParams)
if (response && response.ok) {
//retrieve keys from api's TTN registration
const ttn = new TTNModel(response.data)
//update devEUI on device in db
const deviceUpdateParams = {
id: device.id,
key: devEUI,
hardware_id: device.hardwareId,
}
const updateDeviceResponse = yield guardedRequest(api.updateDevice, device.id, deviceUpdateParams)
if (updateDeviceResponse && updateDeviceResponse.ok) {
yield put(ApiActions.setLoRaConfigState("writingCredentials"))
//write lora credentials to peripheral
yield call(BleHelpers.write, peripheral.id, COMMANDS.WRITE_LORAWAN_APPEUI, APP_EUI)
yield call(BleHelpers.write, peripheral.id, COMMANDS.WRITE_LORAWAN_DEVEUI, ttn.devEUI)
yield call(BleHelpers.write, peripheral.id, COMMANDS.WRITE_LORAWAN_APPKEY, ttn.appKey)
yield call(BleHelpers.write, peripheral.id, COMMANDS.WRITE_LORAWAN_STATE, BITMASK_ENABLED | BITMASK_ADAPTIVE_DATA_RATE | BITMASK_DUTY_CYCLE_LIMITATION)
//read back from device into redux store
yield call(readLoraState, action)
//update device model in beep base store
const newDevice = {
...device,
devEUI,
}
yield put(BeepBaseActions.setDevice(newDevice))
//refresh user device list
yield call(getDevices, null)
//next wizard state
yield put(ApiActions.setLoRaConfigState("checkingConnectivity"))
} else {
yield put(ApiActions.setLoRaConfigState("failedToRegister"))
yield put(ApiActions.apiFailure(updateDeviceResponse))
}
} else {
yield put(ApiActions.setLoRaConfigState("failedToRegister"))
//TODO: messages = response.data.errors.[lorawan_device.app_key]
// msg1 = message[0]
// msg2 = message[1]
yield put(ApiActions.apiFailure(response))
}
}
export function* configureLoRaManual(action: any) {
yield put(ApiActions.setLoRaConfigState("registeringApi"))
const device: DeviceModel = getDevice(yield select())
const { devEUI, appEui, appKey } = action
const peripheral: PairedPeripheralModel = getPairedPeripheral(yield select())
//update devEUI on device in db
const deviceUpdateParams = {
id: device.id,
key: devEUI,
hardware_id: device.hardwareId,
}
const updateDeviceResponse = yield guardedRequest(api.updateDevice, device.id, deviceUpdateParams)
if (updateDeviceResponse && updateDeviceResponse.ok) {
yield put(ApiActions.setLoRaConfigState("writingCredentials"))
//write lora credentials to peripheral
yield call(BleHelpers.write, peripheral.id, COMMANDS.WRITE_LORAWAN_APPEUI, appEui)
yield call(BleHelpers.write, peripheral.id, COMMANDS.WRITE_LORAWAN_DEVEUI, devEUI)
yield call(BleHelpers.write, peripheral.id, COMMANDS.WRITE_LORAWAN_APPKEY, appKey)
yield call(BleHelpers.write, peripheral.id, COMMANDS.WRITE_LORAWAN_STATE, BITMASK_ENABLED | BITMASK_ADAPTIVE_DATA_RATE | BITMASK_DUTY_CYCLE_LIMITATION)
//read back from device into redux store
yield call(readLoraState, action)
//update device model in beep base store
const newDevice = {
...device,
devEUI,
}
yield put(BeepBaseActions.setDevice(newDevice))
//refresh user device list
yield call(getDevices, null)
//next wizard state
yield put(ApiActions.setLoRaConfigState("checkingConnectivity"))
} else {
yield put(ApiActions.setLoRaConfigState("failedToRegister"))
yield put(ApiActions.apiFailure(updateDeviceResponse))
}
}
export function* disableLoRa(action: any) {
const peripheral: PairedPeripheralModel = getPairedPeripheral(yield select())
yield call(BleHelpers.write, peripheral.id, COMMANDS.WRITE_LORAWAN_STATE, BITMASK_DISABLED | BITMASK_ADAPTIVE_DATA_RATE | BITMASK_DUTY_CYCLE_LIMITATION)
//read back from device into redux store
yield call(readLoraState, action)
//next wizard state
yield put(ApiActions.setLoRaConfigState("isDisabled"))
}
export function* initializeTemperatureSensors(action: any) {
const { device, temperatureSensors, navigateToScreen } = action
yield call(getSensorDefinitions, action)
const temperatureSensorDefinitions: Array<SensorDefinitionModel> = getTemperatureSensorDefinitions(yield select())
yield all(temperatureSensors.map((temperatureModel: any, index: number) => {
const sensorAbbr = `t_${index}`
const sensorDefinition = temperatureSensorDefinitions.find(temperatureSensorDefinition => temperatureSensorDefinition.inputAbbreviation === sensorAbbr)
if (!sensorDefinition) {
//definition for this sensor not found in api
const requestParams = {
device_hardware_id: device.hardwareId,
input_measurement_abbreviation: sensorAbbr,
name: `Temperature sensor ${index + 1}`,
inside: true,
}
return call(createSensorDefinition, { requestParams })
}
}))
if (navigateToScreen) {
navigate(navigateToScreen)
}
}
export function* initializeWeightSensor(action: any) {
const { device, weight } = action
yield call(getSensorDefinitions, action)
const weightSensorDefinitions: Array<SensorDefinitionModel> = getWeightSensorDefinitions(yield select())
const sensorAbbr = "w_v"
const sensorDefinition = weightSensorDefinitions.find(weightSensorDefinition => weightSensorDefinition.inputAbbreviation === sensorAbbr)
if (!sensorDefinition) {
//definition for this sensor not found in api
const requestParams = {
device_hardware_id: device.hardwareId,
input_measurement_abbreviation: sensorAbbr,
output_measurement_abbreviation: "weight_kg",
name: "Weight sensor",
// offset: weight.offset,
// multiplier: weight.multiplier,
}
yield call(createSensorDefinition, { requestParams })
}
}
export function* getSensorDefinitions(action: any) {
const { device } = action
const response = yield guardedRequest(api.getSensorDefinitions, device.id)
if (response && response.ok) {
const sensorDefinitions: Array<SensorDefinitionModel> = []
response.data?.map((item: any) => sensorDefinitions.push(new SensorDefinitionModel(item)))
//sort on updated desc
sensorDefinitions.sort((a: SensorDefinitionModel, b: SensorDefinitionModel) => b.updatedAt.valueOf() - a.updatedAt.valueOf())
//store in beep base store because it belongs to the currently connected beep base
yield put(BeepBaseActions.setSensorDefinitions(sensorDefinitions))
} else {
yield put(ApiActions.apiFailure(response))
}
}
export function* createSensorDefinition(action: any) {
const { requestParams } = action
const response = yield guardedRequest(api.createSensorDefinition, requestParams)
if (response && response.ok) {
//TODO: update device with defs from response
} else {
yield put(ApiActions.apiFailure(response))
}
}
export function* updateApiSensorDefinition(action: any) {
const { sensorDefinition } = action
const requestParams: any = {
device_id: sensorDefinition.deviceId,
input_measurement_abbreviation: sensorDefinition.inputAbbreviation,
}
if (sensorDefinition.name != undefined) requestParams.name = sensorDefinition.name
if (sensorDefinition.isInside != undefined) requestParams.inside = sensorDefinition.isInside
if (sensorDefinition.offset != undefined) requestParams.offset = sensorDefinition.offset
if (sensorDefinition.multiplier != undefined) requestParams.multiplier = sensorDefinition.multiplier
const response = yield guardedRequest(api.updateSensorDefinition, sensorDefinition.id, requestParams)
if (response && response.ok) {
const sensorDefinition = new SensorDefinitionModel(response.data)
yield put(BeepBaseActions.updateSensorDefinition(sensorDefinition))
} else {
yield put(ApiActions.apiFailure(response))
}
}
export function* getFirmwares(action: any) {
const response = yield call(api.getFirmwares)
if (response && response.ok) {
const firmwares: Array<FirmwareModel> = []
response.data?.map((item: any) => firmwares.push(new FirmwareModel(item)))
yield put(ApiActions.setFirmwares(firmwares))
} else {
yield put(ApiActions.apiFailure(response))
}
}