diff --git a/.gitmodules b/.gitmodules index 43705f607..ccdfec749 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,39 +1,40 @@ [submodule "LoopKit"] path = LoopKit - url = https://github.com/LoopKit/LoopKit.git - branch = dev + url = https://github.com/loopandlearn/LoopKit.git + branch = trio [submodule "CGMBLEKit"] path = CGMBLEKit - url = https://github.com/LoopKit/CGMBLEKit.git - branch = dev + url = https://github.com/loopandlearn/CGMBLEKit.git + branch = trio [submodule "dexcom-share-client-swift"] path = dexcom-share-client-swift - url = https://github.com/LoopKit/dexcom-share-client-swift.git - branch = dev + url = https://github.com/loopandlearn/dexcom-share-client-swift.git + branch = trio [submodule "RileyLinkKit"] path = RileyLinkKit - url = https://github.com/LoopKit/RileyLinkKit - branch = dev + url = https://github.com/loopandlearn/RileyLinkKit + branch = trio [submodule "OmniBLE"] path = OmniBLE - url = https://github.com/LoopKit/OmniBLE.git - branch = dev + url = https://github.com/loopandlearn/OmniBLE.git + branch = trio [submodule "G7SensorKit"] path = G7SensorKit - url = https://github.com/LoopKit/G7SensorKit.git - branch = main + url = https://github.com/loopandlearn/G7SensorKit.git + branch = trio [submodule "OmniKit"] path = OmniKit - url = https://github.com/LoopKit/OmniKit.git - branch = main + url = https://github.com/loopandlearn/OmniKit.git + branch = trio [submodule "MinimedKit"] path = MinimedKit - url = https://github.com/LoopKit/MinimedKit.git - branch = main + url = https://github.com/loopandlearn/MinimedKit.git + branch = trio [submodule "LibreTransmitter"] path = LibreTransmitter - url = https://github.com/LoopKit/LibreTransmitter.git - branch = main + url = https://github.com/loopandlearn/LibreTransmitter.git + branch = trio [submodule "TidepoolService"] path = TidepoolService - url = https://github.com/LoopKit/TidepoolService.git + url = https://github.com/loopandlearn/TidepoolService.git + branch = trio diff --git a/FreeAPS/Sources/APS/CGM/PluginSource.swift b/FreeAPS/Sources/APS/CGM/PluginSource.swift index 7d9f1363d..2d955ca03 100644 --- a/FreeAPS/Sources/APS/CGM/PluginSource.swift +++ b/FreeAPS/Sources/APS/CGM/PluginSource.swift @@ -24,7 +24,26 @@ final class PluginSource: GlucoseSource { cgmManager?.cgmManagerDelegate = self } + /// Function that fetches blood glucose data + /// This function combines two data fetching mechanisms (`callBLEFetch` and `fetchIfNeeded`) into a single publisher. + /// It returns the first non-empty result from either of the sources within a 5-minute timeout period. + /// If no valid data is fetched within the timeout, it returns an empty array. + /// + /// - Parameter timer: An optional `DispatchTimer` (not used in the function but can be used to trigger fetch logic). + /// - Returns: An `AnyPublisher` that emits an array of `BloodGlucose` values or an empty array if an error occurs or the timeout is reached. func fetch(_: DispatchTimer?) -> AnyPublisher<[BloodGlucose], Never> { + Publishers.Merge( + callBLEFetch(), + fetchIfNeeded() + ) + .filter { !$0.isEmpty } + .first() + .timeout(60 * 5, scheduler: processQueue, options: nil, customError: nil) + .replaceError(with: []) + .eraseToAnyPublisher() + } + + func callBLEFetch() -> AnyPublisher<[BloodGlucose], Never> { Future<[BloodGlucose], Error> { [weak self] promise in self?.promise = promise } @@ -35,17 +54,15 @@ final class PluginSource: GlucoseSource { } func fetchIfNeeded() -> AnyPublisher<[BloodGlucose], Never> { - Future<[BloodGlucose], Error> { _ in + Future<[BloodGlucose], Error> { [weak self] promise in + guard let self = self else { return } self.processQueue.async { guard let cgmManager = self.cgmManager else { return } cgmManager.fetchNewDataIfNeeded { result in - self.processCGMReadingResult(cgmManager, readingResult: result) { - // nothing to do - } + promise(self.readCGMResult(readingResult: result)) } } } - .timeout(60, scheduler: processQueue, options: nil, customError: nil) .replaceError(with: []) .replaceEmpty(with: []) .eraseToAnyPublisher() @@ -92,11 +109,10 @@ extension PluginSource: CGMManagerDelegate { glucoseManager?.cgmGlucoseSourceType = .none } - func cgmManager(_ manager: CGMManager, hasNew readingResult: CGMReadingResult) { + func cgmManager(_: CGMManager, hasNew readingResult: CGMReadingResult) { dispatchPrecondition(condition: .onQueue(processQueue)) - processCGMReadingResult(manager, readingResult: readingResult) { - debug(.deviceManager, "CGM PLUGIN - Direct return done") - } + promise?(readCGMResult(readingResult: readingResult)) + debug(.deviceManager, "CGM PLUGIN - Direct return done") } func cgmManager(_: LoopKit.CGMManager, hasNew events: [LoopKit.PersistedCgmEvent]) { @@ -140,11 +156,7 @@ extension PluginSource: CGMManagerDelegate { } } - private func processCGMReadingResult( - _: CGMManager, - readingResult: CGMReadingResult, - completion: @escaping () -> Void - ) { + private func readCGMResult(readingResult: CGMReadingResult) -> Result<[BloodGlucose], Error> { debug(.deviceManager, "PLUGIN CGM - Process CGM Reading Result launched with \(readingResult)") switch readingResult { case let .newData(values): @@ -177,18 +189,14 @@ extension PluginSource: CGMManagerDelegate { transmitterID: sensorTransmitterID ) } - promise?(.success(bloodGlucose)) - completion() + return .success(bloodGlucose) case .unreliableData: // loopManager.receivedUnreliableCGMReading() - promise?(.failure(GlucoseDataError.unreliableData)) - completion() + return .failure(GlucoseDataError.unreliableData) case .noData: - promise?(.failure(GlucoseDataError.noData)) - completion() + return .failure(GlucoseDataError.noData) case let .error(error): - promise?(.failure(error)) - completion() + return .failure(error) } } } diff --git a/FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigStateModel.swift b/FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigStateModel.swift index 6486cb24d..d6e1ba910 100644 --- a/FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigStateModel.swift +++ b/FreeAPS/Sources/Modules/NightscoutConfig/NightscoutConfigStateModel.swift @@ -51,6 +51,10 @@ extension NightscoutConfig { } func connect() { + if let CheckURL = url.last, CheckURL == "/" { + let fixedURL = url.dropLast() + url = String(fixedURL) + } guard let url = URL(string: url) else { message = "Invalid URL" return diff --git a/FreeAPS/Sources/Modules/Stat/View/ChartsView.swift b/FreeAPS/Sources/Modules/Stat/View/ChartsView.swift index d8ffe216c..e17124068 100644 --- a/FreeAPS/Sources/Modules/Stat/View/ChartsView.swift +++ b/FreeAPS/Sources/Modules/Stat/View/ChartsView.swift @@ -114,7 +114,7 @@ struct ChartsView: View { type: NSLocalizedString( "Low", comment: "" - ) + " (≤\(low.formatted(.number.grouping(.never).rounded().precision(.fractionLength(1)))))", + ) + " (<\(low.formatted(.number.grouping(.never).rounded().precision(.fractionLength(1)))))", percent: fetched[0].decimal ), .init(type: NSLocalizedString("In Range", comment: ""), percent: fetched[1].decimal), @@ -122,7 +122,7 @@ struct ChartsView: View { type: NSLocalizedString( "High", comment: "" - ) + " (≥\(high.formatted(.number.grouping(.never).rounded().precision(.fractionLength(1)))))", + ) + " (>\(high.formatted(.number.grouping(.never).rounded().precision(.fractionLength(1)))))", percent: fetched[2].decimal ) ] @@ -142,12 +142,12 @@ struct ChartsView: View { NSLocalizedString( "Low", comment: "" - ) + " (≤\(low.formatted(.number.grouping(.never).rounded().precision(.fractionLength(1)))))": .red, + ) + " (<\(low.formatted(.number.grouping(.never).rounded().precision(.fractionLength(1)))))": .red, NSLocalizedString("In Range", comment: ""): .green, NSLocalizedString( "High", comment: "" - ) + " (≥\(high.formatted(.number.grouping(.never).rounded().precision(.fractionLength(1)))))": .orange + ) + " (>\(high.formatted(.number.grouping(.never).rounded().precision(.fractionLength(1)))))": .orange ]).frame(maxHeight: 25) } @@ -161,18 +161,18 @@ struct ChartsView: View { type: NSLocalizedString( "Low", comment: "" - ) + " (≤ \(low.formatted(.number.grouping(.never).rounded().precision(.fractionLength(1)))))", + ) + " (< \(low.formatted(.number.grouping(.never).rounded().precision(.fractionLength(1)))))", percent: fetched[0].decimal ), .init( - type: "> \(low.formatted(.number.precision(.fractionLength(fraction)))) - < \(high.formatted(.number.precision(.fractionLength(fraction))))", + type: "\(low.formatted(.number.precision(.fractionLength(fraction)))) - \(high.formatted(.number.precision(.fractionLength(fraction))))", percent: fetched[1].decimal ), .init( type: NSLocalizedString( "High", comment: "" - ) + " (≥ \(high.formatted(.number.grouping(.never).rounded().precision(.fractionLength(1)))))", + ) + " (> \(high.formatted(.number.grouping(.never).rounded().precision(.fractionLength(1)))))", percent: fetched[2].decimal ) ] @@ -196,12 +196,12 @@ struct ChartsView: View { NSLocalizedString( "Low", comment: "" - ) + " (≤ \(low.formatted(.number.grouping(.never).rounded().precision(.fractionLength(1)))))": .red, - "> \(low.formatted(.number.precision(.fractionLength(fraction)))) - < \(high.formatted(.number.precision(.fractionLength(fraction))))": .green, + ) + " (< \(low.formatted(.number.grouping(.never).rounded().precision(.fractionLength(1)))))": .red, + "\(low.formatted(.number.precision(.fractionLength(fraction)))) - \(high.formatted(.number.precision(.fractionLength(fraction))))": .green, NSLocalizedString( "High", comment: "" - ) + " (≥ \(high.formatted(.number.grouping(.never).rounded().precision(.fractionLength(1)))))": .orange + ) + " (> \(high.formatted(.number.grouping(.never).rounded().precision(.fractionLength(1)))))": .orange ]) } @@ -276,11 +276,11 @@ struct ChartsView: View { let justGlucoseArray = glucose.compactMap({ each in Int(each.glucose as Int16) }) let totalReadings = justGlucoseArray.count - let hyperArray = glucose.filter({ $0.glucose >= hyperLimit }) + let hyperArray = glucose.filter({ $0.glucose > hyperLimit }) let hyperReadings = hyperArray.compactMap({ each in each.glucose as Int16 }).count let hyperPercentage = Double(hyperReadings) / Double(totalReadings) * 100 - let hypoArray = glucose.filter({ $0.glucose <= hypoLimit }) + let hypoArray = glucose.filter({ $0.glucose < hypoLimit }) let hypoReadings = hypoArray.compactMap({ each in each.glucose as Int16 }).count let hypoPercentage = Double(hypoReadings) / Double(totalReadings) * 100 diff --git a/fastlane/testflight.md b/fastlane/testflight.md index 2068a88fc..ad30d3a96 100644 --- a/fastlane/testflight.md +++ b/fastlane/testflight.md @@ -16,6 +16,7 @@ These instructions allow you to build Trio without having access to a Mac. > > It also creates an alive branch, if you don't already have one. See [Why do I have an alive branch?](#why-do-i-have-an-alive-branch). > +> The [**Optional**](#optional) section provides instructions to modify the default behavior if desired. > ## Introduction @@ -181,3 +182,70 @@ If a GitHub repository has no activity (no commits are made) in 60 days, then Gi The `build_trio.yml` file uses a special branch called `alive` and adds a dummy commit to the `alive` branch at regular intervals. This "trick" keeps the Actions enabled so the automated build works. The branch `alive` is created automatically for you. Do not delete or rename it! Do not modify `alive` yourself; it is not used for building the app. + +## OPTIONAL + +What if you don't want to allow automated updates of the repository or automatic builds? + +You can affect the default behavior: + +1. [`GH_PAT` `workflow` permission](#gh_pat-workflow-permission) +1. [Modify scheduled building and synchronization](#modify-scheduled-building-and-synchronization) + +### `GH_PAT` `workflow` permission + +To enable the scheduled build and sync, the `GH_PAT` must hold the `workflow` permission scopes. This permission serves as the enabler for automatic and scheduled builds with browser build. To verify your token holds this permission, follow these steps. + +1. Go to your [FastLane Access Token](https://github.com/settings/tokens) +2. It should say `repo`, `workflow` next to the `FastLane Access Token` link +3. If it does not, click on the link to open the token detail view +4. Click to check the `workflow` box. You will see that the checked boxes for the `repo` scope become disabled (change color to dark gray and are not clickable) +5. Scroll all the way down to and click the green `Update token` button +6. Your token now holds both required permissions + +If you choose not to have automatic building enabled, be sure the `GH_PAT` has `repo` scope or you won't be able to manually build. + +### Modify scheduled building and synchronization + +You can modify the automation by creating and using some variables. + +To configure the automated build more granularly involves creating up to two environment variables: `SCHEDULED_BUILD` and/or `SCHEDULED_SYNC`. See [How to configure a variable](#how-to-configure-a-variable). + +Note that the weekly and monthly Build Trio actions will continue, but the actions are modified if one or more of these variables is set to false. **A successful Action Log will still appear, even if no automatic activity happens**. + +* If you want to manually decide when to update your repository to the latest commit, but you want the monthly builds and keep-alive to continue: set `SCHEDULED_SYNC` to false and either do not create `SCHEDULED_BUILD` or set it to true +* If you want to only build when an update has been found: set `SCHEDULED_BUILD` to false and either do not create `SCHEDULED_SYNC` or set it to true + * **Warning**: if no updates to your default branch are detected within 90 days, your previous TestFlight build may expire requiring a manual build + +|`SCHEDULED_SYNC`|`SCHEDULED_BUILD`|Automatic Actions| +|---|---|---| +| `true` (or NA) | `true` (or NA) | keep-alive, weekly update check (auto update/build), monthly build with auto update| +| `true` (or NA) | `false` | keep-alive, weekly update check with auto update, only builds if update detected| +| `false` | `true` (or NA) | keep-alive, monthly build, no auto update | +| `false` | `false` | no automatic activity, no keep-alive| + +### How to configure a variable + +1. Go to the "Settings" tab of your Trio repository. +2. Click on `Secrets and Variables`. +3. Click on `Actions` +4. You will now see a page titled *Actions secrets and variables*. Click on the `Variables` tab +5. To disable ONLY scheduled building, do the following: + - Click on the green `New repository variable` button (upper right) + - Type `SCHEDULED_BUILD` in the "Name" field + - Type `false` in the "Value" field + - Click the green `Add variable` button to save. +7. To disable scheduled syncing, add a variable: + - Click on the green `New repository variable` button (upper right) + - - Type `SCHEDULED_SYNC` in the "Name" field + - Type `false` in the "Value" field + - Click the green `Add variable` button to save + +Your build will run on the following conditions: +- Default behaviour: + - Run weekly, every Wednesday at 08:00 UTC to check for changes; if there are changes, it will update your repository and build + - Run monthly, every first of the month at 06:00 UTC, if there are changes, it will update your repository; regardless of changes, it will build + - Each time the action runs, it makes a keep-alive commit to the `alive` branch if necessary +- If you disable any automation (both variables set to `false`), no updates, keep-alive or building happens when Build Trio runs +- If you disabled just scheduled synchronization (`SCHEDULED_SYNC` set to`false`), it will only run once a month, on the first of the month, no update will happen; keep-alive will run +- If you disabled just scheduled build (`SCHEDULED_BUILD` set to`false`), it will run once weekly, every Wednesday, to check for changes; if there are changes, it will update and build; keep-alive will run \ No newline at end of file