@@ -352,6 +352,12 @@ final class DeviceDataManager {
352352 private func updateReservoirVolume( _ units: Double , at date: Date , withTimeLeft timeLeft: TimeInterval ? ) {
353353 loopManager. addReservoirValue ( units, at: date) { ( result) in
354354 /// TODO: Isolate to queue
355+ //////
356+ // update BG correction range overrides via NS
357+ // this call may be more appropriate somewhere
358+ let allowremoteTempTargets : Bool = true
359+ if allowremoteTempTargets == true { self . setNStemp ( ) }
360+ /////
355361
356362 switch result {
357363 case . failure( let error) :
@@ -737,6 +743,135 @@ final class DeviceDataManager {
737743 // MARK: - Status Extension
738744
739745 fileprivate var statusExtensionManager : StatusExtensionDataManager !
746+
747+
748+ //////////////////////////////////////////
749+ // MARK: - Set Temp Targets From NS
750+ // by LoopKit Authors Ken Stack, Katie DiSimone
751+
752+ struct NStempTarget : Codable {
753+ let created_at : String
754+ let duration : Int
755+ let targetBottom : Double ?
756+ let targetTop : Double ?
757+ }
758+
759+ func doubleIsEqual( _ a: Double , _ b: Double , _ precision: Double ) -> Bool {
760+ return fabs ( a - b) < precision
761+ }
762+
763+ func setNStemp ( ) {
764+ // data from URL logic from modified http://mrgott.com/swift-programing/33-rest-api-in-swift-4-using-urlsession-and-jsondecode
765+ //look at users nightscout treatments collection and implement temporary BG targets using an override called remoteTempTarget that was added to Loopkit
766+ //user set overrides always have precedence
767+ if self . loopManager. settings. glucoseTargetRangeSchedule? . overrideEnabledForContext ( . workout) == true || self . loopManager. settings. glucoseTargetRangeSchedule? . overrideEnabledForContext ( . preMeal) == true { return }
768+ let nightscoutService = remoteDataManager. nightscoutService
769+ guard let nssite = nightscoutService. siteURL? . absoluteString else { return }
770+ let formatter = ISO8601DateFormatter ( )
771+ formatter. formatOptions = [ . withFullDate,
772+ . withTime,
773+ . withDashSeparatorInDate,
774+ . withColonSeparatorInTime]
775+ //how far back to look for valid treatments in hours
776+ let treatmentWindow : TimeInterval = TimeInterval ( . hours( 24 ) )
777+ let now : Date = Date ( )
778+ let lasteventDate : Date = now - treatmentWindow
779+ //only consider treatments from now back to treatmentWindow
780+ let urlString = nssite + " /api/v1/treatments.json?find[eventType]=Temporary%20Target&find[created_at][$gte]= " + formatter. string ( from: lasteventDate) + " &find[created_at][$lte]= " + formatter. string ( from: now)
781+ guard let url = URL ( string: urlString) else {
782+ return
783+ }
784+ let session = URLSession . shared
785+ var request = URLRequest ( url: url)
786+ request. httpMethod = " GET "
787+ request. cachePolicy = NSURLRequest . CachePolicy. reloadIgnoringCacheData
788+ session. dataTask ( with: request as URLRequest ) { ( data, response, error) in
789+ if error != nil {
790+ return
791+ }
792+ guard let data = data else { return }
793+
794+ do {
795+ let temptargets = try JSONDecoder ( ) . decode ( [ NStempTarget ] . self, from: data)
796+ //check to see if we found some recent temp targets
797+ if temptargets. count == 0 { return }
798+ //find the index of the most recent temptargets - sort by date
799+ var cdates = [ Date] ( )
800+ for item in temptargets {
801+ cdates. append ( formatter. date ( from: ( item. created_at as String ) ) !)
802+ }
803+ let last = temptargets [ cdates. index ( of: cdates. max ( ) !) as! Int ]
804+ //if duration is 0 we dont care about minmax levels, if not we need them to exist as Double
805+ //NS doesnt check to see if a duration is created but no targets exist - so we have too
806+ if last. duration != 0 {
807+ guard last. targetBottom != nil else { return }
808+ guard last. targetTop != nil else { return }
809+ }
810+
811+ //cancel any prior remoteTemp if last duration = 0 and remote temp is active else return anyway
812+ if last. duration < 1 {
813+ if self . loopManager. settings. glucoseTargetRangeSchedule? . overrideEnabledForContext ( . remoteTempTarget) == true {
814+ self . loopManager. settings. glucoseTargetRangeSchedule? . clearOverride ( matching: . remoteTempTarget)
815+ NotificationManager . remoteTempSetNotification ( duration: last. duration, lowTarget: 0.0 , highTarget: 0.0 )
816+ }
817+ return
818+ }
819+ // set the remote temp if it's valid and not already set. Handle the nil issue as well
820+ let endlastTemp = cdates. max ( ) ! + TimeInterval( . minutes( Double ( last. duration) ) )
821+ if Date ( ) < endlastTemp {
822+ let NStargetUnit = HKUnit . milligramsPerDeciliter ( )
823+ let userUnit = self . loopManager. settings. glucoseTargetRangeSchedule? . unit
824+ //convert NS temp targets to an HKQuanity with units and set limits (low of 70 mg/dL, high of 300 mg/dL)
825+ //ns temps are always given in mg/dL
826+ let lowerTarget : HKQuantity = HKQuantity ( unit : NStargetUnit, doubleValue: max ( 70.0 , last. targetBottom as! Double ) )
827+ let upperTarget : HKQuantity = HKQuantity ( unit : NStargetUnit, doubleValue: min ( 300.0 , last. targetTop as! Double ) )
828+ //set the temp if override isn't enabled or is nil ie never enabled
829+ if self . loopManager. settings. glucoseTargetRangeSchedule? . overrideEnabledForContext ( . remoteTempTarget) != true {
830+ self . setRemoteTemp ( lowerTarget: lowerTarget, upperTarget: upperTarget, userUnit: userUnit!, endlastTemp: endlastTemp, duration: last. duration)
831+ return
832+ }
833+ let currentRange = self . loopManager. settings. glucoseTargetRangeSchedule? . overrideRanges [ . remoteTempTarget] !
834+ let activeDates = self . loopManager. settings. glucoseTargetRangeSchedule!. override? . activeDates
835+ ///this handles the case if a remote temp was canceled by a different temp, but the app restarts in between
836+ if currentRange == nil || activeDates == nil {
837+ self . setRemoteTemp ( lowerTarget: lowerTarget, upperTarget: upperTarget, userUnit: userUnit!, endlastTemp: endlastTemp, duration: last. duration)
838+ return
839+ }
840+ //if anything has changed - ranges or duration, reset the temp
841+ if self . doubleIsEqual ( ( currentRange? . minValue) !, lowerTarget. doubleValue ( for: userUnit!) , 0.01 ) == false || self . doubleIsEqual ( ( currentRange? . maxValue) !, upperTarget. doubleValue ( for: userUnit!) , 0.01 ) == false || abs ( activeDates!. end. timeIntervalSince ( endlastTemp) ) > TimeInterval ( . minutes( 1 ) ) {
842+
843+ self . setRemoteTemp ( lowerTarget: lowerTarget, upperTarget: upperTarget, userUnit: userUnit!, endlastTemp: endlastTemp, duration: last. duration)
844+ return
845+
846+ }
847+ else
848+ {
849+ //print(" temp already running no changes")
850+ }
851+
852+
853+ }
854+ else {
855+ //last temp has expired - do a hard cancel to fix the UI- it should cancel itself but it doesnt
856+ if self . loopManager. settings. glucoseTargetRangeSchedule? . overrideEnabledForContext ( . remoteTempTarget) == true {
857+ self . loopManager. settings. glucoseTargetRangeSchedule? . clearOverride ( matching: . remoteTempTarget)
858+ }
859+ }
860+ } catch let jsonError {
861+ return
862+ }
863+ } . resume ( )
864+ }
865+
866+ func setRemoteTemp ( lowerTarget: HKQuantity , upperTarget: HKQuantity , userUnit: HKUnit , endlastTemp: Date , duration: Int ) {
867+ self . loopManager. settings. glucoseTargetRangeSchedule? . overrideRanges [ . remoteTempTarget] = DoubleRange ( minValue: lowerTarget. doubleValue ( for: userUnit) , maxValue: upperTarget. doubleValue ( for: userUnit) )
868+ let remoteTempSet = self . loopManager. settings. glucoseTargetRangeSchedule? . setOverride ( . remoteTempTarget, until: endlastTemp)
869+ if remoteTempSet! {
870+ NotificationManager . remoteTempSetNotification ( duration: duration , lowTarget: lowerTarget. doubleValue ( for: userUnit) , highTarget: upperTarget. doubleValue ( for: userUnit) )
871+ }
872+ }
873+
874+ //////////////////////////
740875
741876 // MARK: - Initialization
742877
0 commit comments