From b083df9a25e27980dea6c65e745f72fc27460afe Mon Sep 17 00:00:00 2001 From: Nathaniel Hamming Date: Mon, 25 Sep 2023 15:25:53 -0300 Subject: [PATCH 01/71] [COASTAL-1291] plugin identifier is no longer class property (#92) --- TidepoolServiceKit/TidepoolService.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/TidepoolServiceKit/TidepoolService.swift b/TidepoolServiceKit/TidepoolService.swift index 00332ab..61ce54c 100644 --- a/TidepoolServiceKit/TidepoolService.swift +++ b/TidepoolServiceKit/TidepoolService.swift @@ -32,8 +32,10 @@ public protocol SessionStorage { } public final class TidepoolService: Service, TAPIObserver, ObservableObject { - - public static let pluginIdentifier = "TidepoolService" + + public static let serviceIdentifier: String = "TidepoolService" + + public var pluginIdentifier: String { Self.serviceIdentifier } public static let localizedTitle = LocalizedString("Tidepool", comment: "The title of the Tidepool service") @@ -66,7 +68,7 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { private var hostIdentifier: String? private var hostVersion: String? - private let log = OSLog(category: pluginIdentifier) + private let log = OSLog(category: "TidepoolService") private let tidepoolKitLog = OSLog(category: "TidepoolKit") public init(hostIdentifier: String, hostVersion: String) { From f4f3c4d03d31957387033cc56d40c46d993168fa Mon Sep 17 00:00:00 2001 From: Nathaniel Hamming Date: Mon, 25 Sep 2023 15:25:53 -0300 Subject: [PATCH 02/71] [COASTAL-1291] plugin identifier is no longer class property (#92) --- TidepoolServiceKit/TidepoolService.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/TidepoolServiceKit/TidepoolService.swift b/TidepoolServiceKit/TidepoolService.swift index 00332ab..61ce54c 100644 --- a/TidepoolServiceKit/TidepoolService.swift +++ b/TidepoolServiceKit/TidepoolService.swift @@ -32,8 +32,10 @@ public protocol SessionStorage { } public final class TidepoolService: Service, TAPIObserver, ObservableObject { - - public static let pluginIdentifier = "TidepoolService" + + public static let serviceIdentifier: String = "TidepoolService" + + public var pluginIdentifier: String { Self.serviceIdentifier } public static let localizedTitle = LocalizedString("Tidepool", comment: "The title of the Tidepool service") @@ -66,7 +68,7 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { private var hostIdentifier: String? private var hostVersion: String? - private let log = OSLog(category: pluginIdentifier) + private let log = OSLog(category: "TidepoolService") private let tidepoolKitLog = OSLog(category: "TidepoolKit") public init(hostIdentifier: String, hostVersion: String) { From 9975401517880a1d95660b7bdaed2e12c83b314a Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 26 Sep 2023 11:16:08 -0500 Subject: [PATCH 03/71] Merge fixes --- TidepoolServiceKit/TidepoolService.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/TidepoolServiceKit/TidepoolService.swift b/TidepoolServiceKit/TidepoolService.swift index 805eaa1..ab60271 100644 --- a/TidepoolServiceKit/TidepoolService.swift +++ b/TidepoolServiceKit/TidepoolService.swift @@ -33,7 +33,9 @@ public protocol SessionStorage { public final class TidepoolService: Service, TAPIObserver, ObservableObject { - public static let pluginIdentifier = "TidepoolService" + public static let serviceIdentifier: String = "TidepoolService" + + public var pluginIdentifier: String { Self.serviceIdentifier } public static let localizedTitle = LocalizedString("Tidepool", comment: "The title of the Tidepool service") @@ -66,7 +68,7 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { private var hostIdentifier: String? private var hostVersion: String? - private let log = OSLog(category: pluginIdentifier) + private let log = OSLog(category: "TidepoolService") private let tidepoolKitLog = OSLog(category: "TidepoolKit") public init(hostIdentifier: String, hostVersion: String) { From f75856d86d6d23b6677a68b39e9a9a73e7d1aad2 Mon Sep 17 00:00:00 2001 From: Nathaniel Hamming Date: Tue, 10 Oct 2023 15:43:52 -0300 Subject: [PATCH 04/71] added testflight configuration (#94) --- TidepoolService.xcodeproj/project.pbxproj | 235 +++++++++++++++++++++- 1 file changed, 234 insertions(+), 1 deletion(-) diff --git a/TidepoolService.xcodeproj/project.pbxproj b/TidepoolService.xcodeproj/project.pbxproj index be8ee8e..82cf48e 100644 --- a/TidepoolService.xcodeproj/project.pbxproj +++ b/TidepoolService.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -1354,6 +1354,233 @@ }; name = Release; }; + B4E7CFA82AD03299009B4DF2 /* Testflight */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_GCD_PERFORMANCE = YES; + CLANG_ANALYZER_LOCALIZABILITY_EMPTY_CONTEXT = YES; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES; + CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES; + CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_ASSIGN_ENUM = YES; + CLANG_WARN_ATOMIC_IMPLICIT_SEQ_CST = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES_ERROR; + CLANG_WARN_BOOL_CONVERSION = YES_ERROR; + CLANG_WARN_COMMA = YES_ERROR; + CLANG_WARN_CONSTANT_CONVERSION = YES_ERROR; + CLANG_WARN_CXX0X_EXTENSIONS = YES; + CLANG_WARN_DELETE_NON_VIRTUAL_DTOR = YES_ERROR; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES_ERROR; + CLANG_WARN_FLOAT_CONVERSION = YES_ERROR; + CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES_ERROR; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES_ERROR; + CLANG_WARN_MISSING_NOESCAPE = YES_ERROR; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES_ERROR; + CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE = YES; + CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_INTERFACE_IVARS = YES_ERROR; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES_ERROR; + CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS = YES; + CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES_AGGRESSIVE; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_PRAGMA_PACK = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SEMICOLON_BEFORE_METHOD_BODY = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES_ERROR; + CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES_AGGRESSIVE; + CLANG_WARN_VEXING_PARSE = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES; + GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES_ERROR; + GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; + GCC_WARN_ABOUT_MISSING_NEWLINE = YES; + GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; + GCC_WARN_ABOUT_POINTER_SIGNEDNESS = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES; + GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES; + GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; + GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES; + GCC_WARN_SHADOW = YES; + GCC_WARN_SIGN_COMPARE = YES; + GCC_WARN_STRICT_SELECTOR_MATCH = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNKNOWN_PRAGMAS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_LABEL = YES; + GCC_WARN_UNUSED_PARAMETER = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.1; + LOCALIZED_STRING_MACRO_NAMES = ( + NSLocalizedString, + CFLocalizedString, + LocalizedString, + ); + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + WARNING_CFLAGS = "-Wall"; + }; + name = Testflight; + }; + B4E7CFA92AD03299009B4DF2 /* Testflight */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; + INFOPLIST_FILE = TidepoolServiceKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.tidepool.TidepoolServiceKit; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Testflight; + }; + B4E7CFAA2AD03299009B4DF2 /* Testflight */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = TidepoolServiceKitTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.tidepool.TidepoolServiceKitTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Testflight; + }; + B4E7CFAB2AD03299009B4DF2 /* Testflight */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = TidepoolServiceKitUI/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.tidepool.TidepoolServiceKitUI; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Testflight; + }; + B4E7CFAC2AD03299009B4DF2 /* Testflight */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = TidepoolServiceKitUITests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.tidepool.TidepoolServiceKitUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Testflight; + }; + B4E7CFAD2AD03299009B4DF2 /* Testflight */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = TidepoolServiceKitPlugin/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.tidepool.TidepoolServiceKitPlugin; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + TARGETED_DEVICE_FAMILY = "1,2"; + WRAPPER_EXTENSION = loopplugin; + }; + name = Testflight; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -1361,6 +1588,7 @@ isa = XCConfigurationList; buildConfigurations = ( A94AE4EA235A89B5005CA320 /* Debug */, + B4E7CFAD2AD03299009B4DF2 /* Testflight */, A94AE4EB235A89B5005CA320 /* Release */, ); defaultConfigurationIsVisible = 0; @@ -1370,6 +1598,7 @@ isa = XCConfigurationList; buildConfigurations = ( A9DAACF122E7978800E76C9F /* Debug */, + B4E7CFA82AD03299009B4DF2 /* Testflight */, A9DAACF222E7978800E76C9F /* Release */, ); defaultConfigurationIsVisible = 0; @@ -1379,6 +1608,7 @@ isa = XCConfigurationList; buildConfigurations = ( A9DAAD1122E7987800E76C9F /* Debug */, + B4E7CFA92AD03299009B4DF2 /* Testflight */, A9DAAD1222E7987800E76C9F /* Release */, ); defaultConfigurationIsVisible = 0; @@ -1388,6 +1618,7 @@ isa = XCConfigurationList; buildConfigurations = ( A9DAAD1422E7987800E76C9F /* Debug */, + B4E7CFAA2AD03299009B4DF2 /* Testflight */, A9DAAD1522E7987800E76C9F /* Release */, ); defaultConfigurationIsVisible = 0; @@ -1397,6 +1628,7 @@ isa = XCConfigurationList; buildConfigurations = ( A9DAAD2D22E7988900E76C9F /* Debug */, + B4E7CFAB2AD03299009B4DF2 /* Testflight */, A9DAAD2E22E7988900E76C9F /* Release */, ); defaultConfigurationIsVisible = 0; @@ -1406,6 +1638,7 @@ isa = XCConfigurationList; buildConfigurations = ( A9DAAD3022E7988900E76C9F /* Debug */, + B4E7CFAC2AD03299009B4DF2 /* Testflight */, A9DAAD3122E7988900E76C9F /* Release */, ); defaultConfigurationIsVisible = 0; From 6cf2c2202a88e694f6cd2e62a4789f67913a6138 Mon Sep 17 00:00:00 2001 From: Nathaniel Hamming Date: Tue, 10 Oct 2023 15:43:52 -0300 Subject: [PATCH 05/71] added testflight configuration (#94) --- TidepoolService.xcodeproj/project.pbxproj | 235 +++++++++++++++++++++- 1 file changed, 234 insertions(+), 1 deletion(-) diff --git a/TidepoolService.xcodeproj/project.pbxproj b/TidepoolService.xcodeproj/project.pbxproj index be8ee8e..82cf48e 100644 --- a/TidepoolService.xcodeproj/project.pbxproj +++ b/TidepoolService.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -1354,6 +1354,233 @@ }; name = Release; }; + B4E7CFA82AD03299009B4DF2 /* Testflight */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_GCD_PERFORMANCE = YES; + CLANG_ANALYZER_LOCALIZABILITY_EMPTY_CONTEXT = YES; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES; + CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES; + CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_ASSIGN_ENUM = YES; + CLANG_WARN_ATOMIC_IMPLICIT_SEQ_CST = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES_ERROR; + CLANG_WARN_BOOL_CONVERSION = YES_ERROR; + CLANG_WARN_COMMA = YES_ERROR; + CLANG_WARN_CONSTANT_CONVERSION = YES_ERROR; + CLANG_WARN_CXX0X_EXTENSIONS = YES; + CLANG_WARN_DELETE_NON_VIRTUAL_DTOR = YES_ERROR; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES_ERROR; + CLANG_WARN_FLOAT_CONVERSION = YES_ERROR; + CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES_ERROR; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES_ERROR; + CLANG_WARN_MISSING_NOESCAPE = YES_ERROR; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES_ERROR; + CLANG_WARN_OBJC_EXPLICIT_OWNERSHIP_TYPE = YES; + CLANG_WARN_OBJC_IMPLICIT_ATOMIC_PROPERTIES = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_INTERFACE_IVARS = YES_ERROR; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES_ERROR; + CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS = YES; + CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES_AGGRESSIVE; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_PRAGMA_PACK = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SEMICOLON_BEFORE_METHOD_BODY = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES_ERROR; + CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES_AGGRESSIVE; + CLANG_WARN_VEXING_PARSE = YES_ERROR; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES; + GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES_ERROR; + GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; + GCC_WARN_ABOUT_MISSING_NEWLINE = YES; + GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; + GCC_WARN_ABOUT_POINTER_SIGNEDNESS = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_FOUR_CHARACTER_CONSTANTS = YES; + GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES; + GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; + GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES; + GCC_WARN_SHADOW = YES; + GCC_WARN_SIGN_COMPARE = YES; + GCC_WARN_STRICT_SELECTOR_MATCH = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNKNOWN_PRAGMAS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_LABEL = YES; + GCC_WARN_UNUSED_PARAMETER = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.1; + LOCALIZED_STRING_MACRO_NAMES = ( + NSLocalizedString, + CFLocalizedString, + LocalizedString, + ); + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + WARNING_CFLAGS = "-Wall"; + }; + name = Testflight; + }; + B4E7CFA92AD03299009B4DF2 /* Testflight */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; + INFOPLIST_FILE = TidepoolServiceKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.tidepool.TidepoolServiceKit; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Testflight; + }; + B4E7CFAA2AD03299009B4DF2 /* Testflight */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = TidepoolServiceKitTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.tidepool.TidepoolServiceKitTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Testflight; + }; + B4E7CFAB2AD03299009B4DF2 /* Testflight */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = TidepoolServiceKitUI/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.tidepool.TidepoolServiceKitUI; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Testflight; + }; + B4E7CFAC2AD03299009B4DF2 /* Testflight */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + INFOPLIST_FILE = TidepoolServiceKitUITests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.tidepool.TidepoolServiceKitUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Testflight; + }; + B4E7CFAD2AD03299009B4DF2 /* Testflight */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CODE_SIGN_IDENTITY = ""; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = TidepoolServiceKitPlugin/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = org.tidepool.TidepoolServiceKitPlugin; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = NO; + TARGETED_DEVICE_FAMILY = "1,2"; + WRAPPER_EXTENSION = loopplugin; + }; + name = Testflight; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -1361,6 +1588,7 @@ isa = XCConfigurationList; buildConfigurations = ( A94AE4EA235A89B5005CA320 /* Debug */, + B4E7CFAD2AD03299009B4DF2 /* Testflight */, A94AE4EB235A89B5005CA320 /* Release */, ); defaultConfigurationIsVisible = 0; @@ -1370,6 +1598,7 @@ isa = XCConfigurationList; buildConfigurations = ( A9DAACF122E7978800E76C9F /* Debug */, + B4E7CFA82AD03299009B4DF2 /* Testflight */, A9DAACF222E7978800E76C9F /* Release */, ); defaultConfigurationIsVisible = 0; @@ -1379,6 +1608,7 @@ isa = XCConfigurationList; buildConfigurations = ( A9DAAD1122E7987800E76C9F /* Debug */, + B4E7CFA92AD03299009B4DF2 /* Testflight */, A9DAAD1222E7987800E76C9F /* Release */, ); defaultConfigurationIsVisible = 0; @@ -1388,6 +1618,7 @@ isa = XCConfigurationList; buildConfigurations = ( A9DAAD1422E7987800E76C9F /* Debug */, + B4E7CFAA2AD03299009B4DF2 /* Testflight */, A9DAAD1522E7987800E76C9F /* Release */, ); defaultConfigurationIsVisible = 0; @@ -1397,6 +1628,7 @@ isa = XCConfigurationList; buildConfigurations = ( A9DAAD2D22E7988900E76C9F /* Debug */, + B4E7CFAB2AD03299009B4DF2 /* Testflight */, A9DAAD2E22E7988900E76C9F /* Release */, ); defaultConfigurationIsVisible = 0; @@ -1406,6 +1638,7 @@ isa = XCConfigurationList; buildConfigurations = ( A9DAAD3022E7988900E76C9F /* Debug */, + B4E7CFAC2AD03299009B4DF2 /* Testflight */, A9DAAD3122E7988900E76C9F /* Release */, ); defaultConfigurationIsVisible = 0; From 96ca6ccf04bc5af8855e817eb27729e0e86f3f59 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 24 Oct 2023 10:42:17 -0500 Subject: [PATCH 06/71] Update test for api change --- .../Extensions/StoredDosingDecisionTests.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift b/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift index 78ca2ed..082152a 100644 --- a/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift +++ b/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift @@ -354,7 +354,6 @@ fileprivate extension StoredDosingDecision { duration: .minutes(30)) let automaticDoseRecommendation = AutomaticDoseRecommendation(basalAdjustment: tempBasalRecommendation, bolusUnits: 1.25) let manualBolusRecommendation = ManualBolusRecommendationWithDate(recommendation: ManualBolusRecommendation(amount: 1.2, - pendingInsulin: 0.75, notice: .predictedGlucoseBelowTarget(minGlucose: PredictedGlucoseValue(startDate: dateFormatter.date(from: "2020-05-14T23:03:15Z")!, quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 75.5)))), date: dateFormatter.date(from: "2020-05-14T22:38:16Z")!) From 103fb13296ea113e799010f987ede54661194638 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 24 Oct 2023 10:42:17 -0500 Subject: [PATCH 07/71] Update test for api change --- .../Extensions/StoredDosingDecisionTests.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift b/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift index 78ca2ed..082152a 100644 --- a/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift +++ b/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift @@ -354,7 +354,6 @@ fileprivate extension StoredDosingDecision { duration: .minutes(30)) let automaticDoseRecommendation = AutomaticDoseRecommendation(basalAdjustment: tempBasalRecommendation, bolusUnits: 1.25) let manualBolusRecommendation = ManualBolusRecommendationWithDate(recommendation: ManualBolusRecommendation(amount: 1.2, - pendingInsulin: 0.75, notice: .predictedGlucoseBelowTarget(minGlucose: PredictedGlucoseValue(startDate: dateFormatter.date(from: "2020-05-14T23:03:15Z")!, quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 75.5)))), date: dateFormatter.date(from: "2020-05-14T22:38:16Z")!) From 5b7a17bf89e3549134cc89b9d4db12946829fc13 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 19 Dec 2023 08:49:40 -0600 Subject: [PATCH 08/71] Temporary preset activations moved out of LoopSettings. (#96) --- .../Extensions/StoredSettings.swift | 41 +------ TidepoolServiceKit/TidepoolService.swift | 116 +++++------------- .../Extensions/StoredSettingsTests.swift | 38 ------ .../TidepoolServiceTests.swift | 78 +----------- 4 files changed, 36 insertions(+), 237 deletions(-) diff --git a/TidepoolServiceKit/Extensions/StoredSettings.swift b/TidepoolServiceKit/Extensions/StoredSettings.swift index e3993b6..9e212cb 100644 --- a/TidepoolServiceKit/Extensions/StoredSettings.swift +++ b/TidepoolServiceKit/Extensions/StoredSettings.swift @@ -21,8 +21,6 @@ import TidepoolKit - preMealTargetRange ClosedRange? TPumpSettingsDatum.bloodGlucoseTargetPreprandial - workoutTargetRange ClosedRange? TPumpSettingsDatum.bloodGlucoseTargetPhysicalActivity - overridePresets [TemporaryScheduleOverridePreset]? TPumpSettingsDatum.overridePresets - - scheduleOverride TemporaryScheduleOverride? TPumpSettingsOverrideDeviceEventDatum.* - - preMealOverride TemporaryScheduleOverride? TPumpSettingsOverrideDeviceEventDatum.* - maximumBasalRatePerHour Double? TPumpSettingsDatum.basal.rateMaximum.value - maximumBolus Double? TPumpSettingsDatum.bolus.amountMaximum.value - suspendThreshold GlucoseThreshold? TPumpSettingsDatum.bloodGlucoseSafetyLimit @@ -38,7 +36,6 @@ import TidepoolKit - syncIdentifier UUID .id, .origin, .payload["syncIdentifier"] Notes: - - The active override (scheduleOverride or preMealOverride) are stored in TPumpSettingsOverrideDeviceEventDatum. - Assumes same time zone for basalRateSchedule, glucoseTargetRangeSchedule, carbRatioSchedule, insulinSensitivitySchedule. - StoredSettings.notificationSettings.carPlaySetting is not included as it is unneeded by backend. - StoredSettings.notificationSettings.showPreviewsSetting is not included as it is unneeded by backend. @@ -116,29 +113,6 @@ extension StoredSettings: IdentifiableDatum { origin: origin) } - func datumPumpSettingsOverrideDeviceEvent(for userId: String, hostIdentifier: String, hostVersion: String) -> TPumpSettingsOverrideDeviceEventDatum? { - guard let activeOverride = activeOverride else { - return nil - } - let datum = TPumpSettingsOverrideDeviceEventDatum(time: activeOverride.datumTime, - overrideType: activeOverride.datumOverrideType, - overridePreset: activeOverride.datumOverridePreset, - method: activeOverride.datumMethod, - duration: activeOverride.datumDuration, - expectedDuration: activeOverride.datumExpectedDuration, - bloodGlucoseTarget: activeOverride.datumBloodGlucoseTarget, - basalRateScaleFactor: activeOverride.datumBasalRateScaleFactor, - carbohydrateRatioScaleFactor: activeOverride.datumCarbohydrateRatioScaleFactor, - insulinSensitivityScaleFactor: activeOverride.datumInsulinSensitivityScaleFactor, - units: activeOverride.datumUnits) - let origin = datumOrigin(for: resolvedIdentifier(for: TPumpSettingsOverrideDeviceEventDatum.self), hostIdentifier: hostIdentifier, hostVersion: hostVersion) - return datum.adornWith(id: datumId(for: userId, type: TPumpSettingsOverrideDeviceEventDatum.self), - timeZone: datumTimeZone, - timeZoneOffset: datumTimeZoneOffset, - payload: datumPayload, - origin: origin) - } - var syncIdentifierAsString: String { syncIdentifier.uuidString } private var datumTime: Date { date } @@ -304,7 +278,7 @@ extension StoredSettings: IdentifiableDatum { private var datumPumpName: String? { pumpDevice?.name } private var datumPumpOverridePresets: [String: TPumpSettingsDatum.OverridePreset]? { - guard let overridePresets = overridePresets, !overridePresets.isEmpty else { + guard !overridePresets.isEmpty else { return nil } return overridePresets.reduce(into: [:]) { $0[$1.name] = $1.datum } @@ -333,19 +307,6 @@ extension StoredSettings: IdentifiableDatum { return dictionary } - private var activeOverride: TemporaryScheduleOverride? { - switch (preMealOverride, scheduleOverride) { - case (let preMealOverride?, nil): - return preMealOverride - case (nil, let scheduleOverride?): - return scheduleOverride - case (let preMealOverride?, let scheduleOverride?): - return preMealOverride.scheduledEndDate > date ? preMealOverride : scheduleOverride - case (nil, nil): - return nil - } - } - public static var activeScheduleNameDefault: String { "Default" } } diff --git a/TidepoolServiceKit/TidepoolService.swift b/TidepoolServiceKit/TidepoolService.swift index f7590b6..ba7ea50 100644 --- a/TidepoolServiceKit/TidepoolService.swift +++ b/TidepoolServiceKit/TidepoolService.swift @@ -63,8 +63,6 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { private var lastPumpSettingsDatum: TPumpSettingsDatum? - private var lastPumpSettingsOverrideDeviceEventDatum: TPumpSettingsOverrideDeviceEventDatum? - private var hostIdentifier: String? private var hostVersion: String? @@ -95,7 +93,6 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { self.lastControllerSettingsDatum = (rawState["lastControllerSettingsDatum"] as? Data).flatMap { try? Self.decoder.decode(TControllerSettingsDatum.self, from: $0) } self.lastCGMSettingsDatum = (rawState["lastCGMSettingsDatum"] as? Data).flatMap { try? Self.decoder.decode(TCGMSettingsDatum.self, from: $0) } self.lastPumpSettingsDatum = (rawState["lastPumpSettingsDatum"] as? Data).flatMap { try? Self.decoder.decode(TPumpSettingsDatum.self, from: $0) } - self.lastPumpSettingsOverrideDeviceEventDatum = (rawState["lastPumpSettingsOverrideDeviceEventDatum"] as? Data).flatMap { try? Self.decoder.decode(TPumpSettingsOverrideDeviceEventDatum.self, from: $0) } self.session = try sessionStorage.getSession(for: sessionService) Task { await tapi.setSession(session) @@ -118,7 +115,6 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { rawValue["lastControllerSettingsDatum"] = lastControllerSettingsDatum.flatMap { try? Self.encoder.encode($0) } rawValue["lastCGMSettingsDatum"] = lastCGMSettingsDatum.flatMap { try? Self.encoder.encode($0) } rawValue["lastPumpSettingsDatum"] = lastPumpSettingsDatum.flatMap { try? Self.encoder.encode($0) } - rawValue["lastPumpSettingsOverrideDeviceEventDatum"] = lastPumpSettingsOverrideDeviceEventDatum.flatMap { try? Self.encoder.encode($0) } return rawValue } @@ -280,7 +276,32 @@ extension TidepoolService: TLogging { extension TidepoolService: RemoteDataService { public func uploadTemporaryOverrideData(updated: [TemporaryScheduleOverride], deleted: [TemporaryScheduleOverride], completion: @escaping (Result) -> Void) { - // TODO: Implement + // TODO: https://tidepool.atlassian.net/browse/LOOP-4769 + + // The following code is taken from previous upload code when override events where stored in settings + // To be implemented with + + // guard let activeOverride = activeOverride else { + // return nil + // } + // let datum = TPumpSettingsOverrideDeviceEventDatum(time: activeOverride.datumTime, + // overrideType: activeOverride.datumOverrideType, + // overridePreset: activeOverride.datumOverridePreset, + // method: activeOverride.datumMethod, + // duration: activeOverride.datumDuration, + // expectedDuration: activeOverride.datumExpectedDuration, + // bloodGlucoseTarget: activeOverride.datumBloodGlucoseTarget, + // basalRateScaleFactor: activeOverride.datumBasalRateScaleFactor, + // carbohydrateRatioScaleFactor: activeOverride.datumCarbohydrateRatioScaleFactor, + // insulinSensitivityScaleFactor: activeOverride.datumInsulinSensitivityScaleFactor, + // units: activeOverride.datumUnits) + // let origin = datumOrigin(for: resolvedIdentifier(for: TPumpSettingsOverrideDeviceEventDatum.self), hostIdentifier: hostIdentifier, hostVersion: hostVersion) + // return datum.adornWith(id: datumId(for: userId, type: TPumpSettingsOverrideDeviceEventDatum.self), + // timeZone: datumTimeZone, + // timeZoneOffset: datumTimeZoneOffset, + // payload: datumPayload, + // origin: origin) + completion(.success(true)) } @@ -448,7 +469,7 @@ extension TidepoolService: RemoteDataService { return } - let (created, updated, lastControllerSettingsDatum, lastCGMSettingsDatum, lastPumpSettingsDatum, lastPumpSettingsOverrideDeviceEventDatum) = calculateSettingsData(stored, for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) + let (created, updated, lastControllerSettingsDatum, lastCGMSettingsDatum, lastPumpSettingsDatum) = calculateSettingsData(stored, for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) Task { do { @@ -457,7 +478,6 @@ extension TidepoolService: RemoteDataService { self.lastControllerSettingsDatum = lastControllerSettingsDatum self.lastCGMSettingsDatum = lastCGMSettingsDatum self.lastPumpSettingsDatum = lastPumpSettingsDatum - self.lastPumpSettingsOverrideDeviceEventDatum = lastPumpSettingsOverrideDeviceEventDatum self.completeUpdate() completion(.success(createdUploaded || updatedUploaded)) } catch { @@ -466,19 +486,12 @@ extension TidepoolService: RemoteDataService { } } - func calculateSettingsData(_ stored: [StoredSettings], for userId: String, hostIdentifier: String, hostVersion: String) -> ([TDatum], [TDatum], TControllerSettingsDatum?, TCGMSettingsDatum?, TPumpSettingsDatum?, TPumpSettingsOverrideDeviceEventDatum?) { + func calculateSettingsData(_ stored: [StoredSettings], for userId: String, hostIdentifier: String, hostVersion: String) -> ([TDatum], [TDatum], TControllerSettingsDatum?, TCGMSettingsDatum?, TPumpSettingsDatum?) { var created: [TDatum] = [] var updated: [TDatum] = [] var lastControllerSettingsDatum = lastControllerSettingsDatum var lastCGMSettingsDatum = lastCGMSettingsDatum var lastPumpSettingsDatum = lastPumpSettingsDatum - var lastPumpSettingsOverrideDeviceEventDatum = lastPumpSettingsOverrideDeviceEventDatum - - // A StoredSettings can generate a TPumpSettingsDatum and an optional TPumpSettingsOverrideDeviceEventDatum if there is an - // enabled override. Only upload the TPumpSettingsDatum or TPumpSettingsOverrideDeviceEventDatum if they have CHANGED. - // If the TPumpSettingsOverrideDeviceEventDatum has changed, then also re-upload the previous uploaded - // TPumpSettingsOverrideDeviceEventDatum with an updated duration and potentially expected duration, but only if the - // duration is calculated to be ended early. stored.forEach { @@ -493,15 +506,11 @@ extension TidepoolService: RemoteDataService { let pumpSettingsDatum = $0.datumPumpSettings(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) let pumpSettingsDatumIsEffectivelyEquivalent = TPumpSettingsDatum.areEffectivelyEquivalent(old: lastPumpSettingsDatum, new: pumpSettingsDatum) - let pumpSettingsOverrideDeviceEventDatum = $0.datumPumpSettingsOverrideDeviceEvent(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) - let pumpSettingsOverrideDeviceEventDatumIsEffectivelyEquivalent = TPumpSettingsOverrideDeviceEventDatum.areEffectivelyEquivalent(old: lastPumpSettingsOverrideDeviceEventDatum, new: pumpSettingsOverrideDeviceEventDatum) - // Associate the data var controllerSettingsAssociations: [TAssociation] = [] var cgmSettingsAssociations: [TAssociation] = [] var pumpSettingsAssociations: [TAssociation] = [] - var pumpSettingsOverrideDeviceEventAssociations: [TAssociation] = [] if let controllerSettingsDatum = controllerSettingsDatumIsEffectivelyEquivalent ? lastControllerSettingsDatum : controllerSettingsDatum { let association = TAssociation(type: .datum, id: controllerSettingsDatum.id!, reason: "controllerSettings") @@ -517,13 +526,11 @@ extension TidepoolService: RemoteDataService { let association = TAssociation(type: .datum, id: pumpSettingsDatum.id!, reason: "pumpSettings") controllerSettingsAssociations.append(association) cgmSettingsAssociations.append(association) - pumpSettingsOverrideDeviceEventAssociations.append(association) } controllerSettingsDatum.append(associations: controllerSettingsAssociations) cgmSettingsDatum.append(associations: cgmSettingsAssociations) pumpSettingsDatum.append(associations: pumpSettingsAssociations) - pumpSettingsOverrideDeviceEventDatum?.append(associations: pumpSettingsOverrideDeviceEventAssociations) // Upload and update the data, if necessary @@ -541,27 +548,9 @@ extension TidepoolService: RemoteDataService { created.append(pumpSettingsDatum) lastPumpSettingsDatum = pumpSettingsDatum } - - if !pumpSettingsOverrideDeviceEventDatumIsEffectivelyEquivalent { - - // If we need to update the duration of the last override, then do so - if let lastPumpSettingsOverrideDeviceEventDatum = lastPumpSettingsOverrideDeviceEventDatum, - lastPumpSettingsOverrideDeviceEventDatum.updateDuration(basedUpon: pumpSettingsOverrideDeviceEventDatum?.time ?? pumpSettingsDatum.time) { - - // If it isn't already being created, then update it - if !created.contains(where: { $0 === lastPumpSettingsOverrideDeviceEventDatum }) { - updated.append(lastPumpSettingsOverrideDeviceEventDatum) - } - } - - if let pumpSettingsOverrideDeviceEventDatum = pumpSettingsOverrideDeviceEventDatum { - created.append(pumpSettingsOverrideDeviceEventDatum) - } - lastPumpSettingsOverrideDeviceEventDatum = pumpSettingsOverrideDeviceEventDatum - } } - return (created, updated, lastControllerSettingsDatum, lastCGMSettingsDatum, lastPumpSettingsDatum, lastPumpSettingsOverrideDeviceEventDatum) + return (created, updated, lastControllerSettingsDatum, lastCGMSettingsDatum, lastPumpSettingsDatum) } private func createData(_ data: [TDatum]) async throws -> Bool { @@ -782,53 +771,6 @@ extension TPumpSettingsDatum: EffectivelyEquivalent { } } -extension TPumpSettingsOverrideDeviceEventDatum: EffectivelyEquivalent { - - // All TDatum properties can be ignored EXCEPT time for this datum type - // Time is gather from the actual scheduled override and NOT the StoredSettings so it is valid and necessary for comparison - func isEffectivelyEquivalent(to other: TPumpSettingsOverrideDeviceEventDatum) -> Bool { - return self.time == other.time && - self.overrideType == other.overrideType && - self.overridePreset == other.overridePreset && - self.method == other.method && - self.duration == other.duration && - self.expectedDuration == other.expectedDuration && - self.bloodGlucoseTarget == other.bloodGlucoseTarget && - self.basalRateScaleFactor == other.basalRateScaleFactor && - self.carbohydrateRatioScaleFactor == other.carbohydrateRatioScaleFactor && - self.insulinSensitivityScaleFactor == other.insulinSensitivityScaleFactor && - self.units == other.units - } - - var isEffectivelyEmpty: Bool { - return overrideType == nil && - overridePreset == nil && - method == nil && - duration == nil && - expectedDuration == nil && - bloodGlucoseTarget == nil && - basalRateScaleFactor == nil && - carbohydrateRatioScaleFactor == nil && - insulinSensitivityScaleFactor == nil && - units == nil - } - - func updateDuration(basedUpon endTime: Date?) -> Bool { - guard let endTime = endTime, let time = time, endTime > time else { - return false - } - - let updatedDuration = time.distance(to: endTime) - guard duration == nil || updatedDuration < duration! else { - return false - } - - self.expectedDuration = duration - self.duration = updatedDuration - return true - } -} - fileprivate extension TDosingDecisionDatum { // Ignore reason and units as they are always specified diff --git a/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift b/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift index c08ce84..8e33ddf 100644 --- a/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift +++ b/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift @@ -247,42 +247,6 @@ class StoredSettingsTests: XCTestCase { ) } - func testDatumPumpSettingsOverrideDeviceEvent() { - let data = try! Self.encoder.encode(StoredSettings.test.datumPumpSettingsOverrideDeviceEvent(for: "1234567890", hostIdentifier: "Loop", hostVersion: "1.2.3")) - XCTAssertEqual(String(data: data, encoding: .utf8), """ -{ - "basalRateScaleFactor" : 0.5, - "bgTarget" : { - "high" : 90, - "low" : 80 - }, - "carbRatioScaleFactor" : 2, - "id" : "f89ad59a42430ab89dd2eab3a3e4df84", - "insulinSensitivityScaleFactor" : 2, - "method" : "manual", - "origin" : { - "id" : "2A67A303-1234-4CB8-1234-79498265368E:deviceEvent/pumpSettingsOverride", - "name" : "Loop", - "type" : "application", - "version" : "1.2.3" - }, - "overrideType" : "preprandial", - "payload" : { - "syncIdentifier" : "2A67A303-1234-4CB8-1234-79498265368E" - }, - "subType" : "pumpSettingsOverride", - "time" : "2020-05-14T14:38:39.000Z", - "timezone" : "America/Los_Angeles", - "timezoneOffset" : -420, - "type" : "deviceEvent", - "units" : { - "bg" : "mg/dL" - } -} -""" - ) - } - private static let encoder: JSONEncoder = { let encoder = JSONEncoder.tidepool encoder.outputFormatting.insert(.prettyPrinted) @@ -392,8 +356,6 @@ fileprivate extension StoredSettings { preMealTargetRange: preMealTargetRange, workoutTargetRange: workoutTargetRange, overridePresets: overridePresets, - scheduleOverride: scheduleOverride, - preMealOverride: preMealOverride, maximumBasalRatePerHour: maximumBasalRatePerHour, maximumBolus: maximumBolus, suspendThreshold: suspendThreshold, diff --git a/TidepoolServiceKitTests/TidepoolServiceTests.swift b/TidepoolServiceKitTests/TidepoolServiceTests.swift index 7ace7f1..0d8d70d 100644 --- a/TidepoolServiceKitTests/TidepoolServiceTests.swift +++ b/TidepoolServiceKitTests/TidepoolServiceTests.swift @@ -52,7 +52,7 @@ class TidepoolServiceTests: XCTestCase { let settings = [StoredSettings(controllerDevice: StoredSettings.ControllerDevice(name: "Controller #1"), cgmDevice: HKDevice(name: "CGM #1"), pumpDevice: HKDevice(name: "Pump #1"))] - let (created, updated, lastControllerSettings, lastCGMSettings, lastPumpSettings, lastPumpSettingsOverride) = tidepoolService.calculateSettingsData(settings, for: userID, hostIdentifier: "Loop", hostVersion: "1.2.3") + let (created, updated, lastControllerSettings, lastCGMSettings, lastPumpSettings) = tidepoolService.calculateSettingsData(settings, for: userID, hostIdentifier: "Loop", hostVersion: "1.2.3") XCTAssertEqual(created.count, 3) XCTAssertEqual((created[0] as! TControllerSettingsDatum).device!.name, "Controller #1") XCTAssertEqual((created[0] as! TControllerSettingsDatum).associations!.count, 2) @@ -70,7 +70,6 @@ class TidepoolServiceTests: XCTestCase { XCTAssertEqual(lastControllerSettings!.id, created[0].id) XCTAssertEqual(lastCGMSettings!.id, created[1].id) XCTAssertEqual(lastPumpSettings!.id, created[2].id) - XCTAssertNil(lastPumpSettingsOverride) } func testCalculateSettingsDataUpdateControllerSettings() { @@ -80,7 +79,7 @@ class TidepoolServiceTests: XCTestCase { StoredSettings(controllerDevice: StoredSettings.ControllerDevice(name: "Controller #2"), cgmDevice: HKDevice(name: "CGM #1"), pumpDevice: HKDevice(name: "Pump #1"))] - let (created, updated, lastControllerSettings, lastCGMSettings, lastPumpSettings, lastPumpSettingsOverride) = tidepoolService.calculateSettingsData(settings, for: userID, hostIdentifier: "Loop", hostVersion: "1.2.3") + let (created, updated, lastControllerSettings, lastCGMSettings, lastPumpSettings) = tidepoolService.calculateSettingsData(settings, for: userID, hostIdentifier: "Loop", hostVersion: "1.2.3") XCTAssertEqual(created.count, 4) XCTAssertEqual((created[0] as! TControllerSettingsDatum).device!.name, "Controller #1") XCTAssertEqual((created[1] as! TCGMSettingsDatum).name, "CGM #1") @@ -93,7 +92,6 @@ class TidepoolServiceTests: XCTestCase { XCTAssertEqual(lastControllerSettings!.id, created[3].id) XCTAssertEqual(lastCGMSettings!.id, created[1].id) XCTAssertEqual(lastPumpSettings!.id, created[2].id) - XCTAssertNil(lastPumpSettingsOverride) } func testCalculateSettingsDataUpdateCGMSettings() { @@ -103,7 +101,7 @@ class TidepoolServiceTests: XCTestCase { StoredSettings(controllerDevice: StoredSettings.ControllerDevice(name: "Controller #1"), cgmDevice: HKDevice(name: "CGM #2"), pumpDevice: HKDevice(name: "Pump #1"))] - let (created, updated, lastControllerSettings, lastCGMSettings, lastPumpSettings, lastPumpSettingsOverride) = tidepoolService.calculateSettingsData(settings, for: userID, hostIdentifier: "Loop", hostVersion: "1.2.3") + let (created, updated, lastControllerSettings, lastCGMSettings, lastPumpSettings) = tidepoolService.calculateSettingsData(settings, for: userID, hostIdentifier: "Loop", hostVersion: "1.2.3") XCTAssertEqual(created.count, 4) XCTAssertEqual((created[0] as! TControllerSettingsDatum).device!.name, "Controller #1") XCTAssertEqual((created[1] as! TCGMSettingsDatum).name, "CGM #1") @@ -116,7 +114,6 @@ class TidepoolServiceTests: XCTestCase { XCTAssertEqual(lastControllerSettings!.id, created[0].id) XCTAssertEqual(lastCGMSettings!.id, created[3].id) XCTAssertEqual(lastPumpSettings!.id, created[2].id) - XCTAssertNil(lastPumpSettingsOverride) } func testCalculateSettingsDataUpdatePumpSettings() { @@ -126,7 +123,7 @@ class TidepoolServiceTests: XCTestCase { StoredSettings(controllerDevice: StoredSettings.ControllerDevice(name: "Controller #1"), cgmDevice: HKDevice(name: "CGM #1"), pumpDevice: HKDevice(name: "Pump #2"))] - let (created, updated, lastControllerSettings, lastCGMSettings, lastPumpSettings, lastPumpSettingsOverride) = tidepoolService.calculateSettingsData(settings, for: userID, hostIdentifier: "Loop", hostVersion: "1.2.3") + let (created, updated, lastControllerSettings, lastCGMSettings, lastPumpSettings) = tidepoolService.calculateSettingsData(settings, for: userID, hostIdentifier: "Loop", hostVersion: "1.2.3") XCTAssertEqual(created.count, 4) XCTAssertEqual((created[0] as! TControllerSettingsDatum).device!.name, "Controller #1") XCTAssertEqual((created[1] as! TCGMSettingsDatum).name, "CGM #1") @@ -139,7 +136,6 @@ class TidepoolServiceTests: XCTestCase { XCTAssertEqual(lastControllerSettings!.id, created[0].id) XCTAssertEqual(lastCGMSettings!.id, created[1].id) XCTAssertEqual(lastPumpSettings!.id, created[3].id) - XCTAssertNil(lastPumpSettingsOverride) } func testCalculateSettingsDataUpdateMultipleOne() { @@ -149,7 +145,7 @@ class TidepoolServiceTests: XCTestCase { StoredSettings(controllerDevice: StoredSettings.ControllerDevice(name: "Controller #2"), cgmDevice: HKDevice(name: "CGM #2"), pumpDevice: HKDevice(name: "Pump #2"))] - let (created, updated, lastControllerSettings, lastCGMSettings, lastPumpSettings, lastPumpSettingsOverride) = tidepoolService.calculateSettingsData(settings, for: userID, hostIdentifier: "Loop", hostVersion: "1.2.3") + let (created, updated, lastControllerSettings, lastCGMSettings, lastPumpSettings) = tidepoolService.calculateSettingsData(settings, for: userID, hostIdentifier: "Loop", hostVersion: "1.2.3") XCTAssertEqual(created.count, 6) XCTAssertEqual((created[0] as! TControllerSettingsDatum).device!.name, "Controller #1") XCTAssertEqual((created[1] as! TCGMSettingsDatum).name, "CGM #1") @@ -170,7 +166,6 @@ class TidepoolServiceTests: XCTestCase { XCTAssertEqual(lastControllerSettings!.id, created[3].id) XCTAssertEqual(lastCGMSettings!.id, created[4].id) XCTAssertEqual(lastPumpSettings!.id, created[5].id) - XCTAssertNil(lastPumpSettingsOverride) } func testCalculateSettingsDataUpdateMultipleMultiple() { @@ -186,7 +181,7 @@ class TidepoolServiceTests: XCTestCase { StoredSettings(controllerDevice: StoredSettings.ControllerDevice(name: "Controller #2"), cgmDevice: HKDevice(name: "CGM #2"), pumpDevice: HKDevice(name: "Pump #2"))] - let (created, updated, lastControllerSettings, lastCGMSettings, lastPumpSettings, lastPumpSettingsOverride) = tidepoolService.calculateSettingsData(settings, for: userID, hostIdentifier: "Loop", hostVersion: "1.2.3") + let (created, updated, lastControllerSettings, lastCGMSettings, lastPumpSettings) = tidepoolService.calculateSettingsData(settings, for: userID, hostIdentifier: "Loop", hostVersion: "1.2.3") XCTAssertEqual(created.count, 6) XCTAssertEqual((created[0] as! TControllerSettingsDatum).device!.name, "Controller #1") XCTAssertEqual((created[1] as! TCGMSettingsDatum).name, "CGM #1") @@ -207,69 +202,8 @@ class TidepoolServiceTests: XCTestCase { XCTAssertEqual(lastControllerSettings!.id, created[3].id) XCTAssertEqual(lastCGMSettings!.id, created[4].id) XCTAssertEqual(lastPumpSettings!.id, created[5].id) - XCTAssertNil(lastPumpSettingsOverride) } - func testCalculateSettingsDataPumpOverrideSingle() { - let scheduleOverride = TemporaryScheduleOverride() - let settings = [StoredSettings(pumpDevice: HKDevice(name: "Pump #1")), - StoredSettings(scheduleOverride: scheduleOverride, - pumpDevice: HKDevice(name: "Pump #1")), - StoredSettings(scheduleOverride: scheduleOverride, - controllerDevice: StoredSettings.ControllerDevice(name: "Controller #1"), - pumpDevice: HKDevice(name: "Pump #1"))] - let (created, updated, lastControllerSettings, lastCGMSettings, lastPumpSettings, lastPumpSettingsOverride) = tidepoolService.calculateSettingsData(settings, for: userID, hostIdentifier: "Loop", hostVersion: "1.2.3") - XCTAssertEqual(created.count, 3) - XCTAssertEqual((created[0] as! TPumpSettingsDatum).name, "Pump #1") - XCTAssertNil((created[0] as! TPumpSettingsDatum).associations) - XCTAssertNil((created[1] as! TPumpSettingsOverrideDeviceEventDatum).duration) - XCTAssertEqual((created[1] as! TPumpSettingsOverrideDeviceEventDatum).associations!.count, 1) - XCTAssertEqual((created[1] as! TPumpSettingsOverrideDeviceEventDatum).associations![0].id, created[0].id) - XCTAssertEqual((created[2] as! TControllerSettingsDatum).device!.name, "Controller #1") - XCTAssertTrue(updated.isEmpty) - XCTAssertEqual(lastControllerSettings!.id, created[2].id) - XCTAssertNil(lastCGMSettings) - XCTAssertEqual(lastPumpSettings!.id, created[0].id) - XCTAssertEqual(lastPumpSettingsOverride!.id, created[1].id) - } - - func testCalculateSettingsDataPumpOverrideMultipleAllCreated() { - let settings = [StoredSettings(pumpDevice: HKDevice(name: "Pump #1")), - StoredSettings(scheduleOverride: TemporaryScheduleOverride(duration: .minutes(30)), - pumpDevice: HKDevice(name: "Pump #1")), - StoredSettings(preMealOverride: TemporaryScheduleOverride(), - pumpDevice: HKDevice(name: "Pump #1")), - StoredSettings(pumpDevice: HKDevice(name: "Pump #1")), - StoredSettings(controllerDevice: StoredSettings.ControllerDevice(name: "Controller #1"), - pumpDevice: HKDevice(name: "Pump #1"))] - let (created, updated, lastControllerSettings, lastCGMSettings, lastPumpSettings, lastPumpSettingsOverride) = tidepoolService.calculateSettingsData(settings, for: userID, hostIdentifier: "Loop", hostVersion: "1.2.3") - XCTAssertEqual(created.count, 4) - XCTAssertEqual((created[0] as! TPumpSettingsDatum).name, "Pump #1") - XCTAssertNil((created[0] as! TPumpSettingsDatum).associations) - XCTAssertNotNil((created[1] as! TPumpSettingsOverrideDeviceEventDatum).duration) - XCTAssertEqual((created[1] as! TPumpSettingsOverrideDeviceEventDatum).associations!.count, 1) - XCTAssertEqual((created[1] as! TPumpSettingsOverrideDeviceEventDatum).associations![0].id, created[0].id) - XCTAssertNotNil((created[2] as! TPumpSettingsOverrideDeviceEventDatum).duration) - XCTAssertEqual((created[2] as! TPumpSettingsOverrideDeviceEventDatum).associations!.count, 1) - XCTAssertEqual((created[2] as! TPumpSettingsOverrideDeviceEventDatum).associations![0].id, created[0].id) - XCTAssertEqual((created[3] as! TControllerSettingsDatum).device!.name, "Controller #1") - XCTAssertTrue(updated.isEmpty) - XCTAssertEqual(lastControllerSettings!.id, created[3].id) - XCTAssertNil(lastCGMSettings) - XCTAssertEqual(lastPumpSettings!.id, created[0].id) - XCTAssertNil(lastPumpSettingsOverride) - } -} - -fileprivate extension TemporaryScheduleOverride { - init(duration: TimeInterval? = nil) { - self.init(context: .custom, - settings: TemporaryScheduleOverrideSettings(targetRange: nil, insulinNeedsScaleFactor: 1.2), - startDate: Date(), - duration: duration != nil ? .finite(duration!) : .indefinite, - enactTrigger: .local, - syncIdentifier: UUID()) - } } fileprivate extension StoredSettings.ControllerDevice { From 9fd01cf9000fccab79f3374c508aafccd5d3ee55 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 19 Dec 2023 08:49:40 -0600 Subject: [PATCH 09/71] Temporary preset activations moved out of LoopSettings. (#96) --- .../Extensions/StoredSettings.swift | 41 +------ TidepoolServiceKit/TidepoolService.swift | 116 +++++------------- .../Extensions/StoredSettingsTests.swift | 38 ------ .../TidepoolServiceTests.swift | 78 +----------- 4 files changed, 36 insertions(+), 237 deletions(-) diff --git a/TidepoolServiceKit/Extensions/StoredSettings.swift b/TidepoolServiceKit/Extensions/StoredSettings.swift index e3993b6..9e212cb 100644 --- a/TidepoolServiceKit/Extensions/StoredSettings.swift +++ b/TidepoolServiceKit/Extensions/StoredSettings.swift @@ -21,8 +21,6 @@ import TidepoolKit - preMealTargetRange ClosedRange? TPumpSettingsDatum.bloodGlucoseTargetPreprandial - workoutTargetRange ClosedRange? TPumpSettingsDatum.bloodGlucoseTargetPhysicalActivity - overridePresets [TemporaryScheduleOverridePreset]? TPumpSettingsDatum.overridePresets - - scheduleOverride TemporaryScheduleOverride? TPumpSettingsOverrideDeviceEventDatum.* - - preMealOverride TemporaryScheduleOverride? TPumpSettingsOverrideDeviceEventDatum.* - maximumBasalRatePerHour Double? TPumpSettingsDatum.basal.rateMaximum.value - maximumBolus Double? TPumpSettingsDatum.bolus.amountMaximum.value - suspendThreshold GlucoseThreshold? TPumpSettingsDatum.bloodGlucoseSafetyLimit @@ -38,7 +36,6 @@ import TidepoolKit - syncIdentifier UUID .id, .origin, .payload["syncIdentifier"] Notes: - - The active override (scheduleOverride or preMealOverride) are stored in TPumpSettingsOverrideDeviceEventDatum. - Assumes same time zone for basalRateSchedule, glucoseTargetRangeSchedule, carbRatioSchedule, insulinSensitivitySchedule. - StoredSettings.notificationSettings.carPlaySetting is not included as it is unneeded by backend. - StoredSettings.notificationSettings.showPreviewsSetting is not included as it is unneeded by backend. @@ -116,29 +113,6 @@ extension StoredSettings: IdentifiableDatum { origin: origin) } - func datumPumpSettingsOverrideDeviceEvent(for userId: String, hostIdentifier: String, hostVersion: String) -> TPumpSettingsOverrideDeviceEventDatum? { - guard let activeOverride = activeOverride else { - return nil - } - let datum = TPumpSettingsOverrideDeviceEventDatum(time: activeOverride.datumTime, - overrideType: activeOverride.datumOverrideType, - overridePreset: activeOverride.datumOverridePreset, - method: activeOverride.datumMethod, - duration: activeOverride.datumDuration, - expectedDuration: activeOverride.datumExpectedDuration, - bloodGlucoseTarget: activeOverride.datumBloodGlucoseTarget, - basalRateScaleFactor: activeOverride.datumBasalRateScaleFactor, - carbohydrateRatioScaleFactor: activeOverride.datumCarbohydrateRatioScaleFactor, - insulinSensitivityScaleFactor: activeOverride.datumInsulinSensitivityScaleFactor, - units: activeOverride.datumUnits) - let origin = datumOrigin(for: resolvedIdentifier(for: TPumpSettingsOverrideDeviceEventDatum.self), hostIdentifier: hostIdentifier, hostVersion: hostVersion) - return datum.adornWith(id: datumId(for: userId, type: TPumpSettingsOverrideDeviceEventDatum.self), - timeZone: datumTimeZone, - timeZoneOffset: datumTimeZoneOffset, - payload: datumPayload, - origin: origin) - } - var syncIdentifierAsString: String { syncIdentifier.uuidString } private var datumTime: Date { date } @@ -304,7 +278,7 @@ extension StoredSettings: IdentifiableDatum { private var datumPumpName: String? { pumpDevice?.name } private var datumPumpOverridePresets: [String: TPumpSettingsDatum.OverridePreset]? { - guard let overridePresets = overridePresets, !overridePresets.isEmpty else { + guard !overridePresets.isEmpty else { return nil } return overridePresets.reduce(into: [:]) { $0[$1.name] = $1.datum } @@ -333,19 +307,6 @@ extension StoredSettings: IdentifiableDatum { return dictionary } - private var activeOverride: TemporaryScheduleOverride? { - switch (preMealOverride, scheduleOverride) { - case (let preMealOverride?, nil): - return preMealOverride - case (nil, let scheduleOverride?): - return scheduleOverride - case (let preMealOverride?, let scheduleOverride?): - return preMealOverride.scheduledEndDate > date ? preMealOverride : scheduleOverride - case (nil, nil): - return nil - } - } - public static var activeScheduleNameDefault: String { "Default" } } diff --git a/TidepoolServiceKit/TidepoolService.swift b/TidepoolServiceKit/TidepoolService.swift index f7590b6..ba7ea50 100644 --- a/TidepoolServiceKit/TidepoolService.swift +++ b/TidepoolServiceKit/TidepoolService.swift @@ -63,8 +63,6 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { private var lastPumpSettingsDatum: TPumpSettingsDatum? - private var lastPumpSettingsOverrideDeviceEventDatum: TPumpSettingsOverrideDeviceEventDatum? - private var hostIdentifier: String? private var hostVersion: String? @@ -95,7 +93,6 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { self.lastControllerSettingsDatum = (rawState["lastControllerSettingsDatum"] as? Data).flatMap { try? Self.decoder.decode(TControllerSettingsDatum.self, from: $0) } self.lastCGMSettingsDatum = (rawState["lastCGMSettingsDatum"] as? Data).flatMap { try? Self.decoder.decode(TCGMSettingsDatum.self, from: $0) } self.lastPumpSettingsDatum = (rawState["lastPumpSettingsDatum"] as? Data).flatMap { try? Self.decoder.decode(TPumpSettingsDatum.self, from: $0) } - self.lastPumpSettingsOverrideDeviceEventDatum = (rawState["lastPumpSettingsOverrideDeviceEventDatum"] as? Data).flatMap { try? Self.decoder.decode(TPumpSettingsOverrideDeviceEventDatum.self, from: $0) } self.session = try sessionStorage.getSession(for: sessionService) Task { await tapi.setSession(session) @@ -118,7 +115,6 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { rawValue["lastControllerSettingsDatum"] = lastControllerSettingsDatum.flatMap { try? Self.encoder.encode($0) } rawValue["lastCGMSettingsDatum"] = lastCGMSettingsDatum.flatMap { try? Self.encoder.encode($0) } rawValue["lastPumpSettingsDatum"] = lastPumpSettingsDatum.flatMap { try? Self.encoder.encode($0) } - rawValue["lastPumpSettingsOverrideDeviceEventDatum"] = lastPumpSettingsOverrideDeviceEventDatum.flatMap { try? Self.encoder.encode($0) } return rawValue } @@ -280,7 +276,32 @@ extension TidepoolService: TLogging { extension TidepoolService: RemoteDataService { public func uploadTemporaryOverrideData(updated: [TemporaryScheduleOverride], deleted: [TemporaryScheduleOverride], completion: @escaping (Result) -> Void) { - // TODO: Implement + // TODO: https://tidepool.atlassian.net/browse/LOOP-4769 + + // The following code is taken from previous upload code when override events where stored in settings + // To be implemented with + + // guard let activeOverride = activeOverride else { + // return nil + // } + // let datum = TPumpSettingsOverrideDeviceEventDatum(time: activeOverride.datumTime, + // overrideType: activeOverride.datumOverrideType, + // overridePreset: activeOverride.datumOverridePreset, + // method: activeOverride.datumMethod, + // duration: activeOverride.datumDuration, + // expectedDuration: activeOverride.datumExpectedDuration, + // bloodGlucoseTarget: activeOverride.datumBloodGlucoseTarget, + // basalRateScaleFactor: activeOverride.datumBasalRateScaleFactor, + // carbohydrateRatioScaleFactor: activeOverride.datumCarbohydrateRatioScaleFactor, + // insulinSensitivityScaleFactor: activeOverride.datumInsulinSensitivityScaleFactor, + // units: activeOverride.datumUnits) + // let origin = datumOrigin(for: resolvedIdentifier(for: TPumpSettingsOverrideDeviceEventDatum.self), hostIdentifier: hostIdentifier, hostVersion: hostVersion) + // return datum.adornWith(id: datumId(for: userId, type: TPumpSettingsOverrideDeviceEventDatum.self), + // timeZone: datumTimeZone, + // timeZoneOffset: datumTimeZoneOffset, + // payload: datumPayload, + // origin: origin) + completion(.success(true)) } @@ -448,7 +469,7 @@ extension TidepoolService: RemoteDataService { return } - let (created, updated, lastControllerSettingsDatum, lastCGMSettingsDatum, lastPumpSettingsDatum, lastPumpSettingsOverrideDeviceEventDatum) = calculateSettingsData(stored, for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) + let (created, updated, lastControllerSettingsDatum, lastCGMSettingsDatum, lastPumpSettingsDatum) = calculateSettingsData(stored, for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) Task { do { @@ -457,7 +478,6 @@ extension TidepoolService: RemoteDataService { self.lastControllerSettingsDatum = lastControllerSettingsDatum self.lastCGMSettingsDatum = lastCGMSettingsDatum self.lastPumpSettingsDatum = lastPumpSettingsDatum - self.lastPumpSettingsOverrideDeviceEventDatum = lastPumpSettingsOverrideDeviceEventDatum self.completeUpdate() completion(.success(createdUploaded || updatedUploaded)) } catch { @@ -466,19 +486,12 @@ extension TidepoolService: RemoteDataService { } } - func calculateSettingsData(_ stored: [StoredSettings], for userId: String, hostIdentifier: String, hostVersion: String) -> ([TDatum], [TDatum], TControllerSettingsDatum?, TCGMSettingsDatum?, TPumpSettingsDatum?, TPumpSettingsOverrideDeviceEventDatum?) { + func calculateSettingsData(_ stored: [StoredSettings], for userId: String, hostIdentifier: String, hostVersion: String) -> ([TDatum], [TDatum], TControllerSettingsDatum?, TCGMSettingsDatum?, TPumpSettingsDatum?) { var created: [TDatum] = [] var updated: [TDatum] = [] var lastControllerSettingsDatum = lastControllerSettingsDatum var lastCGMSettingsDatum = lastCGMSettingsDatum var lastPumpSettingsDatum = lastPumpSettingsDatum - var lastPumpSettingsOverrideDeviceEventDatum = lastPumpSettingsOverrideDeviceEventDatum - - // A StoredSettings can generate a TPumpSettingsDatum and an optional TPumpSettingsOverrideDeviceEventDatum if there is an - // enabled override. Only upload the TPumpSettingsDatum or TPumpSettingsOverrideDeviceEventDatum if they have CHANGED. - // If the TPumpSettingsOverrideDeviceEventDatum has changed, then also re-upload the previous uploaded - // TPumpSettingsOverrideDeviceEventDatum with an updated duration and potentially expected duration, but only if the - // duration is calculated to be ended early. stored.forEach { @@ -493,15 +506,11 @@ extension TidepoolService: RemoteDataService { let pumpSettingsDatum = $0.datumPumpSettings(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) let pumpSettingsDatumIsEffectivelyEquivalent = TPumpSettingsDatum.areEffectivelyEquivalent(old: lastPumpSettingsDatum, new: pumpSettingsDatum) - let pumpSettingsOverrideDeviceEventDatum = $0.datumPumpSettingsOverrideDeviceEvent(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) - let pumpSettingsOverrideDeviceEventDatumIsEffectivelyEquivalent = TPumpSettingsOverrideDeviceEventDatum.areEffectivelyEquivalent(old: lastPumpSettingsOverrideDeviceEventDatum, new: pumpSettingsOverrideDeviceEventDatum) - // Associate the data var controllerSettingsAssociations: [TAssociation] = [] var cgmSettingsAssociations: [TAssociation] = [] var pumpSettingsAssociations: [TAssociation] = [] - var pumpSettingsOverrideDeviceEventAssociations: [TAssociation] = [] if let controllerSettingsDatum = controllerSettingsDatumIsEffectivelyEquivalent ? lastControllerSettingsDatum : controllerSettingsDatum { let association = TAssociation(type: .datum, id: controllerSettingsDatum.id!, reason: "controllerSettings") @@ -517,13 +526,11 @@ extension TidepoolService: RemoteDataService { let association = TAssociation(type: .datum, id: pumpSettingsDatum.id!, reason: "pumpSettings") controllerSettingsAssociations.append(association) cgmSettingsAssociations.append(association) - pumpSettingsOverrideDeviceEventAssociations.append(association) } controllerSettingsDatum.append(associations: controllerSettingsAssociations) cgmSettingsDatum.append(associations: cgmSettingsAssociations) pumpSettingsDatum.append(associations: pumpSettingsAssociations) - pumpSettingsOverrideDeviceEventDatum?.append(associations: pumpSettingsOverrideDeviceEventAssociations) // Upload and update the data, if necessary @@ -541,27 +548,9 @@ extension TidepoolService: RemoteDataService { created.append(pumpSettingsDatum) lastPumpSettingsDatum = pumpSettingsDatum } - - if !pumpSettingsOverrideDeviceEventDatumIsEffectivelyEquivalent { - - // If we need to update the duration of the last override, then do so - if let lastPumpSettingsOverrideDeviceEventDatum = lastPumpSettingsOverrideDeviceEventDatum, - lastPumpSettingsOverrideDeviceEventDatum.updateDuration(basedUpon: pumpSettingsOverrideDeviceEventDatum?.time ?? pumpSettingsDatum.time) { - - // If it isn't already being created, then update it - if !created.contains(where: { $0 === lastPumpSettingsOverrideDeviceEventDatum }) { - updated.append(lastPumpSettingsOverrideDeviceEventDatum) - } - } - - if let pumpSettingsOverrideDeviceEventDatum = pumpSettingsOverrideDeviceEventDatum { - created.append(pumpSettingsOverrideDeviceEventDatum) - } - lastPumpSettingsOverrideDeviceEventDatum = pumpSettingsOverrideDeviceEventDatum - } } - return (created, updated, lastControllerSettingsDatum, lastCGMSettingsDatum, lastPumpSettingsDatum, lastPumpSettingsOverrideDeviceEventDatum) + return (created, updated, lastControllerSettingsDatum, lastCGMSettingsDatum, lastPumpSettingsDatum) } private func createData(_ data: [TDatum]) async throws -> Bool { @@ -782,53 +771,6 @@ extension TPumpSettingsDatum: EffectivelyEquivalent { } } -extension TPumpSettingsOverrideDeviceEventDatum: EffectivelyEquivalent { - - // All TDatum properties can be ignored EXCEPT time for this datum type - // Time is gather from the actual scheduled override and NOT the StoredSettings so it is valid and necessary for comparison - func isEffectivelyEquivalent(to other: TPumpSettingsOverrideDeviceEventDatum) -> Bool { - return self.time == other.time && - self.overrideType == other.overrideType && - self.overridePreset == other.overridePreset && - self.method == other.method && - self.duration == other.duration && - self.expectedDuration == other.expectedDuration && - self.bloodGlucoseTarget == other.bloodGlucoseTarget && - self.basalRateScaleFactor == other.basalRateScaleFactor && - self.carbohydrateRatioScaleFactor == other.carbohydrateRatioScaleFactor && - self.insulinSensitivityScaleFactor == other.insulinSensitivityScaleFactor && - self.units == other.units - } - - var isEffectivelyEmpty: Bool { - return overrideType == nil && - overridePreset == nil && - method == nil && - duration == nil && - expectedDuration == nil && - bloodGlucoseTarget == nil && - basalRateScaleFactor == nil && - carbohydrateRatioScaleFactor == nil && - insulinSensitivityScaleFactor == nil && - units == nil - } - - func updateDuration(basedUpon endTime: Date?) -> Bool { - guard let endTime = endTime, let time = time, endTime > time else { - return false - } - - let updatedDuration = time.distance(to: endTime) - guard duration == nil || updatedDuration < duration! else { - return false - } - - self.expectedDuration = duration - self.duration = updatedDuration - return true - } -} - fileprivate extension TDosingDecisionDatum { // Ignore reason and units as they are always specified diff --git a/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift b/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift index c08ce84..8e33ddf 100644 --- a/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift +++ b/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift @@ -247,42 +247,6 @@ class StoredSettingsTests: XCTestCase { ) } - func testDatumPumpSettingsOverrideDeviceEvent() { - let data = try! Self.encoder.encode(StoredSettings.test.datumPumpSettingsOverrideDeviceEvent(for: "1234567890", hostIdentifier: "Loop", hostVersion: "1.2.3")) - XCTAssertEqual(String(data: data, encoding: .utf8), """ -{ - "basalRateScaleFactor" : 0.5, - "bgTarget" : { - "high" : 90, - "low" : 80 - }, - "carbRatioScaleFactor" : 2, - "id" : "f89ad59a42430ab89dd2eab3a3e4df84", - "insulinSensitivityScaleFactor" : 2, - "method" : "manual", - "origin" : { - "id" : "2A67A303-1234-4CB8-1234-79498265368E:deviceEvent/pumpSettingsOverride", - "name" : "Loop", - "type" : "application", - "version" : "1.2.3" - }, - "overrideType" : "preprandial", - "payload" : { - "syncIdentifier" : "2A67A303-1234-4CB8-1234-79498265368E" - }, - "subType" : "pumpSettingsOverride", - "time" : "2020-05-14T14:38:39.000Z", - "timezone" : "America/Los_Angeles", - "timezoneOffset" : -420, - "type" : "deviceEvent", - "units" : { - "bg" : "mg/dL" - } -} -""" - ) - } - private static let encoder: JSONEncoder = { let encoder = JSONEncoder.tidepool encoder.outputFormatting.insert(.prettyPrinted) @@ -392,8 +356,6 @@ fileprivate extension StoredSettings { preMealTargetRange: preMealTargetRange, workoutTargetRange: workoutTargetRange, overridePresets: overridePresets, - scheduleOverride: scheduleOverride, - preMealOverride: preMealOverride, maximumBasalRatePerHour: maximumBasalRatePerHour, maximumBolus: maximumBolus, suspendThreshold: suspendThreshold, diff --git a/TidepoolServiceKitTests/TidepoolServiceTests.swift b/TidepoolServiceKitTests/TidepoolServiceTests.swift index 7ace7f1..0d8d70d 100644 --- a/TidepoolServiceKitTests/TidepoolServiceTests.swift +++ b/TidepoolServiceKitTests/TidepoolServiceTests.swift @@ -52,7 +52,7 @@ class TidepoolServiceTests: XCTestCase { let settings = [StoredSettings(controllerDevice: StoredSettings.ControllerDevice(name: "Controller #1"), cgmDevice: HKDevice(name: "CGM #1"), pumpDevice: HKDevice(name: "Pump #1"))] - let (created, updated, lastControllerSettings, lastCGMSettings, lastPumpSettings, lastPumpSettingsOverride) = tidepoolService.calculateSettingsData(settings, for: userID, hostIdentifier: "Loop", hostVersion: "1.2.3") + let (created, updated, lastControllerSettings, lastCGMSettings, lastPumpSettings) = tidepoolService.calculateSettingsData(settings, for: userID, hostIdentifier: "Loop", hostVersion: "1.2.3") XCTAssertEqual(created.count, 3) XCTAssertEqual((created[0] as! TControllerSettingsDatum).device!.name, "Controller #1") XCTAssertEqual((created[0] as! TControllerSettingsDatum).associations!.count, 2) @@ -70,7 +70,6 @@ class TidepoolServiceTests: XCTestCase { XCTAssertEqual(lastControllerSettings!.id, created[0].id) XCTAssertEqual(lastCGMSettings!.id, created[1].id) XCTAssertEqual(lastPumpSettings!.id, created[2].id) - XCTAssertNil(lastPumpSettingsOverride) } func testCalculateSettingsDataUpdateControllerSettings() { @@ -80,7 +79,7 @@ class TidepoolServiceTests: XCTestCase { StoredSettings(controllerDevice: StoredSettings.ControllerDevice(name: "Controller #2"), cgmDevice: HKDevice(name: "CGM #1"), pumpDevice: HKDevice(name: "Pump #1"))] - let (created, updated, lastControllerSettings, lastCGMSettings, lastPumpSettings, lastPumpSettingsOverride) = tidepoolService.calculateSettingsData(settings, for: userID, hostIdentifier: "Loop", hostVersion: "1.2.3") + let (created, updated, lastControllerSettings, lastCGMSettings, lastPumpSettings) = tidepoolService.calculateSettingsData(settings, for: userID, hostIdentifier: "Loop", hostVersion: "1.2.3") XCTAssertEqual(created.count, 4) XCTAssertEqual((created[0] as! TControllerSettingsDatum).device!.name, "Controller #1") XCTAssertEqual((created[1] as! TCGMSettingsDatum).name, "CGM #1") @@ -93,7 +92,6 @@ class TidepoolServiceTests: XCTestCase { XCTAssertEqual(lastControllerSettings!.id, created[3].id) XCTAssertEqual(lastCGMSettings!.id, created[1].id) XCTAssertEqual(lastPumpSettings!.id, created[2].id) - XCTAssertNil(lastPumpSettingsOverride) } func testCalculateSettingsDataUpdateCGMSettings() { @@ -103,7 +101,7 @@ class TidepoolServiceTests: XCTestCase { StoredSettings(controllerDevice: StoredSettings.ControllerDevice(name: "Controller #1"), cgmDevice: HKDevice(name: "CGM #2"), pumpDevice: HKDevice(name: "Pump #1"))] - let (created, updated, lastControllerSettings, lastCGMSettings, lastPumpSettings, lastPumpSettingsOverride) = tidepoolService.calculateSettingsData(settings, for: userID, hostIdentifier: "Loop", hostVersion: "1.2.3") + let (created, updated, lastControllerSettings, lastCGMSettings, lastPumpSettings) = tidepoolService.calculateSettingsData(settings, for: userID, hostIdentifier: "Loop", hostVersion: "1.2.3") XCTAssertEqual(created.count, 4) XCTAssertEqual((created[0] as! TControllerSettingsDatum).device!.name, "Controller #1") XCTAssertEqual((created[1] as! TCGMSettingsDatum).name, "CGM #1") @@ -116,7 +114,6 @@ class TidepoolServiceTests: XCTestCase { XCTAssertEqual(lastControllerSettings!.id, created[0].id) XCTAssertEqual(lastCGMSettings!.id, created[3].id) XCTAssertEqual(lastPumpSettings!.id, created[2].id) - XCTAssertNil(lastPumpSettingsOverride) } func testCalculateSettingsDataUpdatePumpSettings() { @@ -126,7 +123,7 @@ class TidepoolServiceTests: XCTestCase { StoredSettings(controllerDevice: StoredSettings.ControllerDevice(name: "Controller #1"), cgmDevice: HKDevice(name: "CGM #1"), pumpDevice: HKDevice(name: "Pump #2"))] - let (created, updated, lastControllerSettings, lastCGMSettings, lastPumpSettings, lastPumpSettingsOverride) = tidepoolService.calculateSettingsData(settings, for: userID, hostIdentifier: "Loop", hostVersion: "1.2.3") + let (created, updated, lastControllerSettings, lastCGMSettings, lastPumpSettings) = tidepoolService.calculateSettingsData(settings, for: userID, hostIdentifier: "Loop", hostVersion: "1.2.3") XCTAssertEqual(created.count, 4) XCTAssertEqual((created[0] as! TControllerSettingsDatum).device!.name, "Controller #1") XCTAssertEqual((created[1] as! TCGMSettingsDatum).name, "CGM #1") @@ -139,7 +136,6 @@ class TidepoolServiceTests: XCTestCase { XCTAssertEqual(lastControllerSettings!.id, created[0].id) XCTAssertEqual(lastCGMSettings!.id, created[1].id) XCTAssertEqual(lastPumpSettings!.id, created[3].id) - XCTAssertNil(lastPumpSettingsOverride) } func testCalculateSettingsDataUpdateMultipleOne() { @@ -149,7 +145,7 @@ class TidepoolServiceTests: XCTestCase { StoredSettings(controllerDevice: StoredSettings.ControllerDevice(name: "Controller #2"), cgmDevice: HKDevice(name: "CGM #2"), pumpDevice: HKDevice(name: "Pump #2"))] - let (created, updated, lastControllerSettings, lastCGMSettings, lastPumpSettings, lastPumpSettingsOverride) = tidepoolService.calculateSettingsData(settings, for: userID, hostIdentifier: "Loop", hostVersion: "1.2.3") + let (created, updated, lastControllerSettings, lastCGMSettings, lastPumpSettings) = tidepoolService.calculateSettingsData(settings, for: userID, hostIdentifier: "Loop", hostVersion: "1.2.3") XCTAssertEqual(created.count, 6) XCTAssertEqual((created[0] as! TControllerSettingsDatum).device!.name, "Controller #1") XCTAssertEqual((created[1] as! TCGMSettingsDatum).name, "CGM #1") @@ -170,7 +166,6 @@ class TidepoolServiceTests: XCTestCase { XCTAssertEqual(lastControllerSettings!.id, created[3].id) XCTAssertEqual(lastCGMSettings!.id, created[4].id) XCTAssertEqual(lastPumpSettings!.id, created[5].id) - XCTAssertNil(lastPumpSettingsOverride) } func testCalculateSettingsDataUpdateMultipleMultiple() { @@ -186,7 +181,7 @@ class TidepoolServiceTests: XCTestCase { StoredSettings(controllerDevice: StoredSettings.ControllerDevice(name: "Controller #2"), cgmDevice: HKDevice(name: "CGM #2"), pumpDevice: HKDevice(name: "Pump #2"))] - let (created, updated, lastControllerSettings, lastCGMSettings, lastPumpSettings, lastPumpSettingsOverride) = tidepoolService.calculateSettingsData(settings, for: userID, hostIdentifier: "Loop", hostVersion: "1.2.3") + let (created, updated, lastControllerSettings, lastCGMSettings, lastPumpSettings) = tidepoolService.calculateSettingsData(settings, for: userID, hostIdentifier: "Loop", hostVersion: "1.2.3") XCTAssertEqual(created.count, 6) XCTAssertEqual((created[0] as! TControllerSettingsDatum).device!.name, "Controller #1") XCTAssertEqual((created[1] as! TCGMSettingsDatum).name, "CGM #1") @@ -207,69 +202,8 @@ class TidepoolServiceTests: XCTestCase { XCTAssertEqual(lastControllerSettings!.id, created[3].id) XCTAssertEqual(lastCGMSettings!.id, created[4].id) XCTAssertEqual(lastPumpSettings!.id, created[5].id) - XCTAssertNil(lastPumpSettingsOverride) } - func testCalculateSettingsDataPumpOverrideSingle() { - let scheduleOverride = TemporaryScheduleOverride() - let settings = [StoredSettings(pumpDevice: HKDevice(name: "Pump #1")), - StoredSettings(scheduleOverride: scheduleOverride, - pumpDevice: HKDevice(name: "Pump #1")), - StoredSettings(scheduleOverride: scheduleOverride, - controllerDevice: StoredSettings.ControllerDevice(name: "Controller #1"), - pumpDevice: HKDevice(name: "Pump #1"))] - let (created, updated, lastControllerSettings, lastCGMSettings, lastPumpSettings, lastPumpSettingsOverride) = tidepoolService.calculateSettingsData(settings, for: userID, hostIdentifier: "Loop", hostVersion: "1.2.3") - XCTAssertEqual(created.count, 3) - XCTAssertEqual((created[0] as! TPumpSettingsDatum).name, "Pump #1") - XCTAssertNil((created[0] as! TPumpSettingsDatum).associations) - XCTAssertNil((created[1] as! TPumpSettingsOverrideDeviceEventDatum).duration) - XCTAssertEqual((created[1] as! TPumpSettingsOverrideDeviceEventDatum).associations!.count, 1) - XCTAssertEqual((created[1] as! TPumpSettingsOverrideDeviceEventDatum).associations![0].id, created[0].id) - XCTAssertEqual((created[2] as! TControllerSettingsDatum).device!.name, "Controller #1") - XCTAssertTrue(updated.isEmpty) - XCTAssertEqual(lastControllerSettings!.id, created[2].id) - XCTAssertNil(lastCGMSettings) - XCTAssertEqual(lastPumpSettings!.id, created[0].id) - XCTAssertEqual(lastPumpSettingsOverride!.id, created[1].id) - } - - func testCalculateSettingsDataPumpOverrideMultipleAllCreated() { - let settings = [StoredSettings(pumpDevice: HKDevice(name: "Pump #1")), - StoredSettings(scheduleOverride: TemporaryScheduleOverride(duration: .minutes(30)), - pumpDevice: HKDevice(name: "Pump #1")), - StoredSettings(preMealOverride: TemporaryScheduleOverride(), - pumpDevice: HKDevice(name: "Pump #1")), - StoredSettings(pumpDevice: HKDevice(name: "Pump #1")), - StoredSettings(controllerDevice: StoredSettings.ControllerDevice(name: "Controller #1"), - pumpDevice: HKDevice(name: "Pump #1"))] - let (created, updated, lastControllerSettings, lastCGMSettings, lastPumpSettings, lastPumpSettingsOverride) = tidepoolService.calculateSettingsData(settings, for: userID, hostIdentifier: "Loop", hostVersion: "1.2.3") - XCTAssertEqual(created.count, 4) - XCTAssertEqual((created[0] as! TPumpSettingsDatum).name, "Pump #1") - XCTAssertNil((created[0] as! TPumpSettingsDatum).associations) - XCTAssertNotNil((created[1] as! TPumpSettingsOverrideDeviceEventDatum).duration) - XCTAssertEqual((created[1] as! TPumpSettingsOverrideDeviceEventDatum).associations!.count, 1) - XCTAssertEqual((created[1] as! TPumpSettingsOverrideDeviceEventDatum).associations![0].id, created[0].id) - XCTAssertNotNil((created[2] as! TPumpSettingsOverrideDeviceEventDatum).duration) - XCTAssertEqual((created[2] as! TPumpSettingsOverrideDeviceEventDatum).associations!.count, 1) - XCTAssertEqual((created[2] as! TPumpSettingsOverrideDeviceEventDatum).associations![0].id, created[0].id) - XCTAssertEqual((created[3] as! TControllerSettingsDatum).device!.name, "Controller #1") - XCTAssertTrue(updated.isEmpty) - XCTAssertEqual(lastControllerSettings!.id, created[3].id) - XCTAssertNil(lastCGMSettings) - XCTAssertEqual(lastPumpSettings!.id, created[0].id) - XCTAssertNil(lastPumpSettingsOverride) - } -} - -fileprivate extension TemporaryScheduleOverride { - init(duration: TimeInterval? = nil) { - self.init(context: .custom, - settings: TemporaryScheduleOverrideSettings(targetRange: nil, insulinNeedsScaleFactor: 1.2), - startDate: Date(), - duration: duration != nil ? .finite(duration!) : .indefinite, - enactTrigger: .local, - syncIdentifier: UUID()) - } } fileprivate extension StoredSettings.ControllerDevice { From 8bbb51d975240903365d85482a48124d7d0ff529 Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Tue, 16 Jan 2024 13:53:12 -0800 Subject: [PATCH 10/71] [LOOP-4788] Fix Unit Tests for iOS 17 --- .../Extensions/StoredDosingDecisionTests.swift | 2 +- .../Extensions/StoredGlucoseSampleTests.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift b/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift index 082152a..a0e7695 100644 --- a/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift +++ b/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift @@ -138,7 +138,7 @@ class StoredDosingDecisionTests: XCTestCase { "amount" : 1.25 }, "requestedBolus" : { - "amount" : 0.80000000000000004 + "amount" : 0.8 }, "smbg" : { "time" : "2020-05-14T22:09:00.000Z", diff --git a/TidepoolServiceKitTests/Extensions/StoredGlucoseSampleTests.swift b/TidepoolServiceKitTests/Extensions/StoredGlucoseSampleTests.swift index 18641d5..ac1c165 100644 --- a/TidepoolServiceKitTests/Extensions/StoredGlucoseSampleTests.swift +++ b/TidepoolServiceKitTests/Extensions/StoredGlucoseSampleTests.swift @@ -119,7 +119,7 @@ class StoredGlucoseSampleTests: XCTestCase { }, "time" : "2020-01-02T03:00:23.000Z", "trend" : "constant", - "trendRate" : 0.10000000000000001, + "trendRate" : 0.1, "type" : "cbg", "units" : "mg/dL", "value" : 123 From 5d582adebd688cffc6fceca58a87fa44592df5b4 Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Tue, 16 Jan 2024 13:53:12 -0800 Subject: [PATCH 11/71] [LOOP-4788] Fix Unit Tests for iOS 17 --- .../Extensions/StoredDosingDecisionTests.swift | 2 +- .../Extensions/StoredGlucoseSampleTests.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift b/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift index 082152a..a0e7695 100644 --- a/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift +++ b/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift @@ -138,7 +138,7 @@ class StoredDosingDecisionTests: XCTestCase { "amount" : 1.25 }, "requestedBolus" : { - "amount" : 0.80000000000000004 + "amount" : 0.8 }, "smbg" : { "time" : "2020-05-14T22:09:00.000Z", diff --git a/TidepoolServiceKitTests/Extensions/StoredGlucoseSampleTests.swift b/TidepoolServiceKitTests/Extensions/StoredGlucoseSampleTests.swift index 18641d5..ac1c165 100644 --- a/TidepoolServiceKitTests/Extensions/StoredGlucoseSampleTests.swift +++ b/TidepoolServiceKitTests/Extensions/StoredGlucoseSampleTests.swift @@ -119,7 +119,7 @@ class StoredGlucoseSampleTests: XCTestCase { }, "time" : "2020-01-02T03:00:23.000Z", "trend" : "constant", - "trendRate" : 0.10000000000000001, + "trendRate" : 0.1, "type" : "cbg", "units" : "mg/dL", "value" : 123 From 65ececaa29db9d58626944527b9d1f7e2c5ec4f1 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 5 Mar 2024 12:06:27 -0600 Subject: [PATCH 12/71] LOOP-4781 Types moved to LoopAlgorithm (#98) * Types moved to LoopAlgorithm * Types moved to LoopAlgorithm --- TidepoolServiceKit/Extensions/InsulinType.swift | 1 + .../Extensions/StoredDosingDecisionTests.swift | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/TidepoolServiceKit/Extensions/InsulinType.swift b/TidepoolServiceKit/Extensions/InsulinType.swift index d436afc..1437960 100644 --- a/TidepoolServiceKit/Extensions/InsulinType.swift +++ b/TidepoolServiceKit/Extensions/InsulinType.swift @@ -8,6 +8,7 @@ import LoopKit import TidepoolKit +import LoopAlgorithm extension InsulinType { var datum: TInsulinDatum.Formulation { diff --git a/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift b/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift index a0e7695..668c1cf 100644 --- a/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift +++ b/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift @@ -10,6 +10,7 @@ import XCTest import HealthKit import LoopKit import TidepoolKit +import LoopAlgorithm @testable import TidepoolServiceKit class StoredDosingDecisionTests: XCTestCase { @@ -354,7 +355,7 @@ fileprivate extension StoredDosingDecision { duration: .minutes(30)) let automaticDoseRecommendation = AutomaticDoseRecommendation(basalAdjustment: tempBasalRecommendation, bolusUnits: 1.25) let manualBolusRecommendation = ManualBolusRecommendationWithDate(recommendation: ManualBolusRecommendation(amount: 1.2, - notice: .predictedGlucoseBelowTarget(minGlucose: PredictedGlucoseValue(startDate: dateFormatter.date(from: "2020-05-14T23:03:15Z")!, + notice: .predictedGlucoseBelowTarget(minGlucose: SimpleGlucoseValue(startDate: dateFormatter.date(from: "2020-05-14T23:03:15Z")!, quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 75.5)))), date: dateFormatter.date(from: "2020-05-14T22:38:16Z")!) let manualBolusRequested = 0.8 From 2b8dc515f7dd4946c60384dcc9529effc78b4e4d Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 5 Mar 2024 12:06:27 -0600 Subject: [PATCH 13/71] LOOP-4781 Types moved to LoopAlgorithm (#98) * Types moved to LoopAlgorithm * Types moved to LoopAlgorithm --- TidepoolServiceKit/Extensions/InsulinType.swift | 1 + .../Extensions/StoredDosingDecisionTests.swift | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/TidepoolServiceKit/Extensions/InsulinType.swift b/TidepoolServiceKit/Extensions/InsulinType.swift index d436afc..1437960 100644 --- a/TidepoolServiceKit/Extensions/InsulinType.swift +++ b/TidepoolServiceKit/Extensions/InsulinType.swift @@ -8,6 +8,7 @@ import LoopKit import TidepoolKit +import LoopAlgorithm extension InsulinType { var datum: TInsulinDatum.Formulation { diff --git a/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift b/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift index a0e7695..668c1cf 100644 --- a/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift +++ b/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift @@ -10,6 +10,7 @@ import XCTest import HealthKit import LoopKit import TidepoolKit +import LoopAlgorithm @testable import TidepoolServiceKit class StoredDosingDecisionTests: XCTestCase { @@ -354,7 +355,7 @@ fileprivate extension StoredDosingDecision { duration: .minutes(30)) let automaticDoseRecommendation = AutomaticDoseRecommendation(basalAdjustment: tempBasalRecommendation, bolusUnits: 1.25) let manualBolusRecommendation = ManualBolusRecommendationWithDate(recommendation: ManualBolusRecommendation(amount: 1.2, - notice: .predictedGlucoseBelowTarget(minGlucose: PredictedGlucoseValue(startDate: dateFormatter.date(from: "2020-05-14T23:03:15Z")!, + notice: .predictedGlucoseBelowTarget(minGlucose: SimpleGlucoseValue(startDate: dateFormatter.date(from: "2020-05-14T23:03:15Z")!, quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 75.5)))), date: dateFormatter.date(from: "2020-05-14T22:38:16Z")!) let manualBolusRequested = 0.8 From b4e9cb33da72d1563594f1aa5142ba1e173ba99c Mon Sep 17 00:00:00 2001 From: Nathaniel Hamming Date: Fri, 7 Jun 2024 14:15:39 -0300 Subject: [PATCH 14/71] [LOOP-4801] adding pump inoperable (#101) --- TidepoolServiceKit/Extensions/StoredDosingDecision.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/TidepoolServiceKit/Extensions/StoredDosingDecision.swift b/TidepoolServiceKit/Extensions/StoredDosingDecision.swift index 7710caf..24e3f56 100644 --- a/TidepoolServiceKit/Extensions/StoredDosingDecision.swift +++ b/TidepoolServiceKit/Extensions/StoredDosingDecision.swift @@ -298,7 +298,7 @@ fileprivate extension StoredDosingDecision.ControllerStatus.BatteryState { } fileprivate extension PumpManagerStatus.BasalDeliveryState { - var datum: TPumpStatusDatum.BasalDelivery { + var datum: TPumpStatusDatum.BasalDelivery? { switch self { case .active(let at): return TPumpStatusDatum.BasalDelivery(state: .scheduled, time: at) @@ -314,6 +314,8 @@ fileprivate extension PumpManagerStatus.BasalDeliveryState { return TPumpStatusDatum.BasalDelivery(state: .suspended, time: at) case .resuming: return TPumpStatusDatum.BasalDelivery(state: .resuming) + case .pumpInoperable: + return nil } } } From bbc2c544c06d085f809665b14addd12cc8bf5e02 Mon Sep 17 00:00:00 2001 From: Nathaniel Hamming Date: Fri, 7 Jun 2024 14:15:39 -0300 Subject: [PATCH 15/71] [LOOP-4801] adding pump inoperable (#101) --- TidepoolServiceKit/Extensions/StoredDosingDecision.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/TidepoolServiceKit/Extensions/StoredDosingDecision.swift b/TidepoolServiceKit/Extensions/StoredDosingDecision.swift index 7710caf..24e3f56 100644 --- a/TidepoolServiceKit/Extensions/StoredDosingDecision.swift +++ b/TidepoolServiceKit/Extensions/StoredDosingDecision.swift @@ -298,7 +298,7 @@ fileprivate extension StoredDosingDecision.ControllerStatus.BatteryState { } fileprivate extension PumpManagerStatus.BasalDeliveryState { - var datum: TPumpStatusDatum.BasalDelivery { + var datum: TPumpStatusDatum.BasalDelivery? { switch self { case .active(let at): return TPumpStatusDatum.BasalDelivery(state: .scheduled, time: at) @@ -314,6 +314,8 @@ fileprivate extension PumpManagerStatus.BasalDeliveryState { return TPumpStatusDatum.BasalDelivery(state: .suspended, time: at) case .resuming: return TPumpStatusDatum.BasalDelivery(state: .resuming) + case .pumpInoperable: + return nil } } } From b9b6940d8b921c0ee6cb386ad26b8b473fa0f998 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Mon, 10 Jun 2024 10:15:55 -0500 Subject: [PATCH 16/71] LOOP-1169 - Upload device logs (#100) * Update for remote data service protocol changes * Upload device logs --- TidepoolService.xcodeproj/project.pbxproj | 4 + TidepoolServiceKit/DeviceLogUploader.swift | 130 +++++++++++++++++++ TidepoolServiceKit/TidepoolService.swift | 139 +++++++-------------- 3 files changed, 179 insertions(+), 94 deletions(-) create mode 100644 TidepoolServiceKit/DeviceLogUploader.swift diff --git a/TidepoolService.xcodeproj/project.pbxproj b/TidepoolService.xcodeproj/project.pbxproj index 82cf48e..a9a0191 100644 --- a/TidepoolService.xcodeproj/project.pbxproj +++ b/TidepoolService.xcodeproj/project.pbxproj @@ -69,6 +69,7 @@ C12E4BBB288F2215009C98A2 /* TidepoolServiceKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A9DAACFF22E7987800E76C9F /* TidepoolServiceKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; C12E4BBE288F2215009C98A2 /* TidepoolServiceKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9DAAD1B22E7988900E76C9F /* TidepoolServiceKitUI.framework */; platformFilter = ios; }; C12E4BBF288F2215009C98A2 /* TidepoolServiceKitUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A9DAAD1B22E7988900E76C9F /* TidepoolServiceKitUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + C1A685432C067E410071C171 /* DeviceLogUploader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1A685422C067E410071C171 /* DeviceLogUploader.swift */; }; C1C9414629F0CB21008D3E05 /* UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C9414529F0CB21008D3E05 /* UIImage.swift */; }; C1D0B62929848A460098D215 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D0B62829848A460098D215 /* SettingsView.swift */; }; C1D0B62C29848BEB0098D215 /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D0B62B29848BEB0098D215 /* Image.swift */; }; @@ -231,6 +232,7 @@ C199E4DA29C64072003D32F7 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = ""; }; C1A3529629C640A5002322A5 /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/Localizable.strings; sourceTree = ""; }; C1A3529729C640A5002322A5 /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/Localizable.strings; sourceTree = ""; }; + C1A685422C067E410071C171 /* DeviceLogUploader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceLogUploader.swift; sourceTree = ""; }; C1B0CFE129C786BF0045B04D /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; C1B267AA2995824000BCB7C1 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = ""; }; C1C9414529F0CB21008D3E05 /* UIImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImage.swift; sourceTree = ""; }; @@ -402,6 +404,7 @@ A9DAAD3522E7CAC100E76C9F /* TidepoolService.swift */, A913B37B24200C86000805C4 /* Extensions */, A9DAAD4122E7DF9B00E76C9F /* Localizable.strings */, + C1A685422C067E410071C171 /* DeviceLogUploader.swift */, ); path = TidepoolServiceKit; sourceTree = ""; @@ -752,6 +755,7 @@ A9F9F317271A046E00D19374 /* StoredCarbEntry.swift in Sources */, A9D1AC9D27B1E3C6008C5A12 /* DoseEntry.swift in Sources */, A9752A9B270B941C00E50750 /* SingleQuantitySchedule.swift in Sources */, + C1A685432C067E410071C171 /* DeviceLogUploader.swift in Sources */, A9752A93270B766A00E50750 /* StoredDosingDecision.swift in Sources */, A9752A97270B91E000E50750 /* Double.swift in Sources */, C110888F2A39149100BA4898 /* BuildDetails.swift in Sources */, diff --git a/TidepoolServiceKit/DeviceLogUploader.swift b/TidepoolServiceKit/DeviceLogUploader.swift new file mode 100644 index 0000000..61b5a18 --- /dev/null +++ b/TidepoolServiceKit/DeviceLogUploader.swift @@ -0,0 +1,130 @@ +// +// DeviceLogUploader.swift +// TidepoolServiceKit +// +// Created by Pete Schwamb on 5/28/24. +// Copyright © 2024 LoopKit Authors. All rights reserved. +// + +import Foundation +import os.log +import LoopKit +import TidepoolKit + +/// Periodically uploads device logs in hourly chunks to backend +actor DeviceLogUploader { + private let log = OSLog(category: "DeviceLogUploader") + + private let api: TAPI + + private var delegate: RemoteDataServiceDelegate? + + private var logChunkDuration = TimeInterval(hours: 1) + + func setDelegate(_ delegate: RemoteDataServiceDelegate?) { + self.delegate = delegate + } + + init(api: TAPI) { + self.api = api + + Task { + await main() + } + } + + func main() async { + let backfillLimitInterval = TimeInterval(days: 2) + // Default start uploading logs from 2 days ago + var nextUploadStart = Date().addingTimeInterval(-backfillLimitInterval).dateFlooredToTimeInterval(logChunkDuration) + + // Fetch device log metadata records + while true { + do { + // TODO: fetching logs is not implemented on the backend yet: awaiting https://tidepool.atlassian.net/browse/BACK-3011 + // For now, we expect this to error, so the catch has been modified to break out of the loop. Once this is implemented, + // We will want to retry on error, so the break should eventually be removed. + + var uploadMetadata = try await api.listDeviceLogs(start: Date().addingTimeInterval(-backfillLimitInterval), end: Date()) + uploadMetadata.sort { a, b in + return a.endAtTime > b.endAtTime + } + if let lastEnd = uploadMetadata.last?.endAtTime { + nextUploadStart = lastEnd.dateFlooredToTimeInterval(logChunkDuration) + } + break + } catch { + log.error("Unable to fetch device log metadata: %@", String(describing: error)) + try? await Task.sleep(nanoseconds: TimeInterval(minutes: 1).nanoseconds) + break // TODO: Remove when backend has implemented device log metadata fetching (see above) + } + } + // Start upload loop + while true { + let nextUploadEnd = nextUploadStart.addingTimeInterval(logChunkDuration) + let timeUntilNextUpload = nextUploadEnd.timeIntervalSinceNow + if timeUntilNextUpload > 0 { + log.debug("Waiting %@s until next upload", String(timeUntilNextUpload)) + try? await Task.sleep(nanoseconds: timeUntilNextUpload.nanoseconds) + } + await upload(from: nextUploadStart, to: nextUploadEnd) + nextUploadStart = nextUploadEnd + } + } + + func upload(from start: Date, to end: Date) async { + log.default("Uploading from %@ to %@", String(describing: start), String(describing: end)) + do { + if let logs = try await delegate?.fetchDeviceLogs(startDate: start, endDate: end) { + log.default("Fetched %d logs", logs.count) + if logs.count > 0 { + let data = logs.map({ + entry in + TDeviceLogEntry( + type: entry.type.tidepoolType, + managerIdentifier: entry.managerIdentifier, + deviceIdentifier: entry.deviceIdentifier ?? "unknown", + timestamp: entry.timestamp, + message: entry.message + ) + }) + do { + let metatdata = try await api.uploadDeviceLogs(logs: data, start: start, end: end) + log.default("metadata: %@", String(describing: metatdata)) + print("hi") + } catch { + log.error("error uploading device logs:: %@", String(describing: error)) + print("hi") + } + } + } + } catch { + log.error("Upload failed: %@", String(describing: error)) + } + } +} + +extension TimeInterval { + var nanoseconds: UInt64 { + return UInt64(self * 1e+9) + } +} + +extension DeviceLogEntryType { + var tidepoolType: TDeviceLogEntry.TDeviceLogEntryType { + switch self { + case .send: + return .send + case .receive: + return .receive + case .error: + return .error + case .delegate: + return .delegate + case .delegateResponse: + return .delegateResponse + case .connection: + return .connection + } + } +} diff --git a/TidepoolServiceKit/TidepoolService.swift b/TidepoolServiceKit/TidepoolService.swift index ba7ea50..58d42e5 100644 --- a/TidepoolServiceKit/TidepoolService.swift +++ b/TidepoolServiceKit/TidepoolService.swift @@ -48,6 +48,8 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { public weak var stateDelegate: StatefulPluggableDelegate? + public weak var remoteDataServiceDelegate: RemoteDataServiceDelegate? + public lazy var sessionStorage: SessionStorage = KeychainManager() public let tapi: TAPI = TAPI(clientId: BuildDetails.default.tidepoolServiceClientId, redirectURL: BuildDetails.default.tidepoolServiceRedirectURL) @@ -69,17 +71,25 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { private let log = OSLog(category: "TidepoolService") private let tidepoolKitLog = OSLog(category: "TidepoolKit") + private var deviceLogUploader: DeviceLogUploader? + public init(hostIdentifier: String, hostVersion: String) { self.id = UUID().uuidString self.hostIdentifier = hostIdentifier self.hostVersion = hostVersion Task { - await tapi.setLogging(self) - await tapi.addObserver(self) + await finishSetup() } } + public func finishSetup() async { + await tapi.setLogging(self) + await tapi.addObserver(self) + deviceLogUploader = DeviceLogUploader(api: tapi) + await deviceLogUploader?.setDelegate(remoteDataServiceDelegate) + } + public init?(rawState: RawStateValue) { self.isOnboarded = true // Assume when restoring from state, that we're onboarded guard let id = rawState["id"] as? String else { @@ -96,8 +106,7 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { self.session = try sessionStorage.getSession(for: sessionService) Task { await tapi.setSession(session) - await tapi.setLogging(self) - await tapi.addObserver(self) + await finishSetup() } } catch let error { tidepoolKitLog.error("Error initializing TidepoolService %{public}@", error.localizedDescription) @@ -275,7 +284,7 @@ extension TidepoolService: TLogging { extension TidepoolService: RemoteDataService { - public func uploadTemporaryOverrideData(updated: [TemporaryScheduleOverride], deleted: [TemporaryScheduleOverride], completion: @escaping (Result) -> Void) { + public func uploadTemporaryOverrideData(updated: [TemporaryScheduleOverride], deleted: [TemporaryScheduleOverride]) async throws { // TODO: https://tidepool.atlassian.net/browse/LOOP-4769 // The following code is taken from previous upload code when override events where stored in settings @@ -301,82 +310,49 @@ extension TidepoolService: RemoteDataService { // timeZoneOffset: datumTimeZoneOffset, // payload: datumPayload, // origin: origin) - - completion(.success(true)) } public var alertDataLimit: Int? { return 1000 } - public func uploadAlertData(_ stored: [SyncAlertObject], completion: @escaping (_ result: Result) -> Void) { + public func uploadAlertData(_ stored: [SyncAlertObject]) async throws { guard let userId = userId, let hostIdentifier = hostIdentifier, let hostVersion = hostVersion else { - completion(.failure(TidepoolServiceError.configuration)) - return - } - Task { - do { - let result = try await createData(stored.compactMap { $0.datum(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) }) - completion(.success(result)) - } catch { - completion(.failure(error)) - } + throw TidepoolServiceError.configuration } + + let _ = try await createData(stored.compactMap { $0.datum(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) }) } public var carbDataLimit: Int? { return 1000 } - public func uploadCarbData(created: [SyncCarbObject], updated: [SyncCarbObject], deleted: [SyncCarbObject], completion: @escaping (Result) -> Void) { + public func uploadCarbData(created: [SyncCarbObject], updated: [SyncCarbObject], deleted: [SyncCarbObject]) async throws { guard let userId = userId, let hostIdentifier = hostIdentifier, let hostVersion = hostVersion else { - completion(.failure(TidepoolServiceError.configuration)) - return + throw TidepoolServiceError.configuration } - Task { - do { - let createdUploaded = try await createData(created.compactMap { $0.datum(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) }) - let updatedUploaded = try await updateData(updated.compactMap { $0.datum(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) }) - let deletedUploaded = try await deleteData(withSelectors: deleted.compactMap { $0.selector }) - completion(.success(createdUploaded || updatedUploaded || deletedUploaded)) - } catch { - completion(.failure(error)) - } - } + let _ = try await createData(created.compactMap { $0.datum(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) }) + let _ = try await updateData(updated.compactMap { $0.datum(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) }) + let _ = try await deleteData(withSelectors: deleted.compactMap { $0.selector }) } public var doseDataLimit: Int? { return 1000 } - public func uploadDoseData(created: [DoseEntry], deleted: [DoseEntry], completion: @escaping (_ result: Result) -> Void) { + public func uploadDoseData(created: [DoseEntry], deleted: [DoseEntry]) async throws { guard let userId = userId, let hostIdentifier = hostIdentifier, let hostVersion = hostVersion else { - completion(.failure(TidepoolServiceError.configuration)) - return + throw TidepoolServiceError.configuration } - Task { - do { - let createdUploaded = try await createData(created.flatMap { $0.data(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) }) - let deletedUploaded = try await deleteData(withSelectors: deleted.flatMap { $0.selectors }) - completion(.success(createdUploaded || deletedUploaded)) - } catch { - completion(.failure(error)) - } - } + let _ = try await createData(created.flatMap { $0.data(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) }) + let _ = try await deleteData(withSelectors: deleted.flatMap { $0.selectors }) } public var dosingDecisionDataLimit: Int? { return 50 } // Each can be up to 20K bytes of serialized JSON, target ~1M or less - public func uploadDosingDecisionData(_ stored: [StoredDosingDecision], completion: @escaping (_ result: Result) -> Void) { + public func uploadDosingDecisionData(_ stored: [StoredDosingDecision]) async throws { guard let userId = userId, let hostIdentifier = hostIdentifier, let hostVersion = hostVersion else { - completion(.failure(TidepoolServiceError.configuration)) - return + throw TidepoolServiceError.configuration } - Task { - do { - let result = try await createData(calculateDosingDecisionData(stored, for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion)) - completion(.success(result)) - } catch { - completion(.failure(error)) - } - } + let _ = try await createData(calculateDosingDecisionData(stored, for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion)) } func calculateDosingDecisionData(_ stored: [StoredDosingDecision], for userId: String, hostIdentifier: String, hostVersion: String) -> [TDatum] { @@ -427,63 +403,39 @@ extension TidepoolService: RemoteDataService { public var glucoseDataLimit: Int? { return 1000 } - public func uploadGlucoseData(_ stored: [StoredGlucoseSample], completion: @escaping (Result) -> Void) { + public func uploadGlucoseData(_ stored: [StoredGlucoseSample]) async throws { guard let userId = userId, let hostIdentifier = hostIdentifier, let hostVersion = hostVersion else { - completion(.failure(TidepoolServiceError.configuration)) - return + throw TidepoolServiceError.configuration } - Task { - do { - let result = try await createData(stored.compactMap { $0.datum(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) }) - completion(.success(result)) - } catch { - completion(.failure(error)) - } - } + let _ = try await createData(stored.compactMap { $0.datum(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) }) } public var pumpDataEventLimit: Int? { return 1000 } - public func uploadPumpEventData(_ stored: [PersistedPumpEvent], completion: @escaping (_ result: Result) -> Void) { + public func uploadPumpEventData(_ stored: [PersistedPumpEvent]) async throws { guard let userId = userId, let hostIdentifier = hostIdentifier, let hostVersion = hostVersion else { - completion(.failure(TidepoolServiceError.configuration)) - return + throw TidepoolServiceError.configuration } - Task { - do { - let result = try await createData(stored.flatMap { $0.data(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) }) - completion(.success(result)) - } catch { - completion(.failure(error)) - } - } + let _ = try await createData(stored.flatMap { $0.data(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) }) } public var settingsDataLimit: Int? { return 400 } // Each can be up to 2.5K bytes of serialized JSON, target ~1M or less - public func uploadSettingsData(_ stored: [StoredSettings], completion: @escaping (_ result: Result) -> Void) { + public func uploadSettingsData(_ stored: [StoredSettings]) async throws { guard let userId = userId, let hostIdentifier = hostIdentifier, let hostVersion = hostVersion else { - completion(.failure(TidepoolServiceError.configuration)) - return + throw TidepoolServiceError.configuration } let (created, updated, lastControllerSettingsDatum, lastCGMSettingsDatum, lastPumpSettingsDatum) = calculateSettingsData(stored, for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) - Task { - do { - let createdUploaded = try await createData(created) - let updatedUploaded = try await updateData(updated) - self.lastControllerSettingsDatum = lastControllerSettingsDatum - self.lastCGMSettingsDatum = lastCGMSettingsDatum - self.lastPumpSettingsDatum = lastPumpSettingsDatum - self.completeUpdate() - completion(.success(createdUploaded || updatedUploaded)) - } catch { - completion(.failure(error)) - } - } + let _ = try await createData(created) + let _ = try await updateData(updated) + self.lastControllerSettingsDatum = lastControllerSettingsDatum + self.lastCGMSettingsDatum = lastCGMSettingsDatum + self.lastPumpSettingsDatum = lastPumpSettingsDatum + self.completeUpdate() } func calculateSettingsData(_ stored: [StoredSettings], for userId: String, hostIdentifier: String, hostVersion: String) -> ([TDatum], [TDatum], TControllerSettingsDatum?, TCGMSettingsDatum?, TPumpSettingsDatum?) { @@ -606,9 +558,8 @@ extension TidepoolService: RemoteDataService { } } - public func uploadCgmEventData(_ stored: [LoopKit.PersistedCgmEvent], completion: @escaping (Result) -> Void) { + public func uploadCgmEventData(_ stored: [PersistedCgmEvent]) async throws { // TODO: Upload sensor/transmitter changes - completion(.success(false)) } public func remoteNotificationWasReceived(_ notification: [String: AnyObject]) async throws { From a723ade8980438bae8193d005a91844e58d59f1f Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Mon, 10 Jun 2024 10:15:55 -0500 Subject: [PATCH 17/71] LOOP-1169 - Upload device logs (#100) * Update for remote data service protocol changes * Upload device logs --- TidepoolService.xcodeproj/project.pbxproj | 4 + TidepoolServiceKit/DeviceLogUploader.swift | 130 +++++++++++++++++++ TidepoolServiceKit/TidepoolService.swift | 139 +++++++-------------- 3 files changed, 179 insertions(+), 94 deletions(-) create mode 100644 TidepoolServiceKit/DeviceLogUploader.swift diff --git a/TidepoolService.xcodeproj/project.pbxproj b/TidepoolService.xcodeproj/project.pbxproj index 82cf48e..a9a0191 100644 --- a/TidepoolService.xcodeproj/project.pbxproj +++ b/TidepoolService.xcodeproj/project.pbxproj @@ -69,6 +69,7 @@ C12E4BBB288F2215009C98A2 /* TidepoolServiceKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A9DAACFF22E7987800E76C9F /* TidepoolServiceKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; C12E4BBE288F2215009C98A2 /* TidepoolServiceKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9DAAD1B22E7988900E76C9F /* TidepoolServiceKitUI.framework */; platformFilter = ios; }; C12E4BBF288F2215009C98A2 /* TidepoolServiceKitUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A9DAAD1B22E7988900E76C9F /* TidepoolServiceKitUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + C1A685432C067E410071C171 /* DeviceLogUploader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1A685422C067E410071C171 /* DeviceLogUploader.swift */; }; C1C9414629F0CB21008D3E05 /* UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C9414529F0CB21008D3E05 /* UIImage.swift */; }; C1D0B62929848A460098D215 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D0B62829848A460098D215 /* SettingsView.swift */; }; C1D0B62C29848BEB0098D215 /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D0B62B29848BEB0098D215 /* Image.swift */; }; @@ -231,6 +232,7 @@ C199E4DA29C64072003D32F7 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = ""; }; C1A3529629C640A5002322A5 /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/Localizable.strings; sourceTree = ""; }; C1A3529729C640A5002322A5 /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/Localizable.strings; sourceTree = ""; }; + C1A685422C067E410071C171 /* DeviceLogUploader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceLogUploader.swift; sourceTree = ""; }; C1B0CFE129C786BF0045B04D /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; C1B267AA2995824000BCB7C1 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = ""; }; C1C9414529F0CB21008D3E05 /* UIImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImage.swift; sourceTree = ""; }; @@ -402,6 +404,7 @@ A9DAAD3522E7CAC100E76C9F /* TidepoolService.swift */, A913B37B24200C86000805C4 /* Extensions */, A9DAAD4122E7DF9B00E76C9F /* Localizable.strings */, + C1A685422C067E410071C171 /* DeviceLogUploader.swift */, ); path = TidepoolServiceKit; sourceTree = ""; @@ -752,6 +755,7 @@ A9F9F317271A046E00D19374 /* StoredCarbEntry.swift in Sources */, A9D1AC9D27B1E3C6008C5A12 /* DoseEntry.swift in Sources */, A9752A9B270B941C00E50750 /* SingleQuantitySchedule.swift in Sources */, + C1A685432C067E410071C171 /* DeviceLogUploader.swift in Sources */, A9752A93270B766A00E50750 /* StoredDosingDecision.swift in Sources */, A9752A97270B91E000E50750 /* Double.swift in Sources */, C110888F2A39149100BA4898 /* BuildDetails.swift in Sources */, diff --git a/TidepoolServiceKit/DeviceLogUploader.swift b/TidepoolServiceKit/DeviceLogUploader.swift new file mode 100644 index 0000000..61b5a18 --- /dev/null +++ b/TidepoolServiceKit/DeviceLogUploader.swift @@ -0,0 +1,130 @@ +// +// DeviceLogUploader.swift +// TidepoolServiceKit +// +// Created by Pete Schwamb on 5/28/24. +// Copyright © 2024 LoopKit Authors. All rights reserved. +// + +import Foundation +import os.log +import LoopKit +import TidepoolKit + +/// Periodically uploads device logs in hourly chunks to backend +actor DeviceLogUploader { + private let log = OSLog(category: "DeviceLogUploader") + + private let api: TAPI + + private var delegate: RemoteDataServiceDelegate? + + private var logChunkDuration = TimeInterval(hours: 1) + + func setDelegate(_ delegate: RemoteDataServiceDelegate?) { + self.delegate = delegate + } + + init(api: TAPI) { + self.api = api + + Task { + await main() + } + } + + func main() async { + let backfillLimitInterval = TimeInterval(days: 2) + // Default start uploading logs from 2 days ago + var nextUploadStart = Date().addingTimeInterval(-backfillLimitInterval).dateFlooredToTimeInterval(logChunkDuration) + + // Fetch device log metadata records + while true { + do { + // TODO: fetching logs is not implemented on the backend yet: awaiting https://tidepool.atlassian.net/browse/BACK-3011 + // For now, we expect this to error, so the catch has been modified to break out of the loop. Once this is implemented, + // We will want to retry on error, so the break should eventually be removed. + + var uploadMetadata = try await api.listDeviceLogs(start: Date().addingTimeInterval(-backfillLimitInterval), end: Date()) + uploadMetadata.sort { a, b in + return a.endAtTime > b.endAtTime + } + if let lastEnd = uploadMetadata.last?.endAtTime { + nextUploadStart = lastEnd.dateFlooredToTimeInterval(logChunkDuration) + } + break + } catch { + log.error("Unable to fetch device log metadata: %@", String(describing: error)) + try? await Task.sleep(nanoseconds: TimeInterval(minutes: 1).nanoseconds) + break // TODO: Remove when backend has implemented device log metadata fetching (see above) + } + } + // Start upload loop + while true { + let nextUploadEnd = nextUploadStart.addingTimeInterval(logChunkDuration) + let timeUntilNextUpload = nextUploadEnd.timeIntervalSinceNow + if timeUntilNextUpload > 0 { + log.debug("Waiting %@s until next upload", String(timeUntilNextUpload)) + try? await Task.sleep(nanoseconds: timeUntilNextUpload.nanoseconds) + } + await upload(from: nextUploadStart, to: nextUploadEnd) + nextUploadStart = nextUploadEnd + } + } + + func upload(from start: Date, to end: Date) async { + log.default("Uploading from %@ to %@", String(describing: start), String(describing: end)) + do { + if let logs = try await delegate?.fetchDeviceLogs(startDate: start, endDate: end) { + log.default("Fetched %d logs", logs.count) + if logs.count > 0 { + let data = logs.map({ + entry in + TDeviceLogEntry( + type: entry.type.tidepoolType, + managerIdentifier: entry.managerIdentifier, + deviceIdentifier: entry.deviceIdentifier ?? "unknown", + timestamp: entry.timestamp, + message: entry.message + ) + }) + do { + let metatdata = try await api.uploadDeviceLogs(logs: data, start: start, end: end) + log.default("metadata: %@", String(describing: metatdata)) + print("hi") + } catch { + log.error("error uploading device logs:: %@", String(describing: error)) + print("hi") + } + } + } + } catch { + log.error("Upload failed: %@", String(describing: error)) + } + } +} + +extension TimeInterval { + var nanoseconds: UInt64 { + return UInt64(self * 1e+9) + } +} + +extension DeviceLogEntryType { + var tidepoolType: TDeviceLogEntry.TDeviceLogEntryType { + switch self { + case .send: + return .send + case .receive: + return .receive + case .error: + return .error + case .delegate: + return .delegate + case .delegateResponse: + return .delegateResponse + case .connection: + return .connection + } + } +} diff --git a/TidepoolServiceKit/TidepoolService.swift b/TidepoolServiceKit/TidepoolService.swift index ba7ea50..58d42e5 100644 --- a/TidepoolServiceKit/TidepoolService.swift +++ b/TidepoolServiceKit/TidepoolService.swift @@ -48,6 +48,8 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { public weak var stateDelegate: StatefulPluggableDelegate? + public weak var remoteDataServiceDelegate: RemoteDataServiceDelegate? + public lazy var sessionStorage: SessionStorage = KeychainManager() public let tapi: TAPI = TAPI(clientId: BuildDetails.default.tidepoolServiceClientId, redirectURL: BuildDetails.default.tidepoolServiceRedirectURL) @@ -69,17 +71,25 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { private let log = OSLog(category: "TidepoolService") private let tidepoolKitLog = OSLog(category: "TidepoolKit") + private var deviceLogUploader: DeviceLogUploader? + public init(hostIdentifier: String, hostVersion: String) { self.id = UUID().uuidString self.hostIdentifier = hostIdentifier self.hostVersion = hostVersion Task { - await tapi.setLogging(self) - await tapi.addObserver(self) + await finishSetup() } } + public func finishSetup() async { + await tapi.setLogging(self) + await tapi.addObserver(self) + deviceLogUploader = DeviceLogUploader(api: tapi) + await deviceLogUploader?.setDelegate(remoteDataServiceDelegate) + } + public init?(rawState: RawStateValue) { self.isOnboarded = true // Assume when restoring from state, that we're onboarded guard let id = rawState["id"] as? String else { @@ -96,8 +106,7 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { self.session = try sessionStorage.getSession(for: sessionService) Task { await tapi.setSession(session) - await tapi.setLogging(self) - await tapi.addObserver(self) + await finishSetup() } } catch let error { tidepoolKitLog.error("Error initializing TidepoolService %{public}@", error.localizedDescription) @@ -275,7 +284,7 @@ extension TidepoolService: TLogging { extension TidepoolService: RemoteDataService { - public func uploadTemporaryOverrideData(updated: [TemporaryScheduleOverride], deleted: [TemporaryScheduleOverride], completion: @escaping (Result) -> Void) { + public func uploadTemporaryOverrideData(updated: [TemporaryScheduleOverride], deleted: [TemporaryScheduleOverride]) async throws { // TODO: https://tidepool.atlassian.net/browse/LOOP-4769 // The following code is taken from previous upload code when override events where stored in settings @@ -301,82 +310,49 @@ extension TidepoolService: RemoteDataService { // timeZoneOffset: datumTimeZoneOffset, // payload: datumPayload, // origin: origin) - - completion(.success(true)) } public var alertDataLimit: Int? { return 1000 } - public func uploadAlertData(_ stored: [SyncAlertObject], completion: @escaping (_ result: Result) -> Void) { + public func uploadAlertData(_ stored: [SyncAlertObject]) async throws { guard let userId = userId, let hostIdentifier = hostIdentifier, let hostVersion = hostVersion else { - completion(.failure(TidepoolServiceError.configuration)) - return - } - Task { - do { - let result = try await createData(stored.compactMap { $0.datum(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) }) - completion(.success(result)) - } catch { - completion(.failure(error)) - } + throw TidepoolServiceError.configuration } + + let _ = try await createData(stored.compactMap { $0.datum(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) }) } public var carbDataLimit: Int? { return 1000 } - public func uploadCarbData(created: [SyncCarbObject], updated: [SyncCarbObject], deleted: [SyncCarbObject], completion: @escaping (Result) -> Void) { + public func uploadCarbData(created: [SyncCarbObject], updated: [SyncCarbObject], deleted: [SyncCarbObject]) async throws { guard let userId = userId, let hostIdentifier = hostIdentifier, let hostVersion = hostVersion else { - completion(.failure(TidepoolServiceError.configuration)) - return + throw TidepoolServiceError.configuration } - Task { - do { - let createdUploaded = try await createData(created.compactMap { $0.datum(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) }) - let updatedUploaded = try await updateData(updated.compactMap { $0.datum(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) }) - let deletedUploaded = try await deleteData(withSelectors: deleted.compactMap { $0.selector }) - completion(.success(createdUploaded || updatedUploaded || deletedUploaded)) - } catch { - completion(.failure(error)) - } - } + let _ = try await createData(created.compactMap { $0.datum(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) }) + let _ = try await updateData(updated.compactMap { $0.datum(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) }) + let _ = try await deleteData(withSelectors: deleted.compactMap { $0.selector }) } public var doseDataLimit: Int? { return 1000 } - public func uploadDoseData(created: [DoseEntry], deleted: [DoseEntry], completion: @escaping (_ result: Result) -> Void) { + public func uploadDoseData(created: [DoseEntry], deleted: [DoseEntry]) async throws { guard let userId = userId, let hostIdentifier = hostIdentifier, let hostVersion = hostVersion else { - completion(.failure(TidepoolServiceError.configuration)) - return + throw TidepoolServiceError.configuration } - Task { - do { - let createdUploaded = try await createData(created.flatMap { $0.data(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) }) - let deletedUploaded = try await deleteData(withSelectors: deleted.flatMap { $0.selectors }) - completion(.success(createdUploaded || deletedUploaded)) - } catch { - completion(.failure(error)) - } - } + let _ = try await createData(created.flatMap { $0.data(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) }) + let _ = try await deleteData(withSelectors: deleted.flatMap { $0.selectors }) } public var dosingDecisionDataLimit: Int? { return 50 } // Each can be up to 20K bytes of serialized JSON, target ~1M or less - public func uploadDosingDecisionData(_ stored: [StoredDosingDecision], completion: @escaping (_ result: Result) -> Void) { + public func uploadDosingDecisionData(_ stored: [StoredDosingDecision]) async throws { guard let userId = userId, let hostIdentifier = hostIdentifier, let hostVersion = hostVersion else { - completion(.failure(TidepoolServiceError.configuration)) - return + throw TidepoolServiceError.configuration } - Task { - do { - let result = try await createData(calculateDosingDecisionData(stored, for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion)) - completion(.success(result)) - } catch { - completion(.failure(error)) - } - } + let _ = try await createData(calculateDosingDecisionData(stored, for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion)) } func calculateDosingDecisionData(_ stored: [StoredDosingDecision], for userId: String, hostIdentifier: String, hostVersion: String) -> [TDatum] { @@ -427,63 +403,39 @@ extension TidepoolService: RemoteDataService { public var glucoseDataLimit: Int? { return 1000 } - public func uploadGlucoseData(_ stored: [StoredGlucoseSample], completion: @escaping (Result) -> Void) { + public func uploadGlucoseData(_ stored: [StoredGlucoseSample]) async throws { guard let userId = userId, let hostIdentifier = hostIdentifier, let hostVersion = hostVersion else { - completion(.failure(TidepoolServiceError.configuration)) - return + throw TidepoolServiceError.configuration } - Task { - do { - let result = try await createData(stored.compactMap { $0.datum(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) }) - completion(.success(result)) - } catch { - completion(.failure(error)) - } - } + let _ = try await createData(stored.compactMap { $0.datum(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) }) } public var pumpDataEventLimit: Int? { return 1000 } - public func uploadPumpEventData(_ stored: [PersistedPumpEvent], completion: @escaping (_ result: Result) -> Void) { + public func uploadPumpEventData(_ stored: [PersistedPumpEvent]) async throws { guard let userId = userId, let hostIdentifier = hostIdentifier, let hostVersion = hostVersion else { - completion(.failure(TidepoolServiceError.configuration)) - return + throw TidepoolServiceError.configuration } - Task { - do { - let result = try await createData(stored.flatMap { $0.data(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) }) - completion(.success(result)) - } catch { - completion(.failure(error)) - } - } + let _ = try await createData(stored.flatMap { $0.data(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) }) } public var settingsDataLimit: Int? { return 400 } // Each can be up to 2.5K bytes of serialized JSON, target ~1M or less - public func uploadSettingsData(_ stored: [StoredSettings], completion: @escaping (_ result: Result) -> Void) { + public func uploadSettingsData(_ stored: [StoredSettings]) async throws { guard let userId = userId, let hostIdentifier = hostIdentifier, let hostVersion = hostVersion else { - completion(.failure(TidepoolServiceError.configuration)) - return + throw TidepoolServiceError.configuration } let (created, updated, lastControllerSettingsDatum, lastCGMSettingsDatum, lastPumpSettingsDatum) = calculateSettingsData(stored, for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) - Task { - do { - let createdUploaded = try await createData(created) - let updatedUploaded = try await updateData(updated) - self.lastControllerSettingsDatum = lastControllerSettingsDatum - self.lastCGMSettingsDatum = lastCGMSettingsDatum - self.lastPumpSettingsDatum = lastPumpSettingsDatum - self.completeUpdate() - completion(.success(createdUploaded || updatedUploaded)) - } catch { - completion(.failure(error)) - } - } + let _ = try await createData(created) + let _ = try await updateData(updated) + self.lastControllerSettingsDatum = lastControllerSettingsDatum + self.lastCGMSettingsDatum = lastCGMSettingsDatum + self.lastPumpSettingsDatum = lastPumpSettingsDatum + self.completeUpdate() } func calculateSettingsData(_ stored: [StoredSettings], for userId: String, hostIdentifier: String, hostVersion: String) -> ([TDatum], [TDatum], TControllerSettingsDatum?, TCGMSettingsDatum?, TPumpSettingsDatum?) { @@ -606,9 +558,8 @@ extension TidepoolService: RemoteDataService { } } - public func uploadCgmEventData(_ stored: [LoopKit.PersistedCgmEvent], completion: @escaping (Result) -> Void) { + public func uploadCgmEventData(_ stored: [PersistedCgmEvent]) async throws { // TODO: Upload sensor/transmitter changes - completion(.success(false)) } public func remoteNotificationWasReceived(_ notification: [String: AnyObject]) async throws { From 0aa876d1c6bc7219159ebb305c7873d246df17be Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 27 Aug 2024 11:51:15 -0500 Subject: [PATCH 18/71] Upload temporary presets (#103) --- TidepoolService.xcodeproj/project.pbxproj | 4 + .../Extensions/StoredSettings.swift | 74 ----------- .../TemporaryScheduleOverride.swift | 120 ++++++++++++++++++ TidepoolServiceKit/TidepoolService.swift | 33 +---- 4 files changed, 131 insertions(+), 100 deletions(-) create mode 100644 TidepoolServiceKit/Extensions/TemporaryScheduleOverride.swift diff --git a/TidepoolService.xcodeproj/project.pbxproj b/TidepoolService.xcodeproj/project.pbxproj index a9a0191..76a25dd 100644 --- a/TidepoolService.xcodeproj/project.pbxproj +++ b/TidepoolService.xcodeproj/project.pbxproj @@ -69,6 +69,7 @@ C12E4BBB288F2215009C98A2 /* TidepoolServiceKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A9DAACFF22E7987800E76C9F /* TidepoolServiceKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; C12E4BBE288F2215009C98A2 /* TidepoolServiceKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9DAAD1B22E7988900E76C9F /* TidepoolServiceKitUI.framework */; platformFilter = ios; }; C12E4BBF288F2215009C98A2 /* TidepoolServiceKitUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A9DAAD1B22E7988900E76C9F /* TidepoolServiceKitUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + C14559A62C7CF49100541EF1 /* TemporaryScheduleOverride.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14559A52C7CF49100541EF1 /* TemporaryScheduleOverride.swift */; }; C1A685432C067E410071C171 /* DeviceLogUploader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1A685422C067E410071C171 /* DeviceLogUploader.swift */; }; C1C9414629F0CB21008D3E05 /* UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C9414529F0CB21008D3E05 /* UIImage.swift */; }; C1D0B62929848A460098D215 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D0B62829848A460098D215 /* SettingsView.swift */; }; @@ -226,6 +227,7 @@ C110888E2A39149100BA4898 /* BuildDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildDetails.swift; sourceTree = ""; }; C12522E1298309B5006EA1CD /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; C1317D4129830A0800625B94 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = ""; }; + C14559A52C7CF49100541EF1 /* TemporaryScheduleOverride.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemporaryScheduleOverride.swift; sourceTree = ""; }; C18B726B299581C600F138D3 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; C192C60B29C78711001EFEA6 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; C199E4D929C64072003D32F7 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = ""; }; @@ -322,6 +324,7 @@ A97A60FA243818C900AD69A5 /* TDatum.swift */, A9752A9C270B972D00E50750 /* TimeInterval.swift */, A9D10DB727AB2CCF00814B7B /* SyncAlertObject.swift */, + C14559A52C7CF49100541EF1 /* TemporaryScheduleOverride.swift */, ); path = Extensions; sourceTree = ""; @@ -766,6 +769,7 @@ A98737CD2788E61400A6A23D /* InsulinType.swift in Sources */, A9D1AC9B27B1E046008C5A12 /* Data.swift in Sources */, A9F9F319271A05B100D19374 /* IdentifiableHKDatum.swift in Sources */, + C14559A62C7CF49100541EF1 /* TemporaryScheduleOverride.swift in Sources */, A9057687271F770F0030C3B1 /* IdentifiableDatum.swift in Sources */, A97651752421AA10002EB5D4 /* OSLog.swift in Sources */, A9DAAD3622E7CAC100E76C9F /* TidepoolService.swift in Sources */, diff --git a/TidepoolServiceKit/Extensions/StoredSettings.swift b/TidepoolServiceKit/Extensions/StoredSettings.swift index 9e212cb..4b9a84f 100644 --- a/TidepoolServiceKit/Extensions/StoredSettings.swift +++ b/TidepoolServiceKit/Extensions/StoredSettings.swift @@ -390,80 +390,6 @@ fileprivate extension TemporaryScheduleOverridePreset { var datumDuration: TimeInterval? { duration.isFinite ? duration.timeInterval : nil } } -fileprivate extension TemporaryScheduleOverride { - var datumTime: Date { startDate } - - var datumOverrideType: TPumpSettingsOverrideDeviceEventDatum.OverrideType { context.datumOverrideType } - - var datumOverridePreset: String? { - guard case .preset(let preset) = context else { - return nil - } - return preset.name - } - - var datumMethod: TPumpSettingsOverrideDeviceEventDatum.Method? { .manual } - - var datumDuration: TimeInterval? { - switch duration { - case .finite(let interval): - return interval - case .indefinite: - return nil - } - } - - var datumExpectedDuration: TimeInterval? { nil } - - var datumBloodGlucoseTarget: TPumpSettingsOverrideDeviceEventDatum.BloodGlucoseTarget? { settings.datumBloodGlucoseTarget } - - var datumBasalRateScaleFactor: Double? { settings.datumBasalRateScaleFactor } - - var datumCarbohydrateRatioScaleFactor: Double? { settings.datumCarbohydrateRatioScaleFactor } - - var datumInsulinSensitivityScaleFactor: Double? { settings.datumInsulinSensitivityScaleFactor } - - var datumUnits: TPumpSettingsOverrideDeviceEventDatum.Units? { settings.datumUnits } -} - -fileprivate extension TemporaryScheduleOverride.Context { - var datumOverrideType: TPumpSettingsOverrideDeviceEventDatum.OverrideType { - switch self { - case .preMeal: - return .preprandial - case .legacyWorkout: - return .physicalActivity - case .preset(_): - return .preset - case .custom: - return .custom - } - } -} - -fileprivate extension TemporaryScheduleOverrideSettings { - var datumBloodGlucoseTarget: TPumpSettingsDatum.BloodGlucoseTarget? { - guard let targetRange = targetRange else { - return nil - } - return TPumpSettingsDatum.BloodGlucoseTarget(low: targetRange.lowerBound.doubleValue(for: .milligramsPerDeciliter), - high: targetRange.upperBound.doubleValue(for: .milligramsPerDeciliter)) - } - - var datumBasalRateScaleFactor: Double? { basalRateMultiplier } - - var datumCarbohydrateRatioScaleFactor: Double? { carbRatioMultiplier } - - var datumInsulinSensitivityScaleFactor: Double? { insulinSensitivityMultiplier } - - var datumUnits: TPumpSettingsOverrideDeviceEventDatum.Units? { - guard targetRange != nil else { - return nil - } - return TPumpSettingsOverrideDeviceEventDatum.Units(bloodGlucose: .milligramsPerDeciliter) - } -} - extension TCGMSettingsDatum: TypedDatum { static var resolvedType: String { TDatum.DatumType.cgmSettings.rawValue } } diff --git a/TidepoolServiceKit/Extensions/TemporaryScheduleOverride.swift b/TidepoolServiceKit/Extensions/TemporaryScheduleOverride.swift new file mode 100644 index 0000000..2bcc203 --- /dev/null +++ b/TidepoolServiceKit/Extensions/TemporaryScheduleOverride.swift @@ -0,0 +1,120 @@ +// +// TemporaryScheduleOverride.swift +// TidepoolServiceKit +// +// Created by Pete Schwamb on 8/26/24. +// Copyright © 2024 LoopKit Authors. All rights reserved. +// + +import Foundation +import TidepoolKit +import LoopKit + +fileprivate extension TemporaryScheduleOverride.Context { + var datumOverrideType: TPumpSettingsOverrideDeviceEventDatum.OverrideType { + switch self { + case .preMeal: + return .preprandial + case .legacyWorkout: + return .physicalActivity + case .preset(_): + return .preset + case .custom: + return .custom + } + } +} + +extension TemporaryScheduleOverrideSettings { + var datumBloodGlucoseTarget: TPumpSettingsDatum.BloodGlucoseTarget? { + guard let targetRange = targetRange else { + return nil + } + return TPumpSettingsDatum.BloodGlucoseTarget(low: targetRange.lowerBound.doubleValue(for: .milligramsPerDeciliter), + high: targetRange.upperBound.doubleValue(for: .milligramsPerDeciliter)) + } + + var datumBasalRateScaleFactor: Double? { basalRateMultiplier } + + var datumCarbohydrateRatioScaleFactor: Double? { carbRatioMultiplier } + + var datumInsulinSensitivityScaleFactor: Double? { insulinSensitivityMultiplier } + + var datumUnits: TPumpSettingsOverrideDeviceEventDatum.Units? { + guard targetRange != nil else { + return nil + } + return TPumpSettingsOverrideDeviceEventDatum.Units(bloodGlucose: .milligramsPerDeciliter) + } +} + +extension TemporaryScheduleOverride: IdentifiableDatum { + + var syncIdentifierAsString: String { syncIdentifier.uuidString } + + func datum(for userId: String, hostIdentifier: String, hostVersion: String) -> TDatum { + let datum = TPumpSettingsOverrideDeviceEventDatum(time: datumTime, + overrideType: datumOverrideType, + overridePreset: datumOverridePreset, + method: datumMethod, + duration: datumDuration, + expectedDuration: datumExpectedDuration, + bloodGlucoseTarget: datumBloodGlucoseTarget, + basalRateScaleFactor: datumBasalRateScaleFactor, + carbohydrateRatioScaleFactor: datumCarbohydrateRatioScaleFactor, + insulinSensitivityScaleFactor: datumInsulinSensitivityScaleFactor, + units: datumUnits) + let origin = datumOrigin(for: resolvedIdentifier(for: TPumpSettingsOverrideDeviceEventDatum.self), hostIdentifier: hostIdentifier, hostVersion: hostVersion) + return datum.adornWith(id: datumId(for: userId, type: TPumpSettingsOverrideDeviceEventDatum.self), + payload: datumPayload, + origin: origin) + } + + private var datumPayload: TDictionary { + var dictionary = TDictionary() + dictionary["syncIdentifier"] = syncIdentifierAsString + return dictionary + } + + var datumTime: Date { startDate } + + var datumOverrideType: TPumpSettingsOverrideDeviceEventDatum.OverrideType { context.datumOverrideType } + + var datumOverridePreset: String? { + guard case .preset(let preset) = context else { + return nil + } + return preset.name + } + + var datumMethod: TPumpSettingsOverrideDeviceEventDatum.Method? { .manual } + + var datumDuration: TimeInterval? { + switch duration { + case .finite(let interval): + return interval + case .indefinite: + return nil + } + } + + var datumExpectedDuration: TimeInterval? { nil } + + var datumBloodGlucoseTarget: TPumpSettingsOverrideDeviceEventDatum.BloodGlucoseTarget? { settings.datumBloodGlucoseTarget } + + var datumBasalRateScaleFactor: Double? { settings.datumBasalRateScaleFactor } + + var datumCarbohydrateRatioScaleFactor: Double? { settings.datumCarbohydrateRatioScaleFactor } + + var datumInsulinSensitivityScaleFactor: Double? { settings.datumInsulinSensitivityScaleFactor } + + var datumUnits: TPumpSettingsOverrideDeviceEventDatum.Units? { settings.datumUnits } + +} + +extension TemporaryScheduleOverride { + var selectors: [TDatum.Selector] { + return [datumSelector(for: TPumpSettingsOverrideDeviceEventDatum.self)] + } +} + diff --git a/TidepoolServiceKit/TidepoolService.swift b/TidepoolServiceKit/TidepoolService.swift index 58d42e5..b1a1b1f 100644 --- a/TidepoolServiceKit/TidepoolService.swift +++ b/TidepoolServiceKit/TidepoolService.swift @@ -285,31 +285,12 @@ extension TidepoolService: TLogging { extension TidepoolService: RemoteDataService { public func uploadTemporaryOverrideData(updated: [TemporaryScheduleOverride], deleted: [TemporaryScheduleOverride]) async throws { - // TODO: https://tidepool.atlassian.net/browse/LOOP-4769 - - // The following code is taken from previous upload code when override events where stored in settings - // To be implemented with - - // guard let activeOverride = activeOverride else { - // return nil - // } - // let datum = TPumpSettingsOverrideDeviceEventDatum(time: activeOverride.datumTime, - // overrideType: activeOverride.datumOverrideType, - // overridePreset: activeOverride.datumOverridePreset, - // method: activeOverride.datumMethod, - // duration: activeOverride.datumDuration, - // expectedDuration: activeOverride.datumExpectedDuration, - // bloodGlucoseTarget: activeOverride.datumBloodGlucoseTarget, - // basalRateScaleFactor: activeOverride.datumBasalRateScaleFactor, - // carbohydrateRatioScaleFactor: activeOverride.datumCarbohydrateRatioScaleFactor, - // insulinSensitivityScaleFactor: activeOverride.datumInsulinSensitivityScaleFactor, - // units: activeOverride.datumUnits) - // let origin = datumOrigin(for: resolvedIdentifier(for: TPumpSettingsOverrideDeviceEventDatum.self), hostIdentifier: hostIdentifier, hostVersion: hostVersion) - // return datum.adornWith(id: datumId(for: userId, type: TPumpSettingsOverrideDeviceEventDatum.self), - // timeZone: datumTimeZone, - // timeZoneOffset: datumTimeZoneOffset, - // payload: datumPayload, - // origin: origin) + guard let userId = userId, let hostIdentifier = hostIdentifier, let hostVersion = hostVersion else { + throw TidepoolServiceError.configuration + } + + let _ = try await createData(updated.compactMap { $0.datum(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) }) + let _ = try await deleteData(withSelectors: deleted.flatMap { $0.selectors }) } public var alertDataLimit: Int? { return 1000 } @@ -440,7 +421,7 @@ extension TidepoolService: RemoteDataService { func calculateSettingsData(_ stored: [StoredSettings], for userId: String, hostIdentifier: String, hostVersion: String) -> ([TDatum], [TDatum], TControllerSettingsDatum?, TCGMSettingsDatum?, TPumpSettingsDatum?) { var created: [TDatum] = [] - var updated: [TDatum] = [] + let updated: [TDatum] = [] var lastControllerSettingsDatum = lastControllerSettingsDatum var lastCGMSettingsDatum = lastCGMSettingsDatum var lastPumpSettingsDatum = lastPumpSettingsDatum From 0b9dbdb331cc69adb9042487435d6e8a04ba08f7 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 27 Aug 2024 11:51:15 -0500 Subject: [PATCH 19/71] Upload temporary presets (#103) --- TidepoolService.xcodeproj/project.pbxproj | 4 + .../Extensions/StoredSettings.swift | 74 ----------- .../TemporaryScheduleOverride.swift | 120 ++++++++++++++++++ TidepoolServiceKit/TidepoolService.swift | 33 +---- 4 files changed, 131 insertions(+), 100 deletions(-) create mode 100644 TidepoolServiceKit/Extensions/TemporaryScheduleOverride.swift diff --git a/TidepoolService.xcodeproj/project.pbxproj b/TidepoolService.xcodeproj/project.pbxproj index a9a0191..76a25dd 100644 --- a/TidepoolService.xcodeproj/project.pbxproj +++ b/TidepoolService.xcodeproj/project.pbxproj @@ -69,6 +69,7 @@ C12E4BBB288F2215009C98A2 /* TidepoolServiceKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A9DAACFF22E7987800E76C9F /* TidepoolServiceKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; C12E4BBE288F2215009C98A2 /* TidepoolServiceKitUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9DAAD1B22E7988900E76C9F /* TidepoolServiceKitUI.framework */; platformFilter = ios; }; C12E4BBF288F2215009C98A2 /* TidepoolServiceKitUI.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = A9DAAD1B22E7988900E76C9F /* TidepoolServiceKitUI.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + C14559A62C7CF49100541EF1 /* TemporaryScheduleOverride.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14559A52C7CF49100541EF1 /* TemporaryScheduleOverride.swift */; }; C1A685432C067E410071C171 /* DeviceLogUploader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1A685422C067E410071C171 /* DeviceLogUploader.swift */; }; C1C9414629F0CB21008D3E05 /* UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1C9414529F0CB21008D3E05 /* UIImage.swift */; }; C1D0B62929848A460098D215 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D0B62829848A460098D215 /* SettingsView.swift */; }; @@ -226,6 +227,7 @@ C110888E2A39149100BA4898 /* BuildDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildDetails.swift; sourceTree = ""; }; C12522E1298309B5006EA1CD /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; C1317D4129830A0800625B94 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = ""; }; + C14559A52C7CF49100541EF1 /* TemporaryScheduleOverride.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemporaryScheduleOverride.swift; sourceTree = ""; }; C18B726B299581C600F138D3 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; C192C60B29C78711001EFEA6 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = ""; }; C199E4D929C64072003D32F7 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = ""; }; @@ -322,6 +324,7 @@ A97A60FA243818C900AD69A5 /* TDatum.swift */, A9752A9C270B972D00E50750 /* TimeInterval.swift */, A9D10DB727AB2CCF00814B7B /* SyncAlertObject.swift */, + C14559A52C7CF49100541EF1 /* TemporaryScheduleOverride.swift */, ); path = Extensions; sourceTree = ""; @@ -766,6 +769,7 @@ A98737CD2788E61400A6A23D /* InsulinType.swift in Sources */, A9D1AC9B27B1E046008C5A12 /* Data.swift in Sources */, A9F9F319271A05B100D19374 /* IdentifiableHKDatum.swift in Sources */, + C14559A62C7CF49100541EF1 /* TemporaryScheduleOverride.swift in Sources */, A9057687271F770F0030C3B1 /* IdentifiableDatum.swift in Sources */, A97651752421AA10002EB5D4 /* OSLog.swift in Sources */, A9DAAD3622E7CAC100E76C9F /* TidepoolService.swift in Sources */, diff --git a/TidepoolServiceKit/Extensions/StoredSettings.swift b/TidepoolServiceKit/Extensions/StoredSettings.swift index 9e212cb..4b9a84f 100644 --- a/TidepoolServiceKit/Extensions/StoredSettings.swift +++ b/TidepoolServiceKit/Extensions/StoredSettings.swift @@ -390,80 +390,6 @@ fileprivate extension TemporaryScheduleOverridePreset { var datumDuration: TimeInterval? { duration.isFinite ? duration.timeInterval : nil } } -fileprivate extension TemporaryScheduleOverride { - var datumTime: Date { startDate } - - var datumOverrideType: TPumpSettingsOverrideDeviceEventDatum.OverrideType { context.datumOverrideType } - - var datumOverridePreset: String? { - guard case .preset(let preset) = context else { - return nil - } - return preset.name - } - - var datumMethod: TPumpSettingsOverrideDeviceEventDatum.Method? { .manual } - - var datumDuration: TimeInterval? { - switch duration { - case .finite(let interval): - return interval - case .indefinite: - return nil - } - } - - var datumExpectedDuration: TimeInterval? { nil } - - var datumBloodGlucoseTarget: TPumpSettingsOverrideDeviceEventDatum.BloodGlucoseTarget? { settings.datumBloodGlucoseTarget } - - var datumBasalRateScaleFactor: Double? { settings.datumBasalRateScaleFactor } - - var datumCarbohydrateRatioScaleFactor: Double? { settings.datumCarbohydrateRatioScaleFactor } - - var datumInsulinSensitivityScaleFactor: Double? { settings.datumInsulinSensitivityScaleFactor } - - var datumUnits: TPumpSettingsOverrideDeviceEventDatum.Units? { settings.datumUnits } -} - -fileprivate extension TemporaryScheduleOverride.Context { - var datumOverrideType: TPumpSettingsOverrideDeviceEventDatum.OverrideType { - switch self { - case .preMeal: - return .preprandial - case .legacyWorkout: - return .physicalActivity - case .preset(_): - return .preset - case .custom: - return .custom - } - } -} - -fileprivate extension TemporaryScheduleOverrideSettings { - var datumBloodGlucoseTarget: TPumpSettingsDatum.BloodGlucoseTarget? { - guard let targetRange = targetRange else { - return nil - } - return TPumpSettingsDatum.BloodGlucoseTarget(low: targetRange.lowerBound.doubleValue(for: .milligramsPerDeciliter), - high: targetRange.upperBound.doubleValue(for: .milligramsPerDeciliter)) - } - - var datumBasalRateScaleFactor: Double? { basalRateMultiplier } - - var datumCarbohydrateRatioScaleFactor: Double? { carbRatioMultiplier } - - var datumInsulinSensitivityScaleFactor: Double? { insulinSensitivityMultiplier } - - var datumUnits: TPumpSettingsOverrideDeviceEventDatum.Units? { - guard targetRange != nil else { - return nil - } - return TPumpSettingsOverrideDeviceEventDatum.Units(bloodGlucose: .milligramsPerDeciliter) - } -} - extension TCGMSettingsDatum: TypedDatum { static var resolvedType: String { TDatum.DatumType.cgmSettings.rawValue } } diff --git a/TidepoolServiceKit/Extensions/TemporaryScheduleOverride.swift b/TidepoolServiceKit/Extensions/TemporaryScheduleOverride.swift new file mode 100644 index 0000000..2bcc203 --- /dev/null +++ b/TidepoolServiceKit/Extensions/TemporaryScheduleOverride.swift @@ -0,0 +1,120 @@ +// +// TemporaryScheduleOverride.swift +// TidepoolServiceKit +// +// Created by Pete Schwamb on 8/26/24. +// Copyright © 2024 LoopKit Authors. All rights reserved. +// + +import Foundation +import TidepoolKit +import LoopKit + +fileprivate extension TemporaryScheduleOverride.Context { + var datumOverrideType: TPumpSettingsOverrideDeviceEventDatum.OverrideType { + switch self { + case .preMeal: + return .preprandial + case .legacyWorkout: + return .physicalActivity + case .preset(_): + return .preset + case .custom: + return .custom + } + } +} + +extension TemporaryScheduleOverrideSettings { + var datumBloodGlucoseTarget: TPumpSettingsDatum.BloodGlucoseTarget? { + guard let targetRange = targetRange else { + return nil + } + return TPumpSettingsDatum.BloodGlucoseTarget(low: targetRange.lowerBound.doubleValue(for: .milligramsPerDeciliter), + high: targetRange.upperBound.doubleValue(for: .milligramsPerDeciliter)) + } + + var datumBasalRateScaleFactor: Double? { basalRateMultiplier } + + var datumCarbohydrateRatioScaleFactor: Double? { carbRatioMultiplier } + + var datumInsulinSensitivityScaleFactor: Double? { insulinSensitivityMultiplier } + + var datumUnits: TPumpSettingsOverrideDeviceEventDatum.Units? { + guard targetRange != nil else { + return nil + } + return TPumpSettingsOverrideDeviceEventDatum.Units(bloodGlucose: .milligramsPerDeciliter) + } +} + +extension TemporaryScheduleOverride: IdentifiableDatum { + + var syncIdentifierAsString: String { syncIdentifier.uuidString } + + func datum(for userId: String, hostIdentifier: String, hostVersion: String) -> TDatum { + let datum = TPumpSettingsOverrideDeviceEventDatum(time: datumTime, + overrideType: datumOverrideType, + overridePreset: datumOverridePreset, + method: datumMethod, + duration: datumDuration, + expectedDuration: datumExpectedDuration, + bloodGlucoseTarget: datumBloodGlucoseTarget, + basalRateScaleFactor: datumBasalRateScaleFactor, + carbohydrateRatioScaleFactor: datumCarbohydrateRatioScaleFactor, + insulinSensitivityScaleFactor: datumInsulinSensitivityScaleFactor, + units: datumUnits) + let origin = datumOrigin(for: resolvedIdentifier(for: TPumpSettingsOverrideDeviceEventDatum.self), hostIdentifier: hostIdentifier, hostVersion: hostVersion) + return datum.adornWith(id: datumId(for: userId, type: TPumpSettingsOverrideDeviceEventDatum.self), + payload: datumPayload, + origin: origin) + } + + private var datumPayload: TDictionary { + var dictionary = TDictionary() + dictionary["syncIdentifier"] = syncIdentifierAsString + return dictionary + } + + var datumTime: Date { startDate } + + var datumOverrideType: TPumpSettingsOverrideDeviceEventDatum.OverrideType { context.datumOverrideType } + + var datumOverridePreset: String? { + guard case .preset(let preset) = context else { + return nil + } + return preset.name + } + + var datumMethod: TPumpSettingsOverrideDeviceEventDatum.Method? { .manual } + + var datumDuration: TimeInterval? { + switch duration { + case .finite(let interval): + return interval + case .indefinite: + return nil + } + } + + var datumExpectedDuration: TimeInterval? { nil } + + var datumBloodGlucoseTarget: TPumpSettingsOverrideDeviceEventDatum.BloodGlucoseTarget? { settings.datumBloodGlucoseTarget } + + var datumBasalRateScaleFactor: Double? { settings.datumBasalRateScaleFactor } + + var datumCarbohydrateRatioScaleFactor: Double? { settings.datumCarbohydrateRatioScaleFactor } + + var datumInsulinSensitivityScaleFactor: Double? { settings.datumInsulinSensitivityScaleFactor } + + var datumUnits: TPumpSettingsOverrideDeviceEventDatum.Units? { settings.datumUnits } + +} + +extension TemporaryScheduleOverride { + var selectors: [TDatum.Selector] { + return [datumSelector(for: TPumpSettingsOverrideDeviceEventDatum.self)] + } +} + diff --git a/TidepoolServiceKit/TidepoolService.swift b/TidepoolServiceKit/TidepoolService.swift index 58d42e5..b1a1b1f 100644 --- a/TidepoolServiceKit/TidepoolService.swift +++ b/TidepoolServiceKit/TidepoolService.swift @@ -285,31 +285,12 @@ extension TidepoolService: TLogging { extension TidepoolService: RemoteDataService { public func uploadTemporaryOverrideData(updated: [TemporaryScheduleOverride], deleted: [TemporaryScheduleOverride]) async throws { - // TODO: https://tidepool.atlassian.net/browse/LOOP-4769 - - // The following code is taken from previous upload code when override events where stored in settings - // To be implemented with - - // guard let activeOverride = activeOverride else { - // return nil - // } - // let datum = TPumpSettingsOverrideDeviceEventDatum(time: activeOverride.datumTime, - // overrideType: activeOverride.datumOverrideType, - // overridePreset: activeOverride.datumOverridePreset, - // method: activeOverride.datumMethod, - // duration: activeOverride.datumDuration, - // expectedDuration: activeOverride.datumExpectedDuration, - // bloodGlucoseTarget: activeOverride.datumBloodGlucoseTarget, - // basalRateScaleFactor: activeOverride.datumBasalRateScaleFactor, - // carbohydrateRatioScaleFactor: activeOverride.datumCarbohydrateRatioScaleFactor, - // insulinSensitivityScaleFactor: activeOverride.datumInsulinSensitivityScaleFactor, - // units: activeOverride.datumUnits) - // let origin = datumOrigin(for: resolvedIdentifier(for: TPumpSettingsOverrideDeviceEventDatum.self), hostIdentifier: hostIdentifier, hostVersion: hostVersion) - // return datum.adornWith(id: datumId(for: userId, type: TPumpSettingsOverrideDeviceEventDatum.self), - // timeZone: datumTimeZone, - // timeZoneOffset: datumTimeZoneOffset, - // payload: datumPayload, - // origin: origin) + guard let userId = userId, let hostIdentifier = hostIdentifier, let hostVersion = hostVersion else { + throw TidepoolServiceError.configuration + } + + let _ = try await createData(updated.compactMap { $0.datum(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) }) + let _ = try await deleteData(withSelectors: deleted.flatMap { $0.selectors }) } public var alertDataLimit: Int? { return 1000 } @@ -440,7 +421,7 @@ extension TidepoolService: RemoteDataService { func calculateSettingsData(_ stored: [StoredSettings], for userId: String, hostIdentifier: String, hostVersion: String) -> ([TDatum], [TDatum], TControllerSettingsDatum?, TCGMSettingsDatum?, TPumpSettingsDatum?) { var created: [TDatum] = [] - var updated: [TDatum] = [] + let updated: [TDatum] = [] var lastControllerSettingsDatum = lastControllerSettingsDatum var lastCGMSettingsDatum = lastCGMSettingsDatum var lastPumpSettingsDatum = lastPumpSettingsDatum From 8e856dc53edbba3f4753acc23a819590eabe8595 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Fri, 13 Sep 2024 16:10:37 -0500 Subject: [PATCH 20/71] LOOP-4098 - Specify correct duration for basal segments (#104) * Specify correct duration for basal segments * Fix test --- TidepoolServiceKit/Extensions/DoseEntry.swift | 4 ++-- TidepoolServiceKitTests/Extensions/DoseEntryTests.swift | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/TidepoolServiceKit/Extensions/DoseEntry.swift b/TidepoolServiceKit/Extensions/DoseEntry.swift index c9f7c85..73b1461 100644 --- a/TidepoolServiceKit/Extensions/DoseEntry.swift +++ b/TidepoolServiceKit/Extensions/DoseEntry.swift @@ -82,8 +82,8 @@ extension DoseEntry: IdentifiableDatum { payload["deliveredUnits"] = datumBasalDeliveredUnits var datum = TAutomatedBasalDatum(time: datumTime, - duration: !isMutable ? datumDuration : 0, - expectedDuration: !isMutable && datumDuration < basalDatumExpectedDuration ? basalDatumExpectedDuration : nil, + duration: datumDuration, + expectedDuration: nil, rate: datumScheduledBasalRate, scheduleName: StoredSettings.activeScheduleNameDefault, insulinFormulation: datumInsulinFormulation) diff --git a/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift b/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift index 56d6a9c..5027623 100644 --- a/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift +++ b/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift @@ -38,7 +38,6 @@ class DoseEntryDataTests: XCTestCase { { "deliveryType" : "automated", "duration" : 1500000, - "expectedDuration" : 1800000, "id" : "f839af02f6832d7c81d636dbbbadbc01", "insulinFormulation" : { "simple" : { From 5caeee373a8cddac4324145d4cc3dd13b9edafcd Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Fri, 13 Sep 2024 16:10:37 -0500 Subject: [PATCH 21/71] LOOP-4098 - Specify correct duration for basal segments (#104) * Specify correct duration for basal segments * Fix test --- TidepoolServiceKit/Extensions/DoseEntry.swift | 4 ++-- TidepoolServiceKitTests/Extensions/DoseEntryTests.swift | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/TidepoolServiceKit/Extensions/DoseEntry.swift b/TidepoolServiceKit/Extensions/DoseEntry.swift index c9f7c85..73b1461 100644 --- a/TidepoolServiceKit/Extensions/DoseEntry.swift +++ b/TidepoolServiceKit/Extensions/DoseEntry.swift @@ -82,8 +82,8 @@ extension DoseEntry: IdentifiableDatum { payload["deliveredUnits"] = datumBasalDeliveredUnits var datum = TAutomatedBasalDatum(time: datumTime, - duration: !isMutable ? datumDuration : 0, - expectedDuration: !isMutable && datumDuration < basalDatumExpectedDuration ? basalDatumExpectedDuration : nil, + duration: datumDuration, + expectedDuration: nil, rate: datumScheduledBasalRate, scheduleName: StoredSettings.activeScheduleNameDefault, insulinFormulation: datumInsulinFormulation) diff --git a/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift b/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift index 56d6a9c..5027623 100644 --- a/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift +++ b/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift @@ -38,7 +38,6 @@ class DoseEntryDataTests: XCTestCase { { "deliveryType" : "automated", "duration" : 1500000, - "expectedDuration" : 1800000, "id" : "f839af02f6832d7c81d636dbbbadbc01", "insulinFormulation" : { "simple" : { From edf8616ea2f09ea880e35ff70675ffa75fdc2183 Mon Sep 17 00:00:00 2001 From: Nathaniel Hamming Date: Wed, 18 Sep 2024 16:21:18 -0300 Subject: [PATCH 22/71] [LOOP-5035] report time zone changes (#105) * report time zone changes * removed unneeded print statement * set device log uploader when remoteDataServiceDelegate is set --- TidepoolServiceKit/DeviceLogUploader.swift | 2 - TidepoolServiceKit/TidepoolService.swift | 44 ++++++++++++++++++++-- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/TidepoolServiceKit/DeviceLogUploader.swift b/TidepoolServiceKit/DeviceLogUploader.swift index 61b5a18..4ed7344 100644 --- a/TidepoolServiceKit/DeviceLogUploader.swift +++ b/TidepoolServiceKit/DeviceLogUploader.swift @@ -91,10 +91,8 @@ actor DeviceLogUploader { do { let metatdata = try await api.uploadDeviceLogs(logs: data, start: start, end: end) log.default("metadata: %@", String(describing: metatdata)) - print("hi") } catch { log.error("error uploading device logs:: %@", String(describing: error)) - print("hi") } } } diff --git a/TidepoolServiceKit/TidepoolService.swift b/TidepoolServiceKit/TidepoolService.swift index b1a1b1f..3089277 100644 --- a/TidepoolServiceKit/TidepoolService.swift +++ b/TidepoolServiceKit/TidepoolService.swift @@ -48,8 +48,14 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { public weak var stateDelegate: StatefulPluggableDelegate? - public weak var remoteDataServiceDelegate: RemoteDataServiceDelegate? - + public weak var remoteDataServiceDelegate: RemoteDataServiceDelegate? { + didSet { + Task { + await setDeviceLogUploaderDelegate() + } + } + } + public lazy var sessionStorage: SessionStorage = KeychainManager() public let tapi: TAPI = TAPI(clientId: BuildDetails.default.tidepoolServiceClientId, redirectURL: BuildDetails.default.tidepoolServiceRedirectURL) @@ -72,6 +78,10 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { private let tidepoolKitLog = OSLog(category: "TidepoolKit") private var deviceLogUploader: DeviceLogUploader? + + private func setDeviceLogUploaderDelegate() async { + await deviceLogUploader?.setDelegate(remoteDataServiceDelegate) + } public init(hostIdentifier: String, hostVersion: String) { self.id = UUID().uuidString @@ -87,7 +97,8 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { await tapi.setLogging(self) await tapi.addObserver(self) deviceLogUploader = DeviceLogUploader(api: tapi) - await deviceLogUploader?.setDelegate(remoteDataServiceDelegate) + await setDeviceLogUploaderDelegate() + observeTimeZoneChanges() } public init?(rawState: RawStateValue) { @@ -485,6 +496,33 @@ extension TidepoolService: RemoteDataService { return (created, updated, lastControllerSettingsDatum, lastCGMSettingsDatum, lastPumpSettingsDatum) } + + private func uploadTimeZoneChangeData(from fromTimeZone: TimeZone, to toTimeZone: TimeZone, method: TTimeChangeDeviceEventDatum.Method = .automatic, at date: Date = Date()) async throws { + guard let userId = userId, let hostIdentifier = hostIdentifier, let hostVersion = hostVersion else { + throw TidepoolServiceError.configuration + } + + let timeZoneChangeData = TTimeChangeDeviceEventDatum(time: date, + from: TTimeChangeDeviceEventDatum.Info(timeZoneName: fromTimeZone.identifier), + to: TTimeChangeDeviceEventDatum.Info(timeZoneName: toTimeZone.identifier), + method: method) + let _ = try await createData([timeZoneChangeData]) + } + + private func observeTimeZoneChanges() { + NotificationCenter.default.addObserver(forName: .NSSystemTimeZoneDidChange, object: nil, queue: .main) { notification in + if let previousTimeZone = notification.object as? TimeZone { + let currentTimeZone = TimeZone.current + Task { + do { + try await self.uploadTimeZoneChangeData(from: previousTimeZone, to: currentTimeZone) + } catch { + self.log.error("Failed to upload time zone change data - %{public}@", error.localizedDescription) + } + } + } + } + } private func createData(_ data: [TDatum]) async throws -> Bool { if let error = error { From e192e327530e598c8acffabf14af970001b5a333 Mon Sep 17 00:00:00 2001 From: Nathaniel Hamming Date: Wed, 18 Sep 2024 16:21:18 -0300 Subject: [PATCH 23/71] [LOOP-5035] report time zone changes (#105) * report time zone changes * removed unneeded print statement * set device log uploader when remoteDataServiceDelegate is set --- TidepoolServiceKit/DeviceLogUploader.swift | 2 - TidepoolServiceKit/TidepoolService.swift | 44 ++++++++++++++++++++-- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/TidepoolServiceKit/DeviceLogUploader.swift b/TidepoolServiceKit/DeviceLogUploader.swift index 61b5a18..4ed7344 100644 --- a/TidepoolServiceKit/DeviceLogUploader.swift +++ b/TidepoolServiceKit/DeviceLogUploader.swift @@ -91,10 +91,8 @@ actor DeviceLogUploader { do { let metatdata = try await api.uploadDeviceLogs(logs: data, start: start, end: end) log.default("metadata: %@", String(describing: metatdata)) - print("hi") } catch { log.error("error uploading device logs:: %@", String(describing: error)) - print("hi") } } } diff --git a/TidepoolServiceKit/TidepoolService.swift b/TidepoolServiceKit/TidepoolService.swift index b1a1b1f..3089277 100644 --- a/TidepoolServiceKit/TidepoolService.swift +++ b/TidepoolServiceKit/TidepoolService.swift @@ -48,8 +48,14 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { public weak var stateDelegate: StatefulPluggableDelegate? - public weak var remoteDataServiceDelegate: RemoteDataServiceDelegate? - + public weak var remoteDataServiceDelegate: RemoteDataServiceDelegate? { + didSet { + Task { + await setDeviceLogUploaderDelegate() + } + } + } + public lazy var sessionStorage: SessionStorage = KeychainManager() public let tapi: TAPI = TAPI(clientId: BuildDetails.default.tidepoolServiceClientId, redirectURL: BuildDetails.default.tidepoolServiceRedirectURL) @@ -72,6 +78,10 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { private let tidepoolKitLog = OSLog(category: "TidepoolKit") private var deviceLogUploader: DeviceLogUploader? + + private func setDeviceLogUploaderDelegate() async { + await deviceLogUploader?.setDelegate(remoteDataServiceDelegate) + } public init(hostIdentifier: String, hostVersion: String) { self.id = UUID().uuidString @@ -87,7 +97,8 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { await tapi.setLogging(self) await tapi.addObserver(self) deviceLogUploader = DeviceLogUploader(api: tapi) - await deviceLogUploader?.setDelegate(remoteDataServiceDelegate) + await setDeviceLogUploaderDelegate() + observeTimeZoneChanges() } public init?(rawState: RawStateValue) { @@ -485,6 +496,33 @@ extension TidepoolService: RemoteDataService { return (created, updated, lastControllerSettingsDatum, lastCGMSettingsDatum, lastPumpSettingsDatum) } + + private func uploadTimeZoneChangeData(from fromTimeZone: TimeZone, to toTimeZone: TimeZone, method: TTimeChangeDeviceEventDatum.Method = .automatic, at date: Date = Date()) async throws { + guard let userId = userId, let hostIdentifier = hostIdentifier, let hostVersion = hostVersion else { + throw TidepoolServiceError.configuration + } + + let timeZoneChangeData = TTimeChangeDeviceEventDatum(time: date, + from: TTimeChangeDeviceEventDatum.Info(timeZoneName: fromTimeZone.identifier), + to: TTimeChangeDeviceEventDatum.Info(timeZoneName: toTimeZone.identifier), + method: method) + let _ = try await createData([timeZoneChangeData]) + } + + private func observeTimeZoneChanges() { + NotificationCenter.default.addObserver(forName: .NSSystemTimeZoneDidChange, object: nil, queue: .main) { notification in + if let previousTimeZone = notification.object as? TimeZone { + let currentTimeZone = TimeZone.current + Task { + do { + try await self.uploadTimeZoneChangeData(from: previousTimeZone, to: currentTimeZone) + } catch { + self.log.error("Failed to upload time zone change data - %{public}@", error.localizedDescription) + } + } + } + } + } private func createData(_ data: [TDatum]) async throws -> Bool { if let error = error { From 8f3457eb149101f439a52c0d659837b990ecc4a2 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Wed, 18 Sep 2024 14:30:14 -0500 Subject: [PATCH 24/71] LOOP-5065 Device log upload fixes (#106) * Device log upload fixes * Fix comment to match code --- TidepoolServiceKit/DeviceLogUploader.swift | 113 +++++++++++---------- 1 file changed, 59 insertions(+), 54 deletions(-) diff --git a/TidepoolServiceKit/DeviceLogUploader.swift b/TidepoolServiceKit/DeviceLogUploader.swift index 4ed7344..b27b524 100644 --- a/TidepoolServiceKit/DeviceLogUploader.swift +++ b/TidepoolServiceKit/DeviceLogUploader.swift @@ -21,6 +21,9 @@ actor DeviceLogUploader { private var logChunkDuration = TimeInterval(hours: 1) + private let backfillLimitInterval = TimeInterval(days: 2) + + func setDelegate(_ delegate: RemoteDataServiceDelegate?) { self.delegate = delegate } @@ -34,70 +37,72 @@ actor DeviceLogUploader { } func main() async { - let backfillLimitInterval = TimeInterval(days: 2) - // Default start uploading logs from 2 days ago - var nextUploadStart = Date().addingTimeInterval(-backfillLimitInterval).dateFlooredToTimeInterval(logChunkDuration) + var nextLogStart: Date? - // Fetch device log metadata records + // Start upload loop while true { - do { - // TODO: fetching logs is not implemented on the backend yet: awaiting https://tidepool.atlassian.net/browse/BACK-3011 - // For now, we expect this to error, so the catch has been modified to break out of the loop. Once this is implemented, - // We will want to retry on error, so the break should eventually be removed. - - var uploadMetadata = try await api.listDeviceLogs(start: Date().addingTimeInterval(-backfillLimitInterval), end: Date()) - uploadMetadata.sort { a, b in - return a.endAtTime > b.endAtTime + if nextLogStart == nil { + do { + nextLogStart = try await getMostRecentUploadEndTime() + } catch { + log.error("Unable to fetch device log metadata: %{public}@", String(describing: error)) } - if let lastEnd = uploadMetadata.last?.endAtTime { - nextUploadStart = lastEnd.dateFlooredToTimeInterval(logChunkDuration) + } + + if nextLogStart != nil { + let nextLogEnd = nextLogStart!.addingTimeInterval(logChunkDuration) + let timeUntilNextUpload = nextLogEnd.timeIntervalSinceNow + if timeUntilNextUpload > 0 { + log.debug("Waiting %{public}@s until next upload", String(timeUntilNextUpload)) + try? await Task.sleep(nanoseconds: timeUntilNextUpload.nanoseconds) + } + do { + try await upload(from: nextLogStart!, to: nextLogEnd) + nextLogStart = nextLogEnd + } catch { + log.error("Upload failed: %{public}@", String(describing: error)) + // Upload failed, retry in 5 minutes. + try? await Task.sleep(nanoseconds: TimeInterval(minutes: 5).nanoseconds) } - break - } catch { - log.error("Unable to fetch device log metadata: %@", String(describing: error)) - try? await Task.sleep(nanoseconds: TimeInterval(minutes: 1).nanoseconds) - break // TODO: Remove when backend has implemented device log metadata fetching (see above) + } else { + // Haven't been able to talk to backend to find any previous log uploads. Retry in 15 minutes. + try? await Task.sleep(nanoseconds: TimeInterval(minutes: 15).nanoseconds) } } - // Start upload loop - while true { - let nextUploadEnd = nextUploadStart.addingTimeInterval(logChunkDuration) - let timeUntilNextUpload = nextUploadEnd.timeIntervalSinceNow - if timeUntilNextUpload > 0 { - log.debug("Waiting %@s until next upload", String(timeUntilNextUpload)) - try? await Task.sleep(nanoseconds: timeUntilNextUpload.nanoseconds) - } - await upload(from: nextUploadStart, to: nextUploadEnd) - nextUploadStart = nextUploadEnd + } + + func getMostRecentUploadEndTime() async throws -> Date { + var uploadMetadata = try await api.listDeviceLogs(start: Date().addingTimeInterval(-backfillLimitInterval), end: Date()) + uploadMetadata.sort { a, b in + return a.endAtTime < b.endAtTime + } + if let lastEnd = uploadMetadata.last?.endAtTime { + return lastEnd + } else { + // No previous uploads found in last two days + return Date().addingTimeInterval(-backfillLimitInterval).dateFlooredToTimeInterval(logChunkDuration) } } - func upload(from start: Date, to end: Date) async { - log.default("Uploading from %@ to %@", String(describing: start), String(describing: end)) - do { - if let logs = try await delegate?.fetchDeviceLogs(startDate: start, endDate: end) { - log.default("Fetched %d logs", logs.count) - if logs.count > 0 { - let data = logs.map({ - entry in - TDeviceLogEntry( - type: entry.type.tidepoolType, - managerIdentifier: entry.managerIdentifier, - deviceIdentifier: entry.deviceIdentifier ?? "unknown", - timestamp: entry.timestamp, - message: entry.message - ) - }) - do { - let metatdata = try await api.uploadDeviceLogs(logs: data, start: start, end: end) - log.default("metadata: %@", String(describing: metatdata)) - } catch { - log.error("error uploading device logs:: %@", String(describing: error)) - } - } + func upload(from start: Date, to end: Date) async throws { + if let logs = try await delegate?.fetchDeviceLogs(startDate: start, endDate: end) { + if logs.count > 0 { + let data = logs.map({ + entry in + TDeviceLogEntry( + type: entry.type.tidepoolType, + managerIdentifier: entry.managerIdentifier, + deviceIdentifier: entry.deviceIdentifier ?? "unknown", + timestamp: entry.timestamp, + message: entry.message + ) + }) + let metatdata = try await api.uploadDeviceLogs(logs: data, start: start, end: end) + log.debug("Uploaded %d entries from %{public}@ to %{public}@", logs.count, String(describing: start), String(describing: end)) + log.debug("metadata: %{public}@", String(describing: metatdata)) + } else { + log.debug("No device log entries from %{public}@ to %{public}@", String(describing: start), String(describing: end)) } - } catch { - log.error("Upload failed: %@", String(describing: error)) } } } From b98c3113fda650d04537cef2ecca566fa6d61d88 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Wed, 18 Sep 2024 14:30:14 -0500 Subject: [PATCH 25/71] LOOP-5065 Device log upload fixes (#106) * Device log upload fixes * Fix comment to match code --- TidepoolServiceKit/DeviceLogUploader.swift | 113 +++++++++++---------- 1 file changed, 59 insertions(+), 54 deletions(-) diff --git a/TidepoolServiceKit/DeviceLogUploader.swift b/TidepoolServiceKit/DeviceLogUploader.swift index 4ed7344..b27b524 100644 --- a/TidepoolServiceKit/DeviceLogUploader.swift +++ b/TidepoolServiceKit/DeviceLogUploader.swift @@ -21,6 +21,9 @@ actor DeviceLogUploader { private var logChunkDuration = TimeInterval(hours: 1) + private let backfillLimitInterval = TimeInterval(days: 2) + + func setDelegate(_ delegate: RemoteDataServiceDelegate?) { self.delegate = delegate } @@ -34,70 +37,72 @@ actor DeviceLogUploader { } func main() async { - let backfillLimitInterval = TimeInterval(days: 2) - // Default start uploading logs from 2 days ago - var nextUploadStart = Date().addingTimeInterval(-backfillLimitInterval).dateFlooredToTimeInterval(logChunkDuration) + var nextLogStart: Date? - // Fetch device log metadata records + // Start upload loop while true { - do { - // TODO: fetching logs is not implemented on the backend yet: awaiting https://tidepool.atlassian.net/browse/BACK-3011 - // For now, we expect this to error, so the catch has been modified to break out of the loop. Once this is implemented, - // We will want to retry on error, so the break should eventually be removed. - - var uploadMetadata = try await api.listDeviceLogs(start: Date().addingTimeInterval(-backfillLimitInterval), end: Date()) - uploadMetadata.sort { a, b in - return a.endAtTime > b.endAtTime + if nextLogStart == nil { + do { + nextLogStart = try await getMostRecentUploadEndTime() + } catch { + log.error("Unable to fetch device log metadata: %{public}@", String(describing: error)) } - if let lastEnd = uploadMetadata.last?.endAtTime { - nextUploadStart = lastEnd.dateFlooredToTimeInterval(logChunkDuration) + } + + if nextLogStart != nil { + let nextLogEnd = nextLogStart!.addingTimeInterval(logChunkDuration) + let timeUntilNextUpload = nextLogEnd.timeIntervalSinceNow + if timeUntilNextUpload > 0 { + log.debug("Waiting %{public}@s until next upload", String(timeUntilNextUpload)) + try? await Task.sleep(nanoseconds: timeUntilNextUpload.nanoseconds) + } + do { + try await upload(from: nextLogStart!, to: nextLogEnd) + nextLogStart = nextLogEnd + } catch { + log.error("Upload failed: %{public}@", String(describing: error)) + // Upload failed, retry in 5 minutes. + try? await Task.sleep(nanoseconds: TimeInterval(minutes: 5).nanoseconds) } - break - } catch { - log.error("Unable to fetch device log metadata: %@", String(describing: error)) - try? await Task.sleep(nanoseconds: TimeInterval(minutes: 1).nanoseconds) - break // TODO: Remove when backend has implemented device log metadata fetching (see above) + } else { + // Haven't been able to talk to backend to find any previous log uploads. Retry in 15 minutes. + try? await Task.sleep(nanoseconds: TimeInterval(minutes: 15).nanoseconds) } } - // Start upload loop - while true { - let nextUploadEnd = nextUploadStart.addingTimeInterval(logChunkDuration) - let timeUntilNextUpload = nextUploadEnd.timeIntervalSinceNow - if timeUntilNextUpload > 0 { - log.debug("Waiting %@s until next upload", String(timeUntilNextUpload)) - try? await Task.sleep(nanoseconds: timeUntilNextUpload.nanoseconds) - } - await upload(from: nextUploadStart, to: nextUploadEnd) - nextUploadStart = nextUploadEnd + } + + func getMostRecentUploadEndTime() async throws -> Date { + var uploadMetadata = try await api.listDeviceLogs(start: Date().addingTimeInterval(-backfillLimitInterval), end: Date()) + uploadMetadata.sort { a, b in + return a.endAtTime < b.endAtTime + } + if let lastEnd = uploadMetadata.last?.endAtTime { + return lastEnd + } else { + // No previous uploads found in last two days + return Date().addingTimeInterval(-backfillLimitInterval).dateFlooredToTimeInterval(logChunkDuration) } } - func upload(from start: Date, to end: Date) async { - log.default("Uploading from %@ to %@", String(describing: start), String(describing: end)) - do { - if let logs = try await delegate?.fetchDeviceLogs(startDate: start, endDate: end) { - log.default("Fetched %d logs", logs.count) - if logs.count > 0 { - let data = logs.map({ - entry in - TDeviceLogEntry( - type: entry.type.tidepoolType, - managerIdentifier: entry.managerIdentifier, - deviceIdentifier: entry.deviceIdentifier ?? "unknown", - timestamp: entry.timestamp, - message: entry.message - ) - }) - do { - let metatdata = try await api.uploadDeviceLogs(logs: data, start: start, end: end) - log.default("metadata: %@", String(describing: metatdata)) - } catch { - log.error("error uploading device logs:: %@", String(describing: error)) - } - } + func upload(from start: Date, to end: Date) async throws { + if let logs = try await delegate?.fetchDeviceLogs(startDate: start, endDate: end) { + if logs.count > 0 { + let data = logs.map({ + entry in + TDeviceLogEntry( + type: entry.type.tidepoolType, + managerIdentifier: entry.managerIdentifier, + deviceIdentifier: entry.deviceIdentifier ?? "unknown", + timestamp: entry.timestamp, + message: entry.message + ) + }) + let metatdata = try await api.uploadDeviceLogs(logs: data, start: start, end: end) + log.debug("Uploaded %d entries from %{public}@ to %{public}@", logs.count, String(describing: start), String(describing: end)) + log.debug("metadata: %{public}@", String(describing: metatdata)) + } else { + log.debug("No device log entries from %{public}@ to %{public}@", String(describing: start), String(describing: end)) } - } catch { - log.error("Upload failed: %@", String(describing: error)) } } } From 2bdca0ba6b0ae7e3588259d7c128347c3a0bfc0c Mon Sep 17 00:00:00 2001 From: Nathaniel Hamming Date: Mon, 23 Sep 2024 13:38:39 -0300 Subject: [PATCH 26/71] [LOOP-5035] Pump event time zone sync (#107) * reverting code * uploading pump time zone sync event * response to comments --- .../Extensions/PersistedPumpEvent.swift | 33 +++++++++++++++++++ TidepoolServiceKit/TidepoolService.swift | 31 +---------------- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/TidepoolServiceKit/Extensions/PersistedPumpEvent.swift b/TidepoolServiceKit/Extensions/PersistedPumpEvent.swift index b926997..489f9c1 100644 --- a/TidepoolServiceKit/Extensions/PersistedPumpEvent.swift +++ b/TidepoolServiceKit/Extensions/PersistedPumpEvent.swift @@ -57,6 +57,8 @@ extension PersistedPumpEvent: IdentifiableDatum { return dataForRewind(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) case .suspend: return dataForSuspend(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) + case .timeZoneSync: + return dataForTimeZoneSync(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) default: return [] } @@ -173,6 +175,33 @@ extension PersistedPumpEvent: IdentifiableDatum { origin: origin) return [datum] } + + private func dataForTimeZoneSync(for userId: String, hostIdentifier: String, hostVersion: String) -> [TDatum] { + guard let type = type, + case let .timeZoneSync(fromSecondsFromGMT, toSecondsFromGMT) = type + else { + return [] + } + + let fromTime = formattedDateWithoutTimeZoneOffset(date, for: TimeZone(secondsFromGMT: fromSecondsFromGMT)) + let toTime = formattedDateWithoutTimeZoneOffset(date, for: TimeZone(secondsFromGMT: toSecondsFromGMT)) + var datum = TTimeChangeDeviceEventDatum(time: date, + from: TTimeChangeDeviceEventDatum.Info(time: fromTime), + to: TTimeChangeDeviceEventDatum.Info(time: toTime), + method: .manual) + let origin = datumOrigin(for: resolvedIdentifier(for: TTimeChangeDeviceEventDatum.self), hostIdentifier: hostIdentifier, hostVersion: hostVersion) + datum = datum.adornWith(id: datumId(for: userId, type: TTimeChangeDeviceEventDatum.self), + payload: datumPayload, + origin: origin) + return [datum] + } + + private func formattedDateWithoutTimeZoneOffset(_ date: Date, for timeZone: TimeZone?) -> String { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss" + dateFormatter.timeZone = timeZone + return dateFormatter.string(from: date) + } private var datumTime: Date { dose?.startDate ?? date } @@ -223,3 +252,7 @@ extension TReservoirChangeDeviceEventDatum: TypedDatum { extension TStatusDeviceEventDatum: TypedDatum { static var resolvedType: String { "\(TDatum.DatumType.deviceEvent.rawValue)/\(TDeviceEventDatum.SubType.status.rawValue)" } } + +extension TTimeChangeDeviceEventDatum: TypedDatum { + static var resolvedType: String { "\(TDatum.DatumType.deviceEvent.rawValue)/\(TDeviceEventDatum.SubType.timeChange.rawValue)" } +} diff --git a/TidepoolServiceKit/TidepoolService.swift b/TidepoolServiceKit/TidepoolService.swift index 3089277..e8a5719 100644 --- a/TidepoolServiceKit/TidepoolService.swift +++ b/TidepoolServiceKit/TidepoolService.swift @@ -64,7 +64,6 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { private let id: String - private var lastControllerSettingsDatum: TControllerSettingsDatum? private var lastCGMSettingsDatum: TCGMSettingsDatum? @@ -78,7 +77,7 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { private let tidepoolKitLog = OSLog(category: "TidepoolKit") private var deviceLogUploader: DeviceLogUploader? - + private func setDeviceLogUploaderDelegate() async { await deviceLogUploader?.setDelegate(remoteDataServiceDelegate) } @@ -98,7 +97,6 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { await tapi.addObserver(self) deviceLogUploader = DeviceLogUploader(api: tapi) await setDeviceLogUploaderDelegate() - observeTimeZoneChanges() } public init?(rawState: RawStateValue) { @@ -496,33 +494,6 @@ extension TidepoolService: RemoteDataService { return (created, updated, lastControllerSettingsDatum, lastCGMSettingsDatum, lastPumpSettingsDatum) } - - private func uploadTimeZoneChangeData(from fromTimeZone: TimeZone, to toTimeZone: TimeZone, method: TTimeChangeDeviceEventDatum.Method = .automatic, at date: Date = Date()) async throws { - guard let userId = userId, let hostIdentifier = hostIdentifier, let hostVersion = hostVersion else { - throw TidepoolServiceError.configuration - } - - let timeZoneChangeData = TTimeChangeDeviceEventDatum(time: date, - from: TTimeChangeDeviceEventDatum.Info(timeZoneName: fromTimeZone.identifier), - to: TTimeChangeDeviceEventDatum.Info(timeZoneName: toTimeZone.identifier), - method: method) - let _ = try await createData([timeZoneChangeData]) - } - - private func observeTimeZoneChanges() { - NotificationCenter.default.addObserver(forName: .NSSystemTimeZoneDidChange, object: nil, queue: .main) { notification in - if let previousTimeZone = notification.object as? TimeZone { - let currentTimeZone = TimeZone.current - Task { - do { - try await self.uploadTimeZoneChangeData(from: previousTimeZone, to: currentTimeZone) - } catch { - self.log.error("Failed to upload time zone change data - %{public}@", error.localizedDescription) - } - } - } - } - } private func createData(_ data: [TDatum]) async throws -> Bool { if let error = error { From 78ed7b7a6b83f734cd3d46aa55d9254ea62215f1 Mon Sep 17 00:00:00 2001 From: Nathaniel Hamming Date: Mon, 23 Sep 2024 13:38:39 -0300 Subject: [PATCH 27/71] [LOOP-5035] Pump event time zone sync (#107) * reverting code * uploading pump time zone sync event * response to comments --- .../Extensions/PersistedPumpEvent.swift | 33 +++++++++++++++++++ TidepoolServiceKit/TidepoolService.swift | 31 +---------------- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/TidepoolServiceKit/Extensions/PersistedPumpEvent.swift b/TidepoolServiceKit/Extensions/PersistedPumpEvent.swift index b926997..489f9c1 100644 --- a/TidepoolServiceKit/Extensions/PersistedPumpEvent.swift +++ b/TidepoolServiceKit/Extensions/PersistedPumpEvent.swift @@ -57,6 +57,8 @@ extension PersistedPumpEvent: IdentifiableDatum { return dataForRewind(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) case .suspend: return dataForSuspend(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) + case .timeZoneSync: + return dataForTimeZoneSync(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) default: return [] } @@ -173,6 +175,33 @@ extension PersistedPumpEvent: IdentifiableDatum { origin: origin) return [datum] } + + private func dataForTimeZoneSync(for userId: String, hostIdentifier: String, hostVersion: String) -> [TDatum] { + guard let type = type, + case let .timeZoneSync(fromSecondsFromGMT, toSecondsFromGMT) = type + else { + return [] + } + + let fromTime = formattedDateWithoutTimeZoneOffset(date, for: TimeZone(secondsFromGMT: fromSecondsFromGMT)) + let toTime = formattedDateWithoutTimeZoneOffset(date, for: TimeZone(secondsFromGMT: toSecondsFromGMT)) + var datum = TTimeChangeDeviceEventDatum(time: date, + from: TTimeChangeDeviceEventDatum.Info(time: fromTime), + to: TTimeChangeDeviceEventDatum.Info(time: toTime), + method: .manual) + let origin = datumOrigin(for: resolvedIdentifier(for: TTimeChangeDeviceEventDatum.self), hostIdentifier: hostIdentifier, hostVersion: hostVersion) + datum = datum.adornWith(id: datumId(for: userId, type: TTimeChangeDeviceEventDatum.self), + payload: datumPayload, + origin: origin) + return [datum] + } + + private func formattedDateWithoutTimeZoneOffset(_ date: Date, for timeZone: TimeZone?) -> String { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss" + dateFormatter.timeZone = timeZone + return dateFormatter.string(from: date) + } private var datumTime: Date { dose?.startDate ?? date } @@ -223,3 +252,7 @@ extension TReservoirChangeDeviceEventDatum: TypedDatum { extension TStatusDeviceEventDatum: TypedDatum { static var resolvedType: String { "\(TDatum.DatumType.deviceEvent.rawValue)/\(TDeviceEventDatum.SubType.status.rawValue)" } } + +extension TTimeChangeDeviceEventDatum: TypedDatum { + static var resolvedType: String { "\(TDatum.DatumType.deviceEvent.rawValue)/\(TDeviceEventDatum.SubType.timeChange.rawValue)" } +} diff --git a/TidepoolServiceKit/TidepoolService.swift b/TidepoolServiceKit/TidepoolService.swift index 3089277..e8a5719 100644 --- a/TidepoolServiceKit/TidepoolService.swift +++ b/TidepoolServiceKit/TidepoolService.swift @@ -64,7 +64,6 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { private let id: String - private var lastControllerSettingsDatum: TControllerSettingsDatum? private var lastCGMSettingsDatum: TCGMSettingsDatum? @@ -78,7 +77,7 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { private let tidepoolKitLog = OSLog(category: "TidepoolKit") private var deviceLogUploader: DeviceLogUploader? - + private func setDeviceLogUploaderDelegate() async { await deviceLogUploader?.setDelegate(remoteDataServiceDelegate) } @@ -98,7 +97,6 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { await tapi.addObserver(self) deviceLogUploader = DeviceLogUploader(api: tapi) await setDeviceLogUploaderDelegate() - observeTimeZoneChanges() } public init?(rawState: RawStateValue) { @@ -496,33 +494,6 @@ extension TidepoolService: RemoteDataService { return (created, updated, lastControllerSettingsDatum, lastCGMSettingsDatum, lastPumpSettingsDatum) } - - private func uploadTimeZoneChangeData(from fromTimeZone: TimeZone, to toTimeZone: TimeZone, method: TTimeChangeDeviceEventDatum.Method = .automatic, at date: Date = Date()) async throws { - guard let userId = userId, let hostIdentifier = hostIdentifier, let hostVersion = hostVersion else { - throw TidepoolServiceError.configuration - } - - let timeZoneChangeData = TTimeChangeDeviceEventDatum(time: date, - from: TTimeChangeDeviceEventDatum.Info(timeZoneName: fromTimeZone.identifier), - to: TTimeChangeDeviceEventDatum.Info(timeZoneName: toTimeZone.identifier), - method: method) - let _ = try await createData([timeZoneChangeData]) - } - - private func observeTimeZoneChanges() { - NotificationCenter.default.addObserver(forName: .NSSystemTimeZoneDidChange, object: nil, queue: .main) { notification in - if let previousTimeZone = notification.object as? TimeZone { - let currentTimeZone = TimeZone.current - Task { - do { - try await self.uploadTimeZoneChangeData(from: previousTimeZone, to: currentTimeZone) - } catch { - self.log.error("Failed to upload time zone change data - %{public}@", error.localizedDescription) - } - } - } - } - } private func createData(_ data: [TDatum]) async throws -> Bool { if let error = error { From aa23383ed4d5774e83b735253f92f24f4dabd86c Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Wed, 2 Oct 2024 17:18:39 -0500 Subject: [PATCH 28/71] LOOP-5071 Overlay basal and automation history (#108) * Overlay basal and automation history * Test updates * Annotate suspends with scheduled basal --- TidepoolServiceKit/Extensions/DoseEntry.swift | 193 +++++++++++++++++- TidepoolServiceKit/TidepoolService.swift | 29 ++- .../Extensions/DoseEntryTests.swift | 133 +++++++++++- 3 files changed, 350 insertions(+), 5 deletions(-) diff --git a/TidepoolServiceKit/Extensions/DoseEntry.swift b/TidepoolServiceKit/Extensions/DoseEntry.swift index 73b1461..257d6eb 100644 --- a/TidepoolServiceKit/Extensions/DoseEntry.swift +++ b/TidepoolServiceKit/Extensions/DoseEntry.swift @@ -8,6 +8,8 @@ import LoopKit import TidepoolKit +import LoopAlgorithm +import HealthKit /* DoseEntry @@ -204,8 +206,8 @@ extension DoseEntry: IdentifiableDatum { payload["deliveredUnits"] = deliveredUnits var datum = TAutomatedBasalDatum(time: datumTime, - duration: !isMutable ? datumDuration : 0, - expectedDuration: !isMutable && datumDuration < basalDatumExpectedDuration ? basalDatumExpectedDuration : nil, + duration: datumDuration, + expectedDuration: datumDuration < basalDatumExpectedDuration ? basalDatumExpectedDuration : nil, rate: datumRate, scheduleName: StoredSettings.activeScheduleNameDefault, insulinFormulation: datumInsulinFormulation) @@ -342,3 +344,190 @@ extension TNormalBolusDatum: TypedDatum { extension TInsulinDatum: TypedDatum { static var resolvedType: String { TDatum.DatumType.insulin.rawValue } } + +extension DoseEntry { + + /// Annotates a dose with the context of a history of scheduled basal rates + /// + /// If the dose crosses a schedule boundary, it will be split into multiple doses so each dose has a + /// single scheduled basal rate. + /// + /// - Parameter basalHistory: The history of basal schedule values to apply. Only schedule values overlapping the dose should be included. + /// - Returns: An array of annotated doses + fileprivate func annotated(with basalHistory: [AbsoluteScheduleValue]) -> [DoseEntry] { + + guard type == .tempBasal || type == .suspend, !basalHistory.isEmpty else { + return [self] + } + + if type == .suspend { + guard value == 0 else { + preconditionFailure("suspend with non-zero delivery") + } + } else { + guard unit != .units else { + preconditionFailure("temp basal without rate unsupported") + } + } + + if isMutable { + var newDose = self + let basal = basalHistory.first! + newDose.scheduledBasalRate = HKQuantity(unit: .internationalUnitsPerHour, doubleValue: basal.value) + return [newDose] + } + + var doses: [DoseEntry] = [] + + for (index, basalItem) in basalHistory.enumerated() { + let startDate: Date + let endDate: Date + + if index == 0 { + startDate = self.startDate + } else { + startDate = basalItem.startDate + } + + if index == basalHistory.count - 1 { + endDate = self.endDate + } else { + endDate = basalHistory[index + 1].startDate + } + + let segmentStartDate = max(startDate, self.startDate) + let segmentEndDate = max(startDate, min(endDate, self.endDate)) + let segmentDuration = segmentEndDate.timeIntervalSince(segmentStartDate) + let segmentPortion = (segmentDuration / duration) + + var annotatedDose = self + annotatedDose.startDate = segmentStartDate + annotatedDose.endDate = segmentEndDate + annotatedDose.scheduledBasalRate = HKQuantity(unit: .internationalUnitsPerHour, doubleValue: basalItem.value) + + if let deliveredUnits { + annotatedDose.deliveredUnits = deliveredUnits * segmentPortion + } + + doses.append(annotatedDose) + } + + if doses.count > 1 { + for (index, dose) in doses.enumerated() { + if let originalIdentifier = dose.syncIdentifier, index>0 { + doses[index].syncIdentifier = originalIdentifier + "\(index+1)/\(doses.count)" + } + } + } + + return doses + } + +} + + +extension Collection where Element == DoseEntry { + + /// Annotates a sequence of dose entries with the configured basal history + /// + /// Doses which cross time boundaries in the basal rate schedule are split into multiple entries. + /// + /// - Parameter basalHistory: A history of basal rates covering the timespan of these doses. + /// - Returns: An array of annotated dose entries + public func annotated(with basalHistory: [AbsoluteScheduleValue]) -> [DoseEntry] { + var annotatedDoses: [DoseEntry] = [] + + for dose in self { + let basalItems = basalHistory.filterDateRange(dose.startDate, dose.endDate) + annotatedDoses += dose.annotated(with: basalItems) + } + + return annotatedDoses + } + + + /// Assigns an automation status to any dose where automation is not already specified + /// + /// - Parameters: + /// - automationHistory: A history of automation periods. + /// - Returns: An array of doses, with the automation flag set based on automation history. Doses will be split if the automation state changes mid-dose. + + public func overlayAutomationHistory( + _ automationHistory: [AbsoluteScheduleValue] + ) -> [DoseEntry] { + + guard count > 0 else { + return [] + } + + var newEntries = [DoseEntry]() + + var automation = automationHistory + + // Assume automation if doses start before automationHistory + if let firstAutomation = automation.first, firstAutomation.startDate > first!.startDate { + automation.insert(AbsoluteScheduleValue(startDate: first!.startDate, endDate: firstAutomation.startDate, value: true), at: 0) + } + + // Overlay automation periods + func annotateDoseWithAutomation(dose: DoseEntry) { + + var addedCount = 0 + for period in automation { + if period.endDate > dose.startDate && period.startDate < dose.endDate { + var newDose = dose + + if dose.isMutable { + newDose.automatic = period.value + newEntries.append(newDose) + return + } + + newDose.startDate = Swift.max(period.startDate, dose.startDate) + newDose.endDate = Swift.min(period.endDate, dose.endDate) + if let delivered = dose.deliveredUnits { + newDose.deliveredUnits = newDose.duration / dose.duration * delivered + } + newDose.automatic = period.value + if addedCount > 0 { + newDose.syncIdentifier = "\(dose.syncIdentifierAsString)\(addedCount+1)" + } + newEntries.append(newDose) + addedCount += 1 + } + } + if addedCount == 0 { + // automation history did not cover dose; mark automatic as default + var newDose = dose + newDose.automatic = true + newEntries.append(newDose) + } + } + + for dose in self { + switch dose.type { + case .tempBasal, .basal, .suspend: + if dose.automatic == nil { + annotateDoseWithAutomation(dose: dose) + } else { + newEntries.append(dose) + } + default: + newEntries.append(dose) + break + } + } + return newEntries + } + +} + +extension DoseEntry { + var simpleDesc: String { + let seconds = Int(duration) + let automatic = automatic?.description ?? "na" + return "\(startDate) (\(seconds)s) - \(type) - isMutable:\(isMutable) automatic:\(automatic) value:\(value) delivered:\(String(describing: deliveredUnits)) scheduled:\(String(describing: scheduledBasalRate)) syncId:\(String(describing: syncIdentifier))" + } +} + + diff --git a/TidepoolServiceKit/TidepoolService.swift b/TidepoolServiceKit/TidepoolService.swift index e8a5719..c7fb62e 100644 --- a/TidepoolServiceKit/TidepoolService.swift +++ b/TidepoolServiceKit/TidepoolService.swift @@ -326,13 +326,38 @@ extension TidepoolService: RemoteDataService { public var doseDataLimit: Int? { return 1000 } + private func annotateDoses(_ doses: [DoseEntry]) async throws -> [DoseEntry] { + guard !doses.isEmpty else { + return [] + } + + guard let remoteDataServiceDelegate else { + throw TidepoolServiceError.configuration + } + + let start = doses.map { $0.startDate }.min()! + let end = doses.map { $0.endDate }.max()! + + let basal = try await remoteDataServiceDelegate.getBasalHistory(startDate: start, endDate: end) + let dosesWithBasal = doses.annotated(with: basal) + + let automationHistory = try await remoteDataServiceDelegate.automationHistory(from: start, to: end) + return dosesWithBasal.overlayAutomationHistory(automationHistory) + + } + public func uploadDoseData(created: [DoseEntry], deleted: [DoseEntry]) async throws { guard let userId = userId, let hostIdentifier = hostIdentifier, let hostVersion = hostVersion else { throw TidepoolServiceError.configuration } - let _ = try await createData(created.flatMap { $0.data(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) }) - let _ = try await deleteData(withSelectors: deleted.flatMap { $0.selectors }) + // Syncidentifiers may be changed + let annotatedCreated = try await annotateDoses(created) + let _ = try await createData(annotatedCreated.flatMap { $0.data(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) }) + + // annotating these so we get the correct syncIdentifiers to delete + let annotatedDeleted = try await annotateDoses(deleted) + let _ = try await deleteData(withSelectors: annotatedDeleted.flatMap { $0.selectors }) } public var dosingDecisionDataLimit: Int? { return 50 } // Each can be up to 20K bytes of serialized JSON, target ~1M or less diff --git a/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift b/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift index 5027623..06a2a10 100644 --- a/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift +++ b/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift @@ -13,6 +13,7 @@ import Foundation import HealthKit import LoopKit import TidepoolKit +import LoopAlgorithm @testable import TidepoolServiceKit class DoseEntryDataTests: XCTestCase { @@ -549,7 +550,8 @@ class DoseEntryDataTests: XCTestCase { } ], "deliveryType" : "automated", - "duration" : 0, + "duration" : 1200000, + "expectedDuration" : 1800000, "id" : "f839af02f6832d7c81d636dbbbadbc01", "insulinFormulation" : { "simple" : { @@ -720,5 +722,134 @@ class DoseEntrySelectorTests: XCTestCase { XCTAssertEqual(doseEntry.selectors, [TDatum.Selector(origin: TDatum.Selector.Origin(id: "ab0a722d639669875017a899a5214677:basal/automated"))]) } + func testOverlayAutomationHistory_NoAutomationHistory() { + let doses: [DoseEntry] = [ + DoseEntry(type: .basal, startDate: Date(), endDate: Date().addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, syncIdentifier: "test", automatic: nil, manuallyEntered: false, isMutable: false) + ] + let result = doses.overlayAutomationHistory([]) + + XCTAssertEqual(result.count, 1) + XCTAssertEqual(result[0].automatic, true) // Default to true when no automation history + } + + func testOverlayAutomationHistory_SingleAutomationPeriod() { + let now = Date() + let doses: [DoseEntry] = [ + DoseEntry(type: .basal, startDate: now, endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, syncIdentifier: "test", automatic: nil, manuallyEntered: false, isMutable: false) + ] + let automationHistory: [AbsoluteScheduleValue] = [ + AbsoluteScheduleValue(startDate: now, endDate: now.addingTimeInterval(3600), value: false) + ] + + let result = doses.overlayAutomationHistory(automationHistory) + + XCTAssertEqual(result.count, 1) + XCTAssertEqual(result[0].automatic, false) + } + + func testOverlayAutomationHistory_MultipleAutomationPeriods() { + let now = Date() + let doses: [DoseEntry] = [ + DoseEntry(type: .basal, startDate: now, endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, syncIdentifier: "test", automatic: nil, manuallyEntered: false, isMutable: false) + ] + let automationHistory: [AbsoluteScheduleValue] = [ + AbsoluteScheduleValue(startDate: now, endDate: now.addingTimeInterval(1800), value: false), + AbsoluteScheduleValue(startDate: now.addingTimeInterval(1800), endDate: now.addingTimeInterval(3600), value: true) + ] + + let result = doses.overlayAutomationHistory(automationHistory) + + XCTAssertEqual(result.count, 2) + XCTAssertEqual(result[0].automatic, false) + XCTAssertEqual(result[1].automatic, true) + } + + func testOverlayAutomationHistory_PartialOverlap() { + let now = Date() + let doses: [DoseEntry] = [ + DoseEntry(type: .basal, startDate: now, endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, syncIdentifier: "test", automatic: nil, manuallyEntered: false, isMutable: false) + ] + let automationHistory: [AbsoluteScheduleValue] = [ + AbsoluteScheduleValue(startDate: now.addingTimeInterval(1800), endDate: now.addingTimeInterval(4800), value: false) + ] + + let result = doses.overlayAutomationHistory(automationHistory) + + XCTAssertEqual(result.count, 2) + XCTAssertEqual(result[0].automatic, true) + XCTAssertEqual(result[1].automatic, false) + } + + + func testOverlayAutomationHistory_NonBasalDoses() { + let now = Date() + let doses: [DoseEntry] = [ + DoseEntry(type: .bolus, startDate: now, endDate: now.addingTimeInterval(300), value: 2.0, unit: .unitsPerHour, automatic: nil, manuallyEntered: false, isMutable: false), + DoseEntry(type: .basal, startDate: now.addingTimeInterval(300), endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, automatic: nil, manuallyEntered: false, isMutable: false) + ] + let automationHistory: [AbsoluteScheduleValue] = [ + AbsoluteScheduleValue(startDate: now, endDate: now.addingTimeInterval(3600), value: false) + ] + + let result = doses.overlayAutomationHistory(automationHistory) + + XCTAssertEqual(result.count, 2) + XCTAssertNil(result[0].automatic) // Bolus dose should remain unchanged + XCTAssertEqual(result[1].automatic, false) + } + + func testOverlayAutomationHistory_PreexistingAutomationFlag() { + let now = Date() + let doses: [DoseEntry] = [ + DoseEntry(type: .basal, startDate: now, endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, automatic: true, manuallyEntered: false, isMutable: false) + ] + let automationHistory: [AbsoluteScheduleValue] = [ + AbsoluteScheduleValue(startDate: now, endDate: now.addingTimeInterval(3600), value: false) + ] + + let result = doses.overlayAutomationHistory(automationHistory) + + XCTAssertEqual(result.count, 1) + XCTAssertEqual(result[0].automatic, true) // Should not change preexisting automation flag + } + + func testOverlayAutomationHistory_DeliveredUnitsAdjustment() { + let now = Date() + let doses: [DoseEntry] = [ + DoseEntry(type: .basal, startDate: now, endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, deliveredUnits: 1.0, syncIdentifier: "test", automatic: nil, manuallyEntered: false, isMutable: false) + ] + let automationHistory: [AbsoluteScheduleValue] = [ + AbsoluteScheduleValue(startDate: now, endDate: now.addingTimeInterval(1800), value: false), + AbsoluteScheduleValue(startDate: now.addingTimeInterval(1800), endDate: now.addingTimeInterval(3600), value: true) + ] + + let result = doses.overlayAutomationHistory(automationHistory) + + XCTAssertEqual(result.count, 2) + XCTAssertEqual(result[0].deliveredUnits!, 0.5, accuracy: 0.001) + XCTAssertEqual(result[0].automatic, false) + XCTAssertEqual(result[1].deliveredUnits!, 0.5, accuracy: 0.001) + XCTAssertEqual(result[1].automatic, true) + } + + func testOverlayAutomationHistory_MutableDose() { + let now = Date() + let doses: [DoseEntry] = [ + DoseEntry(type: .basal, startDate: now, endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, deliveredUnits: 1.0, syncIdentifier: "test", automatic: nil, manuallyEntered: false, isMutable: true) + ] + let automationHistory: [AbsoluteScheduleValue] = [ + AbsoluteScheduleValue(startDate: now, endDate: now.addingTimeInterval(1800), value: false), + AbsoluteScheduleValue(startDate: now.addingTimeInterval(1800), endDate: now.addingTimeInterval(3600), value: true) + ] + + let result = doses.overlayAutomationHistory(automationHistory) + + XCTAssertEqual(result.count, 1) + XCTAssertEqual(result[0].deliveredUnits!, 1, accuracy: 0.001) + XCTAssertEqual(result[0].automatic, false) + XCTAssertEqual(result[0].duration, TimeInterval(hours: 1)) + } + + private static let dateFormatter = ISO8601DateFormatter() } From 54f12b50223150178142141a0494a50b574af274 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Wed, 2 Oct 2024 17:18:39 -0500 Subject: [PATCH 29/71] LOOP-5071 Overlay basal and automation history (#108) * Overlay basal and automation history * Test updates * Annotate suspends with scheduled basal --- TidepoolServiceKit/Extensions/DoseEntry.swift | 193 +++++++++++++++++- TidepoolServiceKit/TidepoolService.swift | 29 ++- .../Extensions/DoseEntryTests.swift | 133 +++++++++++- 3 files changed, 350 insertions(+), 5 deletions(-) diff --git a/TidepoolServiceKit/Extensions/DoseEntry.swift b/TidepoolServiceKit/Extensions/DoseEntry.swift index 73b1461..257d6eb 100644 --- a/TidepoolServiceKit/Extensions/DoseEntry.swift +++ b/TidepoolServiceKit/Extensions/DoseEntry.swift @@ -8,6 +8,8 @@ import LoopKit import TidepoolKit +import LoopAlgorithm +import HealthKit /* DoseEntry @@ -204,8 +206,8 @@ extension DoseEntry: IdentifiableDatum { payload["deliveredUnits"] = deliveredUnits var datum = TAutomatedBasalDatum(time: datumTime, - duration: !isMutable ? datumDuration : 0, - expectedDuration: !isMutable && datumDuration < basalDatumExpectedDuration ? basalDatumExpectedDuration : nil, + duration: datumDuration, + expectedDuration: datumDuration < basalDatumExpectedDuration ? basalDatumExpectedDuration : nil, rate: datumRate, scheduleName: StoredSettings.activeScheduleNameDefault, insulinFormulation: datumInsulinFormulation) @@ -342,3 +344,190 @@ extension TNormalBolusDatum: TypedDatum { extension TInsulinDatum: TypedDatum { static var resolvedType: String { TDatum.DatumType.insulin.rawValue } } + +extension DoseEntry { + + /// Annotates a dose with the context of a history of scheduled basal rates + /// + /// If the dose crosses a schedule boundary, it will be split into multiple doses so each dose has a + /// single scheduled basal rate. + /// + /// - Parameter basalHistory: The history of basal schedule values to apply. Only schedule values overlapping the dose should be included. + /// - Returns: An array of annotated doses + fileprivate func annotated(with basalHistory: [AbsoluteScheduleValue]) -> [DoseEntry] { + + guard type == .tempBasal || type == .suspend, !basalHistory.isEmpty else { + return [self] + } + + if type == .suspend { + guard value == 0 else { + preconditionFailure("suspend with non-zero delivery") + } + } else { + guard unit != .units else { + preconditionFailure("temp basal without rate unsupported") + } + } + + if isMutable { + var newDose = self + let basal = basalHistory.first! + newDose.scheduledBasalRate = HKQuantity(unit: .internationalUnitsPerHour, doubleValue: basal.value) + return [newDose] + } + + var doses: [DoseEntry] = [] + + for (index, basalItem) in basalHistory.enumerated() { + let startDate: Date + let endDate: Date + + if index == 0 { + startDate = self.startDate + } else { + startDate = basalItem.startDate + } + + if index == basalHistory.count - 1 { + endDate = self.endDate + } else { + endDate = basalHistory[index + 1].startDate + } + + let segmentStartDate = max(startDate, self.startDate) + let segmentEndDate = max(startDate, min(endDate, self.endDate)) + let segmentDuration = segmentEndDate.timeIntervalSince(segmentStartDate) + let segmentPortion = (segmentDuration / duration) + + var annotatedDose = self + annotatedDose.startDate = segmentStartDate + annotatedDose.endDate = segmentEndDate + annotatedDose.scheduledBasalRate = HKQuantity(unit: .internationalUnitsPerHour, doubleValue: basalItem.value) + + if let deliveredUnits { + annotatedDose.deliveredUnits = deliveredUnits * segmentPortion + } + + doses.append(annotatedDose) + } + + if doses.count > 1 { + for (index, dose) in doses.enumerated() { + if let originalIdentifier = dose.syncIdentifier, index>0 { + doses[index].syncIdentifier = originalIdentifier + "\(index+1)/\(doses.count)" + } + } + } + + return doses + } + +} + + +extension Collection where Element == DoseEntry { + + /// Annotates a sequence of dose entries with the configured basal history + /// + /// Doses which cross time boundaries in the basal rate schedule are split into multiple entries. + /// + /// - Parameter basalHistory: A history of basal rates covering the timespan of these doses. + /// - Returns: An array of annotated dose entries + public func annotated(with basalHistory: [AbsoluteScheduleValue]) -> [DoseEntry] { + var annotatedDoses: [DoseEntry] = [] + + for dose in self { + let basalItems = basalHistory.filterDateRange(dose.startDate, dose.endDate) + annotatedDoses += dose.annotated(with: basalItems) + } + + return annotatedDoses + } + + + /// Assigns an automation status to any dose where automation is not already specified + /// + /// - Parameters: + /// - automationHistory: A history of automation periods. + /// - Returns: An array of doses, with the automation flag set based on automation history. Doses will be split if the automation state changes mid-dose. + + public func overlayAutomationHistory( + _ automationHistory: [AbsoluteScheduleValue] + ) -> [DoseEntry] { + + guard count > 0 else { + return [] + } + + var newEntries = [DoseEntry]() + + var automation = automationHistory + + // Assume automation if doses start before automationHistory + if let firstAutomation = automation.first, firstAutomation.startDate > first!.startDate { + automation.insert(AbsoluteScheduleValue(startDate: first!.startDate, endDate: firstAutomation.startDate, value: true), at: 0) + } + + // Overlay automation periods + func annotateDoseWithAutomation(dose: DoseEntry) { + + var addedCount = 0 + for period in automation { + if period.endDate > dose.startDate && period.startDate < dose.endDate { + var newDose = dose + + if dose.isMutable { + newDose.automatic = period.value + newEntries.append(newDose) + return + } + + newDose.startDate = Swift.max(period.startDate, dose.startDate) + newDose.endDate = Swift.min(period.endDate, dose.endDate) + if let delivered = dose.deliveredUnits { + newDose.deliveredUnits = newDose.duration / dose.duration * delivered + } + newDose.automatic = period.value + if addedCount > 0 { + newDose.syncIdentifier = "\(dose.syncIdentifierAsString)\(addedCount+1)" + } + newEntries.append(newDose) + addedCount += 1 + } + } + if addedCount == 0 { + // automation history did not cover dose; mark automatic as default + var newDose = dose + newDose.automatic = true + newEntries.append(newDose) + } + } + + for dose in self { + switch dose.type { + case .tempBasal, .basal, .suspend: + if dose.automatic == nil { + annotateDoseWithAutomation(dose: dose) + } else { + newEntries.append(dose) + } + default: + newEntries.append(dose) + break + } + } + return newEntries + } + +} + +extension DoseEntry { + var simpleDesc: String { + let seconds = Int(duration) + let automatic = automatic?.description ?? "na" + return "\(startDate) (\(seconds)s) - \(type) - isMutable:\(isMutable) automatic:\(automatic) value:\(value) delivered:\(String(describing: deliveredUnits)) scheduled:\(String(describing: scheduledBasalRate)) syncId:\(String(describing: syncIdentifier))" + } +} + + diff --git a/TidepoolServiceKit/TidepoolService.swift b/TidepoolServiceKit/TidepoolService.swift index e8a5719..c7fb62e 100644 --- a/TidepoolServiceKit/TidepoolService.swift +++ b/TidepoolServiceKit/TidepoolService.swift @@ -326,13 +326,38 @@ extension TidepoolService: RemoteDataService { public var doseDataLimit: Int? { return 1000 } + private func annotateDoses(_ doses: [DoseEntry]) async throws -> [DoseEntry] { + guard !doses.isEmpty else { + return [] + } + + guard let remoteDataServiceDelegate else { + throw TidepoolServiceError.configuration + } + + let start = doses.map { $0.startDate }.min()! + let end = doses.map { $0.endDate }.max()! + + let basal = try await remoteDataServiceDelegate.getBasalHistory(startDate: start, endDate: end) + let dosesWithBasal = doses.annotated(with: basal) + + let automationHistory = try await remoteDataServiceDelegate.automationHistory(from: start, to: end) + return dosesWithBasal.overlayAutomationHistory(automationHistory) + + } + public func uploadDoseData(created: [DoseEntry], deleted: [DoseEntry]) async throws { guard let userId = userId, let hostIdentifier = hostIdentifier, let hostVersion = hostVersion else { throw TidepoolServiceError.configuration } - let _ = try await createData(created.flatMap { $0.data(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) }) - let _ = try await deleteData(withSelectors: deleted.flatMap { $0.selectors }) + // Syncidentifiers may be changed + let annotatedCreated = try await annotateDoses(created) + let _ = try await createData(annotatedCreated.flatMap { $0.data(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) }) + + // annotating these so we get the correct syncIdentifiers to delete + let annotatedDeleted = try await annotateDoses(deleted) + let _ = try await deleteData(withSelectors: annotatedDeleted.flatMap { $0.selectors }) } public var dosingDecisionDataLimit: Int? { return 50 } // Each can be up to 20K bytes of serialized JSON, target ~1M or less diff --git a/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift b/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift index 5027623..06a2a10 100644 --- a/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift +++ b/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift @@ -13,6 +13,7 @@ import Foundation import HealthKit import LoopKit import TidepoolKit +import LoopAlgorithm @testable import TidepoolServiceKit class DoseEntryDataTests: XCTestCase { @@ -549,7 +550,8 @@ class DoseEntryDataTests: XCTestCase { } ], "deliveryType" : "automated", - "duration" : 0, + "duration" : 1200000, + "expectedDuration" : 1800000, "id" : "f839af02f6832d7c81d636dbbbadbc01", "insulinFormulation" : { "simple" : { @@ -720,5 +722,134 @@ class DoseEntrySelectorTests: XCTestCase { XCTAssertEqual(doseEntry.selectors, [TDatum.Selector(origin: TDatum.Selector.Origin(id: "ab0a722d639669875017a899a5214677:basal/automated"))]) } + func testOverlayAutomationHistory_NoAutomationHistory() { + let doses: [DoseEntry] = [ + DoseEntry(type: .basal, startDate: Date(), endDate: Date().addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, syncIdentifier: "test", automatic: nil, manuallyEntered: false, isMutable: false) + ] + let result = doses.overlayAutomationHistory([]) + + XCTAssertEqual(result.count, 1) + XCTAssertEqual(result[0].automatic, true) // Default to true when no automation history + } + + func testOverlayAutomationHistory_SingleAutomationPeriod() { + let now = Date() + let doses: [DoseEntry] = [ + DoseEntry(type: .basal, startDate: now, endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, syncIdentifier: "test", automatic: nil, manuallyEntered: false, isMutable: false) + ] + let automationHistory: [AbsoluteScheduleValue] = [ + AbsoluteScheduleValue(startDate: now, endDate: now.addingTimeInterval(3600), value: false) + ] + + let result = doses.overlayAutomationHistory(automationHistory) + + XCTAssertEqual(result.count, 1) + XCTAssertEqual(result[0].automatic, false) + } + + func testOverlayAutomationHistory_MultipleAutomationPeriods() { + let now = Date() + let doses: [DoseEntry] = [ + DoseEntry(type: .basal, startDate: now, endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, syncIdentifier: "test", automatic: nil, manuallyEntered: false, isMutable: false) + ] + let automationHistory: [AbsoluteScheduleValue] = [ + AbsoluteScheduleValue(startDate: now, endDate: now.addingTimeInterval(1800), value: false), + AbsoluteScheduleValue(startDate: now.addingTimeInterval(1800), endDate: now.addingTimeInterval(3600), value: true) + ] + + let result = doses.overlayAutomationHistory(automationHistory) + + XCTAssertEqual(result.count, 2) + XCTAssertEqual(result[0].automatic, false) + XCTAssertEqual(result[1].automatic, true) + } + + func testOverlayAutomationHistory_PartialOverlap() { + let now = Date() + let doses: [DoseEntry] = [ + DoseEntry(type: .basal, startDate: now, endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, syncIdentifier: "test", automatic: nil, manuallyEntered: false, isMutable: false) + ] + let automationHistory: [AbsoluteScheduleValue] = [ + AbsoluteScheduleValue(startDate: now.addingTimeInterval(1800), endDate: now.addingTimeInterval(4800), value: false) + ] + + let result = doses.overlayAutomationHistory(automationHistory) + + XCTAssertEqual(result.count, 2) + XCTAssertEqual(result[0].automatic, true) + XCTAssertEqual(result[1].automatic, false) + } + + + func testOverlayAutomationHistory_NonBasalDoses() { + let now = Date() + let doses: [DoseEntry] = [ + DoseEntry(type: .bolus, startDate: now, endDate: now.addingTimeInterval(300), value: 2.0, unit: .unitsPerHour, automatic: nil, manuallyEntered: false, isMutable: false), + DoseEntry(type: .basal, startDate: now.addingTimeInterval(300), endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, automatic: nil, manuallyEntered: false, isMutable: false) + ] + let automationHistory: [AbsoluteScheduleValue] = [ + AbsoluteScheduleValue(startDate: now, endDate: now.addingTimeInterval(3600), value: false) + ] + + let result = doses.overlayAutomationHistory(automationHistory) + + XCTAssertEqual(result.count, 2) + XCTAssertNil(result[0].automatic) // Bolus dose should remain unchanged + XCTAssertEqual(result[1].automatic, false) + } + + func testOverlayAutomationHistory_PreexistingAutomationFlag() { + let now = Date() + let doses: [DoseEntry] = [ + DoseEntry(type: .basal, startDate: now, endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, automatic: true, manuallyEntered: false, isMutable: false) + ] + let automationHistory: [AbsoluteScheduleValue] = [ + AbsoluteScheduleValue(startDate: now, endDate: now.addingTimeInterval(3600), value: false) + ] + + let result = doses.overlayAutomationHistory(automationHistory) + + XCTAssertEqual(result.count, 1) + XCTAssertEqual(result[0].automatic, true) // Should not change preexisting automation flag + } + + func testOverlayAutomationHistory_DeliveredUnitsAdjustment() { + let now = Date() + let doses: [DoseEntry] = [ + DoseEntry(type: .basal, startDate: now, endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, deliveredUnits: 1.0, syncIdentifier: "test", automatic: nil, manuallyEntered: false, isMutable: false) + ] + let automationHistory: [AbsoluteScheduleValue] = [ + AbsoluteScheduleValue(startDate: now, endDate: now.addingTimeInterval(1800), value: false), + AbsoluteScheduleValue(startDate: now.addingTimeInterval(1800), endDate: now.addingTimeInterval(3600), value: true) + ] + + let result = doses.overlayAutomationHistory(automationHistory) + + XCTAssertEqual(result.count, 2) + XCTAssertEqual(result[0].deliveredUnits!, 0.5, accuracy: 0.001) + XCTAssertEqual(result[0].automatic, false) + XCTAssertEqual(result[1].deliveredUnits!, 0.5, accuracy: 0.001) + XCTAssertEqual(result[1].automatic, true) + } + + func testOverlayAutomationHistory_MutableDose() { + let now = Date() + let doses: [DoseEntry] = [ + DoseEntry(type: .basal, startDate: now, endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, deliveredUnits: 1.0, syncIdentifier: "test", automatic: nil, manuallyEntered: false, isMutable: true) + ] + let automationHistory: [AbsoluteScheduleValue] = [ + AbsoluteScheduleValue(startDate: now, endDate: now.addingTimeInterval(1800), value: false), + AbsoluteScheduleValue(startDate: now.addingTimeInterval(1800), endDate: now.addingTimeInterval(3600), value: true) + ] + + let result = doses.overlayAutomationHistory(automationHistory) + + XCTAssertEqual(result.count, 1) + XCTAssertEqual(result[0].deliveredUnits!, 1, accuracy: 0.001) + XCTAssertEqual(result[0].automatic, false) + XCTAssertEqual(result[0].duration, TimeInterval(hours: 1)) + } + + private static let dateFormatter = ISO8601DateFormatter() } From 5df0cf804f072f25ebd873be8fb3a6cdd8b35bda Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Fri, 25 Oct 2024 14:38:49 -0500 Subject: [PATCH 30/71] Use continue button during onboarding (#110) --- TidepoolServiceKitUI/SettingsView.swift | 21 ++++++++++++++++--- TidepoolServiceKitUI/TidepoolService+UI.swift | 18 +++++++++------- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/TidepoolServiceKitUI/SettingsView.swift b/TidepoolServiceKitUI/SettingsView.swift index 3280516..bacffd3 100644 --- a/TidepoolServiceKitUI/SettingsView.swift +++ b/TidepoolServiceKitUI/SettingsView.swift @@ -25,12 +25,13 @@ public struct SettingsView: View { private let login: ((TEnvironment) async throws -> Void)? private let dismiss: (() -> Void)? + private let onboarding: Bool var isLoggedIn: Bool { return service.session != nil } - public init(service: TidepoolService, login: ((TEnvironment) async throws -> Void)?, dismiss: (() -> Void)?) + public init(service: TidepoolService, login: ((TEnvironment) async throws -> Void)?, dismiss: (() -> Void)?, onboarding: Bool) { let tapi = service.tapi self.service = service @@ -38,6 +39,7 @@ public struct SettingsView: View { self._selectedEnvironment = State(initialValue: service.session?.environment ?? defaultEnvironment ?? TEnvironment.productionEnvironment) self.login = login self.dismiss = dismiss + self.onboarding = onboarding } public var body: some View { @@ -95,8 +97,10 @@ public struct SettingsView: View { .padding() } Spacer() - if isLoggedIn { + if isLoggedIn && !onboarding { deleteServiceButton + } else if isLoggedIn { + continueButton } else { loginButton } @@ -177,6 +181,17 @@ public struct SettingsView: View { .disabled(isLoggingIn) } + private var continueButton: some View { + Button(action: { + dismiss?() + }) { + Text(LocalizedString("Continue", comment: "Delete Tidepool service button title")) + } + .buttonStyle(ActionButtonStyle(.primary)) + .disabled(isLoggingIn) + } + + private func loginButtonTapped() { guard !isLoggingIn else { return @@ -211,6 +226,6 @@ public struct SettingsView: View { struct SettingsView_Previews: PreviewProvider { @MainActor static var previews: some View { - SettingsView(service: TidepoolService(hostIdentifier: "Previews", hostVersion: "1.0"), login: nil, dismiss: nil) + SettingsView(service: TidepoolService(hostIdentifier: "Previews", hostVersion: "1.0"), login: nil, dismiss: nil, onboarding: false) } } diff --git a/TidepoolServiceKitUI/TidepoolService+UI.swift b/TidepoolServiceKitUI/TidepoolService+UI.swift index b71f023..06e3d2c 100644 --- a/TidepoolServiceKitUI/TidepoolService+UI.swift +++ b/TidepoolServiceKitUI/TidepoolService+UI.swift @@ -29,12 +29,12 @@ enum TidepoolServiceError: Error { case missingWindow } -extension TidepoolService: ServiceUI { +extension TidepoolService: @retroactive ServiceUI { public static var image: UIImage? { UIImage(frameworkImage: "Tidepool Logo") } - public static func setupViewController(colorPalette: LoopUIColorPalette, pluginHost: PluginHost) -> SetupUIResult { + public static func setupViewController(pluginHost: PluginHost, onboarding: Bool) -> SetupUIResult { let navController = ServiceNavigationController() navController.isNavigationBarHidden = true @@ -48,7 +48,7 @@ extension TidepoolService: ServiceUI { throw TidepoolServiceError.missingWindow } - let windowContextProvider = WindowContextProvider(window: window) + let windowContextProvider = await WindowContextProvider(window: window) let sessionProvider = await ASWebAuthenticationSessionProvider(contextProviding: windowContextProvider) let auth = OAuth2Authenticator(api: service.tapi, environment: environment, sessionProvider: sessionProvider) try await auth.login() @@ -58,15 +58,19 @@ extension TidepoolService: ServiceUI { Task { await navController.notifyComplete() } - }) + }, onboarding: onboarding) let hostingController = await UIHostingController(rootView: settingsView) await navController.pushViewController(hostingController, animated: false) } - + return .userInteractionRequired(navController) } + public static func setupViewController(colorPalette: LoopUIColorPalette, pluginHost: PluginHost) -> SetupUIResult { + return setupViewController(pluginHost: pluginHost, onboarding: false) + } + public func settingsViewController(colorPalette: LoopUIColorPalette) -> ServiceViewController { let navController = ServiceNavigationController() @@ -79,7 +83,7 @@ extension TidepoolService: ServiceUI { throw TidepoolServiceError.missingWindow } - let windowContextProvider = WindowContextProvider(window: window) + let windowContextProvider = await WindowContextProvider(window: window) let sessionProvider = await ASWebAuthenticationSessionProvider(contextProviding: windowContextProvider) let auth = OAuth2Authenticator(api: self.tapi, environment: environment, sessionProvider: sessionProvider) try await auth.login() @@ -88,7 +92,7 @@ extension TidepoolService: ServiceUI { Task { await navController.notifyComplete() } - }) + }, onboarding: false) let hostingController = await UIHostingController(rootView: settingsView) await navController.pushViewController(hostingController, animated: false) From e7ad5fe15a1757248f93bc266dd45d2fc2d2e7ef Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Fri, 25 Oct 2024 14:38:49 -0500 Subject: [PATCH 31/71] Use continue button during onboarding (#110) --- TidepoolServiceKitUI/SettingsView.swift | 21 ++++++++++++++++--- TidepoolServiceKitUI/TidepoolService+UI.swift | 18 +++++++++------- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/TidepoolServiceKitUI/SettingsView.swift b/TidepoolServiceKitUI/SettingsView.swift index 3280516..bacffd3 100644 --- a/TidepoolServiceKitUI/SettingsView.swift +++ b/TidepoolServiceKitUI/SettingsView.swift @@ -25,12 +25,13 @@ public struct SettingsView: View { private let login: ((TEnvironment) async throws -> Void)? private let dismiss: (() -> Void)? + private let onboarding: Bool var isLoggedIn: Bool { return service.session != nil } - public init(service: TidepoolService, login: ((TEnvironment) async throws -> Void)?, dismiss: (() -> Void)?) + public init(service: TidepoolService, login: ((TEnvironment) async throws -> Void)?, dismiss: (() -> Void)?, onboarding: Bool) { let tapi = service.tapi self.service = service @@ -38,6 +39,7 @@ public struct SettingsView: View { self._selectedEnvironment = State(initialValue: service.session?.environment ?? defaultEnvironment ?? TEnvironment.productionEnvironment) self.login = login self.dismiss = dismiss + self.onboarding = onboarding } public var body: some View { @@ -95,8 +97,10 @@ public struct SettingsView: View { .padding() } Spacer() - if isLoggedIn { + if isLoggedIn && !onboarding { deleteServiceButton + } else if isLoggedIn { + continueButton } else { loginButton } @@ -177,6 +181,17 @@ public struct SettingsView: View { .disabled(isLoggingIn) } + private var continueButton: some View { + Button(action: { + dismiss?() + }) { + Text(LocalizedString("Continue", comment: "Delete Tidepool service button title")) + } + .buttonStyle(ActionButtonStyle(.primary)) + .disabled(isLoggingIn) + } + + private func loginButtonTapped() { guard !isLoggingIn else { return @@ -211,6 +226,6 @@ public struct SettingsView: View { struct SettingsView_Previews: PreviewProvider { @MainActor static var previews: some View { - SettingsView(service: TidepoolService(hostIdentifier: "Previews", hostVersion: "1.0"), login: nil, dismiss: nil) + SettingsView(service: TidepoolService(hostIdentifier: "Previews", hostVersion: "1.0"), login: nil, dismiss: nil, onboarding: false) } } diff --git a/TidepoolServiceKitUI/TidepoolService+UI.swift b/TidepoolServiceKitUI/TidepoolService+UI.swift index b71f023..06e3d2c 100644 --- a/TidepoolServiceKitUI/TidepoolService+UI.swift +++ b/TidepoolServiceKitUI/TidepoolService+UI.swift @@ -29,12 +29,12 @@ enum TidepoolServiceError: Error { case missingWindow } -extension TidepoolService: ServiceUI { +extension TidepoolService: @retroactive ServiceUI { public static var image: UIImage? { UIImage(frameworkImage: "Tidepool Logo") } - public static func setupViewController(colorPalette: LoopUIColorPalette, pluginHost: PluginHost) -> SetupUIResult { + public static func setupViewController(pluginHost: PluginHost, onboarding: Bool) -> SetupUIResult { let navController = ServiceNavigationController() navController.isNavigationBarHidden = true @@ -48,7 +48,7 @@ extension TidepoolService: ServiceUI { throw TidepoolServiceError.missingWindow } - let windowContextProvider = WindowContextProvider(window: window) + let windowContextProvider = await WindowContextProvider(window: window) let sessionProvider = await ASWebAuthenticationSessionProvider(contextProviding: windowContextProvider) let auth = OAuth2Authenticator(api: service.tapi, environment: environment, sessionProvider: sessionProvider) try await auth.login() @@ -58,15 +58,19 @@ extension TidepoolService: ServiceUI { Task { await navController.notifyComplete() } - }) + }, onboarding: onboarding) let hostingController = await UIHostingController(rootView: settingsView) await navController.pushViewController(hostingController, animated: false) } - + return .userInteractionRequired(navController) } + public static func setupViewController(colorPalette: LoopUIColorPalette, pluginHost: PluginHost) -> SetupUIResult { + return setupViewController(pluginHost: pluginHost, onboarding: false) + } + public func settingsViewController(colorPalette: LoopUIColorPalette) -> ServiceViewController { let navController = ServiceNavigationController() @@ -79,7 +83,7 @@ extension TidepoolService: ServiceUI { throw TidepoolServiceError.missingWindow } - let windowContextProvider = WindowContextProvider(window: window) + let windowContextProvider = await WindowContextProvider(window: window) let sessionProvider = await ASWebAuthenticationSessionProvider(contextProviding: windowContextProvider) let auth = OAuth2Authenticator(api: self.tapi, environment: environment, sessionProvider: sessionProvider) try await auth.login() @@ -88,7 +92,7 @@ extension TidepoolService: ServiceUI { Task { await navController.notifyComplete() } - }) + }, onboarding: false) let hostingController = await UIHostingController(rootView: settingsView) await navController.pushViewController(hostingController, animated: false) From 4075197ac01d6e083a576d7b1e2365fcee6148eb Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Tue, 29 Oct 2024 11:37:33 -0700 Subject: [PATCH 32/71] [LOOP-5132] Remove automatedDelivery and serialNumber from TPumpSettingsDatum and TCGMSettingsDatum --- TidepoolServiceKit/TidepoolService.swift | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/TidepoolServiceKit/TidepoolService.swift b/TidepoolServiceKit/TidepoolService.swift index c7fb62e..699f06b 100644 --- a/TidepoolServiceKit/TidepoolService.swift +++ b/TidepoolServiceKit/TidepoolService.swift @@ -468,9 +468,12 @@ extension TidepoolService: RemoteDataService { let controllerSettingsDatumIsEffectivelyEquivalent = TControllerSettingsDatum.areEffectivelyEquivalent(old: lastControllerSettingsDatum, new: controllerSettingsDatum) let cgmSettingsDatum = $0.datumCGMSettings(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) + cgmSettingsDatum.serialNumber = nil // MARK: Planned for removal in https://tidepool.atlassian.net/browse/LOOP-5130 let cgmSettingsDatumIsEffectivelyEquivalent = TCGMSettingsDatum.areEffectivelyEquivalent(old: lastCGMSettingsDatum, new: cgmSettingsDatum) let pumpSettingsDatum = $0.datumPumpSettings(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) + pumpSettingsDatum.serialNumber = nil // MARK: Planned for removal in https://tidepool.atlassian.net/browse/LOOP-5130 + pumpSettingsDatum.automatedDelivery = nil // MARK: Planned for removal in https://tidepool.atlassian.net/browse/LOOP-5130 let pumpSettingsDatumIsEffectivelyEquivalent = TPumpSettingsDatum.areEffectivelyEquivalent(old: lastPumpSettingsDatum, new: pumpSettingsDatum) // Associate the data @@ -640,7 +643,7 @@ extension TCGMSettingsDatum: EffectivelyEquivalent { self.manufacturers == other.manufacturers && self.model == other.model && self.name == other.name && - self.serialNumber == other.serialNumber && +// self.serialNumber == other.serialNumber && // MARK: Planned for removal in https://tidepool.atlassian.net/browse/LOOP-5130 self.softwareVersion == other.softwareVersion && self.transmitterId == other.transmitterId && self.units == other.units && @@ -676,7 +679,7 @@ extension TPumpSettingsDatum: EffectivelyEquivalent { // All TDatum properties can be ignored for this datum type func isEffectivelyEquivalent(to other: TPumpSettingsDatum) -> Bool { return self.activeScheduleName == other.activeScheduleName && - self.automatedDelivery == other.automatedDelivery && +// self.automatedDelivery == other.automatedDelivery && // MARK: Planned for removal in https://tidepool.atlassian.net/browse/LOOP-5130 self.basal == other.basal && self.basalRateSchedule == other.basalRateSchedule && self.basalRateSchedules == other.basalRateSchedules && @@ -700,7 +703,7 @@ extension TPumpSettingsDatum: EffectivelyEquivalent { self.name == other.name && self.overridePresets == other.overridePresets && self.scheduleTimeZoneOffset == other.scheduleTimeZoneOffset && - self.serialNumber == other.serialNumber && +// self.serialNumber == other.serialNumber && // MARK: Planned for removal in https://tidepool.atlassian.net/browse/LOOP-5130 self.softwareVersion == other.softwareVersion && self.units == other.units } From 65e5c778e16bf583303dddba9bf010fbae48988d Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Tue, 29 Oct 2024 11:37:33 -0700 Subject: [PATCH 33/71] [LOOP-5132] Remove automatedDelivery and serialNumber from TPumpSettingsDatum and TCGMSettingsDatum --- TidepoolServiceKit/TidepoolService.swift | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/TidepoolServiceKit/TidepoolService.swift b/TidepoolServiceKit/TidepoolService.swift index c7fb62e..699f06b 100644 --- a/TidepoolServiceKit/TidepoolService.swift +++ b/TidepoolServiceKit/TidepoolService.swift @@ -468,9 +468,12 @@ extension TidepoolService: RemoteDataService { let controllerSettingsDatumIsEffectivelyEquivalent = TControllerSettingsDatum.areEffectivelyEquivalent(old: lastControllerSettingsDatum, new: controllerSettingsDatum) let cgmSettingsDatum = $0.datumCGMSettings(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) + cgmSettingsDatum.serialNumber = nil // MARK: Planned for removal in https://tidepool.atlassian.net/browse/LOOP-5130 let cgmSettingsDatumIsEffectivelyEquivalent = TCGMSettingsDatum.areEffectivelyEquivalent(old: lastCGMSettingsDatum, new: cgmSettingsDatum) let pumpSettingsDatum = $0.datumPumpSettings(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) + pumpSettingsDatum.serialNumber = nil // MARK: Planned for removal in https://tidepool.atlassian.net/browse/LOOP-5130 + pumpSettingsDatum.automatedDelivery = nil // MARK: Planned for removal in https://tidepool.atlassian.net/browse/LOOP-5130 let pumpSettingsDatumIsEffectivelyEquivalent = TPumpSettingsDatum.areEffectivelyEquivalent(old: lastPumpSettingsDatum, new: pumpSettingsDatum) // Associate the data @@ -640,7 +643,7 @@ extension TCGMSettingsDatum: EffectivelyEquivalent { self.manufacturers == other.manufacturers && self.model == other.model && self.name == other.name && - self.serialNumber == other.serialNumber && +// self.serialNumber == other.serialNumber && // MARK: Planned for removal in https://tidepool.atlassian.net/browse/LOOP-5130 self.softwareVersion == other.softwareVersion && self.transmitterId == other.transmitterId && self.units == other.units && @@ -676,7 +679,7 @@ extension TPumpSettingsDatum: EffectivelyEquivalent { // All TDatum properties can be ignored for this datum type func isEffectivelyEquivalent(to other: TPumpSettingsDatum) -> Bool { return self.activeScheduleName == other.activeScheduleName && - self.automatedDelivery == other.automatedDelivery && +// self.automatedDelivery == other.automatedDelivery && // MARK: Planned for removal in https://tidepool.atlassian.net/browse/LOOP-5130 self.basal == other.basal && self.basalRateSchedule == other.basalRateSchedule && self.basalRateSchedules == other.basalRateSchedules && @@ -700,7 +703,7 @@ extension TPumpSettingsDatum: EffectivelyEquivalent { self.name == other.name && self.overridePresets == other.overridePresets && self.scheduleTimeZoneOffset == other.scheduleTimeZoneOffset && - self.serialNumber == other.serialNumber && +// self.serialNumber == other.serialNumber && // MARK: Planned for removal in https://tidepool.atlassian.net/browse/LOOP-5130 self.softwareVersion == other.softwareVersion && self.units == other.units } From 752d049cca48ef3b7287c81e460af2b4da6d562d Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Tue, 29 Oct 2024 12:47:38 -0700 Subject: [PATCH 34/71] [LOOP-5132] Remove automatedDelivery and serialNumber from TPumpSettingsDatum and TCGMSettingsDatum --- TidepoolServiceKit/Extensions/StoredSettings.swift | 9 --------- TidepoolServiceKit/TidepoolService.swift | 9 --------- 2 files changed, 18 deletions(-) diff --git a/TidepoolServiceKit/Extensions/StoredSettings.swift b/TidepoolServiceKit/Extensions/StoredSettings.swift index 4b9a84f..2051827 100644 --- a/TidepoolServiceKit/Extensions/StoredSettings.swift +++ b/TidepoolServiceKit/Extensions/StoredSettings.swift @@ -65,7 +65,6 @@ extension StoredSettings: IdentifiableDatum { manufacturers: datumCGMManufacturers, model: datumCGMModel, name: datumCGMName, - serialNumber: datumCGMSerialNumber, softwareVersion: datumCGMSoftwareVersion, transmitterId: nil, // TODO: https://tidepool.atlassian.net/browse/LOOP-3929 units: datumCGMUnits, @@ -82,7 +81,6 @@ extension StoredSettings: IdentifiableDatum { func datumPumpSettings(for userId: String, hostIdentifier: String, hostVersion: String) -> TPumpSettingsDatum { let datum = TPumpSettingsDatum(time: datumTime, activeScheduleName: datumPumpActiveScheduleName, - automatedDelivery: datumPumpAutomatedDelivery, basal: datumPumpBasal, basalRateSchedules: datumPumpBasalRateSchedules, bloodGlucoseSafetyLimit: datumPumpBloodGlucoseSafetyLimit, @@ -102,7 +100,6 @@ extension StoredSettings: IdentifiableDatum { name: datumPumpName, overridePresets: datumPumpOverridePresets, scheduleTimeZoneOffset: datumPumpScheduleTimeZoneOffset, - serialNumber: datumPumpSerialNumber, softwareVersion: datumPumpSoftwareVersion, units: datumPumpUnits) let origin = datumOrigin(for: resolvedIdentifier(for: TPumpSettingsDatum.self), hostIdentifier: hostIdentifier, hostVersion: hostVersion) @@ -145,8 +142,6 @@ extension StoredSettings: IdentifiableDatum { private var datumCGMName: String? { cgmDevice?.name } - private var datumCGMSerialNumber: String? { cgmDevice?.localIdentifier } - private var datumCGMSoftwareVersion: String? { cgmDevice?.softwareVersion } private var datumCGMUnits: TCGMSettingsDatum.Units { .milligramsPerDeciliter } @@ -155,8 +150,6 @@ extension StoredSettings: IdentifiableDatum { return Self.activeScheduleNameDefault } - private var datumPumpAutomatedDelivery: Bool { dosingEnabled } - private var datumPumpBasal: TPumpSettingsDatum.Basal? { guard let maximumBasalRatePerHour = maximumBasalRatePerHour else { return nil @@ -293,8 +286,6 @@ extension StoredSettings: IdentifiableDatum { return TimeInterval(seconds: scheduleTimeZone.secondsFromGMT(for: date)) } - private var datumPumpSerialNumber: String? { pumpDevice?.localIdentifier } - private var datumPumpSoftwareVersion: String? { pumpDevice?.softwareVersion } private var datumPumpUnits: TPumpSettingsDatum.Units { diff --git a/TidepoolServiceKit/TidepoolService.swift b/TidepoolServiceKit/TidepoolService.swift index 699f06b..5514e85 100644 --- a/TidepoolServiceKit/TidepoolService.swift +++ b/TidepoolServiceKit/TidepoolService.swift @@ -468,12 +468,9 @@ extension TidepoolService: RemoteDataService { let controllerSettingsDatumIsEffectivelyEquivalent = TControllerSettingsDatum.areEffectivelyEquivalent(old: lastControllerSettingsDatum, new: controllerSettingsDatum) let cgmSettingsDatum = $0.datumCGMSettings(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) - cgmSettingsDatum.serialNumber = nil // MARK: Planned for removal in https://tidepool.atlassian.net/browse/LOOP-5130 let cgmSettingsDatumIsEffectivelyEquivalent = TCGMSettingsDatum.areEffectivelyEquivalent(old: lastCGMSettingsDatum, new: cgmSettingsDatum) let pumpSettingsDatum = $0.datumPumpSettings(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) - pumpSettingsDatum.serialNumber = nil // MARK: Planned for removal in https://tidepool.atlassian.net/browse/LOOP-5130 - pumpSettingsDatum.automatedDelivery = nil // MARK: Planned for removal in https://tidepool.atlassian.net/browse/LOOP-5130 let pumpSettingsDatumIsEffectivelyEquivalent = TPumpSettingsDatum.areEffectivelyEquivalent(old: lastPumpSettingsDatum, new: pumpSettingsDatum) // Associate the data @@ -643,7 +640,6 @@ extension TCGMSettingsDatum: EffectivelyEquivalent { self.manufacturers == other.manufacturers && self.model == other.model && self.name == other.name && -// self.serialNumber == other.serialNumber && // MARK: Planned for removal in https://tidepool.atlassian.net/browse/LOOP-5130 self.softwareVersion == other.softwareVersion && self.transmitterId == other.transmitterId && self.units == other.units && @@ -662,7 +658,6 @@ extension TCGMSettingsDatum: EffectivelyEquivalent { manufacturers == nil && model == nil && name == nil && - serialNumber == nil && softwareVersion == nil && transmitterId == nil && defaultAlerts == nil && @@ -679,7 +674,6 @@ extension TPumpSettingsDatum: EffectivelyEquivalent { // All TDatum properties can be ignored for this datum type func isEffectivelyEquivalent(to other: TPumpSettingsDatum) -> Bool { return self.activeScheduleName == other.activeScheduleName && -// self.automatedDelivery == other.automatedDelivery && // MARK: Planned for removal in https://tidepool.atlassian.net/browse/LOOP-5130 self.basal == other.basal && self.basalRateSchedule == other.basalRateSchedule && self.basalRateSchedules == other.basalRateSchedules && @@ -703,7 +697,6 @@ extension TPumpSettingsDatum: EffectivelyEquivalent { self.name == other.name && self.overridePresets == other.overridePresets && self.scheduleTimeZoneOffset == other.scheduleTimeZoneOffset && -// self.serialNumber == other.serialNumber && // MARK: Planned for removal in https://tidepool.atlassian.net/browse/LOOP-5130 self.softwareVersion == other.softwareVersion && self.units == other.units } @@ -711,7 +704,6 @@ extension TPumpSettingsDatum: EffectivelyEquivalent { // Ignore units as they are always specified var isEffectivelyEmpty: Bool { return activeScheduleName == nil && - automatedDelivery == nil && basal == nil && basalRateSchedule == nil && basalRateSchedules == nil && @@ -735,7 +727,6 @@ extension TPumpSettingsDatum: EffectivelyEquivalent { name == nil && overridePresets == nil && scheduleTimeZoneOffset == nil && - serialNumber == nil && softwareVersion == nil } } From 98545f004b978457f539d66b2b83af56b266b12c Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Tue, 29 Oct 2024 12:47:38 -0700 Subject: [PATCH 35/71] [LOOP-5132] Remove automatedDelivery and serialNumber from TPumpSettingsDatum and TCGMSettingsDatum --- TidepoolServiceKit/Extensions/StoredSettings.swift | 9 --------- TidepoolServiceKit/TidepoolService.swift | 9 --------- 2 files changed, 18 deletions(-) diff --git a/TidepoolServiceKit/Extensions/StoredSettings.swift b/TidepoolServiceKit/Extensions/StoredSettings.swift index 4b9a84f..2051827 100644 --- a/TidepoolServiceKit/Extensions/StoredSettings.swift +++ b/TidepoolServiceKit/Extensions/StoredSettings.swift @@ -65,7 +65,6 @@ extension StoredSettings: IdentifiableDatum { manufacturers: datumCGMManufacturers, model: datumCGMModel, name: datumCGMName, - serialNumber: datumCGMSerialNumber, softwareVersion: datumCGMSoftwareVersion, transmitterId: nil, // TODO: https://tidepool.atlassian.net/browse/LOOP-3929 units: datumCGMUnits, @@ -82,7 +81,6 @@ extension StoredSettings: IdentifiableDatum { func datumPumpSettings(for userId: String, hostIdentifier: String, hostVersion: String) -> TPumpSettingsDatum { let datum = TPumpSettingsDatum(time: datumTime, activeScheduleName: datumPumpActiveScheduleName, - automatedDelivery: datumPumpAutomatedDelivery, basal: datumPumpBasal, basalRateSchedules: datumPumpBasalRateSchedules, bloodGlucoseSafetyLimit: datumPumpBloodGlucoseSafetyLimit, @@ -102,7 +100,6 @@ extension StoredSettings: IdentifiableDatum { name: datumPumpName, overridePresets: datumPumpOverridePresets, scheduleTimeZoneOffset: datumPumpScheduleTimeZoneOffset, - serialNumber: datumPumpSerialNumber, softwareVersion: datumPumpSoftwareVersion, units: datumPumpUnits) let origin = datumOrigin(for: resolvedIdentifier(for: TPumpSettingsDatum.self), hostIdentifier: hostIdentifier, hostVersion: hostVersion) @@ -145,8 +142,6 @@ extension StoredSettings: IdentifiableDatum { private var datumCGMName: String? { cgmDevice?.name } - private var datumCGMSerialNumber: String? { cgmDevice?.localIdentifier } - private var datumCGMSoftwareVersion: String? { cgmDevice?.softwareVersion } private var datumCGMUnits: TCGMSettingsDatum.Units { .milligramsPerDeciliter } @@ -155,8 +150,6 @@ extension StoredSettings: IdentifiableDatum { return Self.activeScheduleNameDefault } - private var datumPumpAutomatedDelivery: Bool { dosingEnabled } - private var datumPumpBasal: TPumpSettingsDatum.Basal? { guard let maximumBasalRatePerHour = maximumBasalRatePerHour else { return nil @@ -293,8 +286,6 @@ extension StoredSettings: IdentifiableDatum { return TimeInterval(seconds: scheduleTimeZone.secondsFromGMT(for: date)) } - private var datumPumpSerialNumber: String? { pumpDevice?.localIdentifier } - private var datumPumpSoftwareVersion: String? { pumpDevice?.softwareVersion } private var datumPumpUnits: TPumpSettingsDatum.Units { diff --git a/TidepoolServiceKit/TidepoolService.swift b/TidepoolServiceKit/TidepoolService.swift index 699f06b..5514e85 100644 --- a/TidepoolServiceKit/TidepoolService.swift +++ b/TidepoolServiceKit/TidepoolService.swift @@ -468,12 +468,9 @@ extension TidepoolService: RemoteDataService { let controllerSettingsDatumIsEffectivelyEquivalent = TControllerSettingsDatum.areEffectivelyEquivalent(old: lastControllerSettingsDatum, new: controllerSettingsDatum) let cgmSettingsDatum = $0.datumCGMSettings(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) - cgmSettingsDatum.serialNumber = nil // MARK: Planned for removal in https://tidepool.atlassian.net/browse/LOOP-5130 let cgmSettingsDatumIsEffectivelyEquivalent = TCGMSettingsDatum.areEffectivelyEquivalent(old: lastCGMSettingsDatum, new: cgmSettingsDatum) let pumpSettingsDatum = $0.datumPumpSettings(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) - pumpSettingsDatum.serialNumber = nil // MARK: Planned for removal in https://tidepool.atlassian.net/browse/LOOP-5130 - pumpSettingsDatum.automatedDelivery = nil // MARK: Planned for removal in https://tidepool.atlassian.net/browse/LOOP-5130 let pumpSettingsDatumIsEffectivelyEquivalent = TPumpSettingsDatum.areEffectivelyEquivalent(old: lastPumpSettingsDatum, new: pumpSettingsDatum) // Associate the data @@ -643,7 +640,6 @@ extension TCGMSettingsDatum: EffectivelyEquivalent { self.manufacturers == other.manufacturers && self.model == other.model && self.name == other.name && -// self.serialNumber == other.serialNumber && // MARK: Planned for removal in https://tidepool.atlassian.net/browse/LOOP-5130 self.softwareVersion == other.softwareVersion && self.transmitterId == other.transmitterId && self.units == other.units && @@ -662,7 +658,6 @@ extension TCGMSettingsDatum: EffectivelyEquivalent { manufacturers == nil && model == nil && name == nil && - serialNumber == nil && softwareVersion == nil && transmitterId == nil && defaultAlerts == nil && @@ -679,7 +674,6 @@ extension TPumpSettingsDatum: EffectivelyEquivalent { // All TDatum properties can be ignored for this datum type func isEffectivelyEquivalent(to other: TPumpSettingsDatum) -> Bool { return self.activeScheduleName == other.activeScheduleName && -// self.automatedDelivery == other.automatedDelivery && // MARK: Planned for removal in https://tidepool.atlassian.net/browse/LOOP-5130 self.basal == other.basal && self.basalRateSchedule == other.basalRateSchedule && self.basalRateSchedules == other.basalRateSchedules && @@ -703,7 +697,6 @@ extension TPumpSettingsDatum: EffectivelyEquivalent { self.name == other.name && self.overridePresets == other.overridePresets && self.scheduleTimeZoneOffset == other.scheduleTimeZoneOffset && -// self.serialNumber == other.serialNumber && // MARK: Planned for removal in https://tidepool.atlassian.net/browse/LOOP-5130 self.softwareVersion == other.softwareVersion && self.units == other.units } @@ -711,7 +704,6 @@ extension TPumpSettingsDatum: EffectivelyEquivalent { // Ignore units as they are always specified var isEffectivelyEmpty: Bool { return activeScheduleName == nil && - automatedDelivery == nil && basal == nil && basalRateSchedule == nil && basalRateSchedules == nil && @@ -735,7 +727,6 @@ extension TPumpSettingsDatum: EffectivelyEquivalent { name == nil && overridePresets == nil && scheduleTimeZoneOffset == nil && - serialNumber == nil && softwareVersion == nil } } From 3b3216ce8e08f8bac00f6c914adcd24e7bb99e0b Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Tue, 29 Oct 2024 12:57:57 -0700 Subject: [PATCH 36/71] [LOOP-5132] Remove automatedDelivery and serialNumber from TPumpSettingsDatum and TCGMSettingsDatum --- TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift b/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift index 8e33ddf..0d985eb 100644 --- a/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift +++ b/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift @@ -78,7 +78,6 @@ class StoredSettingsTests: XCTestCase { "payload" : { "syncIdentifier" : "2A67A303-1234-4CB8-1234-79498265368E" }, - "serialNumber" : "CGM Local Identifier", "softwareVersion" : "CGM Software Version", "time" : "2020-05-14T22:48:15.000Z", "timezone" : "America/Los_Angeles", @@ -95,7 +94,6 @@ class StoredSettingsTests: XCTestCase { XCTAssertEqual(String(data: data, encoding: .utf8), """ { "activeSchedule" : "Default", - "automatedDelivery" : true, "basal" : { "rateMaximum" : { "units" : "Units/hour", @@ -231,7 +229,6 @@ class StoredSettingsTests: XCTestCase { "payload" : { "syncIdentifier" : "2A67A303-1234-4CB8-1234-79498265368E" }, - "serialNumber" : "Pump Local Identifier", "softwareVersion" : "Pump Software Version", "time" : "2020-05-14T22:48:15.000Z", "timezone" : "America/Los_Angeles", From 8c7256364eb32e9ec9627baffa8093d9309aae2c Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Tue, 29 Oct 2024 12:57:57 -0700 Subject: [PATCH 37/71] [LOOP-5132] Remove automatedDelivery and serialNumber from TPumpSettingsDatum and TCGMSettingsDatum --- TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift b/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift index 8e33ddf..0d985eb 100644 --- a/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift +++ b/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift @@ -78,7 +78,6 @@ class StoredSettingsTests: XCTestCase { "payload" : { "syncIdentifier" : "2A67A303-1234-4CB8-1234-79498265368E" }, - "serialNumber" : "CGM Local Identifier", "softwareVersion" : "CGM Software Version", "time" : "2020-05-14T22:48:15.000Z", "timezone" : "America/Los_Angeles", @@ -95,7 +94,6 @@ class StoredSettingsTests: XCTestCase { XCTAssertEqual(String(data: data, encoding: .utf8), """ { "activeSchedule" : "Default", - "automatedDelivery" : true, "basal" : { "rateMaximum" : { "units" : "Units/hour", @@ -231,7 +229,6 @@ class StoredSettingsTests: XCTestCase { "payload" : { "syncIdentifier" : "2A67A303-1234-4CB8-1234-79498265368E" }, - "serialNumber" : "Pump Local Identifier", "softwareVersion" : "Pump Software Version", "time" : "2020-05-14T22:48:15.000Z", "timezone" : "America/Los_Angeles", From 6da74c22ead8b905751465c0530f3ca87698bf3a Mon Sep 17 00:00:00 2001 From: Nathaniel Hamming Date: Wed, 30 Oct 2024 16:31:41 -0300 Subject: [PATCH 38/71] [PAL-818] plugin dependency (#112) * do not allow deleting of the service when it is a dependency * pass allowDebugFeatures to service --- TidepoolService.xcodeproj/project.pbxproj | 4 ++++ TidepoolServiceKit/TidepoolService.swift | 8 +++++++- .../Extensions/EnvironmentValues.swift | 20 +++++++++++++++++++ TidepoolServiceKitUI/SettingsView.swift | 16 ++++++++++----- TidepoolServiceKitUI/TidepoolService+UI.swift | 12 +++++------ 5 files changed, 48 insertions(+), 12 deletions(-) create mode 100644 TidepoolServiceKitUI/Extensions/EnvironmentValues.swift diff --git a/TidepoolService.xcodeproj/project.pbxproj b/TidepoolService.xcodeproj/project.pbxproj index 76a25dd..ed7e8ac 100644 --- a/TidepoolService.xcodeproj/project.pbxproj +++ b/TidepoolService.xcodeproj/project.pbxproj @@ -63,6 +63,7 @@ A9E8C611272C76A500016E2E /* TimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9E8C610272C76A500016E2E /* TimeInterval.swift */; }; A9F9F317271A046E00D19374 /* StoredCarbEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9F9F316271A046E00D19374 /* StoredCarbEntry.swift */; }; A9F9F319271A05B100D19374 /* IdentifiableHKDatum.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9F9F318271A05B100D19374 /* IdentifiableHKDatum.swift */; }; + B40B20CC2CD2AC600027BF35 /* EnvironmentValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40B20CB2CD2AC600027BF35 /* EnvironmentValues.swift */; }; C110888F2A39149100BA4898 /* BuildDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = C110888E2A39149100BA4898 /* BuildDetails.swift */; }; C124239D2A58771A00EAC89E /* TidepoolKit in Frameworks */ = {isa = PBXBuildFile; productRef = C124239C2A58771A00EAC89E /* TidepoolKit */; }; C12E4BBA288F2215009C98A2 /* TidepoolServiceKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9DAACFF22E7987800E76C9F /* TidepoolServiceKit.framework */; platformFilter = ios; }; @@ -224,6 +225,7 @@ A9E8C610272C76A500016E2E /* TimeInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeInterval.swift; sourceTree = ""; }; A9F9F316271A046E00D19374 /* StoredCarbEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoredCarbEntry.swift; sourceTree = ""; }; A9F9F318271A05B100D19374 /* IdentifiableHKDatum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentifiableHKDatum.swift; sourceTree = ""; }; + B40B20CB2CD2AC600027BF35 /* EnvironmentValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentValues.swift; sourceTree = ""; }; C110888E2A39149100BA4898 /* BuildDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildDetails.swift; sourceTree = ""; }; C12522E1298309B5006EA1CD /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; C1317D4129830A0800625B94 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = ""; }; @@ -482,6 +484,7 @@ C1D0B62A29848BD90098D215 /* Extensions */ = { isa = PBXGroup; children = ( + B40B20CB2CD2AC600027BF35 /* EnvironmentValues.swift */, C1D0B62B29848BEB0098D215 /* Image.swift */, C1C9414529F0CB21008D3E05 /* UIImage.swift */, ); @@ -806,6 +809,7 @@ A97651762421AA11002EB5D4 /* OSLog.swift in Sources */, A9DAAD3422E7CA1A00E76C9F /* LocalizedString.swift in Sources */, A9DAAD3922E7DEE000E76C9F /* TidepoolService+UI.swift in Sources */, + B40B20CC2CD2AC600027BF35 /* EnvironmentValues.swift in Sources */, A9DAAD6F22E7EA9700E76C9F /* NibLoadable.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/TidepoolServiceKit/TidepoolService.swift b/TidepoolServiceKit/TidepoolService.swift index 5514e85..7da1509 100644 --- a/TidepoolServiceKit/TidepoolService.swift +++ b/TidepoolServiceKit/TidepoolService.swift @@ -60,7 +60,7 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { public let tapi: TAPI = TAPI(clientId: BuildDetails.default.tidepoolServiceClientId, redirectURL: BuildDetails.default.tidepoolServiceRedirectURL) - public private (set) var error: Error? + public private(set) var error: Error? private let id: String @@ -77,6 +77,8 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { private let tidepoolKitLog = OSLog(category: "TidepoolKit") private var deviceLogUploader: DeviceLogUploader? + + public var isDependency: Bool = false private func setDeviceLogUploaderDelegate() async { await deviceLogUploader?.setDelegate(remoteDataServiceDelegate) @@ -137,6 +139,10 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { } public var isOnboarded = false // No distinction between created and onboarded + + public func markAsDepedency(_ isDependency: Bool) { + self.isDependency = isDependency + } @Published public var session: TSession? diff --git a/TidepoolServiceKitUI/Extensions/EnvironmentValues.swift b/TidepoolServiceKitUI/Extensions/EnvironmentValues.swift new file mode 100644 index 0000000..56b465b --- /dev/null +++ b/TidepoolServiceKitUI/Extensions/EnvironmentValues.swift @@ -0,0 +1,20 @@ +// +// EnvironmentValues.swift +// TidepoolService +// +// Created by Nathaniel Hamming on 2024-10-30. +// Copyright © 2024 LoopKit Authors. All rights reserved. +// + +import SwiftUI + +private struct AllowDebugFeaturesKey: EnvironmentKey { + static let defaultValue: Bool = false +} + +public extension EnvironmentValues { + var allowDebugFeatures: Bool { + get { self[AllowDebugFeaturesKey.self] } + set { self[AllowDebugFeaturesKey.self] = newValue } + } +} diff --git a/TidepoolServiceKitUI/SettingsView.swift b/TidepoolServiceKitUI/SettingsView.swift index bacffd3..3a61ac8 100644 --- a/TidepoolServiceKitUI/SettingsView.swift +++ b/TidepoolServiceKitUI/SettingsView.swift @@ -11,7 +11,8 @@ import TidepoolKit import TidepoolServiceKit public struct SettingsView: View { - + @Environment(\.allowDebugFeatures) var allowDebugFeatures + @State private var isEnvironmentActionSheetPresented = false @State private var showingDeletionConfirmation = false @@ -28,7 +29,12 @@ public struct SettingsView: View { private let onboarding: Bool var isLoggedIn: Bool { - return service.session != nil + service.session != nil + } + + var canDeleteService: Bool { + guard !allowDebugFeatures else { return true } + return !service.isDependency } public init(service: TidepoolService, login: ((TEnvironment) async throws -> Void)?, dismiss: (() -> Void)?, onboarding: Bool) @@ -97,11 +103,11 @@ public struct SettingsView: View { .padding() } Spacer() - if isLoggedIn && !onboarding { + if isLoggedIn && !onboarding && canDeleteService { deleteServiceButton - } else if isLoggedIn { + } else if isLoggedIn && onboarding { continueButton - } else { + } else if !isLoggedIn { loginButton } } diff --git a/TidepoolServiceKitUI/TidepoolService+UI.swift b/TidepoolServiceKitUI/TidepoolService+UI.swift index 06e3d2c..39634d7 100644 --- a/TidepoolServiceKitUI/TidepoolService+UI.swift +++ b/TidepoolServiceKitUI/TidepoolService+UI.swift @@ -34,7 +34,7 @@ extension TidepoolService: @retroactive ServiceUI { UIImage(frameworkImage: "Tidepool Logo") } - public static func setupViewController(pluginHost: PluginHost, onboarding: Bool) -> SetupUIResult { + public static func setupViewController(pluginHost: PluginHost, onboarding: Bool, allowDebugFeatures: Bool) -> SetupUIResult { let navController = ServiceNavigationController() navController.isNavigationBarHidden = true @@ -58,7 +58,7 @@ extension TidepoolService: @retroactive ServiceUI { Task { await navController.notifyComplete() } - }, onboarding: onboarding) + }, onboarding: onboarding).environment(\.allowDebugFeatures, allowDebugFeatures) let hostingController = await UIHostingController(rootView: settingsView) await navController.pushViewController(hostingController, animated: false) @@ -67,11 +67,11 @@ extension TidepoolService: @retroactive ServiceUI { return .userInteractionRequired(navController) } - public static func setupViewController(colorPalette: LoopUIColorPalette, pluginHost: PluginHost) -> SetupUIResult { - return setupViewController(pluginHost: pluginHost, onboarding: false) + public static func setupViewController(colorPalette: LoopUIColorPalette, pluginHost: PluginHost, allowDebugFeatures: Bool) -> SetupUIResult { + return setupViewController(pluginHost: pluginHost, onboarding: false, allowDebugFeatures: allowDebugFeatures) } - public func settingsViewController(colorPalette: LoopUIColorPalette) -> ServiceViewController { + public func settingsViewController(colorPalette: LoopUIColorPalette, allowDebugFeatures: Bool) -> ServiceViewController { let navController = ServiceNavigationController() navController.isNavigationBarHidden = true @@ -92,7 +92,7 @@ extension TidepoolService: @retroactive ServiceUI { Task { await navController.notifyComplete() } - }, onboarding: false) + }, onboarding: false).environment(\.allowDebugFeatures, allowDebugFeatures) let hostingController = await UIHostingController(rootView: settingsView) await navController.pushViewController(hostingController, animated: false) From 342c8e9e07e90559a84722daaa114fd3aee26595 Mon Sep 17 00:00:00 2001 From: Nathaniel Hamming Date: Wed, 30 Oct 2024 16:31:41 -0300 Subject: [PATCH 39/71] [PAL-818] plugin dependency (#112) * do not allow deleting of the service when it is a dependency * pass allowDebugFeatures to service --- TidepoolService.xcodeproj/project.pbxproj | 4 ++++ TidepoolServiceKit/TidepoolService.swift | 8 +++++++- .../Extensions/EnvironmentValues.swift | 20 +++++++++++++++++++ TidepoolServiceKitUI/SettingsView.swift | 16 ++++++++++----- TidepoolServiceKitUI/TidepoolService+UI.swift | 12 +++++------ 5 files changed, 48 insertions(+), 12 deletions(-) create mode 100644 TidepoolServiceKitUI/Extensions/EnvironmentValues.swift diff --git a/TidepoolService.xcodeproj/project.pbxproj b/TidepoolService.xcodeproj/project.pbxproj index 76a25dd..ed7e8ac 100644 --- a/TidepoolService.xcodeproj/project.pbxproj +++ b/TidepoolService.xcodeproj/project.pbxproj @@ -63,6 +63,7 @@ A9E8C611272C76A500016E2E /* TimeInterval.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9E8C610272C76A500016E2E /* TimeInterval.swift */; }; A9F9F317271A046E00D19374 /* StoredCarbEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9F9F316271A046E00D19374 /* StoredCarbEntry.swift */; }; A9F9F319271A05B100D19374 /* IdentifiableHKDatum.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9F9F318271A05B100D19374 /* IdentifiableHKDatum.swift */; }; + B40B20CC2CD2AC600027BF35 /* EnvironmentValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = B40B20CB2CD2AC600027BF35 /* EnvironmentValues.swift */; }; C110888F2A39149100BA4898 /* BuildDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = C110888E2A39149100BA4898 /* BuildDetails.swift */; }; C124239D2A58771A00EAC89E /* TidepoolKit in Frameworks */ = {isa = PBXBuildFile; productRef = C124239C2A58771A00EAC89E /* TidepoolKit */; }; C12E4BBA288F2215009C98A2 /* TidepoolServiceKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A9DAACFF22E7987800E76C9F /* TidepoolServiceKit.framework */; platformFilter = ios; }; @@ -224,6 +225,7 @@ A9E8C610272C76A500016E2E /* TimeInterval.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeInterval.swift; sourceTree = ""; }; A9F9F316271A046E00D19374 /* StoredCarbEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoredCarbEntry.swift; sourceTree = ""; }; A9F9F318271A05B100D19374 /* IdentifiableHKDatum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentifiableHKDatum.swift; sourceTree = ""; }; + B40B20CB2CD2AC600027BF35 /* EnvironmentValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentValues.swift; sourceTree = ""; }; C110888E2A39149100BA4898 /* BuildDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildDetails.swift; sourceTree = ""; }; C12522E1298309B5006EA1CD /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = ""; }; C1317D4129830A0800625B94 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Localizable.strings; sourceTree = ""; }; @@ -482,6 +484,7 @@ C1D0B62A29848BD90098D215 /* Extensions */ = { isa = PBXGroup; children = ( + B40B20CB2CD2AC600027BF35 /* EnvironmentValues.swift */, C1D0B62B29848BEB0098D215 /* Image.swift */, C1C9414529F0CB21008D3E05 /* UIImage.swift */, ); @@ -806,6 +809,7 @@ A97651762421AA11002EB5D4 /* OSLog.swift in Sources */, A9DAAD3422E7CA1A00E76C9F /* LocalizedString.swift in Sources */, A9DAAD3922E7DEE000E76C9F /* TidepoolService+UI.swift in Sources */, + B40B20CC2CD2AC600027BF35 /* EnvironmentValues.swift in Sources */, A9DAAD6F22E7EA9700E76C9F /* NibLoadable.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/TidepoolServiceKit/TidepoolService.swift b/TidepoolServiceKit/TidepoolService.swift index 5514e85..7da1509 100644 --- a/TidepoolServiceKit/TidepoolService.swift +++ b/TidepoolServiceKit/TidepoolService.swift @@ -60,7 +60,7 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { public let tapi: TAPI = TAPI(clientId: BuildDetails.default.tidepoolServiceClientId, redirectURL: BuildDetails.default.tidepoolServiceRedirectURL) - public private (set) var error: Error? + public private(set) var error: Error? private let id: String @@ -77,6 +77,8 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { private let tidepoolKitLog = OSLog(category: "TidepoolKit") private var deviceLogUploader: DeviceLogUploader? + + public var isDependency: Bool = false private func setDeviceLogUploaderDelegate() async { await deviceLogUploader?.setDelegate(remoteDataServiceDelegate) @@ -137,6 +139,10 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { } public var isOnboarded = false // No distinction between created and onboarded + + public func markAsDepedency(_ isDependency: Bool) { + self.isDependency = isDependency + } @Published public var session: TSession? diff --git a/TidepoolServiceKitUI/Extensions/EnvironmentValues.swift b/TidepoolServiceKitUI/Extensions/EnvironmentValues.swift new file mode 100644 index 0000000..56b465b --- /dev/null +++ b/TidepoolServiceKitUI/Extensions/EnvironmentValues.swift @@ -0,0 +1,20 @@ +// +// EnvironmentValues.swift +// TidepoolService +// +// Created by Nathaniel Hamming on 2024-10-30. +// Copyright © 2024 LoopKit Authors. All rights reserved. +// + +import SwiftUI + +private struct AllowDebugFeaturesKey: EnvironmentKey { + static let defaultValue: Bool = false +} + +public extension EnvironmentValues { + var allowDebugFeatures: Bool { + get { self[AllowDebugFeaturesKey.self] } + set { self[AllowDebugFeaturesKey.self] = newValue } + } +} diff --git a/TidepoolServiceKitUI/SettingsView.swift b/TidepoolServiceKitUI/SettingsView.swift index bacffd3..3a61ac8 100644 --- a/TidepoolServiceKitUI/SettingsView.swift +++ b/TidepoolServiceKitUI/SettingsView.swift @@ -11,7 +11,8 @@ import TidepoolKit import TidepoolServiceKit public struct SettingsView: View { - + @Environment(\.allowDebugFeatures) var allowDebugFeatures + @State private var isEnvironmentActionSheetPresented = false @State private var showingDeletionConfirmation = false @@ -28,7 +29,12 @@ public struct SettingsView: View { private let onboarding: Bool var isLoggedIn: Bool { - return service.session != nil + service.session != nil + } + + var canDeleteService: Bool { + guard !allowDebugFeatures else { return true } + return !service.isDependency } public init(service: TidepoolService, login: ((TEnvironment) async throws -> Void)?, dismiss: (() -> Void)?, onboarding: Bool) @@ -97,11 +103,11 @@ public struct SettingsView: View { .padding() } Spacer() - if isLoggedIn && !onboarding { + if isLoggedIn && !onboarding && canDeleteService { deleteServiceButton - } else if isLoggedIn { + } else if isLoggedIn && onboarding { continueButton - } else { + } else if !isLoggedIn { loginButton } } diff --git a/TidepoolServiceKitUI/TidepoolService+UI.swift b/TidepoolServiceKitUI/TidepoolService+UI.swift index 06e3d2c..39634d7 100644 --- a/TidepoolServiceKitUI/TidepoolService+UI.swift +++ b/TidepoolServiceKitUI/TidepoolService+UI.swift @@ -34,7 +34,7 @@ extension TidepoolService: @retroactive ServiceUI { UIImage(frameworkImage: "Tidepool Logo") } - public static func setupViewController(pluginHost: PluginHost, onboarding: Bool) -> SetupUIResult { + public static func setupViewController(pluginHost: PluginHost, onboarding: Bool, allowDebugFeatures: Bool) -> SetupUIResult { let navController = ServiceNavigationController() navController.isNavigationBarHidden = true @@ -58,7 +58,7 @@ extension TidepoolService: @retroactive ServiceUI { Task { await navController.notifyComplete() } - }, onboarding: onboarding) + }, onboarding: onboarding).environment(\.allowDebugFeatures, allowDebugFeatures) let hostingController = await UIHostingController(rootView: settingsView) await navController.pushViewController(hostingController, animated: false) @@ -67,11 +67,11 @@ extension TidepoolService: @retroactive ServiceUI { return .userInteractionRequired(navController) } - public static func setupViewController(colorPalette: LoopUIColorPalette, pluginHost: PluginHost) -> SetupUIResult { - return setupViewController(pluginHost: pluginHost, onboarding: false) + public static func setupViewController(colorPalette: LoopUIColorPalette, pluginHost: PluginHost, allowDebugFeatures: Bool) -> SetupUIResult { + return setupViewController(pluginHost: pluginHost, onboarding: false, allowDebugFeatures: allowDebugFeatures) } - public func settingsViewController(colorPalette: LoopUIColorPalette) -> ServiceViewController { + public func settingsViewController(colorPalette: LoopUIColorPalette, allowDebugFeatures: Bool) -> ServiceViewController { let navController = ServiceNavigationController() navController.isNavigationBarHidden = true @@ -92,7 +92,7 @@ extension TidepoolService: @retroactive ServiceUI { Task { await navController.notifyComplete() } - }, onboarding: false) + }, onboarding: false).environment(\.allowDebugFeatures, allowDebugFeatures) let hostingController = await UIHostingController(rootView: settingsView) await navController.pushViewController(hostingController, animated: false) From 90f543081e98ca84382c1cfd77371b75671a1e36 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Thu, 31 Oct 2024 14:23:07 -0500 Subject: [PATCH 40/71] Enable dsym production (#113) --- TidepoolService.xcodeproj/project.pbxproj | 5 +++-- TidepoolServiceKit/Extensions/DoseEntry.swift | 2 -- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/TidepoolService.xcodeproj/project.pbxproj b/TidepoolService.xcodeproj/project.pbxproj index ed7e8ac..80687fc 100644 --- a/TidepoolService.xcodeproj/project.pbxproj +++ b/TidepoolService.xcodeproj/project.pbxproj @@ -1030,7 +1030,7 @@ CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = dwarf; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -1140,6 +1140,7 @@ CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -1424,7 +1425,7 @@ CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = dwarf; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; diff --git a/TidepoolServiceKit/Extensions/DoseEntry.swift b/TidepoolServiceKit/Extensions/DoseEntry.swift index 257d6eb..3152cd8 100644 --- a/TidepoolServiceKit/Extensions/DoseEntry.swift +++ b/TidepoolServiceKit/Extensions/DoseEntry.swift @@ -100,8 +100,6 @@ extension DoseEntry: IdentifiableDatum { private func dataForBolus(for userId: String, hostIdentifier: String, hostVersion: String) -> [TDatum] { if manuallyEntered { return dataForBolusManuallyEntered(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) - - } else if automatic != true { return dataForBolusManual(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) } else { From feb23ad6e4757625d7cad33d753742df9460b58a Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Thu, 31 Oct 2024 14:23:07 -0500 Subject: [PATCH 41/71] Enable dsym production (#113) --- TidepoolService.xcodeproj/project.pbxproj | 5 +++-- TidepoolServiceKit/Extensions/DoseEntry.swift | 2 -- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/TidepoolService.xcodeproj/project.pbxproj b/TidepoolService.xcodeproj/project.pbxproj index ed7e8ac..80687fc 100644 --- a/TidepoolService.xcodeproj/project.pbxproj +++ b/TidepoolService.xcodeproj/project.pbxproj @@ -1030,7 +1030,7 @@ CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = dwarf; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -1140,6 +1140,7 @@ CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; @@ -1424,7 +1425,7 @@ CLANG_WARN__EXIT_TIME_DESTRUCTORS = YES; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = dwarf; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; FRAMEWORK_SEARCH_PATHS = "$(inherited)"; diff --git a/TidepoolServiceKit/Extensions/DoseEntry.swift b/TidepoolServiceKit/Extensions/DoseEntry.swift index 257d6eb..3152cd8 100644 --- a/TidepoolServiceKit/Extensions/DoseEntry.swift +++ b/TidepoolServiceKit/Extensions/DoseEntry.swift @@ -100,8 +100,6 @@ extension DoseEntry: IdentifiableDatum { private func dataForBolus(for userId: String, hostIdentifier: String, hostVersion: String) -> [TDatum] { if manuallyEntered { return dataForBolusManuallyEntered(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) - - } else if automatic != true { return dataForBolusManual(for: userId, hostIdentifier: hostIdentifier, hostVersion: hostVersion) } else { From 78d5f70ef7d0b08f3a89fa869ddcb763e98d49c6 Mon Sep 17 00:00:00 2001 From: Nathaniel Hamming Date: Wed, 20 Nov 2024 16:08:37 -0400 Subject: [PATCH 42/71] [LOOP-5150] detect demo account (#114) --- TidepoolServiceKit/TidepoolService.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/TidepoolServiceKit/TidepoolService.swift b/TidepoolServiceKit/TidepoolService.swift index 7da1509..56e1526 100644 --- a/TidepoolServiceKit/TidepoolService.swift +++ b/TidepoolServiceKit/TidepoolService.swift @@ -145,6 +145,10 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { } @Published public var session: TSession? + + public var isDemoAccount: Bool { + session?.userRoles.contains("demo") ?? false + } public func apiDidUpdateSession(_ session: TSession?) { guard session != self.session else { From 530723fba5858b4279c8ec58f48808c03086228b Mon Sep 17 00:00:00 2001 From: Nathaniel Hamming Date: Wed, 20 Nov 2024 16:08:37 -0400 Subject: [PATCH 43/71] [LOOP-5150] detect demo account (#114) --- TidepoolServiceKit/TidepoolService.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/TidepoolServiceKit/TidepoolService.swift b/TidepoolServiceKit/TidepoolService.swift index 7da1509..56e1526 100644 --- a/TidepoolServiceKit/TidepoolService.swift +++ b/TidepoolServiceKit/TidepoolService.swift @@ -145,6 +145,10 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { } @Published public var session: TSession? + + public var isDemoAccount: Bool { + session?.userRoles.contains("demo") ?? false + } public func apiDidUpdateSession(_ session: TSession?) { guard session != self.session else { From ecae6f29ec6d3243fba8b621d5eeea77b36e976d Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Thu, 21 Nov 2024 14:32:16 -0800 Subject: [PATCH 44/71] [LOOP-5153] Remove HealthKit dependency from LoopAlgorithm --- TidepoolServiceKit/Extensions/DoseEntry.swift | 4 ++-- TidepoolServiceKit/Extensions/Double.swift | 8 +++---- .../Extensions/GlucoseRangeSchedule.swift | 4 ++-- .../Extensions/SingleQuantitySchedule.swift | 4 ++-- .../Extensions/StoredDosingDecision.swift | 12 +++++----- .../Extensions/StoredSettings.swift | 2 +- .../Extensions/SyncCarbObject.swift | 2 +- .../Extensions/DoseEntryTests.swift | 20 ++++++++--------- .../Extensions/PersistedPumpEventTests.swift | 6 ++--- .../StoredDosingDecisionTests.swift | 22 +++++++++---------- .../Extensions/StoredGlucoseSampleTests.swift | 18 +++++++-------- .../Extensions/StoredSettingsTests.swift | 5 +++-- 12 files changed, 54 insertions(+), 53 deletions(-) diff --git a/TidepoolServiceKit/Extensions/DoseEntry.swift b/TidepoolServiceKit/Extensions/DoseEntry.swift index 3152cd8..9b0525f 100644 --- a/TidepoolServiceKit/Extensions/DoseEntry.swift +++ b/TidepoolServiceKit/Extensions/DoseEntry.swift @@ -371,7 +371,7 @@ extension DoseEntry { if isMutable { var newDose = self let basal = basalHistory.first! - newDose.scheduledBasalRate = HKQuantity(unit: .internationalUnitsPerHour, doubleValue: basal.value) + newDose.scheduledBasalRate = LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: basal.value) return [newDose] } @@ -401,7 +401,7 @@ extension DoseEntry { var annotatedDose = self annotatedDose.startDate = segmentStartDate annotatedDose.endDate = segmentEndDate - annotatedDose.scheduledBasalRate = HKQuantity(unit: .internationalUnitsPerHour, doubleValue: basalItem.value) + annotatedDose.scheduledBasalRate = LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: basalItem.value) if let deliveredUnits { annotatedDose.deliveredUnits = deliveredUnits * segmentPortion diff --git a/TidepoolServiceKit/Extensions/Double.swift b/TidepoolServiceKit/Extensions/Double.swift index 9307ef7..4f55fc4 100644 --- a/TidepoolServiceKit/Extensions/Double.swift +++ b/TidepoolServiceKit/Extensions/Double.swift @@ -6,11 +6,11 @@ // Copyright © 2021 LoopKit Authors. All rights reserved. // -import HealthKit +import LoopAlgorithm import LoopKit extension DoubleRange { - func converted(from: HKUnit, to: HKUnit) -> DoubleRange { + func converted(from: LoopUnit, to: LoopUnit) -> DoubleRange { guard from != to else { return self } @@ -19,10 +19,10 @@ extension DoubleRange { } extension Double { - func converted(from: HKUnit, to: HKUnit) -> Double { + func converted(from: LoopUnit, to: LoopUnit) -> Double { guard from != to else { return self } - return HKQuantity(unit: from, doubleValue: self).doubleValue(for: to) + return LoopQuantity(unit: from, doubleValue: self).doubleValue(for: to) } } diff --git a/TidepoolServiceKit/Extensions/GlucoseRangeSchedule.swift b/TidepoolServiceKit/Extensions/GlucoseRangeSchedule.swift index 6257444..8f6939c 100644 --- a/TidepoolServiceKit/Extensions/GlucoseRangeSchedule.swift +++ b/TidepoolServiceKit/Extensions/GlucoseRangeSchedule.swift @@ -6,11 +6,11 @@ // Copyright © 2021 LoopKit Authors. All rights reserved. // -import HealthKit +import LoopAlgorithm import LoopKit extension GlucoseRangeSchedule { - func items(for unit: HKUnit) -> [RepeatingScheduleValue] { + func items(for unit: LoopUnit) -> [RepeatingScheduleValue] { guard unit != self.unit else { return items } diff --git a/TidepoolServiceKit/Extensions/SingleQuantitySchedule.swift b/TidepoolServiceKit/Extensions/SingleQuantitySchedule.swift index 5576b2d..e83c3f7 100644 --- a/TidepoolServiceKit/Extensions/SingleQuantitySchedule.swift +++ b/TidepoolServiceKit/Extensions/SingleQuantitySchedule.swift @@ -6,11 +6,11 @@ // Copyright © 2021 LoopKit Authors. All rights reserved. // -import HealthKit +import LoopAlgorithm import LoopKit extension SingleQuantitySchedule { - func items(for unit: HKUnit) -> [RepeatingScheduleValue] { + func items(for unit: LoopUnit) -> [RepeatingScheduleValue] { guard unit != self.unit else { return items } diff --git a/TidepoolServiceKit/Extensions/StoredDosingDecision.swift b/TidepoolServiceKit/Extensions/StoredDosingDecision.swift index 24e3f56..6c963d4 100644 --- a/TidepoolServiceKit/Extensions/StoredDosingDecision.swift +++ b/TidepoolServiceKit/Extensions/StoredDosingDecision.swift @@ -6,7 +6,7 @@ // Copyright © 2021 LoopKit Authors. All rights reserved. // -import HealthKit +import LoopAlgorithm import LoopKit import TidepoolKit @@ -126,7 +126,7 @@ extension StoredDosingDecision: IdentifiableDatum { guard let originalCarbEntry = originalCarbEntry else { return nil } - let carbohydrate = TDosingDecisionDatum.Nutrition.Carbohydrate(net: originalCarbEntry.quantity.doubleValue(for: .gram()), units: .grams) + let carbohydrate = TDosingDecisionDatum.Nutrition.Carbohydrate(net: originalCarbEntry.quantity.doubleValue(for: .gram), units: .grams) let nutrition = TDosingDecisionDatum.Nutrition(carbohydrate: carbohydrate, estimatedAbsorptionDuration: originalCarbEntry.absorptionTime) return TDosingDecisionDatum.Food(time: originalCarbEntry.startDate, nutrition: nutrition) } @@ -135,7 +135,7 @@ extension StoredDosingDecision: IdentifiableDatum { guard let carbEntry = carbEntry else { return nil } - let carbohydrate = TDosingDecisionDatum.Nutrition.Carbohydrate(net: carbEntry.quantity.doubleValue(for: .gram()), units: .grams) + let carbohydrate = TDosingDecisionDatum.Nutrition.Carbohydrate(net: carbEntry.quantity.doubleValue(for: .gram), units: .grams) let nutrition = TDosingDecisionDatum.Nutrition(carbohydrate: carbohydrate, estimatedAbsorptionDuration: carbEntry.absorptionTime) return TDosingDecisionDatum.Food(time: carbEntry.startDate, nutrition: nutrition) } @@ -152,7 +152,7 @@ extension StoredDosingDecision: IdentifiableDatum { guard let carbsOnBoard = carbsOnBoard else { return nil } - return TDosingDecisionDatum.CarbohydratesOnBoard(time: carbsOnBoard.startDate, amount: carbsOnBoard.quantity.doubleValue(for: .gram()).rounded(decimalPlaces: 4)) + return TDosingDecisionDatum.CarbohydratesOnBoard(time: carbsOnBoard.startDate, amount: carbsOnBoard.quantity.doubleValue(for: .gram).rounded(decimalPlaces: 4)) } private var datumInsulinOnBoard: TDosingDecisionDatum.InsulinOnBoard? { @@ -345,8 +345,8 @@ fileprivate extension DoseEntry { } } -fileprivate extension HKQuantity { - func doubleValueClampedAndRounded(for unit: HKUnit) -> Double { +fileprivate extension LoopQuantity { + func doubleValueClampedAndRounded(for unit: LoopUnit) -> Double { let value = doubleValue(for: unit) switch unit { case .milligramsPerDeciliter: diff --git a/TidepoolServiceKit/Extensions/StoredSettings.swift b/TidepoolServiceKit/Extensions/StoredSettings.swift index 2051827..8df9712 100644 --- a/TidepoolServiceKit/Extensions/StoredSettings.swift +++ b/TidepoolServiceKit/Extensions/StoredSettings.swift @@ -205,7 +205,7 @@ extension StoredSettings: IdentifiableDatum { guard let carbRatioSchedule = carbRatioSchedule else { return nil } - return [Self.activeScheduleNameDefault: carbRatioSchedule.items(for: .gram()).map { TPumpSettingsDatum.CarbohydrateRatioStart(start: $0.startTime, amount: $0.value) }] + return [Self.activeScheduleNameDefault: carbRatioSchedule.items(for: .gram).map { TPumpSettingsDatum.CarbohydrateRatioStart(start: $0.startTime, amount: $0.value) }] } private var datumPumpDisplay: TPumpSettingsDatum.Display? { diff --git a/TidepoolServiceKit/Extensions/SyncCarbObject.swift b/TidepoolServiceKit/Extensions/SyncCarbObject.swift index 5203776..d758ad0 100644 --- a/TidepoolServiceKit/Extensions/SyncCarbObject.swift +++ b/TidepoolServiceKit/Extensions/SyncCarbObject.swift @@ -55,7 +55,7 @@ extension SyncCarbObject: IdentifiableHKDatum { } private var datumCarbohydrate: TFoodDatum.Nutrition.Carbohydrate { - return TFoodDatum.Nutrition.Carbohydrate(net: quantity.doubleValue(for: .gram()), units: .grams) + return TFoodDatum.Nutrition.Carbohydrate(net: quantity.doubleValue(for: .gram), units: .grams) } private var datumPayload: TDictionary? { diff --git a/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift b/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift index 06a2a10..5fc44c3 100644 --- a/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift +++ b/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift @@ -29,7 +29,7 @@ class DoseEntryDataTests: XCTestCase { deliveredUnits: nil, description: "Test Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", - scheduledBasalRate: HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), + scheduledBasalRate: LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), insulinType: .novolog, automatic: true, manuallyEntered: false) @@ -332,7 +332,7 @@ class DoseEntryDataTests: XCTestCase { deliveredUnits: nil, description: "Test Suspend Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", - scheduledBasalRate: HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), + scheduledBasalRate: LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), insulinType: .fiasp, automatic: true, manuallyEntered: false) @@ -375,7 +375,7 @@ class DoseEntryDataTests: XCTestCase { deliveredUnits: 0.5, description: "Test Temp Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", - scheduledBasalRate: HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), + scheduledBasalRate: LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), insulinType: .fiasp, automatic: false, manuallyEntered: false) @@ -427,7 +427,7 @@ class DoseEntryDataTests: XCTestCase { deliveredUnits: 0.5, description: "Test Temp Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", - scheduledBasalRate: HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), + scheduledBasalRate: LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), insulinType: .fiasp, automatic: false, manuallyEntered: false, @@ -484,7 +484,7 @@ class DoseEntryDataTests: XCTestCase { deliveredUnits: 0.5, description: "Test Temp Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", - scheduledBasalRate: HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), + scheduledBasalRate: LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), insulinType: .fiasp, manuallyEntered: false) let data = doseEntry.data(for: "2B03D96C-6F5D-4140-99CD-80C3E64D6011", hostIdentifier: hostIdentifier, hostVersion: hostVersion) @@ -536,7 +536,7 @@ class DoseEntryDataTests: XCTestCase { deliveredUnits: 0.5, description: "Test Temp Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", - scheduledBasalRate: HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), + scheduledBasalRate: LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), insulinType: .fiasp, manuallyEntered: false, isMutable: true) @@ -604,7 +604,7 @@ class DoseEntrySelectorTests: XCTestCase { deliveredUnits: nil, description: "Test Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", - scheduledBasalRate: HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), + scheduledBasalRate: LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), insulinType: .novolog, automatic: true, manuallyEntered: false) @@ -684,7 +684,7 @@ class DoseEntrySelectorTests: XCTestCase { deliveredUnits: nil, description: "Test Suspend Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", - scheduledBasalRate: HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), + scheduledBasalRate: LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), insulinType: .fiasp, automatic: true, manuallyEntered: false) @@ -700,7 +700,7 @@ class DoseEntrySelectorTests: XCTestCase { deliveredUnits: 0.5, description: "Test Temp Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", - scheduledBasalRate: HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), + scheduledBasalRate: LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), insulinType: .fiasp, automatic: false, manuallyEntered: false) @@ -716,7 +716,7 @@ class DoseEntrySelectorTests: XCTestCase { deliveredUnits: 0.5, description: "Test Temp Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", - scheduledBasalRate: HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), + scheduledBasalRate: LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), insulinType: .fiasp, manuallyEntered: false) XCTAssertEqual(doseEntry.selectors, [TDatum.Selector(origin: TDatum.Selector.Origin(id: "ab0a722d639669875017a899a5214677:basal/automated"))]) diff --git a/TidepoolServiceKitTests/Extensions/PersistedPumpEventTests.swift b/TidepoolServiceKitTests/Extensions/PersistedPumpEventTests.swift index 348a7d3..e7b5bca 100644 --- a/TidepoolServiceKitTests/Extensions/PersistedPumpEventTests.swift +++ b/TidepoolServiceKitTests/Extensions/PersistedPumpEventTests.swift @@ -8,7 +8,7 @@ import XCTest import Foundation -import HealthKit +import LoopAlgorithm import LoopKit @testable import TidepoolServiceKit @@ -259,7 +259,7 @@ class PersistedPumpEventTests: XCTestCase { deliveredUnits: nil, description: "Test Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", - scheduledBasalRate: HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), + scheduledBasalRate: LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), insulinType: .novolog, automatic: true, manuallyEntered: false), @@ -668,7 +668,7 @@ class PersistedPumpEventTests: XCTestCase { deliveredUnits: 0.5, description: "Test Temp Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", - scheduledBasalRate: HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), + scheduledBasalRate: LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), insulinType: .fiasp, automatic: true, manuallyEntered: false), diff --git a/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift b/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift index 668c1cf..190ed20 100644 --- a/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift +++ b/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift @@ -286,13 +286,13 @@ fileprivate extension StoredDosingDecision { let lastReservoirValue = StoredDosingDecision.LastReservoirValue(startDate: dateFormatter.date(from: "2020-05-14T22:07:19Z")!, unitVolume: 113.3) let historicalGlucose = [HistoricalGlucoseValue(startDate: dateFormatter.date(from: "2020-05-14T22:29:15Z")!, - quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 117.3)), + quantity: LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 117.3)), HistoricalGlucoseValue(startDate: dateFormatter.date(from: "2020-05-14T22:33:15Z")!, - quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 119.5)), + quantity: LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 119.5)), HistoricalGlucoseValue(startDate: dateFormatter.date(from: "2020-05-14T22:38:15Z")!, - quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 121.8))] + quantity: LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 121.8))] let originalCarbEntry = StoredCarbEntry(startDate: dateFormatter.date(from: "2020-01-02T03:00:23Z")!, - quantity: HKQuantity(unit: .gram(), doubleValue: 19), + quantity: LoopQuantity(unit: .gram, doubleValue: 19), uuid: UUID(uuidString: "18CF3948-0B3D-4B12-8BFE-14986B0E6784")!, provenanceIdentifier: "com.loopkit.loop", syncIdentifier: "2B03D96C-6F5D-4140-99CD-80C3E64D6010", @@ -303,7 +303,7 @@ fileprivate extension StoredDosingDecision { userCreatedDate: dateFormatter.date(from: "2020-05-14T22:06:12Z")!, userUpdatedDate: nil) let carbEntry = StoredCarbEntry(startDate: dateFormatter.date(from: "2020-01-02T03:00:23Z")!, - quantity: HKQuantity(unit: .gram(), doubleValue: 29), + quantity: LoopQuantity(unit: .gram, doubleValue: 29), uuid: UUID(uuidString: "135CDABE-9343-7242-4233-1020384789AE")!, provenanceIdentifier: "com.loopkit.loop", syncIdentifier: "2B03D96C-6F5D-4140-99CD-80C3E64D6010", @@ -318,10 +318,10 @@ fileprivate extension StoredDosingDecision { syncIdentifier: "d3876f59-adb3-4a4f-8b29-315cda22062e", syncVersion: 1, startDate: dateFormatter.date(from: "2020-05-14T22:09:00Z")!, - quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 400), + quantity: LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 400), condition: .aboveRange, trend: .downDownDown, - trendRate: HKQuantity(unit: .milligramsPerDeciliterPerMinute, doubleValue: -10.2), + trendRate: LoopQuantity(unit: .milligramsPerDeciliterPerMinute, doubleValue: -10.2), isDisplayOnly: false, wasUserEntered: true, device: HKDevice(name: "Device Name", @@ -346,17 +346,17 @@ fileprivate extension StoredDosingDecision { start: dateFormatter.date(from: "2020-05-14T21:12:17Z")!, end: dateFormatter.date(from: "2020-05-14T23:12:17Z")!)) let predictedGlucose = [PredictedGlucoseValue(startDate: dateFormatter.date(from: "2020-05-14T22:43:15Z")!, - quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 123.3)), + quantity: LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 123.3)), PredictedGlucoseValue(startDate: dateFormatter.date(from: "2020-05-14T22:48:15Z")!, - quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 125.5)), + quantity: LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 125.5)), PredictedGlucoseValue(startDate: dateFormatter.date(from: "2020-05-14T22:53:15Z")!, - quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 127.8))] + quantity: LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 127.8))] let tempBasalRecommendation = TempBasalRecommendation(unitsPerHour: 0.75, duration: .minutes(30)) let automaticDoseRecommendation = AutomaticDoseRecommendation(basalAdjustment: tempBasalRecommendation, bolusUnits: 1.25) let manualBolusRecommendation = ManualBolusRecommendationWithDate(recommendation: ManualBolusRecommendation(amount: 1.2, notice: .predictedGlucoseBelowTarget(minGlucose: SimpleGlucoseValue(startDate: dateFormatter.date(from: "2020-05-14T23:03:15Z")!, - quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 75.5)))), + quantity: LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 75.5)))), date: dateFormatter.date(from: "2020-05-14T22:38:16Z")!) let manualBolusRequested = 0.8 let warnings: [Issue] = [Issue(id: "one"), diff --git a/TidepoolServiceKitTests/Extensions/StoredGlucoseSampleTests.swift b/TidepoolServiceKitTests/Extensions/StoredGlucoseSampleTests.swift index ac1c165..25e93be 100644 --- a/TidepoolServiceKitTests/Extensions/StoredGlucoseSampleTests.swift +++ b/TidepoolServiceKitTests/Extensions/StoredGlucoseSampleTests.swift @@ -7,7 +7,7 @@ // import XCTest -import HealthKit +import LoopAlgorithm import TidepoolKit import LoopKit @testable import TidepoolServiceKit @@ -19,7 +19,7 @@ class StoredGlucoseSampleTests: XCTestCase { syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", syncVersion: 1, startDate: Self.dateFormatter.date(from: "2020-01-02T03:00:23Z")!, - quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 123), + quantity: LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 123), condition: nil, trend: nil, trendRate: nil, @@ -57,7 +57,7 @@ class StoredGlucoseSampleTests: XCTestCase { syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", syncVersion: 2, startDate: Self.dateFormatter.date(from: "2020-01-02T03:00:23Z")!, - quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 167), + quantity: LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 167), condition: nil, trend: nil, trendRate: nil, @@ -95,10 +95,10 @@ class StoredGlucoseSampleTests: XCTestCase { syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", syncVersion: 3, startDate: Self.dateFormatter.date(from: "2020-01-02T03:00:23Z")!, - quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 123), + quantity: LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 123), condition: nil, trend: .flat, - trendRate: HKQuantity(unit: .milligramsPerDeciliterPerMinute, doubleValue: 0.1), + trendRate: LoopQuantity(unit: .milligramsPerDeciliterPerMinute, doubleValue: 0.1), isDisplayOnly: false, wasUserEntered: false, device: nil, @@ -134,10 +134,10 @@ class StoredGlucoseSampleTests: XCTestCase { syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", syncVersion: 4, startDate: Self.dateFormatter.date(from: "2020-01-02T03:00:23Z")!, - quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 40.0), + quantity: LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 40.0), condition: .belowRange, trend: .down, - trendRate: HKQuantity(unit: .milligramsPerDeciliterPerMinute, doubleValue: -1.0), + trendRate: LoopQuantity(unit: .milligramsPerDeciliterPerMinute, doubleValue: -1.0), isDisplayOnly: false, wasUserEntered: false, device: nil, @@ -180,10 +180,10 @@ class StoredGlucoseSampleTests: XCTestCase { syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", syncVersion: 5, startDate: Self.dateFormatter.date(from: "2020-01-02T03:00:23Z")!, - quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 400.0), + quantity: LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 400.0), condition: .aboveRange, trend: .upUp, - trendRate: HKQuantity(unit: .milligramsPerDeciliterPerMinute, doubleValue: 4.0), + trendRate: LoopQuantity(unit: .milligramsPerDeciliterPerMinute, doubleValue: 4.0), isDisplayOnly: false, wasUserEntered: false, device: nil, diff --git a/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift b/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift index 0d985eb..5e85976 100644 --- a/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift +++ b/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift @@ -8,6 +8,7 @@ import XCTest import HealthKit +import LoopAlgorithm import LoopKit import TidepoolKit @testable import TidepoolServiceKit @@ -303,7 +304,7 @@ fileprivate extension StoredSettings { RepeatingScheduleValue(startTime: .hours(3), value: 40.0), RepeatingScheduleValue(startTime: .hours(15), value: 50.0)], timeZone: scheduleTimeZone) - let carbRatioSchedule = CarbRatioSchedule(unit: .gram(), + let carbRatioSchedule = CarbRatioSchedule(unit: .gram, dailyItems: [RepeatingScheduleValue(startTime: .hours(0), value: 15.0), RepeatingScheduleValue(startTime: .hours(9), value: 14.0), RepeatingScheduleValue(startTime: .hours(20), value: 18.0)], @@ -344,7 +345,7 @@ fileprivate extension StoredSettings { softwareVersion: "Pump Software Version", localIdentifier: "Pump Local Identifier", udiDeviceIdentifier: "Pump UDI Device Identifier") - let bloodGlucoseUnit = HKUnit.milligramsPerDeciliter + let bloodGlucoseUnit = LoopUnit.milligramsPerDeciliter return StoredSettings(date: dateFormatter.date(from: "2020-05-14T22:48:15Z")!, controllerTimeZone: controllerTimeZone, From 1c1ae33a39ed3a83ca6f6f9c34bf6b522dda77d3 Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Thu, 21 Nov 2024 14:32:16 -0800 Subject: [PATCH 45/71] [LOOP-5153] Remove HealthKit dependency from LoopAlgorithm --- TidepoolServiceKit/Extensions/DoseEntry.swift | 4 ++-- TidepoolServiceKit/Extensions/Double.swift | 8 +++---- .../Extensions/GlucoseRangeSchedule.swift | 4 ++-- .../Extensions/SingleQuantitySchedule.swift | 4 ++-- .../Extensions/StoredDosingDecision.swift | 12 +++++----- .../Extensions/StoredSettings.swift | 2 +- .../Extensions/SyncCarbObject.swift | 2 +- .../Extensions/DoseEntryTests.swift | 20 ++++++++--------- .../Extensions/PersistedPumpEventTests.swift | 6 ++--- .../StoredDosingDecisionTests.swift | 22 +++++++++---------- .../Extensions/StoredGlucoseSampleTests.swift | 18 +++++++-------- .../Extensions/StoredSettingsTests.swift | 5 +++-- 12 files changed, 54 insertions(+), 53 deletions(-) diff --git a/TidepoolServiceKit/Extensions/DoseEntry.swift b/TidepoolServiceKit/Extensions/DoseEntry.swift index 3152cd8..9b0525f 100644 --- a/TidepoolServiceKit/Extensions/DoseEntry.swift +++ b/TidepoolServiceKit/Extensions/DoseEntry.swift @@ -371,7 +371,7 @@ extension DoseEntry { if isMutable { var newDose = self let basal = basalHistory.first! - newDose.scheduledBasalRate = HKQuantity(unit: .internationalUnitsPerHour, doubleValue: basal.value) + newDose.scheduledBasalRate = LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: basal.value) return [newDose] } @@ -401,7 +401,7 @@ extension DoseEntry { var annotatedDose = self annotatedDose.startDate = segmentStartDate annotatedDose.endDate = segmentEndDate - annotatedDose.scheduledBasalRate = HKQuantity(unit: .internationalUnitsPerHour, doubleValue: basalItem.value) + annotatedDose.scheduledBasalRate = LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: basalItem.value) if let deliveredUnits { annotatedDose.deliveredUnits = deliveredUnits * segmentPortion diff --git a/TidepoolServiceKit/Extensions/Double.swift b/TidepoolServiceKit/Extensions/Double.swift index 9307ef7..4f55fc4 100644 --- a/TidepoolServiceKit/Extensions/Double.swift +++ b/TidepoolServiceKit/Extensions/Double.swift @@ -6,11 +6,11 @@ // Copyright © 2021 LoopKit Authors. All rights reserved. // -import HealthKit +import LoopAlgorithm import LoopKit extension DoubleRange { - func converted(from: HKUnit, to: HKUnit) -> DoubleRange { + func converted(from: LoopUnit, to: LoopUnit) -> DoubleRange { guard from != to else { return self } @@ -19,10 +19,10 @@ extension DoubleRange { } extension Double { - func converted(from: HKUnit, to: HKUnit) -> Double { + func converted(from: LoopUnit, to: LoopUnit) -> Double { guard from != to else { return self } - return HKQuantity(unit: from, doubleValue: self).doubleValue(for: to) + return LoopQuantity(unit: from, doubleValue: self).doubleValue(for: to) } } diff --git a/TidepoolServiceKit/Extensions/GlucoseRangeSchedule.swift b/TidepoolServiceKit/Extensions/GlucoseRangeSchedule.swift index 6257444..8f6939c 100644 --- a/TidepoolServiceKit/Extensions/GlucoseRangeSchedule.swift +++ b/TidepoolServiceKit/Extensions/GlucoseRangeSchedule.swift @@ -6,11 +6,11 @@ // Copyright © 2021 LoopKit Authors. All rights reserved. // -import HealthKit +import LoopAlgorithm import LoopKit extension GlucoseRangeSchedule { - func items(for unit: HKUnit) -> [RepeatingScheduleValue] { + func items(for unit: LoopUnit) -> [RepeatingScheduleValue] { guard unit != self.unit else { return items } diff --git a/TidepoolServiceKit/Extensions/SingleQuantitySchedule.swift b/TidepoolServiceKit/Extensions/SingleQuantitySchedule.swift index 5576b2d..e83c3f7 100644 --- a/TidepoolServiceKit/Extensions/SingleQuantitySchedule.swift +++ b/TidepoolServiceKit/Extensions/SingleQuantitySchedule.swift @@ -6,11 +6,11 @@ // Copyright © 2021 LoopKit Authors. All rights reserved. // -import HealthKit +import LoopAlgorithm import LoopKit extension SingleQuantitySchedule { - func items(for unit: HKUnit) -> [RepeatingScheduleValue] { + func items(for unit: LoopUnit) -> [RepeatingScheduleValue] { guard unit != self.unit else { return items } diff --git a/TidepoolServiceKit/Extensions/StoredDosingDecision.swift b/TidepoolServiceKit/Extensions/StoredDosingDecision.swift index 24e3f56..6c963d4 100644 --- a/TidepoolServiceKit/Extensions/StoredDosingDecision.swift +++ b/TidepoolServiceKit/Extensions/StoredDosingDecision.swift @@ -6,7 +6,7 @@ // Copyright © 2021 LoopKit Authors. All rights reserved. // -import HealthKit +import LoopAlgorithm import LoopKit import TidepoolKit @@ -126,7 +126,7 @@ extension StoredDosingDecision: IdentifiableDatum { guard let originalCarbEntry = originalCarbEntry else { return nil } - let carbohydrate = TDosingDecisionDatum.Nutrition.Carbohydrate(net: originalCarbEntry.quantity.doubleValue(for: .gram()), units: .grams) + let carbohydrate = TDosingDecisionDatum.Nutrition.Carbohydrate(net: originalCarbEntry.quantity.doubleValue(for: .gram), units: .grams) let nutrition = TDosingDecisionDatum.Nutrition(carbohydrate: carbohydrate, estimatedAbsorptionDuration: originalCarbEntry.absorptionTime) return TDosingDecisionDatum.Food(time: originalCarbEntry.startDate, nutrition: nutrition) } @@ -135,7 +135,7 @@ extension StoredDosingDecision: IdentifiableDatum { guard let carbEntry = carbEntry else { return nil } - let carbohydrate = TDosingDecisionDatum.Nutrition.Carbohydrate(net: carbEntry.quantity.doubleValue(for: .gram()), units: .grams) + let carbohydrate = TDosingDecisionDatum.Nutrition.Carbohydrate(net: carbEntry.quantity.doubleValue(for: .gram), units: .grams) let nutrition = TDosingDecisionDatum.Nutrition(carbohydrate: carbohydrate, estimatedAbsorptionDuration: carbEntry.absorptionTime) return TDosingDecisionDatum.Food(time: carbEntry.startDate, nutrition: nutrition) } @@ -152,7 +152,7 @@ extension StoredDosingDecision: IdentifiableDatum { guard let carbsOnBoard = carbsOnBoard else { return nil } - return TDosingDecisionDatum.CarbohydratesOnBoard(time: carbsOnBoard.startDate, amount: carbsOnBoard.quantity.doubleValue(for: .gram()).rounded(decimalPlaces: 4)) + return TDosingDecisionDatum.CarbohydratesOnBoard(time: carbsOnBoard.startDate, amount: carbsOnBoard.quantity.doubleValue(for: .gram).rounded(decimalPlaces: 4)) } private var datumInsulinOnBoard: TDosingDecisionDatum.InsulinOnBoard? { @@ -345,8 +345,8 @@ fileprivate extension DoseEntry { } } -fileprivate extension HKQuantity { - func doubleValueClampedAndRounded(for unit: HKUnit) -> Double { +fileprivate extension LoopQuantity { + func doubleValueClampedAndRounded(for unit: LoopUnit) -> Double { let value = doubleValue(for: unit) switch unit { case .milligramsPerDeciliter: diff --git a/TidepoolServiceKit/Extensions/StoredSettings.swift b/TidepoolServiceKit/Extensions/StoredSettings.swift index 2051827..8df9712 100644 --- a/TidepoolServiceKit/Extensions/StoredSettings.swift +++ b/TidepoolServiceKit/Extensions/StoredSettings.swift @@ -205,7 +205,7 @@ extension StoredSettings: IdentifiableDatum { guard let carbRatioSchedule = carbRatioSchedule else { return nil } - return [Self.activeScheduleNameDefault: carbRatioSchedule.items(for: .gram()).map { TPumpSettingsDatum.CarbohydrateRatioStart(start: $0.startTime, amount: $0.value) }] + return [Self.activeScheduleNameDefault: carbRatioSchedule.items(for: .gram).map { TPumpSettingsDatum.CarbohydrateRatioStart(start: $0.startTime, amount: $0.value) }] } private var datumPumpDisplay: TPumpSettingsDatum.Display? { diff --git a/TidepoolServiceKit/Extensions/SyncCarbObject.swift b/TidepoolServiceKit/Extensions/SyncCarbObject.swift index 5203776..d758ad0 100644 --- a/TidepoolServiceKit/Extensions/SyncCarbObject.swift +++ b/TidepoolServiceKit/Extensions/SyncCarbObject.swift @@ -55,7 +55,7 @@ extension SyncCarbObject: IdentifiableHKDatum { } private var datumCarbohydrate: TFoodDatum.Nutrition.Carbohydrate { - return TFoodDatum.Nutrition.Carbohydrate(net: quantity.doubleValue(for: .gram()), units: .grams) + return TFoodDatum.Nutrition.Carbohydrate(net: quantity.doubleValue(for: .gram), units: .grams) } private var datumPayload: TDictionary? { diff --git a/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift b/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift index 06a2a10..5fc44c3 100644 --- a/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift +++ b/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift @@ -29,7 +29,7 @@ class DoseEntryDataTests: XCTestCase { deliveredUnits: nil, description: "Test Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", - scheduledBasalRate: HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), + scheduledBasalRate: LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), insulinType: .novolog, automatic: true, manuallyEntered: false) @@ -332,7 +332,7 @@ class DoseEntryDataTests: XCTestCase { deliveredUnits: nil, description: "Test Suspend Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", - scheduledBasalRate: HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), + scheduledBasalRate: LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), insulinType: .fiasp, automatic: true, manuallyEntered: false) @@ -375,7 +375,7 @@ class DoseEntryDataTests: XCTestCase { deliveredUnits: 0.5, description: "Test Temp Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", - scheduledBasalRate: HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), + scheduledBasalRate: LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), insulinType: .fiasp, automatic: false, manuallyEntered: false) @@ -427,7 +427,7 @@ class DoseEntryDataTests: XCTestCase { deliveredUnits: 0.5, description: "Test Temp Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", - scheduledBasalRate: HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), + scheduledBasalRate: LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), insulinType: .fiasp, automatic: false, manuallyEntered: false, @@ -484,7 +484,7 @@ class DoseEntryDataTests: XCTestCase { deliveredUnits: 0.5, description: "Test Temp Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", - scheduledBasalRate: HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), + scheduledBasalRate: LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), insulinType: .fiasp, manuallyEntered: false) let data = doseEntry.data(for: "2B03D96C-6F5D-4140-99CD-80C3E64D6011", hostIdentifier: hostIdentifier, hostVersion: hostVersion) @@ -536,7 +536,7 @@ class DoseEntryDataTests: XCTestCase { deliveredUnits: 0.5, description: "Test Temp Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", - scheduledBasalRate: HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), + scheduledBasalRate: LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), insulinType: .fiasp, manuallyEntered: false, isMutable: true) @@ -604,7 +604,7 @@ class DoseEntrySelectorTests: XCTestCase { deliveredUnits: nil, description: "Test Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", - scheduledBasalRate: HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), + scheduledBasalRate: LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), insulinType: .novolog, automatic: true, manuallyEntered: false) @@ -684,7 +684,7 @@ class DoseEntrySelectorTests: XCTestCase { deliveredUnits: nil, description: "Test Suspend Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", - scheduledBasalRate: HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), + scheduledBasalRate: LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), insulinType: .fiasp, automatic: true, manuallyEntered: false) @@ -700,7 +700,7 @@ class DoseEntrySelectorTests: XCTestCase { deliveredUnits: 0.5, description: "Test Temp Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", - scheduledBasalRate: HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), + scheduledBasalRate: LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), insulinType: .fiasp, automatic: false, manuallyEntered: false) @@ -716,7 +716,7 @@ class DoseEntrySelectorTests: XCTestCase { deliveredUnits: 0.5, description: "Test Temp Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", - scheduledBasalRate: HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), + scheduledBasalRate: LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), insulinType: .fiasp, manuallyEntered: false) XCTAssertEqual(doseEntry.selectors, [TDatum.Selector(origin: TDatum.Selector.Origin(id: "ab0a722d639669875017a899a5214677:basal/automated"))]) diff --git a/TidepoolServiceKitTests/Extensions/PersistedPumpEventTests.swift b/TidepoolServiceKitTests/Extensions/PersistedPumpEventTests.swift index 348a7d3..e7b5bca 100644 --- a/TidepoolServiceKitTests/Extensions/PersistedPumpEventTests.swift +++ b/TidepoolServiceKitTests/Extensions/PersistedPumpEventTests.swift @@ -8,7 +8,7 @@ import XCTest import Foundation -import HealthKit +import LoopAlgorithm import LoopKit @testable import TidepoolServiceKit @@ -259,7 +259,7 @@ class PersistedPumpEventTests: XCTestCase { deliveredUnits: nil, description: "Test Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", - scheduledBasalRate: HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), + scheduledBasalRate: LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), insulinType: .novolog, automatic: true, manuallyEntered: false), @@ -668,7 +668,7 @@ class PersistedPumpEventTests: XCTestCase { deliveredUnits: 0.5, description: "Test Temp Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", - scheduledBasalRate: HKQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), + scheduledBasalRate: LoopQuantity(unit: .internationalUnitsPerHour, doubleValue: 2.0), insulinType: .fiasp, automatic: true, manuallyEntered: false), diff --git a/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift b/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift index 668c1cf..190ed20 100644 --- a/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift +++ b/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift @@ -286,13 +286,13 @@ fileprivate extension StoredDosingDecision { let lastReservoirValue = StoredDosingDecision.LastReservoirValue(startDate: dateFormatter.date(from: "2020-05-14T22:07:19Z")!, unitVolume: 113.3) let historicalGlucose = [HistoricalGlucoseValue(startDate: dateFormatter.date(from: "2020-05-14T22:29:15Z")!, - quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 117.3)), + quantity: LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 117.3)), HistoricalGlucoseValue(startDate: dateFormatter.date(from: "2020-05-14T22:33:15Z")!, - quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 119.5)), + quantity: LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 119.5)), HistoricalGlucoseValue(startDate: dateFormatter.date(from: "2020-05-14T22:38:15Z")!, - quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 121.8))] + quantity: LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 121.8))] let originalCarbEntry = StoredCarbEntry(startDate: dateFormatter.date(from: "2020-01-02T03:00:23Z")!, - quantity: HKQuantity(unit: .gram(), doubleValue: 19), + quantity: LoopQuantity(unit: .gram, doubleValue: 19), uuid: UUID(uuidString: "18CF3948-0B3D-4B12-8BFE-14986B0E6784")!, provenanceIdentifier: "com.loopkit.loop", syncIdentifier: "2B03D96C-6F5D-4140-99CD-80C3E64D6010", @@ -303,7 +303,7 @@ fileprivate extension StoredDosingDecision { userCreatedDate: dateFormatter.date(from: "2020-05-14T22:06:12Z")!, userUpdatedDate: nil) let carbEntry = StoredCarbEntry(startDate: dateFormatter.date(from: "2020-01-02T03:00:23Z")!, - quantity: HKQuantity(unit: .gram(), doubleValue: 29), + quantity: LoopQuantity(unit: .gram, doubleValue: 29), uuid: UUID(uuidString: "135CDABE-9343-7242-4233-1020384789AE")!, provenanceIdentifier: "com.loopkit.loop", syncIdentifier: "2B03D96C-6F5D-4140-99CD-80C3E64D6010", @@ -318,10 +318,10 @@ fileprivate extension StoredDosingDecision { syncIdentifier: "d3876f59-adb3-4a4f-8b29-315cda22062e", syncVersion: 1, startDate: dateFormatter.date(from: "2020-05-14T22:09:00Z")!, - quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 400), + quantity: LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 400), condition: .aboveRange, trend: .downDownDown, - trendRate: HKQuantity(unit: .milligramsPerDeciliterPerMinute, doubleValue: -10.2), + trendRate: LoopQuantity(unit: .milligramsPerDeciliterPerMinute, doubleValue: -10.2), isDisplayOnly: false, wasUserEntered: true, device: HKDevice(name: "Device Name", @@ -346,17 +346,17 @@ fileprivate extension StoredDosingDecision { start: dateFormatter.date(from: "2020-05-14T21:12:17Z")!, end: dateFormatter.date(from: "2020-05-14T23:12:17Z")!)) let predictedGlucose = [PredictedGlucoseValue(startDate: dateFormatter.date(from: "2020-05-14T22:43:15Z")!, - quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 123.3)), + quantity: LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 123.3)), PredictedGlucoseValue(startDate: dateFormatter.date(from: "2020-05-14T22:48:15Z")!, - quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 125.5)), + quantity: LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 125.5)), PredictedGlucoseValue(startDate: dateFormatter.date(from: "2020-05-14T22:53:15Z")!, - quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 127.8))] + quantity: LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 127.8))] let tempBasalRecommendation = TempBasalRecommendation(unitsPerHour: 0.75, duration: .minutes(30)) let automaticDoseRecommendation = AutomaticDoseRecommendation(basalAdjustment: tempBasalRecommendation, bolusUnits: 1.25) let manualBolusRecommendation = ManualBolusRecommendationWithDate(recommendation: ManualBolusRecommendation(amount: 1.2, notice: .predictedGlucoseBelowTarget(minGlucose: SimpleGlucoseValue(startDate: dateFormatter.date(from: "2020-05-14T23:03:15Z")!, - quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 75.5)))), + quantity: LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 75.5)))), date: dateFormatter.date(from: "2020-05-14T22:38:16Z")!) let manualBolusRequested = 0.8 let warnings: [Issue] = [Issue(id: "one"), diff --git a/TidepoolServiceKitTests/Extensions/StoredGlucoseSampleTests.swift b/TidepoolServiceKitTests/Extensions/StoredGlucoseSampleTests.swift index ac1c165..25e93be 100644 --- a/TidepoolServiceKitTests/Extensions/StoredGlucoseSampleTests.swift +++ b/TidepoolServiceKitTests/Extensions/StoredGlucoseSampleTests.swift @@ -7,7 +7,7 @@ // import XCTest -import HealthKit +import LoopAlgorithm import TidepoolKit import LoopKit @testable import TidepoolServiceKit @@ -19,7 +19,7 @@ class StoredGlucoseSampleTests: XCTestCase { syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", syncVersion: 1, startDate: Self.dateFormatter.date(from: "2020-01-02T03:00:23Z")!, - quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 123), + quantity: LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 123), condition: nil, trend: nil, trendRate: nil, @@ -57,7 +57,7 @@ class StoredGlucoseSampleTests: XCTestCase { syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", syncVersion: 2, startDate: Self.dateFormatter.date(from: "2020-01-02T03:00:23Z")!, - quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 167), + quantity: LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 167), condition: nil, trend: nil, trendRate: nil, @@ -95,10 +95,10 @@ class StoredGlucoseSampleTests: XCTestCase { syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", syncVersion: 3, startDate: Self.dateFormatter.date(from: "2020-01-02T03:00:23Z")!, - quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 123), + quantity: LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 123), condition: nil, trend: .flat, - trendRate: HKQuantity(unit: .milligramsPerDeciliterPerMinute, doubleValue: 0.1), + trendRate: LoopQuantity(unit: .milligramsPerDeciliterPerMinute, doubleValue: 0.1), isDisplayOnly: false, wasUserEntered: false, device: nil, @@ -134,10 +134,10 @@ class StoredGlucoseSampleTests: XCTestCase { syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", syncVersion: 4, startDate: Self.dateFormatter.date(from: "2020-01-02T03:00:23Z")!, - quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 40.0), + quantity: LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 40.0), condition: .belowRange, trend: .down, - trendRate: HKQuantity(unit: .milligramsPerDeciliterPerMinute, doubleValue: -1.0), + trendRate: LoopQuantity(unit: .milligramsPerDeciliterPerMinute, doubleValue: -1.0), isDisplayOnly: false, wasUserEntered: false, device: nil, @@ -180,10 +180,10 @@ class StoredGlucoseSampleTests: XCTestCase { syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", syncVersion: 5, startDate: Self.dateFormatter.date(from: "2020-01-02T03:00:23Z")!, - quantity: HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 400.0), + quantity: LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 400.0), condition: .aboveRange, trend: .upUp, - trendRate: HKQuantity(unit: .milligramsPerDeciliterPerMinute, doubleValue: 4.0), + trendRate: LoopQuantity(unit: .milligramsPerDeciliterPerMinute, doubleValue: 4.0), isDisplayOnly: false, wasUserEntered: false, device: nil, diff --git a/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift b/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift index 0d985eb..5e85976 100644 --- a/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift +++ b/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift @@ -8,6 +8,7 @@ import XCTest import HealthKit +import LoopAlgorithm import LoopKit import TidepoolKit @testable import TidepoolServiceKit @@ -303,7 +304,7 @@ fileprivate extension StoredSettings { RepeatingScheduleValue(startTime: .hours(3), value: 40.0), RepeatingScheduleValue(startTime: .hours(15), value: 50.0)], timeZone: scheduleTimeZone) - let carbRatioSchedule = CarbRatioSchedule(unit: .gram(), + let carbRatioSchedule = CarbRatioSchedule(unit: .gram, dailyItems: [RepeatingScheduleValue(startTime: .hours(0), value: 15.0), RepeatingScheduleValue(startTime: .hours(9), value: 14.0), RepeatingScheduleValue(startTime: .hours(20), value: 18.0)], @@ -344,7 +345,7 @@ fileprivate extension StoredSettings { softwareVersion: "Pump Software Version", localIdentifier: "Pump Local Identifier", udiDeviceIdentifier: "Pump UDI Device Identifier") - let bloodGlucoseUnit = HKUnit.milligramsPerDeciliter + let bloodGlucoseUnit = LoopUnit.milligramsPerDeciliter return StoredSettings(date: dateFormatter.date(from: "2020-05-14T22:48:15Z")!, controllerTimeZone: controllerTimeZone, From 19158f3247c681f5d52a4b5dacc845106afb3d99 Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Thu, 21 Nov 2024 18:21:36 -0800 Subject: [PATCH 46/71] [LOOP-5153] Remove HealthKit dependency from LoopAlgorithm --- TidepoolService.xcodeproj/project.pbxproj | 4 --- TidepoolServiceKit/Extensions/DoseEntry.swift | 1 - TidepoolServiceKit/Extensions/HKUnit.swift | 31 ------------------- .../Extensions/StoredSettings.swift | 1 - .../Extensions/DoseEntryTests.swift | 1 - .../Extensions/SyncCarbObjectTests.swift | 1 - 6 files changed, 39 deletions(-) delete mode 100644 TidepoolServiceKit/Extensions/HKUnit.swift diff --git a/TidepoolService.xcodeproj/project.pbxproj b/TidepoolService.xcodeproj/project.pbxproj index 80687fc..97e35fe 100644 --- a/TidepoolService.xcodeproj/project.pbxproj +++ b/TidepoolService.xcodeproj/project.pbxproj @@ -32,7 +32,6 @@ A98737CD2788E61400A6A23D /* InsulinType.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98737CC2788E61400A6A23D /* InsulinType.swift */; }; A9A53E2F271508D80050C0B1 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9A53E2E271508D80050C0B1 /* String.swift */; }; A9D10DB827AB2CCF00814B7B /* SyncAlertObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D10DB727AB2CCF00814B7B /* SyncAlertObject.swift */; }; - A9D1107C242289720091C620 /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D1107B242289720091C620 /* HKUnit.swift */; }; A9D1AC9B27B1E046008C5A12 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D1AC9A27B1E046008C5A12 /* Data.swift */; }; A9D1AC9D27B1E3C6008C5A12 /* DoseEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D1AC9C27B1E3C6008C5A12 /* DoseEntry.swift */; }; A9D1AC9F27B1E3D4008C5A12 /* DoseEntryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D1AC9E27B1E3D4008C5A12 /* DoseEntryTests.swift */; }; @@ -174,7 +173,6 @@ A9BF371C2418195C008D7F34 /* TidepoolKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = TidepoolKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; A9BF371E24181977008D7F34 /* TidepoolKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = TidepoolKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; A9D10DB727AB2CCF00814B7B /* SyncAlertObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncAlertObject.swift; sourceTree = ""; }; - A9D1107B242289720091C620 /* HKUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HKUnit.swift; sourceTree = ""; }; A9D1AC9A27B1E046008C5A12 /* Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = ""; }; A9D1AC9C27B1E3C6008C5A12 /* DoseEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoseEntry.swift; sourceTree = ""; }; A9D1AC9E27B1E3D4008C5A12 /* DoseEntryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoseEntryTests.swift; sourceTree = ""; }; @@ -313,7 +311,6 @@ A9D1AC9C27B1E3C6008C5A12 /* DoseEntry.swift */, A9752A96270B91E000E50750 /* Double.swift */, A9752A98270B93A700E50750 /* GlucoseRangeSchedule.swift */, - A9D1107B242289720091C620 /* HKUnit.swift */, A98737CC2788E61400A6A23D /* InsulinType.swift */, A98737CA2788DAE400A6A23D /* PersistedPumpEvent.swift */, A9752A9A270B941C00E50750 /* SingleQuantitySchedule.swift */, @@ -752,7 +749,6 @@ buildActionMask = 2147483647; files = ( A9309CAF2436C52900E02268 /* StoredGlucoseSample.swift in Sources */, - A9D1107C242289720091C620 /* HKUnit.swift in Sources */, A9752A9D270B972D00E50750 /* TimeInterval.swift in Sources */, A9309CA72435987000E02268 /* SyncCarbObject.swift in Sources */, A9A53E2F271508D80050C0B1 /* String.swift in Sources */, diff --git a/TidepoolServiceKit/Extensions/DoseEntry.swift b/TidepoolServiceKit/Extensions/DoseEntry.swift index 9b0525f..06eaa03 100644 --- a/TidepoolServiceKit/Extensions/DoseEntry.swift +++ b/TidepoolServiceKit/Extensions/DoseEntry.swift @@ -9,7 +9,6 @@ import LoopKit import TidepoolKit import LoopAlgorithm -import HealthKit /* DoseEntry diff --git a/TidepoolServiceKit/Extensions/HKUnit.swift b/TidepoolServiceKit/Extensions/HKUnit.swift deleted file mode 100644 index bf4dd3b..0000000 --- a/TidepoolServiceKit/Extensions/HKUnit.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// HKUnit.swift -// TidepoolServiceKit -// -// Created by Darin Krauss on 3/18/20. -// Copyright © 2020 LoopKit Authors. All rights reserved. -// - -import HealthKit - -extension HKUnit { - public static let milligramsPerDeciliter: HKUnit = { - return HKUnit.gramUnit(with: .milli).unitDivided(by: .literUnit(with: .deci)) - }() - - public static let millimolesPerLiter: HKUnit = { - return HKUnit.moleUnit(with: .milli, molarMass: HKUnitMolarMassBloodGlucose).unitDivided(by: .liter()) - }() - - public static let milligramsPerDeciliterPerMinute: HKUnit = { - return HKUnit.milligramsPerDeciliter.unitDivided(by: .minute()) - }() - - public static let millimolesPerLiterPerMinute: HKUnit = { - return HKUnit.millimolesPerLiter.unitDivided(by: .minute()) - }() - - public static let internationalUnitsPerHour: HKUnit = { - return HKUnit.internationalUnit().unitDivided(by: .hour()) - }() -} diff --git a/TidepoolServiceKit/Extensions/StoredSettings.swift b/TidepoolServiceKit/Extensions/StoredSettings.swift index 8df9712..5315d2a 100644 --- a/TidepoolServiceKit/Extensions/StoredSettings.swift +++ b/TidepoolServiceKit/Extensions/StoredSettings.swift @@ -6,7 +6,6 @@ // Copyright © 2021 LoopKit Authors. All rights reserved. // -import HealthKit import LoopKit import TidepoolKit diff --git a/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift b/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift index 5fc44c3..4ea07b5 100644 --- a/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift +++ b/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift @@ -10,7 +10,6 @@ import Foundation import XCTest import Foundation -import HealthKit import LoopKit import TidepoolKit import LoopAlgorithm diff --git a/TidepoolServiceKitTests/Extensions/SyncCarbObjectTests.swift b/TidepoolServiceKitTests/Extensions/SyncCarbObjectTests.swift index 8dea646..9818c1e 100644 --- a/TidepoolServiceKitTests/Extensions/SyncCarbObjectTests.swift +++ b/TidepoolServiceKitTests/Extensions/SyncCarbObjectTests.swift @@ -8,7 +8,6 @@ import Foundation import XCTest -import HealthKit import TidepoolKit import LoopKit @testable import TidepoolServiceKit From afe3357c1cad03925c401837922704b59893c53c Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Thu, 21 Nov 2024 18:21:36 -0800 Subject: [PATCH 47/71] [LOOP-5153] Remove HealthKit dependency from LoopAlgorithm --- TidepoolService.xcodeproj/project.pbxproj | 4 --- TidepoolServiceKit/Extensions/DoseEntry.swift | 1 - TidepoolServiceKit/Extensions/HKUnit.swift | 31 ------------------- .../Extensions/StoredSettings.swift | 1 - .../Extensions/DoseEntryTests.swift | 1 - .../Extensions/SyncCarbObjectTests.swift | 1 - 6 files changed, 39 deletions(-) delete mode 100644 TidepoolServiceKit/Extensions/HKUnit.swift diff --git a/TidepoolService.xcodeproj/project.pbxproj b/TidepoolService.xcodeproj/project.pbxproj index 80687fc..97e35fe 100644 --- a/TidepoolService.xcodeproj/project.pbxproj +++ b/TidepoolService.xcodeproj/project.pbxproj @@ -32,7 +32,6 @@ A98737CD2788E61400A6A23D /* InsulinType.swift in Sources */ = {isa = PBXBuildFile; fileRef = A98737CC2788E61400A6A23D /* InsulinType.swift */; }; A9A53E2F271508D80050C0B1 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9A53E2E271508D80050C0B1 /* String.swift */; }; A9D10DB827AB2CCF00814B7B /* SyncAlertObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D10DB727AB2CCF00814B7B /* SyncAlertObject.swift */; }; - A9D1107C242289720091C620 /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D1107B242289720091C620 /* HKUnit.swift */; }; A9D1AC9B27B1E046008C5A12 /* Data.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D1AC9A27B1E046008C5A12 /* Data.swift */; }; A9D1AC9D27B1E3C6008C5A12 /* DoseEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D1AC9C27B1E3C6008C5A12 /* DoseEntry.swift */; }; A9D1AC9F27B1E3D4008C5A12 /* DoseEntryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9D1AC9E27B1E3D4008C5A12 /* DoseEntryTests.swift */; }; @@ -174,7 +173,6 @@ A9BF371C2418195C008D7F34 /* TidepoolKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = TidepoolKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; A9BF371E24181977008D7F34 /* TidepoolKitUI.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = TidepoolKitUI.framework; sourceTree = BUILT_PRODUCTS_DIR; }; A9D10DB727AB2CCF00814B7B /* SyncAlertObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncAlertObject.swift; sourceTree = ""; }; - A9D1107B242289720091C620 /* HKUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HKUnit.swift; sourceTree = ""; }; A9D1AC9A27B1E046008C5A12 /* Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Data.swift; sourceTree = ""; }; A9D1AC9C27B1E3C6008C5A12 /* DoseEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoseEntry.swift; sourceTree = ""; }; A9D1AC9E27B1E3D4008C5A12 /* DoseEntryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoseEntryTests.swift; sourceTree = ""; }; @@ -313,7 +311,6 @@ A9D1AC9C27B1E3C6008C5A12 /* DoseEntry.swift */, A9752A96270B91E000E50750 /* Double.swift */, A9752A98270B93A700E50750 /* GlucoseRangeSchedule.swift */, - A9D1107B242289720091C620 /* HKUnit.swift */, A98737CC2788E61400A6A23D /* InsulinType.swift */, A98737CA2788DAE400A6A23D /* PersistedPumpEvent.swift */, A9752A9A270B941C00E50750 /* SingleQuantitySchedule.swift */, @@ -752,7 +749,6 @@ buildActionMask = 2147483647; files = ( A9309CAF2436C52900E02268 /* StoredGlucoseSample.swift in Sources */, - A9D1107C242289720091C620 /* HKUnit.swift in Sources */, A9752A9D270B972D00E50750 /* TimeInterval.swift in Sources */, A9309CA72435987000E02268 /* SyncCarbObject.swift in Sources */, A9A53E2F271508D80050C0B1 /* String.swift in Sources */, diff --git a/TidepoolServiceKit/Extensions/DoseEntry.swift b/TidepoolServiceKit/Extensions/DoseEntry.swift index 9b0525f..06eaa03 100644 --- a/TidepoolServiceKit/Extensions/DoseEntry.swift +++ b/TidepoolServiceKit/Extensions/DoseEntry.swift @@ -9,7 +9,6 @@ import LoopKit import TidepoolKit import LoopAlgorithm -import HealthKit /* DoseEntry diff --git a/TidepoolServiceKit/Extensions/HKUnit.swift b/TidepoolServiceKit/Extensions/HKUnit.swift deleted file mode 100644 index bf4dd3b..0000000 --- a/TidepoolServiceKit/Extensions/HKUnit.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// HKUnit.swift -// TidepoolServiceKit -// -// Created by Darin Krauss on 3/18/20. -// Copyright © 2020 LoopKit Authors. All rights reserved. -// - -import HealthKit - -extension HKUnit { - public static let milligramsPerDeciliter: HKUnit = { - return HKUnit.gramUnit(with: .milli).unitDivided(by: .literUnit(with: .deci)) - }() - - public static let millimolesPerLiter: HKUnit = { - return HKUnit.moleUnit(with: .milli, molarMass: HKUnitMolarMassBloodGlucose).unitDivided(by: .liter()) - }() - - public static let milligramsPerDeciliterPerMinute: HKUnit = { - return HKUnit.milligramsPerDeciliter.unitDivided(by: .minute()) - }() - - public static let millimolesPerLiterPerMinute: HKUnit = { - return HKUnit.millimolesPerLiter.unitDivided(by: .minute()) - }() - - public static let internationalUnitsPerHour: HKUnit = { - return HKUnit.internationalUnit().unitDivided(by: .hour()) - }() -} diff --git a/TidepoolServiceKit/Extensions/StoredSettings.swift b/TidepoolServiceKit/Extensions/StoredSettings.swift index 8df9712..5315d2a 100644 --- a/TidepoolServiceKit/Extensions/StoredSettings.swift +++ b/TidepoolServiceKit/Extensions/StoredSettings.swift @@ -6,7 +6,6 @@ // Copyright © 2021 LoopKit Authors. All rights reserved. // -import HealthKit import LoopKit import TidepoolKit diff --git a/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift b/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift index 5fc44c3..4ea07b5 100644 --- a/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift +++ b/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift @@ -10,7 +10,6 @@ import Foundation import XCTest import Foundation -import HealthKit import LoopKit import TidepoolKit import LoopAlgorithm diff --git a/TidepoolServiceKitTests/Extensions/SyncCarbObjectTests.swift b/TidepoolServiceKitTests/Extensions/SyncCarbObjectTests.swift index 8dea646..9818c1e 100644 --- a/TidepoolServiceKitTests/Extensions/SyncCarbObjectTests.swift +++ b/TidepoolServiceKitTests/Extensions/SyncCarbObjectTests.swift @@ -8,7 +8,6 @@ import Foundation import XCTest -import HealthKit import TidepoolKit import LoopKit @testable import TidepoolServiceKit From 34da6bf402f653b2f613236632c8020d878dd76c Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Wed, 27 Nov 2024 20:13:13 -0800 Subject: [PATCH 48/71] [LOOP-4754] Presets Storage --- TidepoolService.xcodeproj/project.pbxproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/TidepoolService.xcodeproj/project.pbxproj b/TidepoolService.xcodeproj/project.pbxproj index 80687fc..58b495f 100644 --- a/TidepoolService.xcodeproj/project.pbxproj +++ b/TidepoolService.xcodeproj/project.pbxproj @@ -1063,7 +1063,7 @@ GCC_WARN_UNUSED_LABEL = YES; GCC_WARN_UNUSED_PARAMETER = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.1; + IPHONEOS_DEPLOYMENT_TARGET = 17.6; LOCALIZED_STRING_MACRO_NAMES = ( NSLocalizedString, CFLocalizedString, @@ -1168,7 +1168,7 @@ GCC_WARN_UNUSED_LABEL = YES; GCC_WARN_UNUSED_PARAMETER = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.1; + IPHONEOS_DEPLOYMENT_TARGET = 17.6; LOCALIZED_STRING_MACRO_NAMES = ( NSLocalizedString, CFLocalizedString, @@ -1458,7 +1458,7 @@ GCC_WARN_UNUSED_LABEL = YES; GCC_WARN_UNUSED_PARAMETER = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.1; + IPHONEOS_DEPLOYMENT_TARGET = 17.6; LOCALIZED_STRING_MACRO_NAMES = ( NSLocalizedString, CFLocalizedString, From a6c05ba3b9144ca4f76cb1608d6a5b3b83f762c1 Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Wed, 27 Nov 2024 20:13:13 -0800 Subject: [PATCH 49/71] [LOOP-4754] Presets Storage --- TidepoolService.xcodeproj/project.pbxproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/TidepoolService.xcodeproj/project.pbxproj b/TidepoolService.xcodeproj/project.pbxproj index 80687fc..58b495f 100644 --- a/TidepoolService.xcodeproj/project.pbxproj +++ b/TidepoolService.xcodeproj/project.pbxproj @@ -1063,7 +1063,7 @@ GCC_WARN_UNUSED_LABEL = YES; GCC_WARN_UNUSED_PARAMETER = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.1; + IPHONEOS_DEPLOYMENT_TARGET = 17.6; LOCALIZED_STRING_MACRO_NAMES = ( NSLocalizedString, CFLocalizedString, @@ -1168,7 +1168,7 @@ GCC_WARN_UNUSED_LABEL = YES; GCC_WARN_UNUSED_PARAMETER = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.1; + IPHONEOS_DEPLOYMENT_TARGET = 17.6; LOCALIZED_STRING_MACRO_NAMES = ( NSLocalizedString, CFLocalizedString, @@ -1458,7 +1458,7 @@ GCC_WARN_UNUSED_LABEL = YES; GCC_WARN_UNUSED_PARAMETER = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.1; + IPHONEOS_DEPLOYMENT_TARGET = 17.6; LOCALIZED_STRING_MACRO_NAMES = ( NSLocalizedString, CFLocalizedString, From be8eb1492a46b7c7a637c6d358b771324f093fbc Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Fri, 21 Mar 2025 15:52:52 -0500 Subject: [PATCH 50/71] LOOP-5149 Handle zero duration dose (#117) * Enable dsym production * Handle 0 duration dose --- TidepoolServiceKit/Extensions/DoseEntry.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/TidepoolServiceKit/Extensions/DoseEntry.swift b/TidepoolServiceKit/Extensions/DoseEntry.swift index 06eaa03..d4684fe 100644 --- a/TidepoolServiceKit/Extensions/DoseEntry.swift +++ b/TidepoolServiceKit/Extensions/DoseEntry.swift @@ -483,7 +483,11 @@ extension Collection where Element == DoseEntry { newDose.startDate = Swift.max(period.startDate, dose.startDate) newDose.endDate = Swift.min(period.endDate, dose.endDate) if let delivered = dose.deliveredUnits { - newDose.deliveredUnits = newDose.duration / dose.duration * delivered + if dose.duration == 0 || delivered == 0 { + newDose.deliveredUnits = dose.deliveredUnits + } else { + newDose.deliveredUnits = newDose.duration / dose.duration * delivered + } } newDose.automatic = period.value if addedCount > 0 { From c3af3f835caee9ec4d358e75d059eb25b29f46b2 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Fri, 21 Mar 2025 15:52:52 -0500 Subject: [PATCH 51/71] LOOP-5149 Handle zero duration dose (#117) * Enable dsym production * Handle 0 duration dose --- TidepoolServiceKit/Extensions/DoseEntry.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/TidepoolServiceKit/Extensions/DoseEntry.swift b/TidepoolServiceKit/Extensions/DoseEntry.swift index 06eaa03..d4684fe 100644 --- a/TidepoolServiceKit/Extensions/DoseEntry.swift +++ b/TidepoolServiceKit/Extensions/DoseEntry.swift @@ -483,7 +483,11 @@ extension Collection where Element == DoseEntry { newDose.startDate = Swift.max(period.startDate, dose.startDate) newDose.endDate = Swift.min(period.endDate, dose.endDate) if let delivered = dose.deliveredUnits { - newDose.deliveredUnits = newDose.duration / dose.duration * delivered + if dose.duration == 0 || delivered == 0 { + newDose.deliveredUnits = dose.deliveredUnits + } else { + newDose.deliveredUnits = newDose.duration / dose.duration * delivered + } } newDose.automatic = period.value if addedCount > 0 { From 69c0c8489660eea3a9d0dda6f1f34a5cb61ddc14 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Thu, 24 Apr 2025 10:14:51 -0500 Subject: [PATCH 52/71] Update for new preset type names (#118) --- .../Extensions/StoredSettings.swift | 2 +- .../TemporaryScheduleOverride.swift | 2 +- .../StoredDosingDecisionTests.swift | 2 +- .../Extensions/StoredSettingsTests.swift | 20 ++----------------- 4 files changed, 5 insertions(+), 21 deletions(-) diff --git a/TidepoolServiceKit/Extensions/StoredSettings.swift b/TidepoolServiceKit/Extensions/StoredSettings.swift index 5315d2a..66c195e 100644 --- a/TidepoolServiceKit/Extensions/StoredSettings.swift +++ b/TidepoolServiceKit/Extensions/StoredSettings.swift @@ -365,7 +365,7 @@ fileprivate extension NotificationSettings.AlertStyle { } } -fileprivate extension TemporaryScheduleOverridePreset { +fileprivate extension TemporaryPreset { var datum: TPumpSettingsDatum.OverridePreset { return TPumpSettingsDatum.OverridePreset(abbreviation: datumAbbreviation, duration: datumDuration, diff --git a/TidepoolServiceKit/Extensions/TemporaryScheduleOverride.swift b/TidepoolServiceKit/Extensions/TemporaryScheduleOverride.swift index 2bcc203..42a94f7 100644 --- a/TidepoolServiceKit/Extensions/TemporaryScheduleOverride.swift +++ b/TidepoolServiceKit/Extensions/TemporaryScheduleOverride.swift @@ -25,7 +25,7 @@ fileprivate extension TemporaryScheduleOverride.Context { } } -extension TemporaryScheduleOverrideSettings { +extension TemporaryPresetSettings { var datumBloodGlucoseTarget: TPumpSettingsDatum.BloodGlucoseTarget? { guard let targetRange = targetRange else { return nil diff --git a/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift b/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift index 190ed20..495aa4d 100644 --- a/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift +++ b/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift @@ -250,7 +250,7 @@ fileprivate extension StoredDosingDecision { let reason = "test" let settings = StoredDosingDecision.Settings(syncIdentifier: UUID(uuidString: "2B03D96C-6F5D-4140-99CD-80C3E64D6011")!) let scheduleOverride = TemporaryScheduleOverride(context: .preMeal, - settings: TemporaryScheduleOverrideSettings(unit: .milligramsPerDeciliter, + settings: TemporaryPresetSettings(unit: .milligramsPerDeciliter, targetRange: DoubleRange(minValue: 80.0, maxValue: 90.0), insulinNeedsScaleFactor: 1.5), diff --git a/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift b/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift index 5e85976..6558f69 100644 --- a/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift +++ b/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift @@ -267,29 +267,13 @@ fileprivate extension StoredSettings { end: dateFormatter.date(from: "2020-05-14T14:48:15Z")!)) let preMealTargetRange = DoubleRange(minValue: 80.0, maxValue: 90.0).quantityRange(for: .milligramsPerDeciliter) let workoutTargetRange = DoubleRange(minValue: 150.0, maxValue: 160.0).quantityRange(for: .milligramsPerDeciliter) - let overridePresets = [TemporaryScheduleOverridePreset(id: UUID(uuidString: "2A67A303-5203-4CB8-8263-79498265368E")!, + let overridePresets = [TemporaryPreset(id: UUID(uuidString: "2A67A303-5203-4CB8-8263-79498265368E")!, symbol: "🍎", name: "Apple", - settings: TemporaryScheduleOverrideSettings(unit: .milligramsPerDeciliter, + settings: TemporaryPresetSettings(unit: .milligramsPerDeciliter, targetRange: DoubleRange(minValue: 130.0, maxValue: 140.0), insulinNeedsScaleFactor: 2.0), duration: .finite(.minutes(60)))] - let scheduleOverride = TemporaryScheduleOverride(context: .preMeal, - settings: TemporaryScheduleOverrideSettings(unit: .milligramsPerDeciliter, - targetRange: DoubleRange(minValue: 110.0, maxValue: 120.0), - insulinNeedsScaleFactor: 1.5), - startDate: dateFormatter.date(from: "2020-05-14T14:48:19Z")!, - duration: .finite(.minutes(60)), - enactTrigger: .remote("127.0.0.1"), - syncIdentifier: UUID(uuidString: "2A67A303-1234-4CB8-8263-79498265368E")!) - let preMealOverride = TemporaryScheduleOverride(context: .preMeal, - settings: TemporaryScheduleOverrideSettings(unit: .milligramsPerDeciliter, - targetRange: DoubleRange(minValue: 80.0, maxValue: 90.0), - insulinNeedsScaleFactor: 0.5), - startDate: dateFormatter.date(from: "2020-05-14T14:38:39Z")!, - duration: .indefinite, - enactTrigger: .local, - syncIdentifier: UUID(uuidString: "2A67A303-5203-1234-8263-79498265368E")!) let maximumBasalRatePerHour = 3.5 let maximumBolus = 10.0 let suspendThreshold = GlucoseThreshold(unit: .milligramsPerDeciliter, value: 75.0) From 417f42c0f9147609443be7ee6d4560cf6f10598c Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Thu, 24 Apr 2025 10:14:51 -0500 Subject: [PATCH 53/71] Update for new preset type names (#118) --- .../Extensions/StoredSettings.swift | 2 +- .../TemporaryScheduleOverride.swift | 2 +- .../StoredDosingDecisionTests.swift | 2 +- .../Extensions/StoredSettingsTests.swift | 20 ++----------------- 4 files changed, 5 insertions(+), 21 deletions(-) diff --git a/TidepoolServiceKit/Extensions/StoredSettings.swift b/TidepoolServiceKit/Extensions/StoredSettings.swift index 5315d2a..66c195e 100644 --- a/TidepoolServiceKit/Extensions/StoredSettings.swift +++ b/TidepoolServiceKit/Extensions/StoredSettings.swift @@ -365,7 +365,7 @@ fileprivate extension NotificationSettings.AlertStyle { } } -fileprivate extension TemporaryScheduleOverridePreset { +fileprivate extension TemporaryPreset { var datum: TPumpSettingsDatum.OverridePreset { return TPumpSettingsDatum.OverridePreset(abbreviation: datumAbbreviation, duration: datumDuration, diff --git a/TidepoolServiceKit/Extensions/TemporaryScheduleOverride.swift b/TidepoolServiceKit/Extensions/TemporaryScheduleOverride.swift index 2bcc203..42a94f7 100644 --- a/TidepoolServiceKit/Extensions/TemporaryScheduleOverride.swift +++ b/TidepoolServiceKit/Extensions/TemporaryScheduleOverride.swift @@ -25,7 +25,7 @@ fileprivate extension TemporaryScheduleOverride.Context { } } -extension TemporaryScheduleOverrideSettings { +extension TemporaryPresetSettings { var datumBloodGlucoseTarget: TPumpSettingsDatum.BloodGlucoseTarget? { guard let targetRange = targetRange else { return nil diff --git a/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift b/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift index 190ed20..495aa4d 100644 --- a/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift +++ b/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift @@ -250,7 +250,7 @@ fileprivate extension StoredDosingDecision { let reason = "test" let settings = StoredDosingDecision.Settings(syncIdentifier: UUID(uuidString: "2B03D96C-6F5D-4140-99CD-80C3E64D6011")!) let scheduleOverride = TemporaryScheduleOverride(context: .preMeal, - settings: TemporaryScheduleOverrideSettings(unit: .milligramsPerDeciliter, + settings: TemporaryPresetSettings(unit: .milligramsPerDeciliter, targetRange: DoubleRange(minValue: 80.0, maxValue: 90.0), insulinNeedsScaleFactor: 1.5), diff --git a/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift b/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift index 5e85976..6558f69 100644 --- a/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift +++ b/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift @@ -267,29 +267,13 @@ fileprivate extension StoredSettings { end: dateFormatter.date(from: "2020-05-14T14:48:15Z")!)) let preMealTargetRange = DoubleRange(minValue: 80.0, maxValue: 90.0).quantityRange(for: .milligramsPerDeciliter) let workoutTargetRange = DoubleRange(minValue: 150.0, maxValue: 160.0).quantityRange(for: .milligramsPerDeciliter) - let overridePresets = [TemporaryScheduleOverridePreset(id: UUID(uuidString: "2A67A303-5203-4CB8-8263-79498265368E")!, + let overridePresets = [TemporaryPreset(id: UUID(uuidString: "2A67A303-5203-4CB8-8263-79498265368E")!, symbol: "🍎", name: "Apple", - settings: TemporaryScheduleOverrideSettings(unit: .milligramsPerDeciliter, + settings: TemporaryPresetSettings(unit: .milligramsPerDeciliter, targetRange: DoubleRange(minValue: 130.0, maxValue: 140.0), insulinNeedsScaleFactor: 2.0), duration: .finite(.minutes(60)))] - let scheduleOverride = TemporaryScheduleOverride(context: .preMeal, - settings: TemporaryScheduleOverrideSettings(unit: .milligramsPerDeciliter, - targetRange: DoubleRange(minValue: 110.0, maxValue: 120.0), - insulinNeedsScaleFactor: 1.5), - startDate: dateFormatter.date(from: "2020-05-14T14:48:19Z")!, - duration: .finite(.minutes(60)), - enactTrigger: .remote("127.0.0.1"), - syncIdentifier: UUID(uuidString: "2A67A303-1234-4CB8-8263-79498265368E")!) - let preMealOverride = TemporaryScheduleOverride(context: .preMeal, - settings: TemporaryScheduleOverrideSettings(unit: .milligramsPerDeciliter, - targetRange: DoubleRange(minValue: 80.0, maxValue: 90.0), - insulinNeedsScaleFactor: 0.5), - startDate: dateFormatter.date(from: "2020-05-14T14:38:39Z")!, - duration: .indefinite, - enactTrigger: .local, - syncIdentifier: UUID(uuidString: "2A67A303-5203-1234-8263-79498265368E")!) let maximumBasalRatePerHour = 3.5 let maximumBolus = 10.0 let suspendThreshold = GlucoseThreshold(unit: .milligramsPerDeciliter, value: 75.0) From aaf3e3cf26fd7a01eba3378bfbad79a7a6d93394 Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Tue, 29 Apr 2025 13:17:35 -0700 Subject: [PATCH 54/71] [LOOP-5295] decisionId on DoseEntry and PersistedPumpEvent --- .../Extensions/DoseEntryTests.swift | 38 ++++++++++++++----- .../Extensions/PersistedPumpEventTests.swift | 3 ++ 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift b/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift index 4ea07b5..74df2dc 100644 --- a/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift +++ b/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift @@ -25,6 +25,7 @@ class DoseEntryDataTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:25:23Z")!, value: 0.75, unit: .units, + decisionId: nil, deliveredUnits: nil, description: "Test Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -71,6 +72,7 @@ class DoseEntryDataTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:00:53Z")!, value: 4.25, unit: .units, + decisionId: nil, deliveredUnits: nil, description: "Test Bolus Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -117,6 +119,7 @@ class DoseEntryDataTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:00:53Z")!, value: 4.25, unit: .units, + decisionId: nil, deliveredUnits: 3.5, description: "Test Bolus Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -162,6 +165,7 @@ class DoseEntryDataTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:00:53Z")!, value: 4.25, unit: .units, + decisionId: nil, deliveredUnits: 3.5, description: "Test Bolus Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -212,6 +216,7 @@ class DoseEntryDataTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:00:53Z")!, value: 4.25, unit: .units, + decisionId: nil, deliveredUnits: 3.5, description: "Test Bolus Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -257,6 +262,7 @@ class DoseEntryDataTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:00:53Z")!, value: 4.25, unit: .units, + decisionId: nil, deliveredUnits: 3.5, description: "Test Bolus Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -307,6 +313,7 @@ class DoseEntryDataTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:30:23Z")!, value: 0, unit: .unitsPerHour, + decisionId: nil, deliveredUnits: nil, description: "Test Resume Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -328,6 +335,7 @@ class DoseEntryDataTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:30:23Z")!, value: 0, unit: .unitsPerHour, + decisionId: nil, deliveredUnits: nil, description: "Test Suspend Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -371,6 +379,7 @@ class DoseEntryDataTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:20:23Z")!, value: 1.5, unit: .unitsPerHour, + decisionId: nil, deliveredUnits: 0.5, description: "Test Temp Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -423,6 +432,7 @@ class DoseEntryDataTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:20:23Z")!, value: 1.5, unit: .unitsPerHour, + decisionId: nil, deliveredUnits: 0.5, description: "Test Temp Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -480,6 +490,7 @@ class DoseEntryDataTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:20:23Z")!, value: 1.5, unit: .unitsPerHour, + decisionId: nil, deliveredUnits: 0.5, description: "Test Temp Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -532,6 +543,7 @@ class DoseEntryDataTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:20:23Z")!, value: 1.5, unit: .unitsPerHour, + decisionId: nil, deliveredUnits: 0.5, description: "Test Temp Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -600,6 +612,7 @@ class DoseEntrySelectorTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:25:23Z")!, value: 0.75, unit: .units, + decisionId: nil, deliveredUnits: nil, description: "Test Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -616,6 +629,7 @@ class DoseEntrySelectorTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:00:53Z")!, value: 4.25, unit: .units, + decisionId: nil, deliveredUnits: nil, description: "Test Bolus Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -632,6 +646,7 @@ class DoseEntrySelectorTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:00:53Z")!, value: 4.25, unit: .units, + decisionId: nil, deliveredUnits: 3.5, description: "Test Bolus Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -648,6 +663,7 @@ class DoseEntrySelectorTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:00:53Z")!, value: 4.25, unit: .units, + decisionId: nil, deliveredUnits: 3.5, description: "Test Bolus Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -664,6 +680,7 @@ class DoseEntrySelectorTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:30:23Z")!, value: 0, unit: .unitsPerHour, + decisionId: nil, deliveredUnits: nil, description: "Test Resume Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -680,6 +697,7 @@ class DoseEntrySelectorTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:30:23Z")!, value: 0, unit: .unitsPerHour, + decisionId: nil, deliveredUnits: nil, description: "Test Suspend Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -696,6 +714,7 @@ class DoseEntrySelectorTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:20:23Z")!, value: 1.5, unit: .unitsPerHour, + decisionId: nil, deliveredUnits: 0.5, description: "Test Temp Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -712,6 +731,7 @@ class DoseEntrySelectorTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:20:23Z")!, value: 1.5, unit: .unitsPerHour, + decisionId: nil, deliveredUnits: 0.5, description: "Test Temp Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -723,7 +743,7 @@ class DoseEntrySelectorTests: XCTestCase { func testOverlayAutomationHistory_NoAutomationHistory() { let doses: [DoseEntry] = [ - DoseEntry(type: .basal, startDate: Date(), endDate: Date().addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, syncIdentifier: "test", automatic: nil, manuallyEntered: false, isMutable: false) + DoseEntry(type: .basal, startDate: Date(), endDate: Date().addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, decisionId: nil, syncIdentifier: "test", automatic: nil, manuallyEntered: false, isMutable: false) ] let result = doses.overlayAutomationHistory([]) @@ -734,7 +754,7 @@ class DoseEntrySelectorTests: XCTestCase { func testOverlayAutomationHistory_SingleAutomationPeriod() { let now = Date() let doses: [DoseEntry] = [ - DoseEntry(type: .basal, startDate: now, endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, syncIdentifier: "test", automatic: nil, manuallyEntered: false, isMutable: false) + DoseEntry(type: .basal, startDate: now, endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, decisionId: nil, syncIdentifier: "test", automatic: nil, manuallyEntered: false, isMutable: false) ] let automationHistory: [AbsoluteScheduleValue] = [ AbsoluteScheduleValue(startDate: now, endDate: now.addingTimeInterval(3600), value: false) @@ -749,7 +769,7 @@ class DoseEntrySelectorTests: XCTestCase { func testOverlayAutomationHistory_MultipleAutomationPeriods() { let now = Date() let doses: [DoseEntry] = [ - DoseEntry(type: .basal, startDate: now, endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, syncIdentifier: "test", automatic: nil, manuallyEntered: false, isMutable: false) + DoseEntry(type: .basal, startDate: now, endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, decisionId: nil, syncIdentifier: "test", automatic: nil, manuallyEntered: false, isMutable: false) ] let automationHistory: [AbsoluteScheduleValue] = [ AbsoluteScheduleValue(startDate: now, endDate: now.addingTimeInterval(1800), value: false), @@ -766,7 +786,7 @@ class DoseEntrySelectorTests: XCTestCase { func testOverlayAutomationHistory_PartialOverlap() { let now = Date() let doses: [DoseEntry] = [ - DoseEntry(type: .basal, startDate: now, endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, syncIdentifier: "test", automatic: nil, manuallyEntered: false, isMutable: false) + DoseEntry(type: .basal, startDate: now, endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, decisionId: nil, syncIdentifier: "test", automatic: nil, manuallyEntered: false, isMutable: false) ] let automationHistory: [AbsoluteScheduleValue] = [ AbsoluteScheduleValue(startDate: now.addingTimeInterval(1800), endDate: now.addingTimeInterval(4800), value: false) @@ -783,8 +803,8 @@ class DoseEntrySelectorTests: XCTestCase { func testOverlayAutomationHistory_NonBasalDoses() { let now = Date() let doses: [DoseEntry] = [ - DoseEntry(type: .bolus, startDate: now, endDate: now.addingTimeInterval(300), value: 2.0, unit: .unitsPerHour, automatic: nil, manuallyEntered: false, isMutable: false), - DoseEntry(type: .basal, startDate: now.addingTimeInterval(300), endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, automatic: nil, manuallyEntered: false, isMutable: false) + DoseEntry(type: .bolus, startDate: now, endDate: now.addingTimeInterval(300), value: 2.0, unit: .unitsPerHour, decisionId: nil, automatic: nil, manuallyEntered: false, isMutable: false), + DoseEntry(type: .basal, startDate: now.addingTimeInterval(300), endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, decisionId: nil, automatic: nil, manuallyEntered: false, isMutable: false) ] let automationHistory: [AbsoluteScheduleValue] = [ AbsoluteScheduleValue(startDate: now, endDate: now.addingTimeInterval(3600), value: false) @@ -800,7 +820,7 @@ class DoseEntrySelectorTests: XCTestCase { func testOverlayAutomationHistory_PreexistingAutomationFlag() { let now = Date() let doses: [DoseEntry] = [ - DoseEntry(type: .basal, startDate: now, endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, automatic: true, manuallyEntered: false, isMutable: false) + DoseEntry(type: .basal, startDate: now, endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, decisionId: nil, automatic: true, manuallyEntered: false, isMutable: false) ] let automationHistory: [AbsoluteScheduleValue] = [ AbsoluteScheduleValue(startDate: now, endDate: now.addingTimeInterval(3600), value: false) @@ -815,7 +835,7 @@ class DoseEntrySelectorTests: XCTestCase { func testOverlayAutomationHistory_DeliveredUnitsAdjustment() { let now = Date() let doses: [DoseEntry] = [ - DoseEntry(type: .basal, startDate: now, endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, deliveredUnits: 1.0, syncIdentifier: "test", automatic: nil, manuallyEntered: false, isMutable: false) + DoseEntry(type: .basal, startDate: now, endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, decisionId: nil, deliveredUnits: 1.0, syncIdentifier: "test", automatic: nil, manuallyEntered: false, isMutable: false) ] let automationHistory: [AbsoluteScheduleValue] = [ AbsoluteScheduleValue(startDate: now, endDate: now.addingTimeInterval(1800), value: false), @@ -834,7 +854,7 @@ class DoseEntrySelectorTests: XCTestCase { func testOverlayAutomationHistory_MutableDose() { let now = Date() let doses: [DoseEntry] = [ - DoseEntry(type: .basal, startDate: now, endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, deliveredUnits: 1.0, syncIdentifier: "test", automatic: nil, manuallyEntered: false, isMutable: true) + DoseEntry(type: .basal, startDate: now, endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, decisionId: nil, deliveredUnits: 1.0, syncIdentifier: "test", automatic: nil, manuallyEntered: false, isMutable: true) ] let automationHistory: [AbsoluteScheduleValue] = [ AbsoluteScheduleValue(startDate: now, endDate: now.addingTimeInterval(1800), value: false), diff --git a/TidepoolServiceKitTests/Extensions/PersistedPumpEventTests.swift b/TidepoolServiceKitTests/Extensions/PersistedPumpEventTests.swift index e7b5bca..4d37449 100644 --- a/TidepoolServiceKitTests/Extensions/PersistedPumpEventTests.swift +++ b/TidepoolServiceKitTests/Extensions/PersistedPumpEventTests.swift @@ -256,6 +256,7 @@ class PersistedPumpEventTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:25:23Z")!, value: 0.75, unit: .units, + decisionId: nil, deliveredUnits: nil, description: "Test Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -286,6 +287,7 @@ class PersistedPumpEventTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:00:53Z")!, value: 4.25, unit: .units, + decisionId: nil, deliveredUnits: 3.5, description: "Test Bolus Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -665,6 +667,7 @@ class PersistedPumpEventTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:20:23Z")!, value: 1.5, unit: .unitsPerHour, + decisionId: nil, deliveredUnits: 0.5, description: "Test Temp Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", From 256dcdc34a579e23cca4fec13b3e3096b7198638 Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Tue, 29 Apr 2025 13:17:35 -0700 Subject: [PATCH 55/71] [LOOP-5295] decisionId on DoseEntry and PersistedPumpEvent --- .../Extensions/DoseEntryTests.swift | 38 ++++++++++++++----- .../Extensions/PersistedPumpEventTests.swift | 3 ++ 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift b/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift index 4ea07b5..74df2dc 100644 --- a/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift +++ b/TidepoolServiceKitTests/Extensions/DoseEntryTests.swift @@ -25,6 +25,7 @@ class DoseEntryDataTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:25:23Z")!, value: 0.75, unit: .units, + decisionId: nil, deliveredUnits: nil, description: "Test Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -71,6 +72,7 @@ class DoseEntryDataTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:00:53Z")!, value: 4.25, unit: .units, + decisionId: nil, deliveredUnits: nil, description: "Test Bolus Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -117,6 +119,7 @@ class DoseEntryDataTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:00:53Z")!, value: 4.25, unit: .units, + decisionId: nil, deliveredUnits: 3.5, description: "Test Bolus Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -162,6 +165,7 @@ class DoseEntryDataTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:00:53Z")!, value: 4.25, unit: .units, + decisionId: nil, deliveredUnits: 3.5, description: "Test Bolus Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -212,6 +216,7 @@ class DoseEntryDataTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:00:53Z")!, value: 4.25, unit: .units, + decisionId: nil, deliveredUnits: 3.5, description: "Test Bolus Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -257,6 +262,7 @@ class DoseEntryDataTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:00:53Z")!, value: 4.25, unit: .units, + decisionId: nil, deliveredUnits: 3.5, description: "Test Bolus Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -307,6 +313,7 @@ class DoseEntryDataTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:30:23Z")!, value: 0, unit: .unitsPerHour, + decisionId: nil, deliveredUnits: nil, description: "Test Resume Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -328,6 +335,7 @@ class DoseEntryDataTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:30:23Z")!, value: 0, unit: .unitsPerHour, + decisionId: nil, deliveredUnits: nil, description: "Test Suspend Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -371,6 +379,7 @@ class DoseEntryDataTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:20:23Z")!, value: 1.5, unit: .unitsPerHour, + decisionId: nil, deliveredUnits: 0.5, description: "Test Temp Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -423,6 +432,7 @@ class DoseEntryDataTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:20:23Z")!, value: 1.5, unit: .unitsPerHour, + decisionId: nil, deliveredUnits: 0.5, description: "Test Temp Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -480,6 +490,7 @@ class DoseEntryDataTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:20:23Z")!, value: 1.5, unit: .unitsPerHour, + decisionId: nil, deliveredUnits: 0.5, description: "Test Temp Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -532,6 +543,7 @@ class DoseEntryDataTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:20:23Z")!, value: 1.5, unit: .unitsPerHour, + decisionId: nil, deliveredUnits: 0.5, description: "Test Temp Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -600,6 +612,7 @@ class DoseEntrySelectorTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:25:23Z")!, value: 0.75, unit: .units, + decisionId: nil, deliveredUnits: nil, description: "Test Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -616,6 +629,7 @@ class DoseEntrySelectorTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:00:53Z")!, value: 4.25, unit: .units, + decisionId: nil, deliveredUnits: nil, description: "Test Bolus Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -632,6 +646,7 @@ class DoseEntrySelectorTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:00:53Z")!, value: 4.25, unit: .units, + decisionId: nil, deliveredUnits: 3.5, description: "Test Bolus Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -648,6 +663,7 @@ class DoseEntrySelectorTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:00:53Z")!, value: 4.25, unit: .units, + decisionId: nil, deliveredUnits: 3.5, description: "Test Bolus Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -664,6 +680,7 @@ class DoseEntrySelectorTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:30:23Z")!, value: 0, unit: .unitsPerHour, + decisionId: nil, deliveredUnits: nil, description: "Test Resume Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -680,6 +697,7 @@ class DoseEntrySelectorTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:30:23Z")!, value: 0, unit: .unitsPerHour, + decisionId: nil, deliveredUnits: nil, description: "Test Suspend Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -696,6 +714,7 @@ class DoseEntrySelectorTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:20:23Z")!, value: 1.5, unit: .unitsPerHour, + decisionId: nil, deliveredUnits: 0.5, description: "Test Temp Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -712,6 +731,7 @@ class DoseEntrySelectorTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:20:23Z")!, value: 1.5, unit: .unitsPerHour, + decisionId: nil, deliveredUnits: 0.5, description: "Test Temp Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -723,7 +743,7 @@ class DoseEntrySelectorTests: XCTestCase { func testOverlayAutomationHistory_NoAutomationHistory() { let doses: [DoseEntry] = [ - DoseEntry(type: .basal, startDate: Date(), endDate: Date().addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, syncIdentifier: "test", automatic: nil, manuallyEntered: false, isMutable: false) + DoseEntry(type: .basal, startDate: Date(), endDate: Date().addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, decisionId: nil, syncIdentifier: "test", automatic: nil, manuallyEntered: false, isMutable: false) ] let result = doses.overlayAutomationHistory([]) @@ -734,7 +754,7 @@ class DoseEntrySelectorTests: XCTestCase { func testOverlayAutomationHistory_SingleAutomationPeriod() { let now = Date() let doses: [DoseEntry] = [ - DoseEntry(type: .basal, startDate: now, endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, syncIdentifier: "test", automatic: nil, manuallyEntered: false, isMutable: false) + DoseEntry(type: .basal, startDate: now, endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, decisionId: nil, syncIdentifier: "test", automatic: nil, manuallyEntered: false, isMutable: false) ] let automationHistory: [AbsoluteScheduleValue] = [ AbsoluteScheduleValue(startDate: now, endDate: now.addingTimeInterval(3600), value: false) @@ -749,7 +769,7 @@ class DoseEntrySelectorTests: XCTestCase { func testOverlayAutomationHistory_MultipleAutomationPeriods() { let now = Date() let doses: [DoseEntry] = [ - DoseEntry(type: .basal, startDate: now, endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, syncIdentifier: "test", automatic: nil, manuallyEntered: false, isMutable: false) + DoseEntry(type: .basal, startDate: now, endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, decisionId: nil, syncIdentifier: "test", automatic: nil, manuallyEntered: false, isMutable: false) ] let automationHistory: [AbsoluteScheduleValue] = [ AbsoluteScheduleValue(startDate: now, endDate: now.addingTimeInterval(1800), value: false), @@ -766,7 +786,7 @@ class DoseEntrySelectorTests: XCTestCase { func testOverlayAutomationHistory_PartialOverlap() { let now = Date() let doses: [DoseEntry] = [ - DoseEntry(type: .basal, startDate: now, endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, syncIdentifier: "test", automatic: nil, manuallyEntered: false, isMutable: false) + DoseEntry(type: .basal, startDate: now, endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, decisionId: nil, syncIdentifier: "test", automatic: nil, manuallyEntered: false, isMutable: false) ] let automationHistory: [AbsoluteScheduleValue] = [ AbsoluteScheduleValue(startDate: now.addingTimeInterval(1800), endDate: now.addingTimeInterval(4800), value: false) @@ -783,8 +803,8 @@ class DoseEntrySelectorTests: XCTestCase { func testOverlayAutomationHistory_NonBasalDoses() { let now = Date() let doses: [DoseEntry] = [ - DoseEntry(type: .bolus, startDate: now, endDate: now.addingTimeInterval(300), value: 2.0, unit: .unitsPerHour, automatic: nil, manuallyEntered: false, isMutable: false), - DoseEntry(type: .basal, startDate: now.addingTimeInterval(300), endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, automatic: nil, manuallyEntered: false, isMutable: false) + DoseEntry(type: .bolus, startDate: now, endDate: now.addingTimeInterval(300), value: 2.0, unit: .unitsPerHour, decisionId: nil, automatic: nil, manuallyEntered: false, isMutable: false), + DoseEntry(type: .basal, startDate: now.addingTimeInterval(300), endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, decisionId: nil, automatic: nil, manuallyEntered: false, isMutable: false) ] let automationHistory: [AbsoluteScheduleValue] = [ AbsoluteScheduleValue(startDate: now, endDate: now.addingTimeInterval(3600), value: false) @@ -800,7 +820,7 @@ class DoseEntrySelectorTests: XCTestCase { func testOverlayAutomationHistory_PreexistingAutomationFlag() { let now = Date() let doses: [DoseEntry] = [ - DoseEntry(type: .basal, startDate: now, endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, automatic: true, manuallyEntered: false, isMutable: false) + DoseEntry(type: .basal, startDate: now, endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, decisionId: nil, automatic: true, manuallyEntered: false, isMutable: false) ] let automationHistory: [AbsoluteScheduleValue] = [ AbsoluteScheduleValue(startDate: now, endDate: now.addingTimeInterval(3600), value: false) @@ -815,7 +835,7 @@ class DoseEntrySelectorTests: XCTestCase { func testOverlayAutomationHistory_DeliveredUnitsAdjustment() { let now = Date() let doses: [DoseEntry] = [ - DoseEntry(type: .basal, startDate: now, endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, deliveredUnits: 1.0, syncIdentifier: "test", automatic: nil, manuallyEntered: false, isMutable: false) + DoseEntry(type: .basal, startDate: now, endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, decisionId: nil, deliveredUnits: 1.0, syncIdentifier: "test", automatic: nil, manuallyEntered: false, isMutable: false) ] let automationHistory: [AbsoluteScheduleValue] = [ AbsoluteScheduleValue(startDate: now, endDate: now.addingTimeInterval(1800), value: false), @@ -834,7 +854,7 @@ class DoseEntrySelectorTests: XCTestCase { func testOverlayAutomationHistory_MutableDose() { let now = Date() let doses: [DoseEntry] = [ - DoseEntry(type: .basal, startDate: now, endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, deliveredUnits: 1.0, syncIdentifier: "test", automatic: nil, manuallyEntered: false, isMutable: true) + DoseEntry(type: .basal, startDate: now, endDate: now.addingTimeInterval(3600), value: 1.0, unit: .unitsPerHour, decisionId: nil, deliveredUnits: 1.0, syncIdentifier: "test", automatic: nil, manuallyEntered: false, isMutable: true) ] let automationHistory: [AbsoluteScheduleValue] = [ AbsoluteScheduleValue(startDate: now, endDate: now.addingTimeInterval(1800), value: false), diff --git a/TidepoolServiceKitTests/Extensions/PersistedPumpEventTests.swift b/TidepoolServiceKitTests/Extensions/PersistedPumpEventTests.swift index e7b5bca..4d37449 100644 --- a/TidepoolServiceKitTests/Extensions/PersistedPumpEventTests.swift +++ b/TidepoolServiceKitTests/Extensions/PersistedPumpEventTests.swift @@ -256,6 +256,7 @@ class PersistedPumpEventTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:25:23Z")!, value: 0.75, unit: .units, + decisionId: nil, deliveredUnits: nil, description: "Test Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -286,6 +287,7 @@ class PersistedPumpEventTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:00:53Z")!, value: 4.25, unit: .units, + decisionId: nil, deliveredUnits: 3.5, description: "Test Bolus Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", @@ -665,6 +667,7 @@ class PersistedPumpEventTests: XCTestCase { endDate: Self.dateFormatter.date(from: "2020-01-02T03:20:23Z")!, value: 1.5, unit: .unitsPerHour, + decisionId: nil, deliveredUnits: 0.5, description: "Test Temp Basal Dose", syncIdentifier: "18CF3948-0B3D-4B12-8BFE-14986B0E6784", From bdca0a6b44aec182b5fb578da0aceea5ef706a0c Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Thu, 22 May 2025 15:36:33 -0500 Subject: [PATCH 56/71] Fix empty abbreviation encoding (#120) --- TidepoolServiceKit/Extensions/StoredSettings.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TidepoolServiceKit/Extensions/StoredSettings.swift b/TidepoolServiceKit/Extensions/StoredSettings.swift index 66c195e..aa1fbfd 100644 --- a/TidepoolServiceKit/Extensions/StoredSettings.swift +++ b/TidepoolServiceKit/Extensions/StoredSettings.swift @@ -375,8 +375,8 @@ fileprivate extension TemporaryPreset { insulinSensitivityScaleFactor: settings.datumInsulinSensitivityScaleFactor) } - var datumAbbreviation: String? { symbol } - + var datumAbbreviation: String? { symbol.isEmpty == false ? symbol : nil } + var datumDuration: TimeInterval? { duration.isFinite ? duration.timeInterval : nil } } From 9cbb85d3f1084fde5b3b05731f800b806e8488e5 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Thu, 22 May 2025 15:36:33 -0500 Subject: [PATCH 57/71] Fix empty abbreviation encoding (#120) --- TidepoolServiceKit/Extensions/StoredSettings.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TidepoolServiceKit/Extensions/StoredSettings.swift b/TidepoolServiceKit/Extensions/StoredSettings.swift index 66c195e..aa1fbfd 100644 --- a/TidepoolServiceKit/Extensions/StoredSettings.swift +++ b/TidepoolServiceKit/Extensions/StoredSettings.swift @@ -375,8 +375,8 @@ fileprivate extension TemporaryPreset { insulinSensitivityScaleFactor: settings.datumInsulinSensitivityScaleFactor) } - var datumAbbreviation: String? { symbol } - + var datumAbbreviation: String? { symbol.isEmpty == false ? symbol : nil } + var datumDuration: TimeInterval? { duration.isFinite ? duration.timeInterval : nil } } From 0b5f1ab9cb0830b0121fdf6ae1f682b673ee53b9 Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Fri, 30 May 2025 13:41:29 -0700 Subject: [PATCH 58/71] Merge branch 'dev' into cameron/LOOP-5295-insulin-delivery-log --- .../Extensions/StoredDosingDecisionTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift b/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift index 495aa4d..040b2ab 100644 --- a/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift +++ b/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift @@ -353,7 +353,7 @@ fileprivate extension StoredDosingDecision { quantity: LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 127.8))] let tempBasalRecommendation = TempBasalRecommendation(unitsPerHour: 0.75, duration: .minutes(30)) - let automaticDoseRecommendation = AutomaticDoseRecommendation(basalAdjustment: tempBasalRecommendation, bolusUnits: 1.25) + let automaticDoseRecommendation = AutomaticDoseRecommendation(basalAdjustment: tempBasalRecommendation, direction: .increase, bolusUnits: 1.25) let manualBolusRecommendation = ManualBolusRecommendationWithDate(recommendation: ManualBolusRecommendation(amount: 1.2, notice: .predictedGlucoseBelowTarget(minGlucose: SimpleGlucoseValue(startDate: dateFormatter.date(from: "2020-05-14T23:03:15Z")!, quantity: LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 75.5)))), From 3b0267af88bdf6b6066a85383090805bf3912fa9 Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Fri, 30 May 2025 13:41:29 -0700 Subject: [PATCH 59/71] Merge branch 'dev' into cameron/LOOP-5295-insulin-delivery-log --- .../Extensions/StoredDosingDecisionTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift b/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift index 495aa4d..040b2ab 100644 --- a/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift +++ b/TidepoolServiceKitTests/Extensions/StoredDosingDecisionTests.swift @@ -353,7 +353,7 @@ fileprivate extension StoredDosingDecision { quantity: LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 127.8))] let tempBasalRecommendation = TempBasalRecommendation(unitsPerHour: 0.75, duration: .minutes(30)) - let automaticDoseRecommendation = AutomaticDoseRecommendation(basalAdjustment: tempBasalRecommendation, bolusUnits: 1.25) + let automaticDoseRecommendation = AutomaticDoseRecommendation(basalAdjustment: tempBasalRecommendation, direction: .increase, bolusUnits: 1.25) let manualBolusRecommendation = ManualBolusRecommendationWithDate(recommendation: ManualBolusRecommendation(amount: 1.2, notice: .predictedGlucoseBelowTarget(minGlucose: SimpleGlucoseValue(startDate: dateFormatter.date(from: "2020-05-14T23:03:15Z")!, quantity: LoopQuantity(unit: .milligramsPerDeciliter, doubleValue: 75.5)))), From 508cddefd5815d0723added62e2cca9e3c5d3a3f Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 29 Jul 2025 09:13:54 -0500 Subject: [PATCH 60/71] Enable scheduled presets (#121) --- TidepoolServiceKit/TidepoolService.swift | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/TidepoolServiceKit/TidepoolService.swift b/TidepoolServiceKit/TidepoolService.swift index 56e1526..9871dfd 100644 --- a/TidepoolServiceKit/TidepoolService.swift +++ b/TidepoolServiceKit/TidepoolService.swift @@ -173,10 +173,18 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { let content = Alert.Content(title: LocalizedString("Tidepool Service Authorization", comment: "The title for an alert generated when TidepoolService is no longer authorized."), body: LocalizedString("Tidepool service is no longer authorized. Please navigate to Tidepool Service settings and reauthenticate.", comment: "The body text for an alert generated when TidepoolService is no longer authorized."), acknowledgeActionButtonLabel: LocalizedString("OK", comment: "Alert acknowledgment OK button")) - serviceDelegate?.issueAlert(Alert(identifier: Alert.Identifier(managerIdentifier: pluginIdentifier, - alertIdentifier: "authentication-needed"), - foregroundContent: content, backgroundContent: content, - trigger: .immediate)) + Task { + await serviceDelegate?.issueAlert( + Alert( + identifier: Alert + .Identifier(managerIdentifier: pluginIdentifier, + alertIdentifier: "authentication-needed"), + foregroundContent: content, + backgroundContent: content, + trigger: .immediate + ) + ) + } } } From a81a2bd61ad9b832651ff41d4e372408455bb5fd Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Tue, 29 Jul 2025 09:13:54 -0500 Subject: [PATCH 61/71] Enable scheduled presets (#121) --- TidepoolServiceKit/TidepoolService.swift | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/TidepoolServiceKit/TidepoolService.swift b/TidepoolServiceKit/TidepoolService.swift index 56e1526..9871dfd 100644 --- a/TidepoolServiceKit/TidepoolService.swift +++ b/TidepoolServiceKit/TidepoolService.swift @@ -173,10 +173,18 @@ public final class TidepoolService: Service, TAPIObserver, ObservableObject { let content = Alert.Content(title: LocalizedString("Tidepool Service Authorization", comment: "The title for an alert generated when TidepoolService is no longer authorized."), body: LocalizedString("Tidepool service is no longer authorized. Please navigate to Tidepool Service settings and reauthenticate.", comment: "The body text for an alert generated when TidepoolService is no longer authorized."), acknowledgeActionButtonLabel: LocalizedString("OK", comment: "Alert acknowledgment OK button")) - serviceDelegate?.issueAlert(Alert(identifier: Alert.Identifier(managerIdentifier: pluginIdentifier, - alertIdentifier: "authentication-needed"), - foregroundContent: content, backgroundContent: content, - trigger: .immediate)) + Task { + await serviceDelegate?.issueAlert( + Alert( + identifier: Alert + .Identifier(managerIdentifier: pluginIdentifier, + alertIdentifier: "authentication-needed"), + foregroundContent: content, + backgroundContent: content, + trigger: .immediate + ) + ) + } } } From 845589df56e9c6f9468d00bafc10ac94f7654f90 Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Wed, 20 Aug 2025 13:00:16 -0700 Subject: [PATCH 62/71] [LOOP-5405] Activity Presets Core --- TidepoolServiceKit/Extensions/StoredSettings.swift | 12 +----------- .../Extensions/TemporaryScheduleOverride.swift | 2 +- .../Extensions/StoredSettingsTests.swift | 8 +------- 3 files changed, 3 insertions(+), 19 deletions(-) diff --git a/TidepoolServiceKit/Extensions/StoredSettings.swift b/TidepoolServiceKit/Extensions/StoredSettings.swift index aa1fbfd..59f9161 100644 --- a/TidepoolServiceKit/Extensions/StoredSettings.swift +++ b/TidepoolServiceKit/Extensions/StoredSettings.swift @@ -18,7 +18,6 @@ import TidepoolKit - dosingEnabled Bool TPumpSettingsDatum.automatedDelivery - glucoseTargetRangeSchedule GlucoseRangeSchedule? TPumpSettingsDatum.bloodGlucoseTargetSchedules["Default"] - preMealTargetRange ClosedRange? TPumpSettingsDatum.bloodGlucoseTargetPreprandial - - workoutTargetRange ClosedRange? TPumpSettingsDatum.bloodGlucoseTargetPhysicalActivity - overridePresets [TemporaryScheduleOverridePreset]? TPumpSettingsDatum.overridePresets - maximumBasalRatePerHour Double? TPumpSettingsDatum.basal.rateMaximum.value - maximumBolus Double? TPumpSettingsDatum.bolus.amountMaximum.value @@ -83,7 +82,6 @@ extension StoredSettings: IdentifiableDatum { basal: datumPumpBasal, basalRateSchedules: datumPumpBasalRateSchedules, bloodGlucoseSafetyLimit: datumPumpBloodGlucoseSafetyLimit, - bloodGlucoseTargetPhysicalActivity: datumPumpBloodGlucoseTargetPhysicalActivity, bloodGlucoseTargetPreprandial: datumPumpBloodGlucoseTargetPreprandial, bloodGlucoseTargetSchedules: datumPumpBloodGlucoseTargetSchedules, bolus: datumPumpBolus, @@ -170,14 +168,6 @@ extension StoredSettings: IdentifiableDatum { return suspendThreshold.convertTo(unit: .milligramsPerDeciliter).value } - private var datumPumpBloodGlucoseTargetPhysicalActivity: TPumpSettingsDatum.BloodGlucoseTarget? { - guard let workoutTargetRange = workoutTargetRange else { - return nil - } - return TPumpSettingsDatum.BloodGlucoseTarget(low: workoutTargetRange.lowerBound.doubleValue(for: .milligramsPerDeciliter), - high: workoutTargetRange.upperBound.doubleValue(for: .milligramsPerDeciliter)) - } - private var datumPumpBloodGlucoseTargetPreprandial: TPumpSettingsDatum.BloodGlucoseTarget? { guard let preMealTargetRange = preMealTargetRange else { return nil @@ -375,7 +365,7 @@ fileprivate extension TemporaryPreset { insulinSensitivityScaleFactor: settings.datumInsulinSensitivityScaleFactor) } - var datumAbbreviation: String? { symbol.isEmpty == false ? symbol : nil } + var datumAbbreviation: String? { symbol?.textualRepresentation?.string } var datumDuration: TimeInterval? { duration.isFinite ? duration.timeInterval : nil } } diff --git a/TidepoolServiceKit/Extensions/TemporaryScheduleOverride.swift b/TidepoolServiceKit/Extensions/TemporaryScheduleOverride.swift index 42a94f7..19d2bdf 100644 --- a/TidepoolServiceKit/Extensions/TemporaryScheduleOverride.swift +++ b/TidepoolServiceKit/Extensions/TemporaryScheduleOverride.swift @@ -15,7 +15,7 @@ fileprivate extension TemporaryScheduleOverride.Context { switch self { case .preMeal: return .preprandial - case .legacyWorkout: + case .activity: return .physicalActivity case .preset(_): return .preset diff --git a/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift b/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift index 6558f69..f53d1be 100644 --- a/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift +++ b/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift @@ -118,10 +118,6 @@ class StoredSettingsTests: XCTestCase { ] }, "bgSafetyLimit" : 75, - "bgTargetPhysicalActivity" : { - "high" : 160, - "low" : 150 - }, "bgTargetPreprandial" : { "high" : 90, "low" : 80 @@ -266,8 +262,7 @@ fileprivate extension StoredSettings { start: dateFormatter.date(from: "2020-05-14T12:48:15Z")!, end: dateFormatter.date(from: "2020-05-14T14:48:15Z")!)) let preMealTargetRange = DoubleRange(minValue: 80.0, maxValue: 90.0).quantityRange(for: .milligramsPerDeciliter) - let workoutTargetRange = DoubleRange(minValue: 150.0, maxValue: 160.0).quantityRange(for: .milligramsPerDeciliter) - let overridePresets = [TemporaryPreset(id: UUID(uuidString: "2A67A303-5203-4CB8-8263-79498265368E")!, + let overridePresets = [TemporaryPreset(id: "2A67A303-5203-4CB8-8263-79498265368E", symbol: "🍎", name: "Apple", settings: TemporaryPresetSettings(unit: .milligramsPerDeciliter, @@ -336,7 +331,6 @@ fileprivate extension StoredSettings { dosingEnabled: dosingEnabled, glucoseTargetRangeSchedule: glucoseTargetRangeSchedule, preMealTargetRange: preMealTargetRange, - workoutTargetRange: workoutTargetRange, overridePresets: overridePresets, maximumBasalRatePerHour: maximumBasalRatePerHour, maximumBolus: maximumBolus, From 52a8de478cd1ca0259f61563abaacf7e5e9ca4ec Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Wed, 20 Aug 2025 13:00:16 -0700 Subject: [PATCH 63/71] [LOOP-5405] Activity Presets Core --- TidepoolServiceKit/Extensions/StoredSettings.swift | 12 +----------- .../Extensions/TemporaryScheduleOverride.swift | 2 +- .../Extensions/StoredSettingsTests.swift | 8 +------- 3 files changed, 3 insertions(+), 19 deletions(-) diff --git a/TidepoolServiceKit/Extensions/StoredSettings.swift b/TidepoolServiceKit/Extensions/StoredSettings.swift index aa1fbfd..59f9161 100644 --- a/TidepoolServiceKit/Extensions/StoredSettings.swift +++ b/TidepoolServiceKit/Extensions/StoredSettings.swift @@ -18,7 +18,6 @@ import TidepoolKit - dosingEnabled Bool TPumpSettingsDatum.automatedDelivery - glucoseTargetRangeSchedule GlucoseRangeSchedule? TPumpSettingsDatum.bloodGlucoseTargetSchedules["Default"] - preMealTargetRange ClosedRange? TPumpSettingsDatum.bloodGlucoseTargetPreprandial - - workoutTargetRange ClosedRange? TPumpSettingsDatum.bloodGlucoseTargetPhysicalActivity - overridePresets [TemporaryScheduleOverridePreset]? TPumpSettingsDatum.overridePresets - maximumBasalRatePerHour Double? TPumpSettingsDatum.basal.rateMaximum.value - maximumBolus Double? TPumpSettingsDatum.bolus.amountMaximum.value @@ -83,7 +82,6 @@ extension StoredSettings: IdentifiableDatum { basal: datumPumpBasal, basalRateSchedules: datumPumpBasalRateSchedules, bloodGlucoseSafetyLimit: datumPumpBloodGlucoseSafetyLimit, - bloodGlucoseTargetPhysicalActivity: datumPumpBloodGlucoseTargetPhysicalActivity, bloodGlucoseTargetPreprandial: datumPumpBloodGlucoseTargetPreprandial, bloodGlucoseTargetSchedules: datumPumpBloodGlucoseTargetSchedules, bolus: datumPumpBolus, @@ -170,14 +168,6 @@ extension StoredSettings: IdentifiableDatum { return suspendThreshold.convertTo(unit: .milligramsPerDeciliter).value } - private var datumPumpBloodGlucoseTargetPhysicalActivity: TPumpSettingsDatum.BloodGlucoseTarget? { - guard let workoutTargetRange = workoutTargetRange else { - return nil - } - return TPumpSettingsDatum.BloodGlucoseTarget(low: workoutTargetRange.lowerBound.doubleValue(for: .milligramsPerDeciliter), - high: workoutTargetRange.upperBound.doubleValue(for: .milligramsPerDeciliter)) - } - private var datumPumpBloodGlucoseTargetPreprandial: TPumpSettingsDatum.BloodGlucoseTarget? { guard let preMealTargetRange = preMealTargetRange else { return nil @@ -375,7 +365,7 @@ fileprivate extension TemporaryPreset { insulinSensitivityScaleFactor: settings.datumInsulinSensitivityScaleFactor) } - var datumAbbreviation: String? { symbol.isEmpty == false ? symbol : nil } + var datumAbbreviation: String? { symbol?.textualRepresentation?.string } var datumDuration: TimeInterval? { duration.isFinite ? duration.timeInterval : nil } } diff --git a/TidepoolServiceKit/Extensions/TemporaryScheduleOverride.swift b/TidepoolServiceKit/Extensions/TemporaryScheduleOverride.swift index 42a94f7..19d2bdf 100644 --- a/TidepoolServiceKit/Extensions/TemporaryScheduleOverride.swift +++ b/TidepoolServiceKit/Extensions/TemporaryScheduleOverride.swift @@ -15,7 +15,7 @@ fileprivate extension TemporaryScheduleOverride.Context { switch self { case .preMeal: return .preprandial - case .legacyWorkout: + case .activity: return .physicalActivity case .preset(_): return .preset diff --git a/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift b/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift index 6558f69..f53d1be 100644 --- a/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift +++ b/TidepoolServiceKitTests/Extensions/StoredSettingsTests.swift @@ -118,10 +118,6 @@ class StoredSettingsTests: XCTestCase { ] }, "bgSafetyLimit" : 75, - "bgTargetPhysicalActivity" : { - "high" : 160, - "low" : 150 - }, "bgTargetPreprandial" : { "high" : 90, "low" : 80 @@ -266,8 +262,7 @@ fileprivate extension StoredSettings { start: dateFormatter.date(from: "2020-05-14T12:48:15Z")!, end: dateFormatter.date(from: "2020-05-14T14:48:15Z")!)) let preMealTargetRange = DoubleRange(minValue: 80.0, maxValue: 90.0).quantityRange(for: .milligramsPerDeciliter) - let workoutTargetRange = DoubleRange(minValue: 150.0, maxValue: 160.0).quantityRange(for: .milligramsPerDeciliter) - let overridePresets = [TemporaryPreset(id: UUID(uuidString: "2A67A303-5203-4CB8-8263-79498265368E")!, + let overridePresets = [TemporaryPreset(id: "2A67A303-5203-4CB8-8263-79498265368E", symbol: "🍎", name: "Apple", settings: TemporaryPresetSettings(unit: .milligramsPerDeciliter, @@ -336,7 +331,6 @@ fileprivate extension StoredSettings { dosingEnabled: dosingEnabled, glucoseTargetRangeSchedule: glucoseTargetRangeSchedule, preMealTargetRange: preMealTargetRange, - workoutTargetRange: workoutTargetRange, overridePresets: overridePresets, maximumBasalRatePerHour: maximumBasalRatePerHour, maximumBolus: maximumBolus, From a6f98425e0d62f68f60e1cfdace4b1f3f40dd5d9 Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Thu, 21 Aug 2025 11:15:21 -0700 Subject: [PATCH 64/71] [LOOP-5405] Activity Presets Core --- TidepoolServiceKit/Extensions/StoredSettings.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TidepoolServiceKit/Extensions/StoredSettings.swift b/TidepoolServiceKit/Extensions/StoredSettings.swift index 59f9161..06d958c 100644 --- a/TidepoolServiceKit/Extensions/StoredSettings.swift +++ b/TidepoolServiceKit/Extensions/StoredSettings.swift @@ -365,7 +365,7 @@ fileprivate extension TemporaryPreset { insulinSensitivityScaleFactor: settings.datumInsulinSensitivityScaleFactor) } - var datumAbbreviation: String? { symbol?.textualRepresentation?.string } + var datumAbbreviation: String? { symbol?.textualRepresentation } var datumDuration: TimeInterval? { duration.isFinite ? duration.timeInterval : nil } } From a3aa3175be188f2d7a3e83f21f3383666f3469aa Mon Sep 17 00:00:00 2001 From: Cameron Ingham Date: Thu, 21 Aug 2025 11:15:21 -0700 Subject: [PATCH 65/71] [LOOP-5405] Activity Presets Core --- TidepoolServiceKit/Extensions/StoredSettings.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TidepoolServiceKit/Extensions/StoredSettings.swift b/TidepoolServiceKit/Extensions/StoredSettings.swift index 59f9161..06d958c 100644 --- a/TidepoolServiceKit/Extensions/StoredSettings.swift +++ b/TidepoolServiceKit/Extensions/StoredSettings.swift @@ -365,7 +365,7 @@ fileprivate extension TemporaryPreset { insulinSensitivityScaleFactor: settings.datumInsulinSensitivityScaleFactor) } - var datumAbbreviation: String? { symbol?.textualRepresentation?.string } + var datumAbbreviation: String? { symbol?.textualRepresentation } var datumDuration: TimeInterval? { duration.isFinite ? duration.timeInterval : nil } } From af177f1aacdfd5368a71f0339a7192ac6e1c816a Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Wed, 24 Sep 2025 09:22:10 -0500 Subject: [PATCH 66/71] Abbreviation and preset name cannot be empty string (#124) --- .../xcshareddata/xcschemes/Shared.xcscheme | 2 +- TidepoolServiceKit/Extensions/StoredSettings.swift | 4 +++- TidepoolServiceKit/Extensions/TemporaryScheduleOverride.swift | 4 ++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/TidepoolService.xcodeproj/xcshareddata/xcschemes/Shared.xcscheme b/TidepoolService.xcodeproj/xcshareddata/xcschemes/Shared.xcscheme index 4bd32d3..c9e4db5 100644 --- a/TidepoolService.xcodeproj/xcshareddata/xcschemes/Shared.xcscheme +++ b/TidepoolService.xcodeproj/xcshareddata/xcschemes/Shared.xcscheme @@ -1,6 +1,6 @@ Date: Wed, 24 Sep 2025 09:22:10 -0500 Subject: [PATCH 67/71] Abbreviation and preset name cannot be empty string (#124) --- .../xcshareddata/xcschemes/Shared.xcscheme | 2 +- TidepoolServiceKit/Extensions/StoredSettings.swift | 4 +++- TidepoolServiceKit/Extensions/TemporaryScheduleOverride.swift | 4 ++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/TidepoolService.xcodeproj/xcshareddata/xcschemes/Shared.xcscheme b/TidepoolService.xcodeproj/xcshareddata/xcschemes/Shared.xcscheme index 4bd32d3..c9e4db5 100644 --- a/TidepoolService.xcodeproj/xcshareddata/xcschemes/Shared.xcscheme +++ b/TidepoolService.xcodeproj/xcshareddata/xcschemes/Shared.xcscheme @@ -1,6 +1,6 @@ Date: Thu, 12 Mar 2026 19:45:18 -0500 Subject: [PATCH 68/71] Restore import LoopAlgorithm in DoseEntry and DeviceLogUploader These files use AbsoluteScheduleValue and TDeviceLogEntry from the LoopAlgorithm package. The import was incorrectly removed during the Tidepool sync conflict resolution. --- TidepoolServiceKit/DeviceLogUploader.swift | 3 ++- TidepoolServiceKit/Extensions/DoseEntry.swift | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/TidepoolServiceKit/DeviceLogUploader.swift b/TidepoolServiceKit/DeviceLogUploader.swift index b27b524..109bc7d 100644 --- a/TidepoolServiceKit/DeviceLogUploader.swift +++ b/TidepoolServiceKit/DeviceLogUploader.swift @@ -7,8 +7,9 @@ // import Foundation -import os.log +import LoopAlgorithm import LoopKit +import os.log import TidepoolKit /// Periodically uploads device logs in hourly chunks to backend diff --git a/TidepoolServiceKit/Extensions/DoseEntry.swift b/TidepoolServiceKit/Extensions/DoseEntry.swift index dffe2a9..316681c 100644 --- a/TidepoolServiceKit/Extensions/DoseEntry.swift +++ b/TidepoolServiceKit/Extensions/DoseEntry.swift @@ -6,6 +6,7 @@ // Copyright © 2022 LoopKit Authors. All rights reserved. // +import LoopAlgorithm import LoopKit import TidepoolKit From babbfa9e72c2588853b7ee8213edc538bb5483ce Mon Sep 17 00:00:00 2001 From: LoopKit Developer Date: Wed, 25 Mar 2026 13:42:33 -0500 Subject: [PATCH 69/71] Update TidepoolKit to latest dev (4f4747ff) --- Cartfile.resolved | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cartfile.resolved b/Cartfile.resolved index 4c20147..80e4cdb 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,2 +1,2 @@ github "tidepool-org/LoopKit" "899f958b50dd22014d97c4730e44266ad8135805" -github "tidepool-org/TidepoolKit" "2a1858cf1040d8b01b6f7357551536417ad55b04" +github "tidepool-org/TidepoolKit" "4f4747ff647d836c5a27cc1b9c275e5717901e83" From 33072a4be6e14edc1dee2166c64717229d202f5c Mon Sep 17 00:00:00 2001 From: LoopKit Developer Date: Thu, 9 Apr 2026 15:26:39 -0500 Subject: [PATCH 70/71] Update string catalogs from Xcode build after Tidepool sync Xcode extracted new localizable strings and marked stale entries after the tidepool-sync/2026-03-10 merge and build. --- TidepoolServiceKitUI/Localizable.xcstrings | 3 +++ 1 file changed, 3 insertions(+) diff --git a/TidepoolServiceKitUI/Localizable.xcstrings b/TidepoolServiceKitUI/Localizable.xcstrings index 69a2264..bb89085 100644 --- a/TidepoolServiceKitUI/Localizable.xcstrings +++ b/TidepoolServiceKitUI/Localizable.xcstrings @@ -293,6 +293,9 @@ } } }, + "Continue" : { + "comment" : "Delete Tidepool service button title" + }, "Delete Service" : { "comment" : "Button title to delete a service\nDelete Tidepool service button title", "localizations" : { From 765bcb9b339a1c058428707d6dd8ddb0967e90a5 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Mon, 11 May 2026 10:38:39 -0500 Subject: [PATCH 71/71] DoseEntry: dedupe duplicate import LoopAlgorithm Merge of tidepool/dev created two import LoopAlgorithm lines (DIY had it first alphabetically, Tidepool had it last). Removed the trailing dup. --- TidepoolServiceKit/Extensions/DoseEntry.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/TidepoolServiceKit/Extensions/DoseEntry.swift b/TidepoolServiceKit/Extensions/DoseEntry.swift index d409456..316681c 100644 --- a/TidepoolServiceKit/Extensions/DoseEntry.swift +++ b/TidepoolServiceKit/Extensions/DoseEntry.swift @@ -9,7 +9,6 @@ import LoopAlgorithm import LoopKit import TidepoolKit -import LoopAlgorithm /* DoseEntry