Skip to content

Commit 5341ada

Browse files
committed
huge commit -- oops!
- DFU updates can now be pulled from the GitHub releases API, and there's a link on the home page if there's a newer version of InfiniTime than what's on the connected device. Clicking that link takes you to the DFU page and sets everything up so you can just press "Start transfer" and skip the whole downloading process. - Manual DFU updates are still available for those who wish to flash custom firmware files or InfiniTime PRs - Uptime tracker - super rudimentary uptime tracker is now on the home screen. Shows the last disconnect time, and the uptime of your current session. This probably won't be useful long-term, but with all of the BLE fixes going on at InfiniTime it's fun to track the improvement in uptime. - no long-term tracking yet, and if you disconnect for anything (even just stepping out of range for a second), it resets the timer. - DFU manager is a singleton now, and there are no environment objects in the app anymore!
1 parent 34ff8db commit 5341ada

16 files changed

Lines changed: 387 additions & 85 deletions

Infini-iOS.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
263B20C726CF07BB00676BF0 /* DFUProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 263B20C626CF07BB00676BF0 /* DFUProgressBar.swift */; };
2323
2644261226DC093D009BD54A /* SetTime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2644261126DC093D009BD54A /* SetTime.swift */; };
2424
2644261626DC1D8E009BD54A /* Onboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2644261526DC1D8E009BD54A /* Onboarding.swift */; };
25+
264AE04627026BD6001504A7 /* UptimeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 264AE04527026BD6001504A7 /* UptimeManager.swift */; };
2526
264BFE4226BC51CE0050A223 /* Infini_iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 264BFE4126BC51CE0050A223 /* Infini_iOSApp.swift */; };
2627
264BFE4426BC51CE0050A223 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 264BFE4326BC51CE0050A223 /* ContentView.swift */; };
2728
264BFE4626BC51CF0050A223 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 264BFE4526BC51CF0050A223 /* Assets.xcassets */; };
@@ -51,6 +52,8 @@
5152
26C5FAD826FC129400921207 /* ArbitraryNotificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26C5FAD726FC129400921207 /* ArbitraryNotificationView.swift */; };
5253
26C5FADC26FE7EB000921207 /* SheetCloseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26C5FADB26FE7EB000921207 /* SheetCloseButton.swift */; };
5354
26C5FAE126FE98F500921207 /* BLEAutoconnectManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26C5FAE026FE98F500921207 /* BLEAutoconnectManager.swift */; };
55+
26C5FAE326FEB0A400921207 /* DFUDownloadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26C5FAE226FEB0A400921207 /* DFUDownloadView.swift */; };
56+
26C5FAE8270100FF00921207 /* DownloadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26C5FAE7270100FF00921207 /* DownloadManager.swift */; };
5457
26D7816526C9EB3800BBF555 /* BLEDelegates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26D7816426C9EB3800BBF555 /* BLEDelegates.swift */; };
5558
26D7816726C9EC3100BBF555 /* BLEManagerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26D7816626C9EC3100BBF555 /* BLEManagerExtensions.swift */; };
5659
26D7816E26CA003B00BBF555 /* SettingsFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26D7816D26CA003B00BBF555 /* SettingsFunctions.swift */; };
@@ -91,6 +94,7 @@
9194
263B20C626CF07BB00676BF0 /* DFUProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DFUProgressBar.swift; sourceTree = "<group>"; };
9295
2644261126DC093D009BD54A /* SetTime.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetTime.swift; sourceTree = "<group>"; };
9396
2644261526DC1D8E009BD54A /* Onboarding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Onboarding.swift; sourceTree = "<group>"; };
97+
264AE04527026BD6001504A7 /* UptimeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UptimeManager.swift; sourceTree = "<group>"; };
9498
264BFE3E26BC51CE0050A223 /* Infini-iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Infini-iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
9599
264BFE4126BC51CE0050A223 /* Infini_iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Infini_iOSApp.swift; sourceTree = "<group>"; };
96100
264BFE4326BC51CE0050A223 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
@@ -126,6 +130,8 @@
126130
26C5FAD726FC129400921207 /* ArbitraryNotificationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArbitraryNotificationView.swift; sourceTree = "<group>"; };
127131
26C5FADB26FE7EB000921207 /* SheetCloseButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SheetCloseButton.swift; sourceTree = "<group>"; };
128132
26C5FAE026FE98F500921207 /* BLEAutoconnectManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLEAutoconnectManager.swift; sourceTree = "<group>"; };
133+
26C5FAE226FEB0A400921207 /* DFUDownloadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DFUDownloadView.swift; sourceTree = "<group>"; };
134+
26C5FAE7270100FF00921207 /* DownloadManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadManager.swift; sourceTree = "<group>"; };
129135
26D7816426C9EB3800BBF555 /* BLEDelegates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLEDelegates.swift; sourceTree = "<group>"; };
130136
26D7816626C9EC3100BBF555 /* BLEManagerExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BLEManagerExtensions.swift; sourceTree = "<group>"; };
131137
26D7816D26CA003B00BBF555 /* SettingsFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsFunctions.swift; sourceTree = "<group>"; };
@@ -301,6 +307,7 @@
301307
26A6315926C49839005AE404 /* DFU */ = {
302308
isa = PBXGroup;
303309
children = (
310+
26C5FAE7270100FF00921207 /* DownloadManager.swift */,
304311
26A6315A26C49841005AE404 /* DFU.swift */,
305312
);
306313
path = DFU;
@@ -326,6 +333,7 @@
326333
2675CA5626DC447500967E4D /* SheetManager.swift */,
327334
26A6316226C4C6D6005AE404 /* BLEConnectView.swift */,
328335
26C5FAD726FC129400921207 /* ArbitraryNotificationView.swift */,
336+
26C5FAE226FEB0A400921207 /* DFUDownloadView.swift */,
329337
26C5FADB26FE7EB000921207 /* SheetCloseButton.swift */,
330338
2632389326F29A4100D72B43 /* Onboarding */,
331339
);
@@ -344,6 +352,7 @@
344352
isa = PBXGroup;
345353
children = (
346354
264BFE7526BC52720050A223 /* HexStringToData.swift */,
355+
264AE04527026BD6001504A7 /* UptimeManager.swift */,
347356
26D7817926CAD19F00BBF555 /* ColorPalette.swift */,
348357
26C5FAD426FBB76100921207 /* DeviceNameManager.swift */,
349358
);
@@ -497,6 +506,7 @@
497506
26C5FAE126FE98F500921207 /* BLEAutoconnectManager.swift in Sources */,
498507
26A6316026C4BC25005AE404 /* DFUView.swift in Sources */,
499508
2632389026F2980A00D72B43 /* OnboardingDismissButton.swift in Sources */,
509+
264AE04627026BD6001504A7 /* UptimeManager.swift in Sources */,
500510
26A6315226BF45E0005AE404 /* BLEMusic.swift in Sources */,
501511
2632388C26F265B100D72B43 /* MainView.swift in Sources */,
502512
2632388826F2576D00D72B43 /* DFUFileSelectButton.swift in Sources */,
@@ -507,6 +517,7 @@
507517
2644261626DC1D8E009BD54A /* Onboarding.swift in Sources */,
508518
2632388426F2531C00D72B43 /* DFUWithBLE.swift in Sources */,
509519
263B20C726CF07BB00676BF0 /* DFUProgressBar.swift in Sources */,
520+
26C5FAE8270100FF00921207 /* DownloadManager.swift in Sources */,
510521
26A6314D26BEFD2C005AE404 /* MusicController.swift in Sources */,
511522
264DB80B26C62ED600E812C3 /* SideMenu.swift in Sources */,
512523
264DB80D26C633CF00E812C3 /* PageSwitcher.swift in Sources */,
@@ -518,6 +529,7 @@
518529
264BFE7626BC52720050A223 /* HexStringToData.swift in Sources */,
519530
2632388626F2548600D72B43 /* DFUWithoutBLE.swift in Sources */,
520531
26C5FADC26FE7EB000921207 /* SheetCloseButton.swift in Sources */,
532+
26C5FAE326FEB0A400921207 /* DFUDownloadView.swift in Sources */,
521533
2632388A26F2591900D72B43 /* DFUStartTransferButton.swift in Sources */,
522534
2675CA5A26DC81AA00967E4D /* DFUComplete.swift in Sources */,
523535
263B20BC26CDF20400676BF0 /* BatteryChart.swift in Sources */,

