Skip to content

Commit 77b543a

Browse files
committed
Merge branch 'export-settings' of https://github.com/loopandlearn/LoopFollow into export-settings
2 parents 4f651e8 + c84b218 commit 77b543a

22 files changed

+550
-366
lines changed

.github/workflows/build_LoopFollow.yml

Lines changed: 101 additions & 188 deletions
Large diffs are not rendered by default.

.github/workflows/validate_secrets.yml

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ on: [workflow_call, workflow_dispatch]
55
jobs:
66
validate-access-token:
77
name: Access
8-
runs-on: macos-15
8+
runs-on: ubuntu-latest
99
env:
1010
GH_PAT: ${{ secrets.GH_PAT }}
1111
GH_TOKEN: ${{ secrets.GH_PAT }}
@@ -71,13 +71,6 @@ jobs:
7171
exit 2
7272
fi
7373
74-
validate-match-secrets:
75-
name: Match-Secrets
76-
needs: validate-access-token
77-
runs-on: macos-15
78-
env:
79-
GH_TOKEN: ${{ secrets.GH_PAT }}
80-
steps:
8174
- name: Validate Match-Secrets
8275
run: |
8376
# Validate Match-Secrets
@@ -111,7 +104,7 @@ jobs:
111104
112105
validate-fastlane-secrets:
113106
name: Fastlane
114-
needs: [validate-access-token, validate-match-secrets]
107+
needs: [validate-access-token]
115108
runs-on: macos-15
116109
env:
117110
GH_PAT: ${{ secrets.GH_PAT }}
@@ -175,8 +168,8 @@ jobs:
175168
elif ! echo "$FASTLANE_KEY" | openssl pkcs8 -nocrypt >/dev/null; then
176169
failed=true
177170
echo "::error::The FASTLANE_KEY secret is set but invalid. Verify that you copied it correctly from the API Key file (*.p8) you downloaded and try again."
178-
elif ! (bundle exec fastlane validate_secrets 2>&1 || true) | tee fastlane.log; then # ignore "fastlane validate_secrets" errors and continue on errors without annotating an exit code
179-
if grep -q "bad decrypt" fastlane.log; then
171+
elif ! bundle exec fastlane validate_secrets 2>&1 | tee fastlane.log; then
172+
if grep -q "Couldn't decrypt the repo" fastlane.log; then
180173
failed=true
181174
echo "::error::Unable to decrypt the Match-Secrets repository using the MATCH_PASSWORD secret. Verify that it is set correctly and try again."
182175
elif grep -q -e "required agreement" -e "license agreement" fastlane.log; then

Config.xcconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
unique_id = ${DEVELOPMENT_TEAM}
77

88
//Version (DEFAULT)
9-
LOOP_FOLLOW_MARKETING_VERSION = 4.1.0
9+
LOOP_FOLLOW_MARKETING_VERSION = 4.2.2

