-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Expand file tree
/
Copy pathBGData.swift
More file actions
284 lines (247 loc) · 12.6 KB
/
BGData.swift
File metadata and controls
284 lines (247 loc) · 12.6 KB
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
// LoopFollow
// BGData.swift
import Foundation
import UIKit
extension MainViewController {
// Dex Share Web Call
func webLoadDexShare() {
// Dexcom Share only returns 24 hrs of data as of now
// Requesting more just for consistency with NS
let graphHours = 24 * Storage.shared.downloadDays.value
let count = graphHours * 12
dexShare?.fetchData(count) { err, result in
if let error = err {
LogManager.shared.log(category: .dexcom, message: "Error fetching Dexcom data: \(error.localizedDescription)", limitIdentifier: "Error fetching Dexcom data")
self.webLoadNSBGData()
return
}
guard let data = result, !data.isEmpty else {
LogManager.shared.log(category: .dexcom, message: "Received empty data array from Dexcom", limitIdentifier: "Received empty data array from Dexcom")
self.webLoadNSBGData()
return
}
// If Dex data is old, load from NS instead
let latestDate = data[0].date
let now = dateTimeUtils.getNowTimeIntervalUTC()
if (latestDate + 330) < now, IsNightscoutEnabled() {
LogManager.shared.log(category: .dexcom, message: "Dexcom data is old, loading from NS instead", limitIdentifier: "Dexcom data is old, loading from NS instead")
self.webLoadNSBGData()
return
}
// Dexcom only returns 24 hrs of data. If we need more, call NS.
if graphHours > 24, IsNightscoutEnabled() {
self.webLoadNSBGData(dexData: data)
} else {
self.ProcessDexBGData(data: data, sourceName: "Dexcom")
}
}
}
// NS BG Data Web call
func webLoadNSBGData(dexData: [ShareGlucoseData] = []) {
// This kicks it out in the instance where dexcom fails but they aren't using NS &&
if !IsNightscoutEnabled() {
Storage.shared.lastBGChecked.value = Date()
return
}
var parameters: [String: String] = [:]
let date = Calendar.current.date(byAdding: .day, value: -1 * Storage.shared.downloadDays.value, to: Date())!
parameters["count"] = "\(Storage.shared.downloadDays.value * 2 * 24 * 60 / 5)"
parameters["find[date][$gte]"] = "\(Int(date.timeIntervalSince1970 * 1000))"
// Exclude 'cal' entries
parameters["find[type][$ne]"] = "cal"
NightscoutUtils.executeRequest(eventType: .sgv, parameters: parameters) { (result: Result<[ShareGlucoseData], Error>) in
switch result {
case let .success(entriesResponse):
var nsData = entriesResponse
DispatchQueue.main.async {
// transform NS data to look like Dex data
for i in 0 ..< nsData.count {
// convert the NS timestamp to seconds instead of milliseconds
nsData[i].date /= 1000
nsData[i].date.round(FloatingPointRoundingRule.toNearestOrEven)
}
var nsData2: [ShareGlucoseData] = []
var lastAddedTime = Double.infinity
var lastAddedSGV: Int?
let minInterval: Double = 30
for reading in nsData {
if (lastAddedSGV == nil || lastAddedSGV != reading.sgv) || (lastAddedTime - reading.date >= minInterval) {
nsData2.append(reading)
lastAddedTime = reading.date
lastAddedSGV = reading.sgv
}
}
// merge NS and Dex data if needed; use recent Dex data and older NS data
var sourceName = "Nightscout"
if !dexData.isEmpty {
let oldestDexDate = dexData[dexData.count - 1].date
var itemsToRemove = 0
while itemsToRemove < nsData2.count, nsData2[itemsToRemove].date >= oldestDexDate {
itemsToRemove += 1
}
nsData2.removeFirst(itemsToRemove)
nsData2 = dexData + nsData2
sourceName = "Dexcom"
}
// trigger the processor for the data after downloading.
self.ProcessDexBGData(data: nsData2, sourceName: sourceName)
}
case let .failure(error):
LogManager.shared.log(category: .nightscout, message: "Failed to fetch bg data: \(error)", limitIdentifier: "Failed to fetch bg data")
DispatchQueue.main.async {
TaskScheduler.shared.rescheduleTask(
id: .fetchBG,
to: Date().addingTimeInterval(10)
)
}
// if we have Dex data, use it
if !dexData.isEmpty {
self.ProcessDexBGData(data: dexData, sourceName: "Dexcom")
} else {
Storage.shared.lastBGChecked.value = Date()
}
return
}
}
}
/// Processes incoming BG data.
func ProcessDexBGData(data: [ShareGlucoseData], sourceName: String) {
let graphHours = 24 * Storage.shared.downloadDays.value
guard !data.isEmpty else {
LogManager.shared.log(category: .nightscout, message: "No bg data received. Skipping processing.", limitIdentifier: "No bg data received. Skipping processing.")
Storage.shared.lastBGChecked.value = Date()
return
}
let latestReading = data[0]
let sensorTimestamp = latestReading.date
let now = dateTimeUtils.getNowTimeIntervalUTC()
// secondsAgo is how old the newest reading is
let secondsAgo = now - sensorTimestamp
// Compute the current sensor schedule offset
let currentOffset = CycleHelper.cycleOffset(for: sensorTimestamp, interval: 5 * 60)
if Storage.shared.sensorScheduleOffset.value != currentOffset {
Storage.shared.sensorScheduleOffset.value = currentOffset
LogManager.shared.log(category: .nightscout,
message: "Sensor schedule offset: \(currentOffset) seconds.",
isDebug: true)
}
// Determine the next polling delay.
var delayToSchedule: Double = 0
DispatchQueue.main.async {
// Fallback scheduling for older readings.
if secondsAgo >= (20 * 60) {
delayToSchedule = 5 * 60
LogManager.shared.log(category: .nightscout,
message: "Reading is very old (\(secondsAgo) sec). Scheduling next fetch in 5 minutes.",
isDebug: true)
} else if secondsAgo >= (10 * 60) {
delayToSchedule = 60
LogManager.shared.log(category: .nightscout,
message: "Reading is moderately old (\(secondsAgo) sec). Scheduling next fetch in 60 seconds.",
isDebug: true)
} else if secondsAgo >= (7 * 60) {
delayToSchedule = 30
LogManager.shared.log(category: .nightscout,
message: "Reading is a bit old (\(secondsAgo) sec). Scheduling next fetch in 30 seconds.",
isDebug: true)
} else if secondsAgo >= (5 * 60) {
delayToSchedule = 5
LogManager.shared.log(category: .nightscout,
message: "Reading is close to 5 minutes old (\(secondsAgo) sec). Scheduling next fetch in 5 seconds.",
isDebug: true)
} else {
delayToSchedule = 300 - secondsAgo + Double(Storage.shared.bgUpdateDelay.value)
LogManager.shared.log(category: .nightscout,
message: "Fresh reading. Scheduling next fetch in \(delayToSchedule) seconds.",
isDebug: true)
TaskScheduler.shared.rescheduleTask(id: .alarmCheck, to: Date().addingTimeInterval(3))
}
TaskScheduler.shared.rescheduleTask(id: .fetchBG, to: Date().addingTimeInterval(delayToSchedule))
// Evaluate speak conditions if there is a previous value.
if data.count > 1 {
self.evaluateSpeakConditions(currentValue: data[0].sgv, previousValue: data[1].sgv)
}
}
// Process data for graph display.
bgData.removeAll()
for i in 0 ..< data.count {
let readingTimestamp = data[data.count - 1 - i].date
if readingTimestamp >= dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours) {
let sgvValue = data[data.count - 1 - i].sgv
// Skip outlier values (e.g. first reading of a new sensor might be abnormally high).
if sgvValue > 600 {
LogManager.shared.log(category: .nightscout,
message: "Skipping reading with sgv \(sgvValue) as it exceeds threshold.",
isDebug: true)
continue
}
let reading = ShareGlucoseData(sgv: sgvValue, date: readingTimestamp, direction: data[data.count - 1 - i].direction)
bgData.append(reading)
}
}
LogManager.shared.log(category: .nightscout,
message: "Graph data updated with \(bgData.count) entries.",
isDebug: true)
viewUpdateNSBG(sourceName: sourceName)
}
func updateServerText(with serverText: String? = nil) {
if Storage.shared.showDisplayName.value, let displayName = Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String {
self.serverText.text = displayName
} else if let serverText = serverText {
self.serverText.text = serverText
}
}
// NS BG Data Front end updater
func viewUpdateNSBG(sourceName: String) {
DispatchQueue.main.async {
TaskScheduler.shared.rescheduleTask(id: .minAgoUpdate, to: Date())
let entries = self.bgData
if entries.count < 2 { // Protect index out of bounds
Storage.shared.lastBGChecked.value = Date()
return
}
self.updateBGGraph()
self.updateStats()
let latestEntryIndex = entries.count - 1
let latestBG = entries[latestEntryIndex].sgv
let priorBG = entries[latestEntryIndex - 1].sgv
let deltaBG = latestBG - priorBG
self.updateServerText(with: sourceName)
// Set BGText with the latest BG value
self.updateBGTextAppearance()
if latestBG <= globalVariables.minDisplayGlucose {
Observable.shared.bgText.value = "LOW"
} else if latestBG >= globalVariables.maxDisplayGlucose {
Observable.shared.bgText.value = "HIGH"
} else {
Observable.shared.bgText.value = Localizer.toDisplayUnits(String(latestBG))
}
Observable.shared.bg.value = latestBG
// Direction handling
if let directionBG = entries[latestEntryIndex].direction {
Observable.shared.directionText.value = self.bgDirectionGraphic(directionBG)
} else {
Observable.shared.directionText.value = ""
}
// Delta handling
if deltaBG < 0 {
Observable.shared.deltaText.value = Localizer.toDisplayUnits(String(deltaBG))
} else {
Observable.shared.deltaText.value = "+" + Localizer.toDisplayUnits(String(deltaBG))
}
// Mark BG data as loaded for initial loading state
self.markDataLoaded("bg")
// Update contact
if Storage.shared.contactEnabled.value {
self.contactImageUpdater
.updateContactImage(
bgValue: Observable.shared.bgText.value,
trend: Observable.shared.directionText.value,
delta: Observable.shared.deltaText.value,
stale: Observable.shared.bgStale.value
)
}
Storage.shared.lastBGChecked.value = Date()
}
}
}