Infini-iOS/BLE/BLEManager.swift

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -133,26 +133,12 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate {
133133
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
134134

135135
var peripheralName: String!
136-
// TODO: Recreate the process below.
137-
/*
138-
- the hash I'm using is only unique between PineTimes because the peripheral struct includes an incrementing ID number that's part of the hash.
139-
- this works for getting more than one PT in the menu, but is obviously a drag because it's not at all guaranteed to be persistent
140-
- there's some stuff happening here that doesn't need to happen - ex. there's an array and a dictionary doing basically the same thing?
141-
- I mistakenly was under the impression that the UUID was generated by InfiniTime and was probably the same across all instances of InfiniTime, but it seems like that's incorrect!
142-
*/
143136

144137
if let name = advertisementData[CBAdvertisementDataLocalNameKey] as? String {
145138
peripheralName = name
146139
let devUUIDString: String = peripheral.identifier.uuidString
147140
let devUUID: CBUUID = CBUUID(string: devUUIDString)
148141
let newPeripheral = Peripheral(id: peripheralDictionary.count, name: peripheralName, rssi: RSSI.intValue, peripheralHash: peripheral.hash, deviceUUID: devUUID, stringUUID: peripheral.identifier.uuidString)
149-
150-
151-
// handle autoconnect defaults
152-
//let settings = UserDefaults.standard
153-
//let autoconnect = settings.object(forKey: "autoconnect") as? Bool ?? true
154-
//let autoconnectUUID = settings.object(forKey: "autoconnectUUID") as? String ?? ""
155-
156142

157143
guard BLEAutoconnectManager.shared.connect(peripheral: peripheral) else {
158144
// compare peripheral UUIDs to make sure we're only adding each device once -- super helpful if you have a very noisy BLE advertiser nearby!
@@ -172,12 +158,14 @@ class BLEManager: NSObject, ObservableObject, CBCentralManagerDelegate {
172158
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
173159
self.infiniTime.discoverServices(nil)
174160
DeviceInfoManager().setDeviceName(uuid: peripheral.identifier.uuidString)
161+
UptimeManager.shared.connectTime = Date()
175162
}
176163

177164
func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
178165
if error != nil {
179166
chartReconnect = true
180167
connect(peripheral: peripheral)
168+
UptimeManager.shared.lastDisconnect = Date()
181169
} else {
182170
DeviceInfoManager.init().clearDeviceInfo()
183171
}

Infini-iOS/ContentView.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,6 @@ struct ContentView: View {
5959
MainView()
6060
.sheet(isPresented: $sheetManager.showSheet, content: {
6161
SheetManager.CurrentSheet()
62-
.onAppear {
63-
print(sheetManager.sheetSelection)
64-
}
6562
.onDisappear {
6663
if onboarding {
6764
onboarding = false

Infini-iOS/DFU/DFU.swift

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,30 @@ import NordicDFU
1010

1111
class DFU_Updater: ObservableObject, DFUServiceDelegate, DFUProgressDelegate, LoggerDelegate {
1212

13+
static let shared = DFU_Updater()
14+
1315
private var url: URL = URL(fileURLWithPath: "")
14-
var bleManager: BLEManager!
16+
var bleManager: BLEManager = BLEManager.shared
1517
var dfuController: DFUServiceController!
1618

1719
@Published var dfuState: String = ""
1820
@Published var transferFailed = false
1921
@Published var transferCompleted = false
2022
@Published var percentComplete: Double = 0
23+
24+
@Published var firmwareFilename = ""
25+
@Published var firmwareSelected: Bool = false
26+
public var local = true
27+
public var firmwareURL: URL!
2128

2229

23-
func prepare(location: URL, device: BLEManager) {
24-
url = location
25-
bleManager = device
26-
}
30+
// func prepare(location: URL, device: BLEManager) {
31+
// url = location
32+
// bleManager = device
33+
// }
2734

2835
func transfer() {
36+
guard let url = firmwareURL else {return}
2937
guard url.startAccessingSecurityScopedResource() else { return }
3038
guard let selectedFirmware = DFUFirmware(urlToZipFile:url) else { return }
3139
let initiator = DFUServiceInitiator().with(firmware: selectedFirmware)
@@ -43,6 +51,26 @@ class DFU_Updater: ObservableObject, DFUServiceDelegate, DFUProgressDelegate, Lo
4351
url.stopAccessingSecurityScopedResource()
4452
}
4553

54+
func downloadTransfer() {
55+
print("transfer function:")
56+
57+
58+
guard let selectedFirmware = DFUFirmware(urlToZipFile: firmwareURL) else { print("failed to load file"); return }
59+
60+
let initiator = DFUServiceInitiator().with(firmware: selectedFirmware)
61+
62+
// Optional:
63+
// initiator.forceDfu = true/false // default false
64+
// initiator.packetReceiptNotificationParameter = N // default is 12
65+
initiator.logger = self // - to get log info
66+
initiator.delegate = self // - to be informed about current state and errors
67+
initiator.progressDelegate = self // - to show progress bar
68+
// initiator.peripheralSelector = ... // the default selector is used
69+
if bleManager.infiniTime != nil {
70+
dfuController = initiator.start(target: bleManager.infiniTime)
71+
}
72+
}
73+
4674
func stopTransfer() {
4775
if dfuController != nil {
4876
_ = dfuController.abort()
@@ -54,14 +82,14 @@ class DFU_Updater: ObservableObject, DFUServiceDelegate, DFUProgressDelegate, Lo
5482
dfuState = state.description()
5583
print(dfuState)
5684
if state.rawValue == 6 {
57-
print("hooray")
5885
transferCompleted = true
5986
print(transferCompleted)
6087
}
6188
}
6289

6390
func dfuError(_ error: DFUError, didOccurWithMessage message: String) {
6491
print("DFU Error:", message)
92+
6593
dfuController = nil
6694
transferFailed = true
6795
}
@@ -71,6 +99,7 @@ class DFU_Updater: ObservableObject, DFUServiceDelegate, DFUProgressDelegate, Lo
7199
}
72100

73101
func logWith(_ level: LogLevel, message: String) {
102+
//print("DFU \(level.name()): \(message)")
74103
}
75104

76105

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
//
2+
// DownloadManager.swift
3+
// Infini-iOS
4+
//
5+
// Created by Alex Emry on 9/26/21.
6+
//
7+
//
8+
9+
10+
import Foundation
11+
import NordicDFU
12+
13+
class DownloadManager: NSObject, ObservableObject {
14+
static var shared = DownloadManager()
15+
16+
@Published var tasks: [URLSessionTask] = []
17+
@Published var results: [Result] = []
18+
@Published var downloading = false
19+
@Published var updateAvailable = false
20+
@Published var autoUpgrade: Result!
21+
22+
private lazy var urlSession = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
23+
private var downloadTask: URLSessionDownloadTask!
24+
25+
struct Asset: Codable {
26+
let id: Int
27+
let name: String
28+
let browser_download_url: URL
29+
}
30+
31+
struct Result: Codable {
32+
let tag_name: String
33+
let assets: [Asset]
34+
var zipAsset: Asset!
35+
36+
private enum CodingKeys: String, CodingKey {
37+
case tag_name, assets
38+
}
39+
}
40+
41+
func checkForUpdates() -> Bool {
42+
for i in results {
43+
if i.tag_name.first != "v" {
44+
let comparison = BLEDeviceInfo.shared.firmware.compare(i.tag_name, options: .numeric)
45+
if comparison == .orderedAscending {
46+
print(i.tag_name)
47+
autoUpgrade = i
48+
return true
49+
}
50+
}
51+
}
52+
return false
53+
}
54+
55+
func getDownloadUrls() {
56+
results = []
57+
guard let url = URL(string: "https://api.github.com/repos/jf002/infinitime/releases") else {
58+
return
59+
}
60+
URLSession.shared.dataTask(with: url) { data, response, error in
61+
if let data = data {
62+
do {
63+
let res = try JSONDecoder().decode([Result].self, from: data)
64+
DispatchQueue.main.async {
65+
for i in res {
66+
if i.tag_name.first != "v" {
67+
if UserDefaults.standard.value(forKey: "showNewDownloadsOnly") as? Bool ?? true {
68+
let comparison = BLEDeviceInfo.shared.firmware.compare(i.tag_name, options: .numeric)
69+
if comparison == .orderedAscending || comparison == .orderedSame {
70+
self.results.append(i)
71+
}
72+
} else {
73+
self.results.append(i)
74+
}
75+
}
76+
}
77+
self.updateAvailable = self.checkForUpdates()
78+
}
79+
} catch {
80+
print(error)
81+
}
82+
}
83+
}.resume()
84+
}
85+
86+
func chooseAsset(response: Result) -> Asset {
87+
// for now, I'm pulling the .zip file from the releases. This is not guaranteed to be successful (ie if there's more than one zip file in the release), but it's a start
88+
for x in response.assets {
89+
if x.name.suffix(4) == ".zip" {
90+
return x
91+
}
92+
}
93+
return Asset(id: Int(), name: String(), browser_download_url: URL(fileURLWithPath: ""))
94+
}
95+
96+
func startDownload(url: URL) {
97+
self.downloading = true
98+
let downloadTask = urlSession.downloadTask(with: url)
99+
downloadTask.resume()
100+
self.downloadTask = downloadTask
101+
}
102+
103+
private func updateTasks() {
104+
urlSession.getAllTasks { tasks in
105+
DispatchQueue.main.async {
106+
self.tasks = tasks
107+
}
108+
}
109+
}
110+
}
111+
112+
extension DownloadManager: URLSessionDelegate, URLSessionDownloadDelegate {
113+
func urlSession(_: URLSession, downloadTask: URLSessionDownloadTask, didWriteData _: Int64, totalBytesWritten _: Int64, totalBytesExpectedToWrite _: Int64) {
114+
// uncomment for progress updates in console
115+
//print("Progress " + String(downloadTask.progress.fractionCompleted))
116+
}
117+
118+
func urlSession(_: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
119+
do {
120+
121+
let documentsURL = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
122+
let savedURL = documentsURL.appendingPathComponent(
123+
"firmware.zip")
124+
125+
// check for existing file and delete it if there is anything.
126+
if FileManager.default.fileExists(atPath: savedURL.path) {
127+
try? FileManager.default.removeItem(at: savedURL)
128+
print("old file deleted")
129+
}
130+
131+
// move downloaded file out of ephemeral storage and tell DFU where to look
132+
try FileManager.default.moveItem(at: location, to: savedURL)
133+
DFU_Updater.shared.firmwareURL = savedURL
134+
135+
} catch let fmerror {
136+
print("filesystem error: \(fmerror)")
137+
// handle filesystem error
138+
}
139+
DispatchQueue.main.async {
140+
self.downloading = false
141+
}
142+
}
143+
144+
func urlSession(_: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
145+
if let error = error {
146+
print("Download error: %@" + String(describing: error))
147+
// uncomment to print that it finished
148+
// } else {
149+
// print("Task finished: %@" )//+ task)
150+
}
151+
}
152+
}

Infini-iOS/Infini_iOSApp.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ struct Infini_iOSApp: App {
1515
WindowGroup {
1616
ContentView()
1717
.environment(\.managedObjectContext, persistenceController.container.viewContext)
18-
// .environmentObject(BLEManager())
19-
.environmentObject(DFU_Updater())
2018
}
2119
}
2220
}

Infini-iOS/Info.plist

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
<string>This app uses bluetooth to communicate with your PineTime.</string>
2727
<key>NSBluetoothPeripheralUsageDescription</key>
2828
<string>This app uses bluetooth to communicate with your PineTime.</string>
29+
<key>NSDownloadsFolderUsageDescription</key>
30+
<string></string>
2931
<key>UIApplicationSceneManifest</key>
3032
<dict>
3133
<key>UIApplicationSupportsMultipleScenes</key>

0 commit comments

Comments
 (0)