LoopFollow.xcodeproj/project.pbxproj

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,12 @@
3434
6589CC712E9E814F00BB18FE /* AlarmSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6589CC702E9E814F00BB18FE /* AlarmSelectionView.swift */; };
3535
6589CC752E9EAFB700BB18FE /* SettingsMigrationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6589CC742E9EAFB700BB18FE /* SettingsMigrationManager.swift */; };
3636
65E153C32E4BB69100693A4F /* URLTokenValidationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65E153C22E4BB69100693A4F /* URLTokenValidationView.swift */; };
37+
65E153C32E4BB69100693A4F /* URLTokenValidationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65E153C22E4BB69100693A4F /* URLTokenValidationView.swift */; };
3738
65E8A2862E44B0300065037B /* VolumeButtonHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65E8A2852E44B0300065037B /* VolumeButtonHandler.swift */; };
3839
DD0247592DB2E89600FCADF6 /* AlarmCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0247582DB2E89600FCADF6 /* AlarmCondition.swift */; };
3940
DD0247712DB4337700FCADF6 /* BuildExpireCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD02475B2DB2E8FB00FCADF6 /* BuildExpireCondition.swift */; };
41+
DD026E592EA2C8A200A39CB5 /* InsulinPrecisionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD026E582EA2C8A200A39CB5 /* InsulinPrecisionManager.swift */; };
42+
DD026E5B2EA2C9C300A39CB5 /* InsulinFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD026E5A2EA2C9C300A39CB5 /* InsulinFormatter.swift */; };
4043
DD0650A92DCA8A10004D3B41 /* AlarmBGSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0650A82DCA8A10004D3B41 /* AlarmBGSection.swift */; };
4144
DD0650EB2DCE8385004D3B41 /* LowBGCondition.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0650EA2DCE8385004D3B41 /* LowBGCondition.swift */; };
4245
DD0650ED2DCE9371004D3B41 /* HighBgAlarmEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD0650EC2DCE9371004D3B41 /* HighBgAlarmEditor.swift */; };
@@ -431,10 +434,13 @@
431434
6589CC702E9E814F00BB18FE /* AlarmSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmSelectionView.swift; sourceTree = "<group>"; };
432435
6589CC742E9EAFB700BB18FE /* SettingsMigrationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsMigrationManager.swift; sourceTree = "<group>"; };
433436
65E153C22E4BB69100693A4F /* URLTokenValidationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLTokenValidationView.swift; sourceTree = "<group>"; };
437+
65E153C22E4BB69100693A4F /* URLTokenValidationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLTokenValidationView.swift; sourceTree = "<group>"; };
434438
65E8A2852E44B0300065037B /* VolumeButtonHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VolumeButtonHandler.swift; sourceTree = "<group>"; };
435439
A7D55B42A22051DAD69E89D0 /* Pods_LoopFollow.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_LoopFollow.framework; sourceTree = BUILT_PRODUCTS_DIR; };
436440
DD0247582DB2E89600FCADF6 /* AlarmCondition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmCondition.swift; sourceTree = "<group>"; };
437441
DD02475B2DB2E8FB00FCADF6 /* BuildExpireCondition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildExpireCondition.swift; sourceTree = "<group>"; };
442+
DD026E582EA2C8A200A39CB5 /* InsulinPrecisionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsulinPrecisionManager.swift; sourceTree = "<group>"; };
443+
DD026E5A2EA2C9C300A39CB5 /* InsulinFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InsulinFormatter.swift; sourceTree = "<group>"; };
438444
DD0650A82DCA8A10004D3B41 /* AlarmBGSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmBGSection.swift; sourceTree = "<group>"; };
439445
DD0650EA2DCE8385004D3B41 /* LowBGCondition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LowBGCondition.swift; sourceTree = "<group>"; };
440446
DD0650EC2DCE9371004D3B41 /* HighBgAlarmEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighBgAlarmEditor.swift; sourceTree = "<group>"; };
@@ -1528,6 +1534,8 @@
15281534
FCC688542489367300A0279D /* Helpers */ = {
15291535
isa = PBXGroup;
15301536
children = (
1537+
DD026E5A2EA2C9C300A39CB5 /* InsulinFormatter.swift */,
1538+
DD026E582EA2C8A200A39CB5 /* InsulinPrecisionManager.swift */,
15311539
656F8C112E49F3780008DC1D /* QRCodeGenerator.swift */,
15321540
DD4A407D2E6AFEE6007B318B /* AuthService.swift */,
15331541
DD1D52B82E1EB5DC00432050 /* TabPosition.swift */,
@@ -1992,6 +2000,7 @@
19922000
DD48780E2C7B74A40048F05C /* TrioRemoteControlViewModel.swift in Sources */,
19932001
DDEF503A2D31615000999A5D /* LogManager.swift in Sources */,
19942002
DD4878172C7B75350048F05C /* BolusView.swift in Sources */,
2003+
DD026E592EA2C8A200A39CB5 /* InsulinPrecisionManager.swift in Sources */,
19952004
DD493AE72ACF23CF009A6922 /* DeviceStatus.swift in Sources */,
19962005
DDC7E5162DBCFA7F00EB1127 /* SnoozerView.swift in Sources */,
19972006
FCFEECA2248857A600402A7F /* SettingsViewController.swift in Sources */,
@@ -2117,6 +2126,7 @@
21172126
DD0C0C662C46E54C00DBADDF /* InfoDataSeparator.swift in Sources */,
21182127
DD58171C2D299F940041FB98 /* BluetoothDevice.swift in Sources */,
21192128
DD7E198A2ACDA62600DBD158 /* SensorStart.swift in Sources */,
2129+
DD026E5B2EA2C9C300A39CB5 /* InsulinFormatter.swift in Sources */,
21202130
DD5334B02D1447C500CDD6EA /* BLEManager.swift in Sources */,
21212131
DD4878032C7B297E0048F05C /* StorageValue.swift in Sources */,
21222132
DD4878192C7C56D60048F05C /* TrioNightscoutRemoteController.swift in Sources */,

LoopFollow/Alarm/AlarmCondition/MissedReadingCondition.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,17 @@ struct MissedReadingCondition: AlarmCondition {
1717
// Skip if we have *no* readings
1818
guard let last = data.bgReadings.last else { return false }
1919

20+
guard let lastChecked = Storage.shared.lastBGChecked.value else {
21+
// Never checked, so don't alarm.
22+
return false
23+
}
24+
25+
let checkedAgeSeconds = now.timeIntervalSince(lastChecked)
26+
if checkedAgeSeconds > 360 { // 6 minutes
27+
// The check itself is stale, so the data is unreliable. Don't alarm.
28+
return false
29+
}
30+
2031
let secondsSinceLast = now.timeIntervalSince(last.date)
2132
return secondsSinceLast >= thresholdMinutes * 60
2233
}

LoopFollow/BackgroundRefresh/BT/BluetoothDevice.swift

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -223,11 +223,23 @@ class BluetoothDevice: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate
223223

224224
func centralManager(_: CBCentralManager, didDisconnectPeripheral _: CBPeripheral, error _: Error?) {
225225
timeStampLastStatusUpdate = Date()
226-
227226
bluetoothDeviceDelegate?.didDisconnectFrom(bluetoothDevice: self)
227+
cancelConnectionTimer()
228+
229+
guard let ownPeripheral = peripheral else { return }
228230

229-
if let ownPeripheral = peripheral {
230-
centralManager?.connect(ownPeripheral, options: nil)
231+
DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [weak self] in
232+
guard let self = self,
233+
let manager = self.centralManager,
234+
manager.state == .poweredOn else { return }
235+
236+
switch ownPeripheral.state {
237+
case .connected, .connecting:
238+
// Already (re)connecting; do nothing
239+
break
240+
default:
241+
manager.connect(ownPeripheral, options: nil)
242+
}
231243
}
232244
}
233245

LoopFollow/Controllers/Nightscout/BGData.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ extension MainViewController {
4646
func webLoadNSBGData(dexData: [ShareGlucoseData] = []) {
4747
// This kicks it out in the instance where dexcom fails but they aren't using NS &&
4848
if !IsNightscoutEnabled() {
49+
Storage.shared.lastBGChecked.value = Date()
4950
return
5051
}
5152

@@ -109,6 +110,8 @@ extension MainViewController {
109110
// if we have Dex data, use it
110111
if !dexData.isEmpty {
111112
self.ProcessDexBGData(data: dexData, sourceName: "Dexcom")
113+
} else {
114+
Storage.shared.lastBGChecked.value = Date()
112115
}
113116
return
114117
}
@@ -121,6 +124,7 @@ extension MainViewController {
121124

122125
guard !data.isEmpty else {
123126
LogManager.shared.log(category: .nightscout, message: "No bg data received. Skipping processing.", limitIdentifier: "No bg data received. Skipping processing.")
127+
Storage.shared.lastBGChecked.value = Date()
124128
return
125129
}
126130

@@ -221,7 +225,10 @@ extension MainViewController {
221225
TaskScheduler.shared.rescheduleTask(id: .minAgoUpdate, to: Date())
222226

223227
let entries = self.bgData
224-
if entries.count < 2 { return } // Protect index out of bounds
228+
if entries.count < 2 { // Protect index out of bounds
229+
Storage.shared.lastBGChecked.value = Date()
230+
return
231+
}
225232

226233
self.updateBGGraph()
227234
self.updateStats()
@@ -267,6 +274,7 @@ extension MainViewController {
267274
stale: Observable.shared.bgStale.value
268275
)
269276
}
277+
Storage.shared.lastBGChecked.value = Date()
270278
}
271279
}
272280
}

LoopFollow/Controllers/Nightscout/DeviceStatus.swift

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,19 @@
33

44
import Charts
55
import Foundation
6+
import HealthKit
67
import UIKit
78

89
extension MainViewController {
910
func webLoadNSDeviceStatus() {
10-
Storage.shared.lastLoopingChecked.value = Date()
11-
1211
let parameters = ["count": "1"]
1312
NightscoutUtils.executeDynamicRequest(eventType: .deviceStatus, parameters: parameters) { result in
1413
switch result {
1514
case let .success(json):
1615
if let jsonDeviceStatus = json as? [[String: AnyObject]] {
1716
DispatchQueue.main.async {
1817
self.updateDeviceStatusDisplay(jsonDeviceStatus: jsonDeviceStatus)
18+
Storage.shared.lastLoopingChecked.value = Date()
1919
}
2020
} else {
2121
self.handleDeviceStatusError()
@@ -29,6 +29,7 @@ extension MainViewController {
2929
private func handleDeviceStatusError() {
3030
LogManager.shared.log(category: .deviceStatus, message: "Device status fetch failed!", limitIdentifier: "Device status fetch failed!")
3131
DispatchQueue.main.async {
32+
Storage.shared.lastLoopingChecked.value = Date()
3233
TaskScheduler.shared.rescheduleTask(id: .deviceStatus, to: Date().addingTimeInterval(10))
3334
self.evaluateNotLooping()
3435
}
@@ -95,36 +96,56 @@ extension MainViewController {
9596
.withTime,
9697
.withDashSeparatorInDate,
9798
.withColonSeparatorInTime]
99+
100+
Observable.shared.previousAlertLastLoopTime.value = Observable.shared.alertLastLoopTime.value
101+
98102
if let lastPumpRecord = lastDeviceStatus?["pump"] as! [String: AnyObject]? {
99-
if let lastPumpTime = formatter.date(from: (lastPumpRecord["clock"] as! String))?.timeIntervalSince1970 {
103+
if let bolusIncrement = lastPumpRecord["bolusIncrement"] as? Double, bolusIncrement > 0 {
104+
Storage.shared.bolusIncrement.value = HKQuantity(unit: .internationalUnit(), doubleValue: bolusIncrement)
105+
Storage.shared.bolusIncrementDetected.value = true
106+
} else if let model = lastPumpRecord["model"] as? String, model == "Dash" {
107+
Storage.shared.bolusIncrement.value = HKQuantity(unit: .internationalUnit(), doubleValue: 0.05)
108+
Storage.shared.bolusIncrementDetected.value = true
109+
} else {
110+
Storage.shared.bolusIncrementDetected.value = false
111+
}
112+
113+
if let clockString = lastPumpRecord["clock"] as? String,
114+
let lastPumpTime = formatter.date(from: clockString)?.timeIntervalSince1970
115+
{
116+
let storedTime = Observable.shared.alertLastLoopTime.value ?? 0
117+
if lastPumpTime > storedTime {
118+
Observable.shared.alertLastLoopTime.value = lastPumpTime
119+
}
120+
100121
if let reservoirData = lastPumpRecord["reservoir"] as? Double {
101122
latestPumpVolume = reservoirData
102123
infoManager.updateInfoData(type: .pump, value: String(format: "%.0f", reservoirData) + "U")
103124
} else {
104125
latestPumpVolume = 50.0
105126
infoManager.updateInfoData(type: .pump, value: "50+U")
106127
}
128+
}
107129

108-
if let uploader = lastDeviceStatus?["uploader"] as? [String: AnyObject],
109-
let upbat = uploader["battery"] as? Double
110-
{
111-
let batteryText: String
112-
if let isCharging = uploader["isCharging"] as? Bool, isCharging {
113-
batteryText = "⚡️ " + String(format: "%.0f", upbat) + "%"
114-
} else {
115-
batteryText = String(format: "%.0f", upbat) + "%"
116-
}
117-
infoManager.updateInfoData(type: .battery, value: batteryText)
118-
Observable.shared.deviceBatteryLevel.value = upbat
130+
if let uploader = lastDeviceStatus?["uploader"] as? [String: AnyObject],
131+
let upbat = uploader["battery"] as? Double
132+
{
133+
let batteryText: String
134+
if let isCharging = uploader["isCharging"] as? Bool, isCharging {
135+
batteryText = "⚡️ " + String(format: "%.0f", upbat) + "%"
136+
} else {
137+
batteryText = String(format: "%.0f", upbat) + "%"
138+
}
139+
infoManager.updateInfoData(type: .battery, value: batteryText)
140+
Observable.shared.deviceBatteryLevel.value = upbat
119141

120-
let timestamp = uploader["timestamp"] as? Date ?? Date()
121-
let currentBattery = DataStructs.batteryStruct(batteryLevel: upbat, timestamp: timestamp)
122-
deviceBatteryData.append(currentBattery)
142+
let timestamp = uploader["timestamp"] as? Date ?? Date()
143+
let currentBattery = DataStructs.batteryStruct(batteryLevel: upbat, timestamp: timestamp)
144+
deviceBatteryData.append(currentBattery)
123145

124-
// store only the last 30 battery readings
125-
if deviceBatteryData.count > 30 {
126-
deviceBatteryData.removeFirst()
127-
}
146+
// store only the last 30 battery readings
147+
if deviceBatteryData.count > 30 {
148+
deviceBatteryData.removeFirst()
128149
}
129150
}
130151
}

0 commit comments

Comments
 (0)