Przeglądaj źródła

Merge pull request #138 from ostap-korkuna/dev

Show more than 1 day of data in the chart
jonfawcett 4 lat temu
rodzic
commit
c5dc84d113

+ 2 - 0
LoopFollow/Controllers/AppStateController.swift

@@ -28,6 +28,8 @@ enum ChartSettingsChangeEnum: Int {
   case lowLineChanged = 512
   case highLineChanged = 1024
   case smallGraphHeight = 2048
+  case showDIALinesChanged = 4096
+  case showMidnightLinesChanged = 8192
 }
 
 // General Settings Flags

+ 64 - 18
LoopFollow/Controllers/Graphs.swift

@@ -303,8 +303,8 @@ extension MainViewController {
         ul.lineColor = NSUIColor.systemYellow.withAlphaComponent(0.5)
         BGChart.rightAxis.addLimitLine(ul)
         
-        // Add Now Line
-        createNowAndDIALines()
+        // Add vertical lines as configured
+        createVerticalLines()
         startGraphNowTimer()
         
         // Setup the main graph overall details
@@ -345,8 +345,14 @@ extension MainViewController {
         
     }
     
-    func createNowAndDIALines() {
+    func createVerticalLines() {
         BGChart.xAxis.removeAllLimitLines()
+        BGChartFull.xAxis.removeAllLimitLines()
+        createNowAndDIALines()
+        createMidnightLines()
+    }
+    
+    func createNowAndDIALines() {
         let ul = ChartLimitLine()
         ul.limit = Double(dateTimeUtils.getNowTimeIntervalUTC())
         ul.lineColor = NSUIColor.systemGray.withAlphaComponent(0.5)
@@ -368,6 +374,34 @@ extension MainViewController {
         }
     }
     
+    func createMidnightLines() {
+        // Draw a line at midnight: useful when showing multiple days of data
+        if UserDefaultsRepository.showMidnightLines.value {
+            var midnightTimeInterval = dateTimeUtils.getTimeIntervalMidnightToday()
+            let graphHours = 24 * UserDefaultsRepository.downloadDays.value
+            let graphStart = dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours)
+            while midnightTimeInterval > graphStart {
+                // Large chart
+                let ul = ChartLimitLine()
+                ul.limit = Double(midnightTimeInterval)
+                ul.lineColor = NSUIColor.systemTeal.withAlphaComponent(0.5)
+                ul.lineDashLengths = [CGFloat(2), CGFloat(5)]
+                ul.lineWidth = 1
+                BGChart.xAxis.addLimitLine(ul)
+
+                // Small chart
+                let sl = ChartLimitLine()
+                sl.limit = Double(midnightTimeInterval)
+                sl.lineColor = NSUIColor.systemTeal
+                sl.lineDashLengths = [CGFloat(2), CGFloat(2)]
+                sl.lineWidth = 1
+                BGChartFull.xAxis.addLimitLine(sl)
+                
+                midnightTimeInterval = midnightTimeInterval.advanced(by: -24*60*60)
+            }
+        }
+    }
+    
     func updateBGGraphSettings() {
         let dataIndex = 0
         let dataIndexPrediction = 1
@@ -404,6 +438,9 @@ extension MainViewController {
         ul.limit = Double(UserDefaultsRepository.highLine.value)
         ul.lineColor = NSUIColor.systemYellow.withAlphaComponent(0.5)
         BGChart.rightAxis.addLimitLine(ul)
+        
+        // Re-create vertical markers in case their settings changed
+        createVerticalLines()
     
         BGChart.data?.dataSets[dataIndex].notifyDataSetChanged()
         BGChart.data?.notifyDataChanged()
@@ -641,8 +678,9 @@ extension MainViewController {
                 dateTimeStamp = dateTimeStamp - 150
             }
             
-            // skip if > 24 hours old
-            if dateTimeStamp < dateTimeUtils.getTimeInterval24HoursAgo() { continue }
+            // skip if outside of visible area
+            let graphHours = 24 * UserDefaultsRepository.downloadDays.value
+            if dateTimeStamp < dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours) { continue }
   
             let dot = ChartDataEntry(x: Double(dateTimeStamp), y: Double(bolusData[i].sgv), data: formatter.string(from: NSNumber(value: bolusData[i].value)))
             mainChart.addEntry(dot)
@@ -717,8 +755,9 @@ extension MainViewController {
                 colors.append(NSUIColor.systemOrange.withAlphaComponent(CGFloat(thisAlpha)))
             }
             
-            // skip if > 24 hours old
-            if dateTimeStamp < dateTimeUtils.getTimeInterval24HoursAgo() { continue }
+            // skip if outside of visible area
+            let graphHours = 24 * UserDefaultsRepository.downloadDays.value
+            if dateTimeStamp < dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours) { continue }
             
             if carbShift {
                 dateTimeStamp = dateTimeStamp - 250
@@ -773,8 +812,9 @@ extension MainViewController {
             formatter.maximumFractionDigits = 2
             formatter.minimumIntegerDigits = 1
             
-            // skip if > 24 hours old
-            if bgCheckData[i].date < dateTimeUtils.getTimeInterval24HoursAgo() { continue }
+            // skip if outside of visible area
+            let graphHours = 24 * UserDefaultsRepository.downloadDays.value
+            if bgCheckData[i].date < dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours) { continue }
             
             let value = ChartDataEntry(x: Double(bgCheckData[i].date), y: Double(bgCheckData[i].sgv), data: formatPillText(line1: bgUnits.toDisplayUnits(String(bgCheckData[i].sgv)), time: bgCheckData[i].date))
             BGChart.data?.dataSets[dataIndex].addEntry(value)
@@ -800,8 +840,9 @@ extension MainViewController {
         BGChartFull.lineData?.dataSets[dataIndex].clear()
         let thisData = suspendGraphData
         for i in 0..<thisData.count{
-            // skip if > 24 hours old
-            if thisData[i].date < dateTimeUtils.getTimeInterval24HoursAgo() { continue }
+            // skip if outside of visible area
+            let graphHours = 24 * UserDefaultsRepository.downloadDays.value
+            if thisData[i].date < dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours) { continue }
             
             let value = ChartDataEntry(x: Double(thisData[i].date), y: Double(thisData[i].sgv), data: formatPillText(line1: "Suspend Pump", time: thisData[i].date))
             BGChart.data?.dataSets[dataIndex].addEntry(value)
@@ -826,8 +867,9 @@ extension MainViewController {
         BGChartFull.lineData?.dataSets[dataIndex].clear()
         let thisData = resumeGraphData
         for i in 0..<thisData.count{
-            // skip if > 24 hours old
-            if thisData[i].date < dateTimeUtils.getTimeInterval24HoursAgo() { continue }
+            // skip if outside of visible area
+            let graphHours = 24 * UserDefaultsRepository.downloadDays.value
+            if thisData[i].date < dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours) { continue }
             
             let value = ChartDataEntry(x: Double(thisData[i].date), y: Double(thisData[i].sgv), data: formatPillText(line1: "Resume Pump", time: thisData[i].date))
             BGChart.data?.dataSets[dataIndex].addEntry(value)
@@ -852,8 +894,9 @@ extension MainViewController {
         BGChartFull.lineData?.dataSets[dataIndex].clear()
         let thisData = sensorStartGraphData
         for i in 0..<thisData.count{
-            // skip if > 24 hours old
-            if thisData[i].date < dateTimeUtils.getTimeInterval24HoursAgo() { continue }
+            // skip if outside of visible area
+            let graphHours = 24 * UserDefaultsRepository.downloadDays.value
+            if thisData[i].date < dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours) { continue }
             
             let value = ChartDataEntry(x: Double(thisData[i].date), y: Double(thisData[i].sgv), data: formatPillText(line1: "Start Sensor", time: thisData[i].date))
             BGChart.data?.dataSets[dataIndex].addEntry(value)
@@ -879,8 +922,9 @@ extension MainViewController {
         let thisData = noteGraphData
         for i in 0..<thisData.count{
             
-            // skip if > 24 hours old
-            if thisData[i].date < dateTimeUtils.getTimeInterval24HoursAgo() { continue }
+            // skip if outside of visible area
+            let graphHours = 24 * UserDefaultsRepository.downloadDays.value
+            if thisData[i].date < dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours) { continue }
             
             let value = ChartDataEntry(x: Double(thisData[i].date), y: Double(thisData[i].sgv), data: formatPillText(line1: thisData[i].note, time: thisData[i].date))
             BGChart.data?.dataSets[dataIndex].addEntry(value)
@@ -1115,7 +1159,9 @@ extension MainViewController {
         BGChartFull.rightAxis.axisMinimum = 0.0
         BGChartFull.rightAxis.axisMaximum = Double(maxBG)
                                                
-        BGChartFull.xAxis.enabled = false
+        BGChartFull.xAxis.drawLabelsEnabled = false
+        BGChartFull.xAxis.drawGridLinesEnabled = false
+        BGChartFull.xAxis.drawAxisLineEnabled = false
         BGChartFull.legend.enabled = false
         BGChartFull.scaleYEnabled = false
         BGChartFull.scaleXEnabled = false

+ 119 - 97
LoopFollow/Controllers/NightScout.swift

@@ -64,14 +64,32 @@ extension MainViewController {
     
     // Dex Share Web Call
     func webLoadDexShare(onlyPullLastRecord: Bool = false) {
-        var count = 288
+        // Dexcom Share only returns 24 hrs of data as of now
+        // Requesting more just for consistency with NS
+        let graphHours = 24 * UserDefaultsRepository.downloadDays.value
+        var count = graphHours * 12
         if onlyPullLastRecord { count = 1 }
         dexShare?.fetchData(count) { (err, result) -> () in
             
             // TODO: add error checking
             if(err == nil) {
                 var data = result!
-                self.ProcessNSBGData(data: data, onlyPullLastRecord: onlyPullLastRecord)
+                
+                // If Dex data is old, load from NS instead
+                let latestDate = data[0].date
+                let now = dateTimeUtils.getNowTimeIntervalUTC()
+                if (latestDate + 330) < now {
+                    self.webLoadNSBGData(onlyPullLastRecord: onlyPullLastRecord)
+                    print("dex didn't load, triggered NS attempt")
+                    return
+                }
+                
+                // Dexcom only returns 24 hrs of data. If we need more, call NS.
+                if graphHours > 24 && !onlyPullLastRecord {
+                    self.webLoadNSBGData(onlyPullLastRecord: onlyPullLastRecord, dexData: data)
+                } else {
+                    self.ProcessDexBGData(data: data, onlyPullLastRecord: onlyPullLastRecord, sourceName: "Dexcom")
+                }
             } else {
                 // If we get an error, immediately try to pull NS BG Data
                 self.webLoadNSBGData(onlyPullLastRecord: onlyPullLastRecord)
@@ -87,12 +105,13 @@ extension MainViewController {
     }
     
     // NS BG Data Web call
-    func webLoadNSBGData(onlyPullLastRecord: Bool = false) {
+    func webLoadNSBGData(onlyPullLastRecord: Bool = false, dexData: [ShareGlucoseData] = []) {
         if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Download: BG") }
-        // Set the count= in the url either to pull 24 hours or only the last record
+        let graphHours = 24 * UserDefaultsRepository.downloadDays.value
+        // Set the count= in the url either to pull day(s) of data or only the last record
         var points = "1"
         if !onlyPullLastRecord {
-            points = String(self.graphHours * 12 + 1)
+            points = String(graphHours * 12 + 1)
         }
         
         // URL processor
@@ -103,6 +122,12 @@ extension MainViewController {
             urlBGDataPath = urlBGDataPath + "token=" + token + "&count=" + points
         }
         guard let urlBGData = URL(string: urlBGDataPath) else {
+            // if we have Dex data, use it
+            if !dexData.isEmpty {
+                self.ProcessDexBGData(data: dexData, onlyPullLastRecord: onlyPullLastRecord, sourceName: "Dexcom")
+                return
+            }
+            
             if globalVariables.nsVerifiedAlert < dateTimeUtils.getNowTimeIntervalUTC() + 300 {
                 globalVariables.nsVerifiedAlert = dateTimeUtils.getNowTimeIntervalUTC()
                 //self.sendNotification(title: "Nightscout Error", body: "Please double check url, token, and internet connection. This may also indicate a temporary Nightscout issue")
@@ -131,6 +156,10 @@ extension MainViewController {
                     }
                     self.startBGTimer(time: 10)
                 }
+                // if we have Dex data, use it
+                if !dexData.isEmpty {
+                    self.ProcessDexBGData(data: dexData, onlyPullLastRecord: onlyPullLastRecord, sourceName: "Dexcom")
+                }
                 return
                 
             }
@@ -151,10 +180,30 @@ extension MainViewController {
             
             let decoder = JSONDecoder()
             let entriesResponse = try? decoder.decode([ShareGlucoseData].self, from: data)
-            if let entriesResponse = entriesResponse {
+            if 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)
+                    }
+                    
+                    // 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 < nsData.count && nsData[itemsToRemove].date >= oldestDexDate {
+                            itemsToRemove += 1
+                        }
+                        nsData.removeFirst(itemsToRemove)
+                        nsData = dexData + nsData
+                        sourceName = "Dexcom"
+                    }
+                    
                     // trigger the processor for the data after downloading.
-                    self.ProcessNSBGData(data: entriesResponse, onlyPullLastRecord: onlyPullLastRecord, isNS: true)
+                    self.ProcessDexBGData(data: nsData, onlyPullLastRecord: onlyPullLastRecord, sourceName: sourceName)
                     
                 }
             } else {
@@ -175,28 +224,15 @@ extension MainViewController {
         getBGTask.resume()
     }
     
-    // NS BG Data Response processor
-    func ProcessNSBGData(data: [ShareGlucoseData], onlyPullLastRecord: Bool, isNS: Bool = false){
+    // Dexcom BG Data Response processor
+    func ProcessDexBGData(data: [ShareGlucoseData], onlyPullLastRecord: Bool, sourceName: String){
         if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Process: BG") }
         
-        var pullDate = data[data.count - 1].date
-        if isNS {
-            pullDate = data[data.count - 1].date / 1000
-            pullDate.round(FloatingPointRoundingRule.toNearestOrEven)
-        }
-        
-        var latestDate = data[0].date
-        if isNS {
-            latestDate = data[0].date / 1000
-            latestDate.round(FloatingPointRoundingRule.toNearestOrEven)
-        }
+        let graphHours = 24 * UserDefaultsRepository.downloadDays.value
         
+        let pullDate = data[data.count - 1].date
+        let latestDate = data[0].date
         let now = dateTimeUtils.getNowTimeIntervalUTC()
-        if !isNS && (latestDate + 330) < now {
-            webLoadNSBGData(onlyPullLastRecord: onlyPullLastRecord)
-            print("dex didn't load, triggered NS attempt")
-            return
-        }
         
         // Start the BG timer based on the reading
         let secondsAgo = now - latestDate
@@ -245,25 +281,21 @@ extension MainViewController {
             return
         }
         
-        // loop through the data so we can reverse the order to oldest first for the graph and convert the NS timestamp to seconds instead of milliseconds. Makes date comparisons easier for everything else.
+        // loop through the data so we can reverse the order to oldest first for the graph
         for i in 0..<data.count{
-            var dateString = data[data.count - 1 - i].date
-            if isNS {
-                dateString = data[data.count - 1 - i].date / 1000
-                dateString.round(FloatingPointRoundingRule.toNearestOrEven)
-            }
-            if dateString >= dateTimeUtils.getTimeInterval24HoursAgo() {
+            let dateString = data[data.count - 1 - i].date
+            if dateString >= dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours) {
                 let reading = ShareGlucoseData(sgv: data[data.count - 1 - i].sgv, date: dateString, direction: data[data.count - 1 - i].direction)
                 bgData.append(reading)
             }
             
         }
-        
-        viewUpdateNSBG(isNS: isNS)
+
+        viewUpdateNSBG(sourceName: sourceName)
     }
     
     // NS BG Data Front end updater
-    func viewUpdateNSBG (isNS: Bool) {
+    func viewUpdateNSBG (sourceName: String) {
         DispatchQueue.main.async {
             if UserDefaultsRepository.debugLog.value {
                 self.writeDebugLog(value: "Display: BG")
@@ -287,11 +319,7 @@ extension MainViewController {
                 userUnit = " mmol/L"
             }
             
-            if isNS {
-                self.serverText.text = "Nightscout"
-            } else {
-                self.serverText.text = "Dexcom"
-            }
+            self.serverText.text = sourceName
             
             var snoozerBG = ""
             var snoozerDirection = ""
@@ -822,65 +850,60 @@ extension MainViewController {
         
         // Don't process the basal or draw the graph until after the BG has been fully processeed and drawn
         if firstGraphLoad { return }
-        
-        // Make temporary array with all values of yesterday and today
-        let yesterdayStart = dateTimeUtils.getTimeIntervalMidnightYesterday()
-        let todayStart = dateTimeUtils.getTimeIntervalMidnightToday()
-        
-        var basal2Day: [DataStructs.basal2DayProfile] = []
-        // Run twice to add in order yesterday then today.
-        for p in 0..<basalProfile.count {
-            let start = yesterdayStart + basalProfile[p].timeAsSeconds
-            var end = yesterdayStart
-            // set the endings 1 second before the next one starts
-            if p < basalProfile.count - 1 {
-                end = yesterdayStart + basalProfile[p + 1].timeAsSeconds - 1
-            } else {
-                // set the end 1 second before midnight
-                end = yesterdayStart + 86399
-            }
-            let entry = DataStructs.basal2DayProfile(basalRate: basalProfile[p].value, startDate: start, endDate: end)
-            basal2Day.append(entry)
-        }
-        for p in 0..<basalProfile.count {
-            let start = todayStart + basalProfile[p].timeAsSeconds
-            var end = todayStart
-            // set the endings 1 second before the next one starts
-            if p < basalProfile.count - 1 {
-                end = todayStart + basalProfile[p + 1].timeAsSeconds - 1
-            } else {
-                // set the end 1 second before midnight
-                end = todayStart + 86399
-            }
-            let entry = DataStructs.basal2DayProfile(basalRate: basalProfile[p].value, startDate: start, endDate: end)
-            basal2Day.append(entry)
-        }
+
+        var basalSegments: [DataStructs.basalProfileSegment] = []
+        
+        let graphHours = 24 * UserDefaultsRepository.downloadDays.value
+        // Build scheduled basal segments from right to left by
+        // moving pointers to the current midnight and current basal
+        var midnight = dateTimeUtils.getTimeIntervalMidnightToday()
+        var basalProfileIndex = basalProfile.count - 1
+        var start = midnight + basalProfile[basalProfileIndex].timeAsSeconds
+        var end = dateTimeUtils.getNowTimeIntervalUTC()
+        // Move back until we're in the graph range
+        while start > end {
+            basalProfileIndex -= 1
+            start = midnight + basalProfile[basalProfileIndex].timeAsSeconds
+        }
+        // Add records while they're still within the graph
+        let graphStart = dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours)
+        while end >= graphStart {
+            let entry = DataStructs.basalProfileSegment(
+                basalRate: basalProfile[basalProfileIndex].value, startDate: start, endDate: end)
+            basalSegments.append(entry)
+            
+            basalProfileIndex -= 1
+            if basalProfileIndex < 0 {
+                basalProfileIndex = basalProfile.count - 1
+                midnight = midnight.advanced(by: -24*60*60)
+            }
+            end = start - 1
+            start = midnight + basalProfile[basalProfileIndex].timeAsSeconds
+        }
+        // reverse the result to get chronological order
+        basalSegments.reverse()
         
         var firstPass = true
         // Runs the scheduled basal to the end of the prediction line
         var predictionEndTime = dateTimeUtils.getNowTimeIntervalUTC() + (3600 * UserDefaultsRepository.predictionToLoad.value)
         basalScheduleData.removeAll()
-        for i in 0..<basal2Day.count {
-            let timeYesterday = dateTimeUtils.getTimeInterval24HoursAgo()
-            
+        
+        for i in 0..<basalSegments.count {
+            let timeStart = dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours)
             
             // This processed everything after the first one.
             if firstPass == false
-                && basal2Day[i].startDate <= predictionEndTime {
-                let startDot = basalGraphStruct(basalRate: basal2Day[i].basalRate, date: basal2Day[i].startDate)
+                && basalSegments[i].startDate <= predictionEndTime {
+                let startDot = basalGraphStruct(basalRate: basalSegments[i].basalRate, date: basalSegments[i].startDate)
                 basalScheduleData.append(startDot)
-                var endDate = basal2Day[i].endDate
+                var endDate = basalSegments[i].endDate
                 
                 // if it's the last one needed, set it to end at the prediction end time
-                if endDate > predictionEndTime || i == basal2Day.count - 1 {
+                if endDate > predictionEndTime || i == basalSegments.count - 1 {
                     endDate = Double(predictionEndTime)
                 }
 
-                
-                
-
-
-                let endDot = basalGraphStruct(basalRate: basal2Day[i].basalRate, date: endDate)
+                let endDot = basalGraphStruct(basalRate: basalSegments[i].basalRate, date: endDate)
                 basalScheduleData.append(endDot)
             }
             
@@ -888,21 +911,18 @@ extension MainViewController {
             // Check that this is the first one and there are no existing entries
             if firstPass == true {
                 // check that the timestamp is > the current entry and < the next entry
-                if timeYesterday >= basal2Day[i].startDate && timeYesterday < basal2Day[i].endDate {
+                if timeStart >= basalSegments[i].startDate && timeStart < basalSegments[i].endDate {
                     // Set the start time to match the BG start
-                    let startDot = basalGraphStruct(basalRate: basal2Day[i].basalRate, date: Double(dateTimeUtils.getTimeInterval24HoursAgo() + (60 * 5)))
+                    let startDot = basalGraphStruct(basalRate: basalSegments[i].basalRate, date: Double(timeStart + (60 * 5)))
                     basalScheduleData.append(startDot)
                     
                     // set the enddot where the next one will start
-                    var endDate = basal2Day[i].endDate
-                    let endDot = basalGraphStruct(basalRate: basal2Day[i].basalRate, date: endDate)
+                    var endDate = basalSegments[i].endDate
+                    let endDot = basalGraphStruct(basalRate: basalSegments[i].basalRate, date: endDate)
                     basalScheduleData.append(endDot)
                     firstPass = false
                 }
             }
-            
-
-            
         }
         
         if UserDefaultsRepository.graphBasal.value {
@@ -917,11 +937,12 @@ extension MainViewController {
         if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Download: Treatments") }
         if !UserDefaultsRepository.downloadTreatments.value { return }
         
-        let yesterdayString = dateTimeUtils.nowMinus24HoursTimeInterval()
+        let graphHours = 24 * UserDefaultsRepository.downloadDays.value
+        let startTimeString = dateTimeUtils.nowMinusNHoursTimeInterval(N: graphHours)
         
-        var urlString = UserDefaultsRepository.url.value + "/api/v1/treatments.json?find[created_at][$gte]=" + yesterdayString
+        var urlString = UserDefaultsRepository.url.value + "/api/v1/treatments.json?find[created_at][$gte]=" + startTimeString
         if token != "" {
-            urlString = UserDefaultsRepository.url.value + "/api/v1/treatments.json?token=" + token + "&find[created_at][$gte]=" + yesterdayString
+            urlString = UserDefaultsRepository.url.value + "/api/v1/treatments.json?token=" + token + "&find[created_at][$gte]=" + startTimeString
         }
         
         guard let urlData = URL(string: urlString) else {
@@ -1670,8 +1691,9 @@ extension MainViewController {
             dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
             let dateString = dateFormatter.date(from: strippedZone)
             var dateTimeStamp = dateString!.timeIntervalSince1970
-            if dateTimeStamp < dateTimeUtils.getTimeInterval24HoursAgo() {
-                dateTimeStamp = dateTimeUtils.getTimeInterval24HoursAgo()
+            let graphHours = 24 * UserDefaultsRepository.downloadDays.value
+            if dateTimeStamp < dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours) {
+                dateTimeStamp = dateTimeUtils.getTimeIntervalNHoursAgo(N: graphHours)
             }
             
             var multiplier: Double = 1.0

+ 13 - 1
LoopFollow/Controllers/StatsView.swift

@@ -16,7 +16,19 @@ extension MainViewController {
     func updateStats()
     {
         if bgData.count > 0 {
-           let stats = StatsData(bgData: bgData)
+            var lastDayOfData = bgData
+            let graphHours = 24 * UserDefaultsRepository.downloadDays.value
+            // If we loaded more than 1 day of data, only use the last day for the stats
+            if graphHours > 24 {
+                let oneDayAgo = dateTimeUtils.getTimeIntervalNHoursAgo(N: 24)
+                var startIndex = 0
+                while startIndex < bgData.count && bgData[startIndex].date < oneDayAgo {
+                    startIndex += 1
+                }
+                lastDayOfData = Array(bgData.dropFirst(startIndex))
+            }
+            
+            let stats = StatsData(bgData: lastDayOfData)
             
             statsLowPercent.text = String(format:"%.1f%", stats.percentLow) + "%"
             statsInRangePercent.text = String(format:"%.1f%", stats.percentRange) + "%"

+ 1 - 1
LoopFollow/Controllers/Timers.swift

@@ -87,7 +87,7 @@ extension MainViewController {
     }
     
     @objc func graphNowTimerDidEnd(_ timer:Timer) {
-        createNowAndDIALines()
+        createVerticalLines()
     }
     
     // Runs a 60 second timer when an alarm is snoozed

+ 34 - 1
LoopFollow/ViewControllers/GraphSettingsViewController.swift

@@ -127,6 +127,11 @@ class GraphSettingsViewController: FormViewController {
                         guard let value = row.value else { return }
                         UserDefaultsRepository.showDIALines.value = value
                         
+                // tell main screen that graph needs updating
+                if let appState = self!.appStateController {
+                   appState.chartSettingsChanged = true
+                   appState.chartSettingsChanges |= ChartSettingsChangeEnum.showDIALinesChanged.rawValue
+                }
             }
             <<< SwitchRow("smallGraphTreatments"){ row in
                 row.title = "Treatments on Small Graph"
@@ -238,7 +243,35 @@ class GraphSettingsViewController: FormViewController {
                appState.chartSettingsChanges |= ChartSettingsChangeEnum.highLineChanged.rawValue
              }
         }
-       
+        <<< StepperRow("downloadDays") { row in
+            // NS supports up to 4 days
+            row.title = "Show Days Back"
+            row.cell.stepper.stepValue = 1
+            row.cell.stepper.minimumValue = 1
+            row.cell.stepper.maximumValue = 4
+            row.value = Double(UserDefaultsRepository.downloadDays.value)
+            row.displayValueFor = { value in
+                    guard let value = value else { return nil }
+                    return "\(Int(value))"
+                }
+        }.onChange { [weak self] row in
+                guard let value = row.value else { return }
+                UserDefaultsRepository.downloadDays.value = Int(value)
+        }
+        <<< SwitchRow("showMidnightMarkers"){ row in
+            row.title = "Show Midnight Lines"
+            row.value = UserDefaultsRepository.showMidnightLines.value
+        }.onChange { [weak self] row in
+                    guard let value = row.value else { return }
+                    UserDefaultsRepository.showMidnightLines.value = value
+                    
+            // tell main screen that graph needs updating
+            if let appState = self!.appStateController {
+               appState.chartSettingsChanged = true
+               appState.chartSettingsChanges |= ChartSettingsChangeEnum.showMidnightLinesChanged.rawValue
+            }
+        }
+
             
        +++ ButtonRow() {
           $0.title = "DONE"

+ 0 - 1
LoopFollow/ViewControllers/MainViewController.swift

@@ -60,7 +60,6 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
     var currentOverride = 1.0
     
     // Vars for NS Pull
-    var graphHours:Int=24
     var mmol = false as Bool
     var urlUser = UserDefaultsRepository.url.value as String
     var token = UserDefaultsRepository.token.value as String

+ 1 - 1
LoopFollow/helpers/DataStructs.swift

@@ -17,7 +17,7 @@ class DataStructs {
     }
     
     //NS Basal Profile  Struct
-    struct basal2DayProfile: Codable {
+    struct basalProfileSegment: Codable {
         var basalRate: Double
         var startDate: TimeInterval
         var endDate: TimeInterval

+ 7 - 7
LoopFollow/helpers/DateTime.swift

@@ -39,10 +39,10 @@ class dateTimeUtils {
         return midnightTimeInterval
     }
     
-    static func getTimeInterval24HoursAgo() -> TimeInterval {
+    static func getTimeIntervalNHoursAgo(N: Int) -> TimeInterval {
         let today = Date()
-        let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: today)!
-        return yesterday.timeIntervalSince1970
+        let nHoursAgo = Calendar.current.date(byAdding: .hour, value: -N, to: today)!
+        return nHoursAgo.timeIntervalSince1970
     }
     
     static func getNowTimeIntervalUTC() -> TimeInterval {
@@ -57,15 +57,15 @@ class dateTimeUtils {
         return utcTime
     }
     
-    static func nowMinus24HoursTimeInterval() -> String {
+    static func nowMinusNHoursTimeInterval(N: Int) -> String {
         let today = Date()
-        let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: today)!
+        let nHoursAgo = Calendar.current.date(byAdding: .hour, value: -N, to: today)!
         let dateFormatter = DateFormatter()
         dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss"
         dateFormatter.locale = Locale(identifier: "en_US")
         dateFormatter.timeZone = TimeZone.init(secondsFromGMT: 0)
-        let yesterdayString = dateFormatter.string(from: yesterday)
-        return yesterdayString
+        let nHoursAgoString = dateFormatter.string(from: nHoursAgo)
+        return nHoursAgoString
     }
     
     static func nowMinus10DaysTimeInterval() -> String {

+ 3 - 1
LoopFollow/repository/UserDefaults.swift

@@ -55,6 +55,7 @@ class UserDefaultsRepository {
     static let minBasalScale = UserDefaultsValue<Double>(key: "minBasalScale", default: 5.0)
     static let minBGScale = UserDefaultsValue<Float>(key: "minBGScale", default: 250.0)
     static let showDIALines = UserDefaultsValue<Bool>(key: "showDIAMarkers", default: true)
+    static let showMidnightLines = UserDefaultsValue<Bool>(key: "showMidnightMarkers", default: false)
     static let lowLine = UserDefaultsValue<Float>(key: "lowLine", default: 70.0)
     static let highLine = UserDefaultsValue<Float>(key: "highLine", default: 180.0)
     static let smallGraphHeight = UserDefaultsValue<Int>(key: "smallGraphHeight", default: 40)
@@ -91,7 +92,8 @@ class UserDefaultsRepository {
     static let debugLog = UserDefaultsValue<Bool>(key: "debugLog", default: false)
     static let alwaysDownloadAllBG = UserDefaultsValue<Bool>(key: "alwaysDownloadAllBG", default: true)
     static let bgUpdateDelay = UserDefaultsValue<Int>(key: "bgUpdateDelay", default: 10)
-    
+    static let downloadDays = UserDefaultsValue<Int>(key: "downloadDays", default: 1)
+
     
     // Watch Calendar Settings
     static let calendarIdentifier = UserDefaultsValue<String>(key: "calendarIdentifier", default: "")

+ 4 - 1
Pods/ShareClient/ShareClient/ShareClient.swift

@@ -163,9 +163,12 @@ public class ShareClient {
                 return callback(.fetchError, nil)
             }
 
+            // Dexcom Share only returns up to 24 hrs of data today
+            // Requesting more just in case this changes in the future
+            let minutes = max(1440, n * 5)
             components.queryItems = [
                 URLQueryItem(name: "sessionId", value: self.token),
-                URLQueryItem(name: "minutes", value: String(1440)),
+                URLQueryItem(name: "minutes", value: String(minutes)),
                 URLQueryItem(name: "maxCount", value: String(n))
             ]